diff --git a/lookandfeel/contents/lockscreen/LockScreenUi.qml b/lookandfeel/contents/lockscreen/LockScreenUi.qml index 9bc551e34..b454925ba 100644 --- a/lookandfeel/contents/lockscreen/LockScreenUi.qml +++ b/lookandfeel/contents/lockscreen/LockScreenUi.qml @@ -1,297 +1,385 @@ /******************************************************************** This file is part of the KDE project. Copyright (C) 2014 Aleix Pol Gonzalez This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ import QtQuick 2.5 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.private.sessions 2.0 import "../components" PlasmaCore.ColorScope { colorGroup: PlasmaCore.Theme.ComplementaryColorGroup Connections { target: authenticator onFailed: { root.notification = i18nd("plasma_lookandfeel_org.kde.lookandfeel","Unlocking failed"); } onGraceLockedChanged: { if (!authenticator.graceLocked) { root.notification = ""; root.clearPassword(); } } onMessage: { root.notification = msg; } onError: { root.notification = err; } } SessionsModel { id: sessionsModel showNewSessionEntry: true } PlasmaCore.DataSource { id: keystateSource engine: "keystate" connectedSources: "Caps Lock" } Loader { id: changeSessionComponent active: false source: "ChangeSession.qml" visible: false } Item { id: lockScreenRoot x: parent.x y: parent.y width: parent.width height: parent.height Component.onCompleted: PropertyAnimation { id: launchAnimation; target: lockScreenRoot; property: "opacity"; from: 0; to: 1; duration: 1000 } states: [ State { name: "onOtherSession" // for slide out animation PropertyChanges { target: lockScreenRoot; y: lockScreenRoot.height } // we also change the opacity just to be sure it's not visible even on unexpected screen dimension changes with possible race conditions PropertyChanges { target: lockScreenRoot; opacity: 0 } } ] transitions: Transition { // we only animate switchting to another session, because kscreenlocker doesn't get notified when // coming from another session back and so we wouldn't know when to trigger the animation exactly from: "" to: "onOtherSession" PropertyAnimation { id: stateChangeAnimation; properties: "y"; duration: 300; easing: Easing.InQuad} PropertyAnimation { properties: "opacity"; duration: 300} onRunningChanged: { // after the animation has finished switch session: since we only animate the transition TO state "onOtherSession" // and not the other way around, we don't have to check the state we transitioned into if (/* lockScreenRoot.state == "onOtherSession" && */ !running) { mainStack.currentItem.switchSession() } } } - ColumnLayout { - anchors.top: parent.top - anchors.bottom: parent.verticalCenter + Clock { + id: clock anchors.horizontalCenter: parent.horizontalCenter - Clock { - Layout.alignment: Qt.AlignBaseline - } - Item { - Layout.maximumHeight: units.gridUnit * 13 - Layout.fillHeight: true - } + y: (mainBlock.userList.y + mainStack.y)/2 - height/2 + visible: y > 0 + Layout.alignment: Qt.AlignBaseline } ListModel { id: users Component.onCompleted: { users.append({name: kscreenlocker_userName, realName: kscreenlocker_userName, icon: kscreenlocker_userImage, }) } } - ColumnLayout { - anchors.fill: parent - StackView { - id: mainStack - Layout.fillWidth: true - Layout.fillHeight: true - focus: true //StackView is an implicit focus scope, so we need to give this focus so the item inside will have it - initialItem: MainBlock { - id: mainBlock + StackView { + id: mainStack + anchors { + left: parent.left + right: parent.right + } + height: lockScreenRoot.height + focus: true //StackView is an implicit focus scope, so we need to give this focus so the item inside will have it - Stack.onStatusChanged: { - // prepare for presenting again to the user - if (Stack.status == Stack.Activating) { - mainPasswordBox.remove(0, mainPasswordBox.length) - mainPasswordBox.focus = true - } + initialItem: MainBlock { + id: mainBlock + + showUserList: userList.y + mainStack.y > 0 + + Stack.onStatusChanged: { + // prepare for presenting again to the user + if (Stack.status == Stack.Activating) { + mainPasswordBox.remove(0, mainPasswordBox.length) + mainPasswordBox.focus = true } - userListModel: users - notificationMessage: { - var text = "" - if (keystateSource.data["Caps Lock"]["Locked"]) { - text += i18nd("plasma_lookandfeel_org.kde.lookandfeel","Caps Lock is on") - if (root.notification) { - text += " • " - } + } + userListModel: users + notificationMessage: { + var text = "" + if (keystateSource.data["Caps Lock"]["Locked"]) { + text += i18nd("plasma_lookandfeel_org.kde.lookandfeel","Caps Lock is on") + if (root.notification) { + text += " • " } - text += root.notification - return text } + text += root.notification + return text + } - onLoginRequest: { - root.notification = "" - authenticator.tryUnlock(password) + onLoginRequest: { + root.notification = "" + authenticator.tryUnlock(password) + } + + actionItems: [ + ActionButton { + text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Switch User") + iconSource: "system-switch-user" + onClicked: mainStack.push(switchSessionPage) + // the current session isn't listed in the model, hence a check for greater than zero, not one + visible: (sessionsModel.count > 0 || sessionsModel.canStartNewSession) && sessionsModel.canSwitchUser } + ] - actionItems: [ - ActionButton { - text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Switch User") - iconSource: "system-switch-user" - onClicked: mainStack.push(switchSessionPage) - // the current session isn't listed in the model, hence a check for greater than zero, not one - visible: (sessionsModel.count > 0 || sessionsModel.canStartNewSession) && sessionsModel.canSwitchUser - } - ] + Loader { + Layout.fillWidth: true + Layout.preferredHeight: item ? item.implicitHeight : 0 + active: true // TODO configurable + source: "MediaControls.qml" + } + } + } - Loader { - Layout.fillWidth: true - Layout.preferredHeight: item ? item.implicitHeight : 0 - active: true // TODO configurable - source: "MediaControls.qml" + Loader { + id: inputPanel + state: "hidden" + readonly property bool keyboardActive: item ? item.active : false + anchors { + left: parent.left + right: parent.right + } + function showHide() { + state = state == "hidden" ? "visible" : "hidden"; + } + Component.onCompleted: inputPanel.source = "VirtualKeyboard.qml" + + states: [ + State { + name: "visible" + PropertyChanges { + target: mainStack + y: Math.min(0, lockScreenRoot.height - inputPanel.height - mainBlock.visibleBoundary) + } + PropertyChanges { + target: inputPanel + y: lockScreenRoot.height - inputPanel.height + opacity: 1 + } + }, + State { + name: "hidden" + PropertyChanges { + target: mainStack + y: 0 + } + PropertyChanges { + target: inputPanel + y: lockScreenRoot.height - lockScreenRoot.height/4 + opacity: 0 } } - } - Loader { - id: inputPanel - property bool keyboardActive: item ? item.active : false - Layout.fillWidth: true - Layout.preferredHeight: item ? (item.active ? item.implicitHeight : 0) : 0 - function showHide() { - if (Qt.inputMethod.visible) { - Qt.inputMethod.hide(); - } else { - item.activated = true; - Qt.inputMethod.show(); + ] + transitions: [ + Transition { + from: "hidden" + to: "visible" + SequentialAnimation { + ScriptAction { + script: { + inputPanel.item.activated = true; + Qt.inputMethod.show(); + } + } + ParallelAnimation { + NumberAnimation { + target: mainStack + property: "y" + duration: units.longDuration + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: inputPanel + property: "y" + duration: units.longDuration + easing.type: Easing.OutQuad + } + OpacityAnimator { + target: inputPanel + duration: units.longDuration + easing.type: Easing.OutQuad + } + } + } + }, + Transition { + from: "visible" + to: "hidden" + SequentialAnimation { + ParallelAnimation { + NumberAnimation { + target: mainStack + property: "y" + duration: units.longDuration + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: inputPanel + property: "y" + duration: units.longDuration + easing.type: Easing.InQuad + } + OpacityAnimator { + target: inputPanel + duration: units.longDuration + easing.type: Easing.InQuad + } + } + ScriptAction { + script: { + Qt.inputMethod.hide(); + } + } } } - Component.onCompleted: inputPanel.source = "../components/VirtualKeyboard.qml" - } + ] } Component { id: switchSessionPage SessionManagementScreen { property var switchSession: finalSwitchSession Stack.onStatusChanged: { if (Stack.status == Stack.Activating) { focus = true } } userListModel: sessionsModel // initiating animation of lockscreen for session switch function initSwitchSession() { lockScreenRoot.state = 'onOtherSession' } // initiating session switch and preparing lockscreen for possible return of user function finalSwitchSession() { mainStack.pop({immediate:true}) sessionsModel.switchUser(userListCurrentModelData.vtNumber) lockScreenRoot.state = '' } Keys.onLeftPressed: userList.decrementCurrentIndex() Keys.onRightPressed: userList.incrementCurrentIndex() Keys.onEnterPressed: initSwitchSession() Keys.onReturnPressed: initSwitchSession() Keys.onEscapePressed: mainStack.pop() PlasmaComponents.Button { Layout.fillWidth: true // the magic "-1" vtNumber indicates the "New Session" entry text: userListCurrentModelData.vtNumber === -1 ? i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Start New Session") : i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Switch Session") onClicked: initSwitchSession() } actionItems: [ ActionButton { iconSource: "go-previous" text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Back") onClicked: mainStack.pop() } ] } } Loader { active: root.viewVisible source: "LockOsd.qml" anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom } } RowLayout { id: footer anchors { bottom: parent.bottom left: parent.left right: parent.right margins: units.smallSpacing } PlasmaComponents.ToolButton { text: i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "Button to show/hide virtual keyboard", "Virtual Keyboard") iconName: inputPanel.keyboardActive ? "input-keyboard-virtual-on" : "input-keyboard-virtual-off" onClicked: inputPanel.showHide() + visible: inputPanel.status == Loader.Ready } KeyboardLayoutButton { } Item { Layout.fillWidth: true } Battery {} } } Component.onCompleted: { // version support checks if (root.interfaceVersion < 1) { // ksmserver of 5.4, with greeter of 5.5 root.viewVisible = true; } } } diff --git a/lookandfeel/contents/lockscreen/MainBlock.qml b/lookandfeel/contents/lockscreen/MainBlock.qml index 6b4563533..865ad643d 100644 --- a/lookandfeel/contents/lockscreen/MainBlock.qml +++ b/lookandfeel/contents/lockscreen/MainBlock.qml @@ -1,91 +1,95 @@ /* * Copyright 2016 David Edmundson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 or * (at your option) any later version. * * 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 General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.2 import QtQuick.Layouts 1.1 import QtQuick.Controls 1.1 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import "../components" SessionManagementScreen { property Item mainPasswordBox: passwordBox + + //the y position that should be ensured visible when the on screen keyboard is visible + property int visibleBoundary: mapFromItem(loginButton, 0, 0).y + onHeightChanged: visibleBoundary = mapFromItem(loginButton, 0, 0).y + loginButton.height + units.smallSpacing /* * Login has been requested with the following username and password * If username field is visible, it will be taken from that, otherwise from the "name" property of the currentIndex */ signal loginRequest(string password) function startLogin() { var password = passwordBox.text //this is partly because it looks nicer //but more importantly it works round a Qt bug that can trigger if the app is closed with a TextField focussed //See https://bugreports.qt.io/browse/QTBUG-55460 loginButton.forceActiveFocus(); loginRequest(password); } PlasmaComponents.TextField { id: passwordBox Layout.fillWidth: true placeholderText: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Password") focus: true echoMode: TextInput.Password inputMethodHints: Qt.ImhHiddenText | Qt.ImhSensitiveData | Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText enabled: !authenticator.graceLocked revealPasswordButtonShown: true onAccepted: startLogin() //if empty and left or right is pressed change selection in user switch //this cannot be in keys.onLeftPressed as then it doesn't reach the password box Keys.onPressed: { if (event.key == Qt.Key_Left && !text) { userList.decrementCurrentIndex(); event.accepted = true } if (event.key == Qt.Key_Right && !text) { userList.incrementCurrentIndex(); event.accepted = true } } Connections { target: root onClearPassword: { passwordBox.forceActiveFocus() passwordBox.selectAll() } } } PlasmaComponents.Button { id: loginButton Layout.fillWidth: true text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Unlock") onClicked: startLogin() } }