feat: add an animation speed setting

This commit is contained in:
Felitendo committed 2026-09-22 23:22:33 +02:00
1 parent dc0f680652
commit 71d01cd710
11 files changed
+156 -100

No files matched your search

+5 -3
View File
@@ -16,8 +16,9 @@ instead of a password. Before a face counts, it has to show a sign of life, so
holding up a photo of somebody is not enough. holding up a photo of somebody is not enough.
A bubble at the top of the screen shows what is going on: it drops down when A bubble at the top of the screen shows what is going on: it drops down when
the camera starts looking, the face in it looks around, and it turns into a the camera starts looking, the face in it looks around, and when it recognises
tick when it recognises somebody. It shows above the lock screen too. somebody two green rings spin and land around a tick. It shows above the lock
screen too. *Animation speed* makes all of it faster or slower.
Run without a command it shows an interactive menu. Everything it can be told Run without a command it shows an interactive menu. Everything it can be told
is reachable from there; the settings files behind it do not need to be edited is reachable from there; the settings files behind it do not need to be edited
@@ -228,7 +229,8 @@ A laptop with its lid shut is not scanned (*Not while the lid is closed*).
# FILES # FILES
_~/.config/plasma-face-unlock/config_ _~/.config/plasma-face-unlock/config_
This user's settings: the lock screen, the bubble. Written by the menu. This user's settings: the lock screen, the bubble and its animation
speed. Written by the menu.
_/etc/plasma-face-unlock/config_ _/etc/plasma-face-unlock/config_
The system settings: camera, photo check, strictness, attention, scan The system settings: camera, photo check, strictness, attention, scan
+12
View File
@@ -763,3 +763,15 @@ msgstr "Los geht's"
msgid "Done" msgid "Done"
msgstr "Fertig" msgstr "Fertig"
#: src/lib/menu.sh
msgid "Animation speed"
msgstr "Animationsgeschwindigkeit"
#: src/lib/menu.sh
msgid "fast"
msgstr "schnell"
#: src/lib/menu.sh
msgid "slow"
msgstr "langsam"
+63 -51
View File
@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: plasma-face-unlock 1.0.0\n" "Project-Id-Version: plasma-face-unlock 1.0.0\n"
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/plasma-face-unlock/" "Report-Msgid-Bugs-To: https://github.com/LoonixTools/plasma-face-unlock/"
"issues\n" "issues\n"
"POT-Creation-Date: 2026-09-22 23:06+0200\n" "POT-Creation-Date: 2026-09-22 23:12+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -55,8 +55,8 @@ msgstr ""
msgid "First, set up your face." msgid "First, set up your face."
msgstr "" msgstr ""
#: src/plasma-face-unlock:100 src/plasma-face-unlock:120 src/lib/menu.sh:348 #: src/plasma-face-unlock:100 src/plasma-face-unlock:120 src/lib/menu.sh:352
#: src/lib/menu.sh:352 src/lib/menu.sh:358 src/lib/menu.sh:360 #: src/lib/menu.sh:356 src/lib/menu.sh:362 src/lib/menu.sh:364
msgid "Could not save the setting." msgid "Could not save the setting."
msgstr "" msgstr ""
@@ -84,16 +84,16 @@ msgstr ""
msgid "No face is set up yet." msgid "No face is set up yet."
msgstr "" msgstr ""
#: src/plasma-face-unlock:148 src/lib/menu.sh:167 src/lib/menu.sh:472 #: src/plasma-face-unlock:148 src/lib/menu.sh:167 src/lib/menu.sh:476
msgid "on" msgid "on"
msgstr "" msgstr ""
#: src/plasma-face-unlock:148 src/lib/menu.sh:169 src/lib/menu.sh:179 #: src/plasma-face-unlock:148 src/lib/menu.sh:169 src/lib/menu.sh:179
#: src/lib/menu.sh:473 #: src/lib/menu.sh:477
msgid "off" msgid "off"
msgstr "" msgstr ""
#: src/plasma-face-unlock:150 src/lib/menu.sh:490 #: src/plasma-face-unlock:150 src/lib/menu.sh:494
#, sh-printf-format #, sh-printf-format
msgid "%s samples, %s learned" msgid "%s samples, %s learned"
msgstr "" msgstr ""
@@ -123,7 +123,7 @@ msgstr ""
msgid "Turn it off (faces are kept)" msgid "Turn it off (faces are kept)"
msgstr "" msgstr ""
#: src/plasma-face-unlock:176 src/lib/menu.sh:555 #: src/plasma-face-unlock:176 src/lib/menu.sh:559
msgid "Add a face" msgid "Add a face"
msgstr "" msgstr ""
@@ -222,7 +222,7 @@ msgstr ""
msgid "The camera could not be used." msgid "The camera could not be used."
msgstr "" msgstr ""
#: src/lib/daemon.sh:111 src/lib/menu.sh:243 #: src/lib/daemon.sh:111 src/lib/menu.sh:246
msgid "The recognition models are missing. Reinstall the package." msgid "The recognition models are missing. Reinstall the package."
msgstr "" msgstr ""
@@ -323,11 +323,11 @@ msgstr ""
msgid "[y/N]" msgid "[y/N]"
msgstr "" msgstr ""
#: src/lib/menu.sh:159 src/lib/menu.sh:392 #: src/lib/menu.sh:159 src/lib/menu.sh:396
msgid "ON" msgid "ON"
msgstr "" msgstr ""
#: src/lib/menu.sh:161 src/lib/menu.sh:393 #: src/lib/menu.sh:161 src/lib/menu.sh:397
msgid "OFF" msgid "OFF"
msgstr "" msgstr ""
@@ -339,7 +339,7 @@ msgstr ""
msgid "basic (screens and phone edges)" msgid "basic (screens and phone edges)"
msgstr "" msgstr ""
#: src/lib/menu.sh:180 #: src/lib/menu.sh:180 src/lib/menu.sh:185
msgid "normal" msgid "normal"
msgstr "" msgstr ""
@@ -359,132 +359,140 @@ msgstr ""
msgid "small pill with a lock" msgid "small pill with a lock"
msgstr "" msgstr ""
#: src/lib/menu.sh:185 #: src/lib/menu.sh:186
msgid "fast"
msgstr ""
#: src/lib/menu.sh:187
msgid "slow"
msgstr ""
#: src/lib/menu.sh:188
#, sh-printf-format #, sh-printf-format
msgid "%s seconds" msgid "%s seconds"
msgstr "" msgstr ""
#: src/lib/menu.sh:186 src/lib/menu.sh:323 #: src/lib/menu.sh:189 src/lib/menu.sh:327
msgid "automatic" msgid "automatic"
msgstr "" msgstr ""
#: src/lib/menu.sh:203 #: src/lib/menu.sh:206
msgid "Face unlock" msgid "Face unlock"
msgstr "" msgstr ""
#: src/lib/menu.sh:207 #: src/lib/menu.sh:210
msgid "Service" msgid "Service"
msgstr "" msgstr ""
#: src/lib/menu.sh:207 #: src/lib/menu.sh:210
msgid "not running" msgid "not running"
msgstr "" msgstr ""
#: src/lib/menu.sh:209 #: src/lib/menu.sh:212
msgid "Turning face unlock on again starts it." msgid "Turning face unlock on again starts it."
msgstr "" msgstr ""
#: src/lib/menu.sh:220 src/lib/menu.sh:222 src/lib/menu.sh:469 #: src/lib/menu.sh:223 src/lib/menu.sh:225 src/lib/menu.sh:473
#: src/lib/menu.sh:556 #: src/lib/menu.sh:560
msgid "Faces" msgid "Faces"
msgstr "" msgstr ""
#: src/lib/menu.sh:220 #: src/lib/menu.sh:223
msgid "none set up yet" msgid "none set up yet"
msgstr "" msgstr ""
#: src/lib/menu.sh:222 #: src/lib/menu.sh:225
msgid "all turned off" msgid "all turned off"
msgstr "" msgstr ""
#: src/lib/menu.sh:228 #: src/lib/menu.sh:231
msgid "none found" msgid "none found"
msgstr "" msgstr ""
#: src/lib/menu.sh:230 /tmp/tmp.FOzKaHKE4a/settings.sh:9 #: src/lib/menu.sh:233 /tmp/tmp.PgsbSPb4Sv/settings.sh:9
msgid "Camera" msgid "Camera"
msgstr "" msgstr ""
#: src/lib/menu.sh:232 #: src/lib/menu.sh:235
msgid "Lock screen" msgid "Lock screen"
msgstr "" msgstr ""
#: src/lib/menu.sh:233 #: src/lib/menu.sh:236
msgid "sudo" msgid "sudo"
msgstr "" msgstr ""
#: src/lib/menu.sh:234 #: src/lib/menu.sh:237
msgid "Admin prompts" msgid "Admin prompts"
msgstr "" msgstr ""
#: src/lib/menu.sh:235 /tmp/tmp.FOzKaHKE4a/settings.sh:6 #: src/lib/menu.sh:238 /tmp/tmp.PgsbSPb4Sv/settings.sh:6
msgid "Photo check" msgid "Photo check"
msgstr "" msgstr ""
#: src/lib/menu.sh:238 #: src/lib/menu.sh:241
msgid "Paused" msgid "Paused"
msgstr "" msgstr ""
#: src/lib/menu.sh:238 #: src/lib/menu.sh:241
#, sh-printf-format #, sh-printf-format
msgid "for %d more minutes, or until the password is used" msgid "for %d more minutes, or until the password is used"
msgstr "" msgstr ""
#: src/lib/menu.sh:240 #: src/lib/menu.sh:243
msgid "Last unlock" msgid "Last unlock"
msgstr "" msgstr ""
#: src/lib/menu.sh:319 #: src/lib/menu.sh:323
#, sh-printf-format #, sh-printf-format
msgid "automatic (%s)" msgid "automatic (%s)"
msgstr "" msgstr ""
#: src/lib/menu.sh:329 #: src/lib/menu.sh:333
msgid "(infrared)" msgid "(infrared)"
msgstr "" msgstr ""
#: src/lib/menu.sh:333 #: src/lib/menu.sh:337
msgid "(not connected)" msgid "(not connected)"
msgstr "" msgstr ""
#: src/lib/menu.sh:389 src/lib/menu.sh:557 #: src/lib/menu.sh:393 src/lib/menu.sh:561
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
#: src/lib/menu.sh:390 #: src/lib/menu.sh:394
msgid "Up/Down: select, Space or Right: change, q: back" msgid "Up/Down: select, Space or Right: change, q: back"
msgstr "" msgstr ""
#: src/lib/menu.sh:391 #: src/lib/menu.sh:395
msgid "Settings marked * are for the whole computer and ask for your password." msgid "Settings marked * are for the whole computer and ask for your password."
msgstr "" msgstr ""
#: src/lib/menu.sh:470 #: src/lib/menu.sh:474
msgid "Up/Down: select, Space: on/off, r: rename, d: delete, a: add, q: back" msgid "Up/Down: select, Space: on/off, r: rename, d: delete, a: add, q: back"
msgstr "" msgstr ""
#: src/lib/menu.sh:471 #: src/lib/menu.sh:475
msgid "No face is set up yet. Press a to add one." msgid "No face is set up yet. Press a to add one."
msgstr "" msgstr ""
#: src/lib/menu.sh:516 #: src/lib/menu.sh:520
msgid "New name:" msgid "New name:"
msgstr "" msgstr ""
#: src/lib/menu.sh:523 #: src/lib/menu.sh:527
#, sh-printf-format #, sh-printf-format
msgid "Delete \"%s\"?" msgid "Delete \"%s\"?"
msgstr "" msgstr ""
#: src/lib/menu.sh:554 #: src/lib/menu.sh:558
msgid "Turn face unlock on or off" msgid "Turn face unlock on or off"
msgstr "" msgstr ""
#: src/lib/menu.sh:558 #: src/lib/menu.sh:562
msgid "Try it" msgid "Try it"
msgstr "" msgstr ""
#: src/lib/menu.sh:559 #: src/lib/menu.sh:563
msgid "Quit" msgid "Quit"
msgstr "" msgstr ""
@@ -564,39 +572,43 @@ msgstr ""
msgid "Bubble style" msgid "Bubble style"
msgstr "" msgstr ""
#: src/lib/menu.sh
msgid "Animation speed"
msgstr ""
#: src/lib/menu.sh #: src/lib/menu.sh
msgid "Bubble for sudo and admin prompts too" msgid "Bubble for sudo and admin prompts too"
msgstr "" msgstr ""
#: src/agent/bubblecontroller.cpp:92 #: src/agent/bubblecontroller.cpp:98
msgid "Blink once" msgid "Blink once"
msgstr "" msgstr ""
#: src/agent/bubblecontroller.cpp:94 #: src/agent/bubblecontroller.cpp:100
msgid "Look at the screen" msgid "Look at the screen"
msgstr "" msgstr ""
#: src/agent/bubblecontroller.cpp:96 #: src/agent/bubblecontroller.cpp:102
msgid "Move closer" msgid "Move closer"
msgstr "" msgstr ""
#: src/agent/bubblecontroller.cpp:98 #: src/agent/bubblecontroller.cpp:104
msgid "Too dark" msgid "Too dark"
msgstr "" msgstr ""
#: src/agent/bubblecontroller.cpp:118 #: src/agent/bubblecontroller.cpp:124
msgid "Use your password" msgid "Use your password"
msgstr "" msgstr ""
#: src/agent/bubblecontroller.cpp:124 #: src/agent/bubblecontroller.cpp:130
msgid "Not recognized" msgid "Not recognized"
msgstr "" msgstr ""
#: src/agent/bubblecontroller.cpp:126 #: src/agent/bubblecontroller.cpp:132
msgid "Try again and blink" msgid "Try again and blink"
msgstr "" msgstr ""
#: src/agent/bubblecontroller.cpp:128 #: src/agent/bubblecontroller.cpp:134
msgid "Camera unavailable" msgid "Camera unavailable"
msgstr "" msgstr ""
+9 -3
View File
@@ -28,6 +28,12 @@ BubbleController::BubbleController(UserConfig *config, QObject *parent)
m_hide.setSingleShot(true); m_hide.setSingleShot(true);
connect(&m_hide, &QTimer::timeout, this, &BubbleController::dismiss); connect(&m_hide, &QTimer::timeout, this, &BubbleController::dismiss);
connect(m_config, &UserConfig::changed, this, &BubbleController::styleChanged); 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 QString BubbleController::style() const
@@ -106,7 +112,7 @@ void BubbleController::succeeded()
} }
setMessage({}); setMessage({});
setPhase(QStringLiteral("success")); setPhase(QStringLiteral("success"));
m_hide.start(SuccessHoldMs); m_hide.start(int(SuccessHoldMs * pace()));
} }
void BubbleController::failed(const QString &reason, qint64 lockout) 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") { if (lockout > 0 || reason == u"lockout") {
setMessage(i18n("Use your password")); setMessage(i18n("Use your password"));
setPhase(QStringLiteral("lockout")); setPhase(QStringLiteral("lockout"));
m_hide.start(LockoutHoldMs); m_hide.start(int(LockoutHoldMs * pace()));
return; return;
} }
if (reason == u"mismatch" || reason == u"spoof") { if (reason == u"mismatch" || reason == u"spoof") {
@@ -135,7 +141,7 @@ void BubbleController::failed(const QString &reason, qint64 lockout)
return; return;
} }
setPhase(QStringLiteral("failure")); setPhase(QStringLiteral("failure"));
m_hide.start(FailureHoldMs); m_hide.start(int(FailureHoldMs * pace()));
} }
void BubbleController::dismiss() void BubbleController::dismiss()
+4
View File
@@ -27,6 +27,8 @@ class BubbleController : public QObject
// Whether the window should be on screen. Stays true after the phase // Whether the window should be on screen. Stays true after the phase
// goes back to hidden until the bubble has finished closing. // goes back to hidden until the bubble has finished closing.
Q_PROPERTY(bool shown READ shown NOTIFY shownChanged) 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: public:
explicit BubbleController(UserConfig *config, QObject *parent = nullptr); explicit BubbleController(UserConfig *config, QObject *parent = nullptr);
@@ -47,6 +49,7 @@ public:
{ {
return m_shown; return m_shown;
} }
qreal pace() const;
void scanStarted(); void scanStarted();
void faceFound(); void faceFound();
@@ -68,6 +71,7 @@ Q_SIGNALS:
void faceSeenChanged(); void faceSeenChanged();
void styleChanged(); void styleChanged();
void shownChanged(); void shownChanged();
void paceChanged();
private: private:
void setPhase(const QString &phase); void setPhase(const QString &phase);
+1 -1
View File
@@ -189,7 +189,7 @@ void LockController::onScanFinished(const QJsonObject &result)
const QString reason = result.value(u"reason").toString(); const QString reason = result.value(u"reason").toString();
if (result.value(u"ok").toBool()) { if (result.value(u"ok").toBool()) {
m_bubble->succeeded(); m_bubble->succeeded();
QTimer::singleShot(UnlockAfterSuccessMs, this, &LockController::unlock); QTimer::singleShot(int(UnlockAfterSuccessMs * m_config->pace()), this, &LockController::unlock);
return; return;
} }
+26 -22
View File
@@ -26,6 +26,8 @@ Item {
readonly property string phase: bubble ? bubble.phase : "hidden" readonly property string phase: bubble ? bubble.phase : "hidden"
readonly property bool minimal: bubble && bubble.style === "minimal" readonly property bool minimal: bubble && bubble.style === "minimal"
readonly property bool wantOpen: phase !== "hidden" 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 positioned: false
property bool expanded: false property bool expanded: false
@@ -54,12 +56,12 @@ Item {
Timer { Timer {
id: expandLater id: expandLater
interval: Theme.expandDelay interval: Theme.expandDelay * root.pace
onTriggered: root.expanded = true onTriggered: root.expanded = true
} }
Timer { Timer {
id: slideOut id: slideOut
interval: Theme.slideOutDelay interval: Theme.slideOutDelay * root.pace
onTriggered: { onTriggered: {
root.positioned = false root.positioned = false
closeDone.restart() closeDone.restart()
@@ -67,7 +69,7 @@ Item {
} }
Timer { Timer {
id: closeDone id: closeDone
interval: Theme.slideOutDuration + 60 interval: Theme.slideOutDuration * root.pace + 60
onTriggered: if (root.bubble) root.bubble.closed() onTriggered: if (root.bubble) root.bubble.closed()
} }
@@ -85,11 +87,11 @@ Item {
id: pulseAnimation id: pulseAnimation
running: root.phase === "scanning" && root.expanded running: root.phase === "scanning" && root.expanded
loops: Animation.Infinite loops: Animation.Infinite
PauseAnimation { duration: 600 } PauseAnimation { duration: 600 * root.pace }
NumberAnimation { from: 0; to: 1; duration: 400; easing.type: Easing.InOutQuad } NumberAnimation { from: 0; to: 1; duration: 400 * root.pace; easing.type: Easing.InOutQuad }
PauseAnimation { duration: 50 } PauseAnimation { duration: 50 * root.pace }
NumberAnimation { from: 1; to: 0; duration: 400; easing.type: Easing.InOutQuad } NumberAnimation { from: 1; to: 0; duration: 400 * root.pace; easing.type: Easing.InOutQuad }
PauseAnimation { duration: 50 } PauseAnimation { duration: 50 * root.pace }
onRunningChanged: if (!running) settle.restart() onRunningChanged: if (!running) settle.restart()
} }
NumberAnimation { NumberAnimation {
@@ -97,7 +99,7 @@ Item {
target: root target: root
property: "pulse" property: "pulse"
to: 0 to: 0
duration: 200 duration: 200 * root.pace
easing.type: Easing.OutQuad easing.type: Easing.OutQuad
} }
@@ -105,11 +107,11 @@ Item {
property real shake: 0 property real shake: 0
SequentialAnimation { SequentialAnimation {
id: pillShake id: pillShake
NumberAnimation { target: root; property: "shake"; to: -10; 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 } NumberAnimation { target: root; property: "shake"; to: 9; duration: 70 * root.pace }
NumberAnimation { target: root; property: "shake"; to: -6; duration: 65 } NumberAnimation { target: root; property: "shake"; to: -6; duration: 65 * root.pace }
NumberAnimation { target: root; property: "shake"; to: 4; duration: 60 } NumberAnimation { target: root; property: "shake"; to: 4; duration: 60 * root.pace }
NumberAnimation { target: root; property: "shake"; to: 0; duration: 55; easing.type: Easing.OutQuad } NumberAnimation { target: root; property: "shake"; to: 0; duration: 55 * root.pace; easing.type: Easing.OutQuad }
} }
Item { Item {
@@ -126,14 +128,14 @@ Item {
// not sag. // not sag.
Behavior on width { Behavior on width {
NumberAnimation { 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.type: root.opening ? Easing.OutBack : Easing.OutCubic
easing.overshoot: 1.6 easing.overshoot: 1.6
} }
} }
Behavior on height { Behavior on height {
NumberAnimation { 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.type: root.opening ? Easing.OutBack : Easing.OutCubic
easing.overshoot: 0.9 easing.overshoot: 0.9
} }
@@ -145,7 +147,7 @@ Item {
y: root.positioned ? Theme.topGap : -Theme.closedHeight - 20 y: root.positioned ? Theme.topGap : -Theme.closedHeight - 20
Behavior on y { Behavior on y {
NumberAnimation { 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 easing.type: root.opening ? Easing.OutCubic : Easing.InCubic
} }
} }
@@ -164,7 +166,7 @@ Item {
shadowOpacity: root.expanded ? 0.35 : 0 shadowOpacity: root.expanded ? 0.35 : 0
shadowBlur: 0.7 shadowBlur: 0.7
shadowVerticalOffset: 3 shadowVerticalOffset: 3
Behavior on shadowOpacity { NumberAnimation { duration: 200 } } Behavior on shadowOpacity { NumberAnimation { duration: 200 * root.pace } }
} }
} }
@@ -178,7 +180,7 @@ Item {
visible: !root.minimal visible: !root.minimal
scale: island.fit scale: island.fit
opacity: root.expanded ? 1 : 0 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 readonly property bool hasMessage: root.bubble && root.bubble.message.length > 0
@@ -193,8 +195,9 @@ Item {
height: 100 height: 100
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
y: full.hasMessage ? 28 : 40 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 mode: root.glyphMode
pace: root.pace
} }
Text { Text {
@@ -209,7 +212,7 @@ Item {
font.pixelSize: 13 font.pixelSize: 13
font.weight: Font.Medium font.weight: Font.Medium
opacity: full.hasMessage ? 1 : 0 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 anchors.fill: parent
visible: root.minimal visible: root.minimal
opacity: root.expanded ? 1 : 0 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 { LockGlyph {
width: 18 width: 18
@@ -239,6 +242,7 @@ Item {
anchors.rightMargin: 14 anchors.rightMargin: 14
lineWidth: 2.4 lineWidth: 2.4
mode: root.glyphMode === "lockout" ? "failure" : root.glyphMode mode: root.glyphMode === "lockout" ? "failure" : root.glyphMode
pace: root.pace
onSettled: ok => { onSettled: ok => {
if (!ok) { if (!ok) {
pillShake.restart() pillShake.restart()
+22 -20
View File
@@ -26,6 +26,8 @@ Item {
property string mode: "idle" property string mode: "idle"
property color color: Theme.textPrimary property color color: Theme.textPrimary
property real lineWidth: width * 0.055 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. // The verdict is showing: after the rings on success, at once on failure.
signal settled(bool success) signal settled(bool success)
@@ -45,11 +47,11 @@ Item {
running: root.looking running: root.looking
from: 0 from: 0
to: 2 * Math.PI to: 2 * Math.PI
duration: 2000 duration: 2000 * root.pace
loops: Animation.Infinite loops: Animation.Infinite
} }
property real lookAmount: root.looking ? 1 : 0 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 yaw: 0.34 * Math.cos(lookAngle) * lookAmount
readonly property real pitch: 0.2 * Math.sin(lookAngle) * lookAmount readonly property real pitch: 0.2 * Math.sin(lookAngle) * lookAmount
@@ -65,15 +67,15 @@ Item {
// -- the smile, flat on failure // -- the smile, flat on failure
property real smile: mode === "failure" ? 0 : 1 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 // -- the brackets breathe while scanning and close in on a face
property real breathe: 0 property real breathe: 0
SequentialAnimation on breathe { SequentialAnimation on breathe {
running: root.looking running: root.looking
loops: Animation.Infinite loops: Animation.Infinite
NumberAnimation { from: 0; to: 1; 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; easing.type: Easing.InOutSine } NumberAnimation { from: 1; to: 0; duration: 700 * root.pace; easing.type: Easing.InOutSine }
onRunningChanged: if (!running) root.breathe = 0 onRunningChanged: if (!running) root.breathe = 0
} }
readonly property real bracketScale: mode === "tracking" ? 0.9 : 1 - 0.04 * breathe readonly property real bracketScale: mode === "tracking" ? 0.9 : 1 - 0.04 * breathe
@@ -126,11 +128,11 @@ Item {
SequentialAnimation { SequentialAnimation {
id: succeed id: succeed
ParallelAnimation { 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 // The tick starts as the ring comes to rest, so the one motion
// runs on into the other. // runs on into the other.
SequentialAnimation { SequentialAnimation {
PauseAnimation { duration: 470 } PauseAnimation { duration: 470 * root.pace }
ScriptAction { script: drawTick.start() } ScriptAction { script: drawTick.start() }
} }
} }
@@ -141,7 +143,7 @@ Item {
target: root target: root
property: "tick" property: "tick"
to: 1 to: 1
duration: 260 duration: 260 * root.pace
easing.type: Easing.OutQuad easing.type: Easing.OutQuad
} }
@@ -222,12 +224,12 @@ Item {
property real shake: 0 property real shake: 0
SequentialAnimation { SequentialAnimation {
id: shakeAnimation id: shakeAnimation
NumberAnimation { target: root; property: "shake"; to: -9; duration: 55; 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; easing.type: Easing.InOutQuad } NumberAnimation { target: root; property: "shake"; to: 8; duration: 70 * root.pace; easing.type: Easing.InOutQuad }
NumberAnimation { target: root; property: "shake"; to: -6; duration: 65; 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; 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; 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; easing.type: Easing.OutQuad } NumberAnimation { target: root; property: "shake"; to: 0; duration: 50 * root.pace; easing.type: Easing.OutQuad }
} }
Item { Item {
@@ -246,7 +248,7 @@ Item {
anchors.fill: parent anchors.fill: parent
preferredRendererType: Shape.CurveRenderer preferredRendererType: Shape.CurveRenderer
scale: root.bracketScale 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 { component Bracket: ShapePath {
strokeColor: root.bracketColor strokeColor: root.bracketColor
@@ -254,7 +256,7 @@ Item {
fillColor: "transparent" fillColor: "transparent"
capStyle: ShapePath.RoundCap capStyle: ShapePath.RoundCap
joinStyle: ShapePath.RoundJoin joinStyle: ShapePath.RoundJoin
Behavior on strokeColor { ColorAnimation { duration: 220 } } Behavior on strokeColor { ColorAnimation { duration: 220 * root.pace } }
} }
Bracket { Bracket {
@@ -352,7 +354,7 @@ Item {
preferredRendererType: Shape.CurveRenderer preferredRendererType: Shape.CurveRenderer
// Gives way to the rings, and to the lock. // Gives way to the rings, and to the lock.
property real shown: root.mode === "lockout" ? 0 : 1 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) opacity: shown * (1 - root.faceGone)
scale: 1 - 0.2 * root.faceGone scale: 1 - 0.2 * root.faceGone
@@ -362,7 +364,7 @@ Item {
fillColor: "transparent" fillColor: "transparent"
capStyle: ShapePath.RoundCap capStyle: ShapePath.RoundCap
joinStyle: ShapePath.RoundJoin joinStyle: ShapePath.RoundJoin
Behavior on strokeColor { ColorAnimation { duration: 220 } } Behavior on strokeColor { ColorAnimation { duration: 220 * root.pace } }
} }
// Eyes // Eyes
@@ -412,8 +414,8 @@ Item {
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
Behavior on opacity { NumberAnimation { duration: 200 } } Behavior on opacity { NumberAnimation { duration: 200 * root.pace } }
Behavior on scale { NumberAnimation { duration: 260; easing.type: Easing.OutBack } } Behavior on scale { NumberAnimation { duration: 260 * root.pace; easing.type: Easing.OutBack } }
} }
} }
} }
+2
View File
@@ -35,6 +35,8 @@ void UserConfig::load()
m_bubble = kv.boolean(QStringLiteral("Bubble"), true); m_bubble = kv.boolean(QStringLiteral("Bubble"), true);
m_bubbleStyle = kv.value(QStringLiteral("BubbleStyle"), QStringLiteral("full")) == u"minimal" ? QStringLiteral("minimal") : QStringLiteral("full"); m_bubbleStyle = kv.value(QStringLiteral("BubbleStyle"), QStringLiteral("full")) == u"minimal" ? QStringLiteral("minimal") : QStringLiteral("full");
m_bubbleForPrompts = kv.boolean(QStringLiteral("BubbleForPrompts"), true); 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())) { if (QFileInfo::exists(path()) && !m_watcher.files().contains(path())) {
m_watcher.addPath(path()); m_watcher.addPath(path());
+8
View File
@@ -13,6 +13,7 @@ class UserConfig : public QObject
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool bubble READ bubble NOTIFY changed) Q_PROPERTY(bool bubble READ bubble NOTIFY changed)
Q_PROPERTY(QString bubbleStyle READ bubbleStyle NOTIFY changed) Q_PROPERTY(QString bubbleStyle READ bubbleStyle NOTIFY changed)
Q_PROPERTY(qreal pace READ pace NOTIFY changed)
public: public:
explicit UserConfig(QObject *parent = nullptr); explicit UserConfig(QObject *parent = nullptr);
@@ -54,6 +55,12 @@ public:
{ {
return m_bubbleForPrompts; 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(); static QString path();
@@ -70,5 +77,6 @@ private:
bool m_bubble = true; bool m_bubble = true;
QString m_bubbleStyle = QStringLiteral("full"); QString m_bubbleStyle = QStringLiteral("full");
bool m_bubbleForPrompts = true; bool m_bubbleForPrompts = true;
qreal m_pace = 1;
QString m_styleOverride; QString m_styleOverride;
}; };
+4
View File
@@ -182,6 +182,9 @@ pfu_value_label() {
Strictness:relaxed) pfu_msg "relaxed" ;; Strictness:relaxed) pfu_msg "relaxed" ;;
BubbleStyle:full) pfu_msg "island with the face" ;; BubbleStyle:full) pfu_msg "island with the face" ;;
BubbleStyle:minimal) pfu_msg "small pill with a lock" ;; BubbleStyle:minimal) pfu_msg "small pill with a lock" ;;
AnimationSpeed:normal) pfu_msg "normal" ;;
AnimationSpeed:fast) pfu_msg "fast" ;;
AnimationSpeed:slow) pfu_msg "slow" ;;
ScanSeconds:*) pfu_msg "%s seconds" "$2" ;; ScanSeconds:*) pfu_msg "%s seconds" "$2" ;;
Camera:auto) pfu_msg "automatic" ;; Camera:auto) pfu_msg "automatic" ;;
*) printf '%s' "$2" ;; *) printf '%s' "$2" ;;
@@ -266,6 +269,7 @@ PFU_SETTINGS=(
"sys|SkipLidClosed|bool|yes|Not while the lid is closed" "sys|SkipLidClosed|bool|yes|Not while the lid is closed"
"user|Bubble|bool|yes|Show the bubble at the top" "user|Bubble|bool|yes|Show the bubble at the top"
"user|BubbleStyle|choice|full|Bubble style|full,minimal" "user|BubbleStyle|choice|full|Bubble style|full,minimal"
"user|AnimationSpeed|choice|normal|Animation speed|normal,fast,slow"
"user|BubbleForPrompts|bool|yes|Bubble for sudo and admin prompts too" "user|BubbleForPrompts|bool|yes|Bubble for sudo and admin prompts too"
) )