have a working edition of the fingerprint visuals
This commit is contained in:
parent
798b6879fd
commit
b39fa30e15
@ -257,5 +257,11 @@
|
|||||||
<file>ui/devicepages/FingerprintReaderDevicePage.qml</file>
|
<file>ui/devicepages/FingerprintReaderDevicePage.qml</file>
|
||||||
<file>ui/images/account.svg</file>
|
<file>ui/images/account.svg</file>
|
||||||
<file>ui/images/contact-new.svg</file>
|
<file>ui/images/contact-new.svg</file>
|
||||||
|
<file>ui/components/SegmentedImage.qml</file>
|
||||||
|
<file>ui/components/SegmentRenderer.qml</file>
|
||||||
|
<file>ui/components/SegmentBoundingBoxes.qml</file>
|
||||||
|
<file>ui/components/FingerprintVisual.qml</file>
|
||||||
|
<file>ui/images/fingerprint/fingerprint_boxes.json</file>
|
||||||
|
<file>ui/images/fingerprint/fingerprint_segmented.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
54
nymea-app/ui/components/FingerprintVisual.qml
Normal file
54
nymea-app/ui/components/FingerprintVisual.qml
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import QtQuick 2.9
|
||||||
|
|
||||||
|
SegmentedImage {
|
||||||
|
id: segmentedImage
|
||||||
|
|
||||||
|
property var masks: []
|
||||||
|
|
||||||
|
property bool debug: false
|
||||||
|
|
||||||
|
// http://stackoverflow.com/a/1830844/538866
|
||||||
|
function isNumeric (n) {
|
||||||
|
return !isNaN(parseFloat(n)) && isFinite(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMasksToEnroll () {
|
||||||
|
var outMasks = [];
|
||||||
|
if (masks && masks.length) {
|
||||||
|
masks.forEach(function (mask, i) {
|
||||||
|
// Format is "<source>/[x1,y1,w1,h1],…,[xn,yn,wn,hn]"
|
||||||
|
// If any value is non-numeric, we drop the mask.
|
||||||
|
if (!isNumeric(mask.x) || !isNumeric(mask.y) || !isNumeric(mask.width)
|
||||||
|
|| !isNumeric(mask.height))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Translate the box so as to mirror the mask
|
||||||
|
mask.x = (1 - (mask.x + mask.width));
|
||||||
|
|
||||||
|
outMasks.push(mask);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return outMasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMasksChanged: segmentedImage.enrollMasks(getMasksToEnroll())
|
||||||
|
|
||||||
|
textureSource: "../images/fingerprint/fingerprint_segmented.png"
|
||||||
|
boxesSource: "../images/fingerprint/fingerprint_boxes.json"
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: segmentedImage.masks
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: segmentedImage.debug
|
||||||
|
color: "red"
|
||||||
|
opacity: 0.25
|
||||||
|
x: modelData.x * segmentedImage.implicitWidth
|
||||||
|
y: modelData.y * segmentedImage.implicitHeight
|
||||||
|
width: modelData.width * segmentedImage.implicitWidth
|
||||||
|
height: modelData.height * segmentedImage.implicitHeight
|
||||||
|
|
||||||
|
Component.onCompleted: console.log('Scanner mask (x, y, w, h):', x, y, width, height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
nymea-app/ui/components/SegmentBoundingBoxes.qml
Normal file
81
nymea-app/ui/components/SegmentBoundingBoxes.qml
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Canonical Ltd.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation; version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Authored by Florian Boucault <florian.boucault@canonical.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick 2.4
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: segmentBoundingBoxes
|
||||||
|
|
||||||
|
property string source
|
||||||
|
onSourceChanged: parseBoundingBoxes(source)
|
||||||
|
|
||||||
|
property var boundingBoxes: []
|
||||||
|
property real width
|
||||||
|
property real height
|
||||||
|
property int count: boundingBoxes.length
|
||||||
|
|
||||||
|
// The API cannot be used reliably before this signal has been emitted.
|
||||||
|
signal ready()
|
||||||
|
|
||||||
|
function parseBoundingBoxes(source) {
|
||||||
|
var xhr = new XMLHttpRequest;
|
||||||
|
xhr.open("GET", source);
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if (xhr.readyState == XMLHttpRequest.DONE) {
|
||||||
|
var b = [];
|
||||||
|
var json = JSON.parse(xhr.responseText);
|
||||||
|
boundingBoxes = json["boxes"];
|
||||||
|
width = json["width"];
|
||||||
|
height = json["height"];
|
||||||
|
ready();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function intersects(box1, box2) {
|
||||||
|
// TODO: optimize
|
||||||
|
var x11 = box1[0];
|
||||||
|
var y11 = box1[1];
|
||||||
|
var x12 = box1[0] + box1[2];
|
||||||
|
var y12 = box1[1] + box1[3];
|
||||||
|
var x21 = box2[0];
|
||||||
|
var y21 = box2[1];
|
||||||
|
var x22 = box2[0] + box2[2];
|
||||||
|
var y22 = box2[1] + box2[3];
|
||||||
|
var x_overlap = Math.max(0, Math.min(x12,x22) - Math.max(x11,x21));
|
||||||
|
var y_overlap = Math.max(0, Math.min(y12,y22) - Math.max(y11,y21));
|
||||||
|
return (x_overlap / Math.min(box1[2], box2[2]) > 0.25
|
||||||
|
&& y_overlap / Math.min(box1[3], box2[3]) > 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeIntersections(hitBox) {
|
||||||
|
var absoluteHitBox = [hitBox[0] * width, hitBox[1] * height,
|
||||||
|
hitBox[2] * width, hitBox[3] * height];
|
||||||
|
|
||||||
|
var intersections = [];
|
||||||
|
for (var i in boundingBoxes) {
|
||||||
|
var boundingBox = boundingBoxes[i];
|
||||||
|
if (intersects(absoluteHitBox, boundingBox)) {
|
||||||
|
intersections.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return intersections;
|
||||||
|
}
|
||||||
|
}
|
||||||
76
nymea-app/ui/components/SegmentRenderer.qml
Normal file
76
nymea-app/ui/components/SegmentRenderer.qml
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import QtQuick 2.9
|
||||||
|
|
||||||
|
ShaderEffect {
|
||||||
|
id: segmentRenderer
|
||||||
|
|
||||||
|
implicitWidth: texture.width
|
||||||
|
implicitHeight: texture.height
|
||||||
|
|
||||||
|
function animate(segments) {
|
||||||
|
for (var i in segments) {
|
||||||
|
var progressPixel = progressTexture.children[segments[i]];
|
||||||
|
if (progressPixel.progress == 0.0) {
|
||||||
|
progressPixel.animation.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property string source
|
||||||
|
property int segmentsCount
|
||||||
|
property color backgroundColor: app.foregroundColor
|
||||||
|
property color fillColor: app.accentColor
|
||||||
|
property Image texture: Image {
|
||||||
|
source: segmentRenderer.source
|
||||||
|
}
|
||||||
|
property var progressTexture: progressTexture
|
||||||
|
property int progressTextureSize: progressTexture.size
|
||||||
|
|
||||||
|
fragmentShader: "
|
||||||
|
varying mediump vec2 qt_TexCoord0;
|
||||||
|
uniform lowp float qt_Opacity;
|
||||||
|
uniform lowp vec4 backgroundColor;
|
||||||
|
uniform lowp vec4 fillColor;
|
||||||
|
uniform lowp sampler2D texture;
|
||||||
|
uniform lowp sampler2D progressTexture;
|
||||||
|
uniform lowp int progressTextureSize;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
lowp vec4 p = texture2D(texture, qt_TexCoord0);
|
||||||
|
lowp float segment = p.r * 255.0;
|
||||||
|
lowp vec4 segmentProgress = step(0.9, segment) * texture2D(progressTexture, vec2((segment - 1.0 + 0.5) / float(progressTextureSize), 0.5));
|
||||||
|
lowp vec4 color = mix(fillColor, backgroundColor, step(segmentProgress.r, p.g));
|
||||||
|
gl_FragColor = vec4(color.rgb, 1.0) * p.b * qt_Opacity;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
|
||||||
|
// TODO: not the most efficient; could be replaced with an image provider
|
||||||
|
Row {
|
||||||
|
id: progressTexture
|
||||||
|
|
||||||
|
property int size: 128
|
||||||
|
layer.enabled: true
|
||||||
|
layer.sourceRect: Qt.rect(0, 0, size, 1)
|
||||||
|
layer.textureSize: Qt.size(size, 1)
|
||||||
|
layer.wrapMode: ShaderEffectSource.ClampToEdge
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: segmentRenderer.segmentsCount
|
||||||
|
Rectangle {
|
||||||
|
id: progressPixel
|
||||||
|
width: 1
|
||||||
|
height: 1
|
||||||
|
color: Qt.rgba(progress, progress, progress, 1.0)
|
||||||
|
property real progress
|
||||||
|
property NumberAnimation animation: NumberAnimation {
|
||||||
|
target: progressPixel
|
||||||
|
property: "progress"
|
||||||
|
from: 0.0
|
||||||
|
to: 1.0
|
||||||
|
duration: 1000
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
nymea-app/ui/components/SegmentedImage.qml
Normal file
52
nymea-app/ui/components/SegmentedImage.qml
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Canonical Ltd.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation; version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Authored by Florian Boucault <florian.boucault@canonical.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick 2.4
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: segImg
|
||||||
|
property alias textureSource: segmentRenderer.source
|
||||||
|
property alias boxesSource: segmentBoundingBoxes.source
|
||||||
|
|
||||||
|
implicitWidth: segmentRenderer.implicitWidth
|
||||||
|
implicitHeight: segmentRenderer.implicitHeight
|
||||||
|
|
||||||
|
// Ready to enroll.
|
||||||
|
signal ready()
|
||||||
|
|
||||||
|
function enrollMasks(masks) {
|
||||||
|
if (masks && masks.length) {
|
||||||
|
var segments = [];
|
||||||
|
masks.forEach(function (mask, i) {
|
||||||
|
var hitBox = [mask.x, mask.y, mask.width, mask.height];
|
||||||
|
segments = segments.concat(segmentBoundingBoxes.computeIntersections(hitBox));
|
||||||
|
});
|
||||||
|
segmentRenderer.animate(segments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SegmentRenderer {
|
||||||
|
id: segmentRenderer
|
||||||
|
segmentsCount: segmentBoundingBoxes.count
|
||||||
|
}
|
||||||
|
|
||||||
|
SegmentBoundingBoxes {
|
||||||
|
id: segmentBoundingBoxes
|
||||||
|
onReady: segImg.ready()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,6 +15,8 @@ Item {
|
|||||||
|
|
||||||
property alias delegate: listView.delegate
|
property alias delegate: listView.delegate
|
||||||
|
|
||||||
|
property bool autoscroll: true
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
@ -40,7 +42,11 @@ Item {
|
|||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
model: logsModel
|
model: logsModel
|
||||||
clip: true
|
clip: true
|
||||||
// onCountChanged: positionViewAtEnd()
|
onCountChanged: {
|
||||||
|
if (root.autoscroll) {
|
||||||
|
positionViewAtEnd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onContentYChanged: {
|
onContentYChanged: {
|
||||||
if (!logsModel.busy && contentY - originY < 5 * height) {
|
if (!logsModel.busy && contentY - originY < 5 * height) {
|
||||||
|
|||||||
@ -17,39 +17,9 @@ DevicePageBase {
|
|||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
// Item {
|
|
||||||
// Layout.fillWidth: true
|
|
||||||
// Layout.preferredHeight: root.inputVisible ? inputColumn.implicitHeight : 0
|
|
||||||
// Behavior on Layout.preferredHeight { NumberAnimation { duration: 130; easing.type: Easing.InOutQuad } }
|
|
||||||
|
|
||||||
// ColumnLayout {
|
|
||||||
// id: inputColumn
|
|
||||||
// anchors { left: parent.left; bottom: parent.bottom; right: parent.right }
|
|
||||||
|
|
||||||
// TextField {
|
|
||||||
// id: titleTextField
|
|
||||||
// Layout.fillWidth: true
|
|
||||||
// Layout.topMargin: app.margins
|
|
||||||
// Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
|
|
||||||
// placeholderText: qsTr("Title")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TextArea {
|
|
||||||
// id: bodyTextField
|
|
||||||
// Layout.fillWidth: true
|
|
||||||
// Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
|
|
||||||
// placeholderText: qsTr("Text")
|
|
||||||
// wrapMode: Text.WordWrap
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.margins: app.margins
|
Layout.leftMargin: app.margins; Layout.topMargin: app.margins; Layout.rightMargin: app.margins
|
||||||
text: qsTr("Manage access")
|
text: qsTr("Manage access")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
pageStack.push(manageUsersComponent)
|
pageStack.push(manageUsersComponent)
|
||||||
@ -69,12 +39,12 @@ DevicePageBase {
|
|||||||
live: true
|
live: true
|
||||||
Component.onCompleted: update()
|
Component.onCompleted: update()
|
||||||
typeIds: [root.accessGrantedEventType.id, root.accessDeniedEventType.id];
|
typeIds: [root.accessGrantedEventType.id, root.accessDeniedEventType.id];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: MeaListItemDelegate {
|
delegate: MeaListItemDelegate {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
iconName: "../images/notification.svg"
|
iconName: model.typeId === root.accessGrantedEventType.id ? "../images/tick.svg" : "../images/dialog-error-symbolic.svg"
|
||||||
|
iconColor: model.typeId === root.accessGrantedEventType.id ? "green" : "red"
|
||||||
text: model.typeId === root.accessGrantedEventType.id ? qsTr("Access granted for user %1").arg(model.value) : qsTr("Access denied")
|
text: model.typeId === root.accessGrantedEventType.id ? qsTr("Access granted for user %1").arg(model.value) : qsTr("Access denied")
|
||||||
subText: Qt.formatDateTime(model.timestamp)
|
subText: Qt.formatDateTime(model.timestamp)
|
||||||
progressive: false
|
progressive: false
|
||||||
@ -134,6 +104,28 @@ DevicePageBase {
|
|||||||
engine.deviceManager.executeAction(root.device.id, actionType.id, params)
|
engine.deviceManager.executeAction(root.device.id, actionType.id, params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 200
|
||||||
|
spacing: app.margins * 2
|
||||||
|
visible: parent.count === 0
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: width
|
||||||
|
FingerprintVisual {
|
||||||
|
id: fingerprintVisual
|
||||||
|
anchors.centerIn: parent
|
||||||
|
scale: parent.height / implicitHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: qsTr("Add a fingerprint")
|
||||||
|
onClicked: pageStack.push(addUserComponent)
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,8 +145,8 @@ DevicePageBase {
|
|||||||
Connections {
|
Connections {
|
||||||
target: engine.deviceManager
|
target: engine.deviceManager
|
||||||
onExecuteActionReply: {
|
onExecuteActionReply: {
|
||||||
addUserPage.error = params["deviceError"] !== DeviceManager.DeviceErrorNoError
|
addUserPage.error = params["deviceError"] !== "DeviceErrorNoError"
|
||||||
print("Execute action reply:", params);
|
print("Execute action reply:", params["deviceError"]);
|
||||||
addUserSwipeView.currentIndex++
|
addUserSwipeView.currentIndex++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,6 +156,7 @@ DevicePageBase {
|
|||||||
SwipeView {
|
SwipeView {
|
||||||
id: addUserSwipeView
|
id: addUserSwipeView
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: app.margins * 2
|
||||||
Layout.preferredHeight: 200
|
Layout.preferredHeight: 200
|
||||||
Layout.alignment: Qt.AlignTop
|
Layout.alignment: Qt.AlignTop
|
||||||
Item {
|
Item {
|
||||||
@ -182,6 +175,27 @@ DevicePageBase {
|
|||||||
id: userIdTextField
|
id: userIdTextField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: qsTr("Finger")
|
||||||
|
}
|
||||||
|
ComboBox {
|
||||||
|
id: fingerComboBox
|
||||||
|
Layout.fillWidth: true
|
||||||
|
model: ListModel {
|
||||||
|
ListElement { modelData: qsTr("Left thumb"); enumValue: "ThumbLeft" }
|
||||||
|
ListElement { modelData: qsTr("Left index finger"); enumValue: "IndexFingerLeft" }
|
||||||
|
ListElement { modelData: qsTr("Left middle finger"); enumValue: "MiddleFingerLeft" }
|
||||||
|
ListElement { modelData: qsTr("Left ring finger"); enumValue: "RingFingerLeft" }
|
||||||
|
ListElement { modelData: qsTr("Left pinky finger"); enumValue: "PinkyLeft" }
|
||||||
|
ListElement { modelData: qsTr("Right thumb"); enumValue: "ThumbRight" }
|
||||||
|
ListElement { modelData: qsTr("Right index finger"); enumValue: "IndexFingerRight" }
|
||||||
|
ListElement { modelData: qsTr("Right middle finger"); enumValue: "MiddleFingerRight" }
|
||||||
|
ListElement { modelData: qsTr("Right ring finger"); enumValue: "RingFingerRight" }
|
||||||
|
ListElement { modelData: qsTr("Right pinky finger"); enumValue: "PinkyRight" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: qsTr("Add user")
|
text: qsTr("Add user")
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@ -192,6 +206,10 @@ DevicePageBase {
|
|||||||
titleParam["paramTypeId"] = actionType.paramTypes.findByName("userId").id
|
titleParam["paramTypeId"] = actionType.paramTypes.findByName("userId").id
|
||||||
titleParam["value"] = userIdTextField.displayText
|
titleParam["value"] = userIdTextField.displayText
|
||||||
params.push(titleParam)
|
params.push(titleParam)
|
||||||
|
var fingerParam = {}
|
||||||
|
fingerParam["paramTypeId"] = actionType.paramTypes.findByName("finger").id
|
||||||
|
fingerParam["value"] = fingerComboBox.model.get(fingerComboBox.currentIndex).enumValue
|
||||||
|
params.push(fingerParam)
|
||||||
engine.deviceManager.executeAction(root.device.id, actionType.id, params)
|
engine.deviceManager.executeAction(root.device.id, actionType.id, params)
|
||||||
addUserSwipeView.currentIndex++
|
addUserSwipeView.currentIndex++
|
||||||
}
|
}
|
||||||
@ -206,9 +224,43 @@ DevicePageBase {
|
|||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: app.margins
|
anchors.margins: app.margins
|
||||||
|
spacing: app.margins * 2
|
||||||
Label {
|
Label {
|
||||||
text: qsTr("Please scan the fingerprint now")
|
text: qsTr("Please scan the fingerprint now")
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
font.pixelSize: app.largeFont
|
||||||
|
color: app.accentColor
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredWidth: 200
|
||||||
|
Layout.preferredHeight: 200
|
||||||
|
Layout.alignment: Qt.AlignCenter
|
||||||
|
|
||||||
|
FingerprintVisual {
|
||||||
|
id: fingerprintVisual
|
||||||
|
scale: parent.height / implicitHeight
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
Timer {
|
||||||
|
interval: 500
|
||||||
|
property real position: 0
|
||||||
|
running: addUserSwipeView.currentIndex == 1
|
||||||
|
repeat: true
|
||||||
|
onTriggered: {
|
||||||
|
var masks = [];
|
||||||
|
masks.push({x: 0, y: 0, width: 1, height: position})
|
||||||
|
position += 0.1
|
||||||
|
if (position < 1.1) {
|
||||||
|
fingerprintVisual.masks = masks
|
||||||
|
} else {
|
||||||
|
position = 0
|
||||||
|
fingerprintVisual.masks = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
nymea-app/ui/images/fingerprint/fingerprint_boxes.json
Normal file
1
nymea-app/ui/images/fingerprint/fingerprint_boxes.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"width": 451, "boxes": [[291, 515, 104, 50], [269, 510, 96, 34], [227, 506, 62, 16], [198, 505, 72, 60], [144, 498, 46, 67], [200, 467, 133, 32], [214, 449, 87, 28], [67, 432, 25, 104], [396, 420, 31, 89], [177, 395, 22, 103], [133, 390, 15, 96], [266, 375, 8, 31], [1, 356, 7, 42], [336, 355, 27, 114], [221, 350, 95, 105], [243, 323, 17, 106], [155, 317, 68, 248], [110, 317, 43, 243], [22, 314, 12, 155], [199, 310, 13, 127], [302, 296, 83, 223], [276, 295, 20, 136], [311, 275, 30, 189], [177, 266, 8, 114], [88, 263, 34, 287], [221, 249, 9, 86], [441, 238, 9, 169], [110, 231, 9, 70], [44, 226, 16, 280], [310, 225, 8, 181], [199, 223, 53, 86], [370, 217, 37, 288], [249, 208, 25, 153], [177, 201, 61, 49], [281, 195, 15, 85], [133, 183, 16, 196], [177, 178, 96, 29], [155, 167, 35, 134], [345, 166, 18, 173], [200, 156, 81, 31], [1, 149, 17, 192], [386, 145, 18, 60], [305, 142, 36, 118], [66, 139, 26, 277], [149, 134, 168, 77], [111, 132, 45, 85], [88, 130, 36, 120], [350, 118, 35, 162], [165, 111, 134, 28], [262, 92, 85, 64], [414, 91, 35, 133], [129, 89, 119, 36], [22, 88, 42, 211], [92, 67, 256, 62], [47, 64, 75, 149], [349, 47, 80, 360], [132, 44, 257, 90], [13, 41, 56, 95], [66, 22, 273, 60], [198, 1, 215, 76], [74, 1, 109, 39]], "height": 566}
|
||||||
BIN
nymea-app/ui/images/fingerprint/fingerprint_segmented.png
Normal file
BIN
nymea-app/ui/images/fingerprint/fingerprint_segmented.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 135 KiB |
Reference in New Issue
Block a user