feat: add an animation speed setting
This commit is contained in:
1 parent
dc0f680652
commit
71d01cd710
11 files changed
+156
-100
No files matched your search
@@ -28,6 +28,12 @@ BubbleController::BubbleController(UserConfig *config, QObject *parent)
|
||||
m_hide.setSingleShot(true);
|
||||
connect(&m_hide, &QTimer::timeout, this, &BubbleController::dismiss);
|
||||
connect(m_config, &UserConfig::changed, this, &BubbleController::styleChanged);
|
||||
connect(m_config, &UserConfig::changed, this, &BubbleController::paceChanged);
|
||||
}
|
||||
|
||||
qreal BubbleController::pace() const
|
||||
{
|
||||
return m_config->pace();
|
||||
}
|
||||
|
||||
QString BubbleController::style() const
|
||||
@@ -106,7 +112,7 @@ void BubbleController::succeeded()
|
||||
}
|
||||
setMessage({});
|
||||
setPhase(QStringLiteral("success"));
|
||||
m_hide.start(SuccessHoldMs);
|
||||
m_hide.start(int(SuccessHoldMs * pace()));
|
||||
}
|
||||
|
||||
void BubbleController::failed(const QString &reason, qint64 lockout)
|
||||
@@ -117,7 +123,7 @@ void BubbleController::failed(const QString &reason, qint64 lockout)
|
||||
if (lockout > 0 || reason == u"lockout") {
|
||||
setMessage(i18n("Use your password"));
|
||||
setPhase(QStringLiteral("lockout"));
|
||||
m_hide.start(LockoutHoldMs);
|
||||
m_hide.start(int(LockoutHoldMs * pace()));
|
||||
return;
|
||||
}
|
||||
if (reason == u"mismatch" || reason == u"spoof") {
|
||||
@@ -135,7 +141,7 @@ void BubbleController::failed(const QString &reason, qint64 lockout)
|
||||
return;
|
||||
}
|
||||
setPhase(QStringLiteral("failure"));
|
||||
m_hide.start(FailureHoldMs);
|
||||
m_hide.start(int(FailureHoldMs * pace()));
|
||||
}
|
||||
|
||||
void BubbleController::dismiss()
|
||||
|
||||
@@ -27,6 +27,8 @@ class BubbleController : public QObject
|
||||
// Whether the window should be on screen. Stays true after the phase
|
||||
// goes back to hidden until the bubble has finished closing.
|
||||
Q_PROPERTY(bool shown READ shown NOTIFY shownChanged)
|
||||
// The animation speed setting, as a multiple of each animation's length.
|
||||
Q_PROPERTY(qreal pace READ pace NOTIFY paceChanged)
|
||||
public:
|
||||
explicit BubbleController(UserConfig *config, QObject *parent = nullptr);
|
||||
|
||||
@@ -47,6 +49,7 @@ public:
|
||||
{
|
||||
return m_shown;
|
||||
}
|
||||
qreal pace() const;
|
||||
|
||||
void scanStarted();
|
||||
void faceFound();
|
||||
@@ -68,6 +71,7 @@ Q_SIGNALS:
|
||||
void faceSeenChanged();
|
||||
void styleChanged();
|
||||
void shownChanged();
|
||||
void paceChanged();
|
||||
|
||||
private:
|
||||
void setPhase(const QString &phase);
|
||||
|
||||
@@ -189,7 +189,7 @@ void LockController::onScanFinished(const QJsonObject &result)
|
||||
const QString reason = result.value(u"reason").toString();
|
||||
if (result.value(u"ok").toBool()) {
|
||||
m_bubble->succeeded();
|
||||
QTimer::singleShot(UnlockAfterSuccessMs, this, &LockController::unlock);
|
||||
QTimer::singleShot(int(UnlockAfterSuccessMs * m_config->pace()), this, &LockController::unlock);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+26
-22
@@ -26,6 +26,8 @@ Item {
|
||||
readonly property string phase: bubble ? bubble.phase : "hidden"
|
||||
readonly property bool minimal: bubble && bubble.style === "minimal"
|
||||
readonly property bool wantOpen: phase !== "hidden"
|
||||
// The animation speed setting: how long everything takes, 1 normal.
|
||||
readonly property real pace: bubble ? bubble.pace : 1
|
||||
|
||||
property bool positioned: false
|
||||
property bool expanded: false
|
||||
@@ -54,12 +56,12 @@ Item {
|
||||
|
||||
Timer {
|
||||
id: expandLater
|
||||
interval: Theme.expandDelay
|
||||
interval: Theme.expandDelay * root.pace
|
||||
onTriggered: root.expanded = true
|
||||
}
|
||||
Timer {
|
||||
id: slideOut
|
||||
interval: Theme.slideOutDelay
|
||||
interval: Theme.slideOutDelay * root.pace
|
||||
onTriggered: {
|
||||
root.positioned = false
|
||||
closeDone.restart()
|
||||
@@ -67,7 +69,7 @@ Item {
|
||||
}
|
||||
Timer {
|
||||
id: closeDone
|
||||
interval: Theme.slideOutDuration + 60
|
||||
interval: Theme.slideOutDuration * root.pace + 60
|
||||
onTriggered: if (root.bubble) root.bubble.closed()
|
||||
}
|
||||
|
||||
@@ -85,11 +87,11 @@ Item {
|
||||
id: pulseAnimation
|
||||
running: root.phase === "scanning" && root.expanded
|
||||
loops: Animation.Infinite
|
||||
PauseAnimation { duration: 600 }
|
||||
NumberAnimation { from: 0; to: 1; duration: 400; easing.type: Easing.InOutQuad }
|
||||
PauseAnimation { duration: 50 }
|
||||
NumberAnimation { from: 1; to: 0; duration: 400; easing.type: Easing.InOutQuad }
|
||||
PauseAnimation { duration: 50 }
|
||||
PauseAnimation { duration: 600 * root.pace }
|
||||
NumberAnimation { from: 0; to: 1; duration: 400 * root.pace; easing.type: Easing.InOutQuad }
|
||||
PauseAnimation { duration: 50 * root.pace }
|
||||
NumberAnimation { from: 1; to: 0; duration: 400 * root.pace; easing.type: Easing.InOutQuad }
|
||||
PauseAnimation { duration: 50 * root.pace }
|
||||
onRunningChanged: if (!running) settle.restart()
|
||||
}
|
||||
NumberAnimation {
|
||||
@@ -97,7 +99,7 @@ Item {
|
||||
target: root
|
||||
property: "pulse"
|
||||
to: 0
|
||||
duration: 200
|
||||
duration: 200 * root.pace
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
|
||||
@@ -105,11 +107,11 @@ Item {
|
||||
property real shake: 0
|
||||
SequentialAnimation {
|
||||
id: pillShake
|
||||
NumberAnimation { target: root; property: "shake"; to: -10; duration: 55; easing.type: Easing.OutQuad }
|
||||
NumberAnimation { target: root; property: "shake"; to: 9; duration: 70 }
|
||||
NumberAnimation { target: root; property: "shake"; to: -6; duration: 65 }
|
||||
NumberAnimation { target: root; property: "shake"; to: 4; duration: 60 }
|
||||
NumberAnimation { target: root; property: "shake"; to: 0; duration: 55; easing.type: Easing.OutQuad }
|
||||
NumberAnimation { target: root; property: "shake"; to: -10; duration: 55 * root.pace; easing.type: Easing.OutQuad }
|
||||
NumberAnimation { target: root; property: "shake"; to: 9; duration: 70 * root.pace }
|
||||
NumberAnimation { target: root; property: "shake"; to: -6; duration: 65 * root.pace }
|
||||
NumberAnimation { target: root; property: "shake"; to: 4; duration: 60 * root.pace }
|
||||
NumberAnimation { target: root; property: "shake"; to: 0; duration: 55 * root.pace; easing.type: Easing.OutQuad }
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -126,14 +128,14 @@ Item {
|
||||
// not sag.
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: root.opening ? Theme.growDuration : Theme.shrinkDuration
|
||||
duration: (root.opening ? Theme.growDuration : Theme.shrinkDuration) * root.pace
|
||||
easing.type: root.opening ? Easing.OutBack : Easing.OutCubic
|
||||
easing.overshoot: 1.6
|
||||
}
|
||||
}
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: root.opening ? Theme.growDuration : Theme.shrinkDuration
|
||||
duration: (root.opening ? Theme.growDuration : Theme.shrinkDuration) * root.pace
|
||||
easing.type: root.opening ? Easing.OutBack : Easing.OutCubic
|
||||
easing.overshoot: 0.9
|
||||
}
|
||||
@@ -145,7 +147,7 @@ Item {
|
||||
y: root.positioned ? Theme.topGap : -Theme.closedHeight - 20
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: root.opening ? Theme.slideInDuration : Theme.slideOutDuration
|
||||
duration: (root.opening ? Theme.slideInDuration : Theme.slideOutDuration) * root.pace
|
||||
easing.type: root.opening ? Easing.OutCubic : Easing.InCubic
|
||||
}
|
||||
}
|
||||
@@ -164,7 +166,7 @@ Item {
|
||||
shadowOpacity: root.expanded ? 0.35 : 0
|
||||
shadowBlur: 0.7
|
||||
shadowVerticalOffset: 3
|
||||
Behavior on shadowOpacity { NumberAnimation { duration: 200 } }
|
||||
Behavior on shadowOpacity { NumberAnimation { duration: 200 * root.pace } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +180,7 @@ Item {
|
||||
visible: !root.minimal
|
||||
scale: island.fit
|
||||
opacity: root.expanded ? 1 : 0
|
||||
Behavior on opacity { NumberAnimation { duration: root.opening ? 260 : 120 } }
|
||||
Behavior on opacity { NumberAnimation { duration: (root.opening ? 260 : 120) * root.pace } }
|
||||
|
||||
readonly property bool hasMessage: root.bubble && root.bubble.message.length > 0
|
||||
|
||||
@@ -193,8 +195,9 @@ Item {
|
||||
height: 100
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: full.hasMessage ? 28 : 40
|
||||
Behavior on y { NumberAnimation { duration: 220; easing.type: Easing.OutCubic } }
|
||||
Behavior on y { NumberAnimation { duration: 220 * root.pace; easing.type: Easing.OutCubic } }
|
||||
mode: root.glyphMode
|
||||
pace: root.pace
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -209,7 +212,7 @@ Item {
|
||||
font.pixelSize: 13
|
||||
font.weight: Font.Medium
|
||||
opacity: full.hasMessage ? 1 : 0
|
||||
Behavior on opacity { NumberAnimation { duration: 200 } }
|
||||
Behavior on opacity { NumberAnimation { duration: 200 * root.pace } }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,7 +223,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
visible: root.minimal
|
||||
opacity: root.expanded ? 1 : 0
|
||||
Behavior on opacity { NumberAnimation { duration: root.opening ? 200 : 120 } }
|
||||
Behavior on opacity { NumberAnimation { duration: (root.opening ? 200 : 120) * root.pace } }
|
||||
|
||||
LockGlyph {
|
||||
width: 18
|
||||
@@ -239,6 +242,7 @@ Item {
|
||||
anchors.rightMargin: 14
|
||||
lineWidth: 2.4
|
||||
mode: root.glyphMode === "lockout" ? "failure" : root.glyphMode
|
||||
pace: root.pace
|
||||
onSettled: ok => {
|
||||
if (!ok) {
|
||||
pillShake.restart()
|
||||
|
||||
+22
-20
@@ -26,6 +26,8 @@ Item {
|
||||
property string mode: "idle"
|
||||
property color color: Theme.textPrimary
|
||||
property real lineWidth: width * 0.055
|
||||
// How long everything takes, 1 normal (the animation speed setting).
|
||||
property real pace: 1
|
||||
|
||||
// The verdict is showing: after the rings on success, at once on failure.
|
||||
signal settled(bool success)
|
||||
@@ -45,11 +47,11 @@ Item {
|
||||
running: root.looking
|
||||
from: 0
|
||||
to: 2 * Math.PI
|
||||
duration: 2000
|
||||
duration: 2000 * root.pace
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
property real lookAmount: root.looking ? 1 : 0
|
||||
Behavior on lookAmount { NumberAnimation { duration: 350; easing.type: Easing.InOutQuad } }
|
||||
Behavior on lookAmount { NumberAnimation { duration: 350 * root.pace; easing.type: Easing.InOutQuad } }
|
||||
readonly property real yaw: 0.34 * Math.cos(lookAngle) * lookAmount
|
||||
readonly property real pitch: 0.2 * Math.sin(lookAngle) * lookAmount
|
||||
|
||||
@@ -65,15 +67,15 @@ Item {
|
||||
|
||||
// -- the smile, flat on failure
|
||||
property real smile: mode === "failure" ? 0 : 1
|
||||
Behavior on smile { NumberAnimation { duration: 220; easing.type: Easing.OutQuad } }
|
||||
Behavior on smile { NumberAnimation { duration: 220 * root.pace; easing.type: Easing.OutQuad } }
|
||||
|
||||
// -- the brackets breathe while scanning and close in on a face
|
||||
property real breathe: 0
|
||||
SequentialAnimation on breathe {
|
||||
running: root.looking
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation { from: 0; to: 1; duration: 700; easing.type: Easing.InOutSine }
|
||||
NumberAnimation { from: 1; to: 0; duration: 700; easing.type: Easing.InOutSine }
|
||||
NumberAnimation { from: 0; to: 1; duration: 700 * root.pace; easing.type: Easing.InOutSine }
|
||||
NumberAnimation { from: 1; to: 0; duration: 700 * root.pace; easing.type: Easing.InOutSine }
|
||||
onRunningChanged: if (!running) root.breathe = 0
|
||||
}
|
||||
readonly property real bracketScale: mode === "tracking" ? 0.9 : 1 - 0.04 * breathe
|
||||
@@ -126,11 +128,11 @@ Item {
|
||||
SequentialAnimation {
|
||||
id: succeed
|
||||
ParallelAnimation {
|
||||
NumberAnimation { target: root; property: "t"; from: 0; to: 1; duration: 850 }
|
||||
NumberAnimation { target: root; property: "t"; from: 0; to: 1; duration: 850 * root.pace }
|
||||
// The tick starts as the ring comes to rest, so the one motion
|
||||
// runs on into the other.
|
||||
SequentialAnimation {
|
||||
PauseAnimation { duration: 470 }
|
||||
PauseAnimation { duration: 470 * root.pace }
|
||||
ScriptAction { script: drawTick.start() }
|
||||
}
|
||||
}
|
||||
@@ -141,7 +143,7 @@ Item {
|
||||
target: root
|
||||
property: "tick"
|
||||
to: 1
|
||||
duration: 260
|
||||
duration: 260 * root.pace
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
|
||||
@@ -222,12 +224,12 @@ Item {
|
||||
property real shake: 0
|
||||
SequentialAnimation {
|
||||
id: shakeAnimation
|
||||
NumberAnimation { target: root; property: "shake"; to: -9; duration: 55; easing.type: Easing.OutQuad }
|
||||
NumberAnimation { target: root; property: "shake"; to: 8; duration: 70; easing.type: Easing.InOutQuad }
|
||||
NumberAnimation { target: root; property: "shake"; to: -6; duration: 65; easing.type: Easing.InOutQuad }
|
||||
NumberAnimation { target: root; property: "shake"; to: 4; duration: 60; easing.type: Easing.InOutQuad }
|
||||
NumberAnimation { target: root; property: "shake"; to: -2; duration: 55; easing.type: Easing.InOutQuad }
|
||||
NumberAnimation { target: root; property: "shake"; to: 0; duration: 50; easing.type: Easing.OutQuad }
|
||||
NumberAnimation { target: root; property: "shake"; to: -9; duration: 55 * root.pace; easing.type: Easing.OutQuad }
|
||||
NumberAnimation { target: root; property: "shake"; to: 8; duration: 70 * root.pace; easing.type: Easing.InOutQuad }
|
||||
NumberAnimation { target: root; property: "shake"; to: -6; duration: 65 * root.pace; easing.type: Easing.InOutQuad }
|
||||
NumberAnimation { target: root; property: "shake"; to: 4; duration: 60 * root.pace; easing.type: Easing.InOutQuad }
|
||||
NumberAnimation { target: root; property: "shake"; to: -2; duration: 55 * root.pace; easing.type: Easing.InOutQuad }
|
||||
NumberAnimation { target: root; property: "shake"; to: 0; duration: 50 * root.pace; easing.type: Easing.OutQuad }
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -246,7 +248,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
scale: root.bracketScale
|
||||
Behavior on scale { NumberAnimation { duration: 260; easing.type: Easing.OutBack } }
|
||||
Behavior on scale { NumberAnimation { duration: 260 * root.pace; easing.type: Easing.OutBack } }
|
||||
|
||||
component Bracket: ShapePath {
|
||||
strokeColor: root.bracketColor
|
||||
@@ -254,7 +256,7 @@ Item {
|
||||
fillColor: "transparent"
|
||||
capStyle: ShapePath.RoundCap
|
||||
joinStyle: ShapePath.RoundJoin
|
||||
Behavior on strokeColor { ColorAnimation { duration: 220 } }
|
||||
Behavior on strokeColor { ColorAnimation { duration: 220 * root.pace } }
|
||||
}
|
||||
|
||||
Bracket {
|
||||
@@ -352,7 +354,7 @@ Item {
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
// Gives way to the rings, and to the lock.
|
||||
property real shown: root.mode === "lockout" ? 0 : 1
|
||||
Behavior on shown { NumberAnimation { duration: 180 } }
|
||||
Behavior on shown { NumberAnimation { duration: 180 * root.pace } }
|
||||
opacity: shown * (1 - root.faceGone)
|
||||
scale: 1 - 0.2 * root.faceGone
|
||||
|
||||
@@ -362,7 +364,7 @@ Item {
|
||||
fillColor: "transparent"
|
||||
capStyle: ShapePath.RoundCap
|
||||
joinStyle: ShapePath.RoundJoin
|
||||
Behavior on strokeColor { ColorAnimation { duration: 220 } }
|
||||
Behavior on strokeColor { ColorAnimation { duration: 220 * root.pace } }
|
||||
}
|
||||
|
||||
// Eyes
|
||||
@@ -412,8 +414,8 @@ Item {
|
||||
color: root.color
|
||||
opacity: root.mode === "lockout" ? 1 : 0
|
||||
scale: root.mode === "lockout" ? 1 : 0.7
|
||||
Behavior on opacity { NumberAnimation { duration: 200 } }
|
||||
Behavior on scale { NumberAnimation { duration: 260; easing.type: Easing.OutBack } }
|
||||
Behavior on opacity { NumberAnimation { duration: 200 * root.pace } }
|
||||
Behavior on scale { NumberAnimation { duration: 260 * root.pace; easing.type: Easing.OutBack } }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,8 @@ void UserConfig::load()
|
||||
m_bubble = kv.boolean(QStringLiteral("Bubble"), true);
|
||||
m_bubbleStyle = kv.value(QStringLiteral("BubbleStyle"), QStringLiteral("full")) == u"minimal" ? QStringLiteral("minimal") : QStringLiteral("full");
|
||||
m_bubbleForPrompts = kv.boolean(QStringLiteral("BubbleForPrompts"), true);
|
||||
const QString speed = kv.value(QStringLiteral("AnimationSpeed"), QStringLiteral("normal"));
|
||||
m_pace = speed == u"fast" ? 0.6 : speed == u"slow" ? 1.5 : 1.0;
|
||||
|
||||
if (QFileInfo::exists(path()) && !m_watcher.files().contains(path())) {
|
||||
m_watcher.addPath(path());
|
||||
|
||||
@@ -13,6 +13,7 @@ class UserConfig : public QObject
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool bubble READ bubble NOTIFY changed)
|
||||
Q_PROPERTY(QString bubbleStyle READ bubbleStyle NOTIFY changed)
|
||||
Q_PROPERTY(qreal pace READ pace NOTIFY changed)
|
||||
public:
|
||||
explicit UserConfig(QObject *parent = nullptr);
|
||||
|
||||
@@ -54,6 +55,12 @@ public:
|
||||
{
|
||||
return m_bubbleForPrompts;
|
||||
}
|
||||
// How long the bubble's animations take, as a multiple of normal: the
|
||||
// animation speed setting (fast 0.6, normal 1, slow 1.5).
|
||||
qreal pace() const
|
||||
{
|
||||
return m_pace;
|
||||
}
|
||||
|
||||
static QString path();
|
||||
|
||||
@@ -70,5 +77,6 @@ private:
|
||||
bool m_bubble = true;
|
||||
QString m_bubbleStyle = QStringLiteral("full");
|
||||
bool m_bubbleForPrompts = true;
|
||||
qreal m_pace = 1;
|
||||
QString m_styleOverride;
|
||||
};
|
||||
Reference in new issue
Block a user