198 lines
6.7 KiB
QML
198 lines
6.7 KiB
QML
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
// Setting up a face: a short introduction, the password (polkit's own
|
|
// dialog), then the camera in a circle with the ring of ticks around it, and
|
|
// a tick at the end. Laid out like the phone's setup and Glance's onboarding.
|
|
|
|
import QtQuick
|
|
import QtQuick.Window
|
|
import FaceUnlock
|
|
|
|
Window {
|
|
id: window
|
|
|
|
required property var controller
|
|
|
|
// One fixed size: the layout needs no more room, and tiling compositors
|
|
// (Hyprland, Niri) float a window that cannot be resized instead of
|
|
// stretching it over half the screen.
|
|
width: 520
|
|
height: 680
|
|
minimumWidth: width
|
|
maximumWidth: width
|
|
minimumHeight: height
|
|
maximumHeight: height
|
|
visible: true
|
|
color: Theme.panel
|
|
title: i18n("Set up Face Unlock")
|
|
|
|
readonly property string step: controller.state
|
|
readonly property bool scanning: step === "center" || step === "circle"
|
|
readonly property bool done: step === "done"
|
|
|
|
onClosing: controller.cancel()
|
|
|
|
Item {
|
|
id: stage
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
y: 40
|
|
width: 360
|
|
height: 360
|
|
|
|
// Before the camera, and when something went wrong: the face, alive.
|
|
FaceGlyph {
|
|
anchors.centerIn: parent
|
|
width: 150
|
|
height: 150
|
|
mode: window.step === "failed" ? "failure" : "scanning"
|
|
opacity: window.scanning || window.done ? 0 : 1
|
|
scale: window.scanning || window.done ? 0.8 : 1
|
|
Behavior on opacity { NumberAnimation { duration: 250 } }
|
|
Behavior on scale { NumberAnimation { duration: 300; easing.type: Easing.OutCubic } }
|
|
}
|
|
|
|
// The camera, the ring, and the tick at the end.
|
|
Item {
|
|
anchors.fill: parent
|
|
opacity: window.scanning || window.done ? 1 : 0
|
|
scale: window.scanning || window.done ? 1 : 0.9
|
|
Behavior on opacity { NumberAnimation { duration: 300 } }
|
|
Behavior on scale { NumberAnimation { duration: 350; easing.type: Easing.OutCubic } }
|
|
|
|
PreviewItem {
|
|
id: preview
|
|
anchors.centerIn: parent
|
|
width: 264
|
|
height: 264
|
|
controller: window.controller
|
|
opacity: window.done ? 0.25 : 1
|
|
Behavior on opacity { NumberAnimation { duration: 400 } }
|
|
}
|
|
|
|
TickRing {
|
|
anchors.fill: parent
|
|
sectors: window.controller.sectors
|
|
pose: window.controller.pose
|
|
tracking: window.step === "circle" && window.controller.faceVisible
|
|
complete: window.done
|
|
}
|
|
|
|
Checkmark {
|
|
anchors.centerIn: parent
|
|
width: 150
|
|
height: 150
|
|
color: Theme.accent
|
|
progress: window.done ? 1 : 0
|
|
Behavior on progress { SequentialAnimation {
|
|
PauseAnimation { duration: 350 }
|
|
NumberAnimation { duration: 450; easing.type: Easing.OutCubic }
|
|
} }
|
|
}
|
|
}
|
|
}
|
|
|
|
Column {
|
|
id: texts
|
|
anchors.top: stage.bottom
|
|
anchors.topMargin: 26
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
width: parent.width - 80
|
|
spacing: 12
|
|
|
|
Text {
|
|
width: parent.width
|
|
horizontalAlignment: Text.AlignHCenter
|
|
wrapMode: Text.WordWrap
|
|
color: Theme.textPrimary
|
|
font.pixelSize: 26
|
|
font.weight: Font.Bold
|
|
visible: text.length > 0
|
|
text: window.step === "intro" ? i18n("Face Unlock")
|
|
: window.step === "authorizing" ? i18n("Confirm it is you")
|
|
: window.done ? i18n("You are all set")
|
|
: window.step === "failed" ? i18n("Setup did not finish")
|
|
: ""
|
|
}
|
|
|
|
Text {
|
|
width: parent.width
|
|
horizontalAlignment: Text.AlignHCenter
|
|
wrapMode: Text.WordWrap
|
|
color: window.scanning ? Theme.textPrimary : Theme.textSecondary
|
|
font.pixelSize: window.scanning ? 15 : 13
|
|
font.weight: Font.Medium
|
|
lineHeight: 1.15
|
|
text: window.step === "intro"
|
|
? i18n("Look at the camera, then move your head slowly in a circle. Your face is turned into numbers on this computer and never stored as a picture.")
|
|
: window.done
|
|
? i18n("Lock the screen and look at it to try it out. You can add a second look, with glasses for example, from the menu.")
|
|
: window.controller.instruction
|
|
}
|
|
|
|
// A name, so more than one face can be told apart in the menu.
|
|
Rectangle {
|
|
visible: window.step === "intro"
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
width: 260
|
|
height: 38
|
|
radius: 10
|
|
color: Theme.surfaceRaised
|
|
|
|
TextInput {
|
|
id: nameInput
|
|
anchors.fill: parent
|
|
anchors.leftMargin: 14
|
|
anchors.rightMargin: 14
|
|
verticalAlignment: TextInput.AlignVCenter
|
|
color: Theme.textPrimary
|
|
selectionColor: Theme.accent
|
|
font.pixelSize: 13
|
|
clip: true
|
|
maximumLength: 64
|
|
focus: true
|
|
text: window.controller.name
|
|
onTextEdited: window.controller.name = text
|
|
onAccepted: window.controller.start()
|
|
}
|
|
Text {
|
|
anchors.fill: nameInput
|
|
verticalAlignment: Text.AlignVCenter
|
|
color: Theme.textSecondary
|
|
font.pixelSize: 13
|
|
text: i18n("Name (optional)")
|
|
visible: nameInput.text.length === 0
|
|
}
|
|
}
|
|
}
|
|
|
|
Row {
|
|
anchors.bottom: parent.bottom
|
|
anchors.bottomMargin: 32
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
spacing: 12
|
|
|
|
PillButton {
|
|
visible: window.step !== "done"
|
|
primary: false
|
|
text: i18n("Cancel")
|
|
onClicked: window.controller.cancel()
|
|
}
|
|
PillButton {
|
|
visible: window.controller.canFinish
|
|
primary: false
|
|
text: i18n("Finish now")
|
|
onClicked: window.controller.finish()
|
|
}
|
|
PillButton {
|
|
visible: window.step === "intro" || window.step === "failed"
|
|
text: window.step === "failed" ? i18n("Try again") : i18n("Get started")
|
|
onClicked: window.controller.start()
|
|
}
|
|
PillButton {
|
|
visible: window.done
|
|
text: i18n("Done")
|
|
onClicked: window.controller.cancel()
|
|
}
|
|
}
|
|
}
|