diff --git a/lookandfeel/contents/components/ActionButton.qml b/lookandfeel/contents/components/ActionButton.qml index b471c92b5..b9e5a872e 100644 --- a/lookandfeel/contents/components/ActionButton.qml +++ b/lookandfeel/contents/components/ActionButton.qml @@ -1,111 +1,112 @@ /* * 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.8 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents Item { id: root property alias text: label.text property alias iconSource: icon.source property alias containsMouse: mouseArea.containsMouse property alias font: label.font property alias labelRendering: label.renderType property alias circleOpacity: iconCircle.opacity property alias circleVisiblity: iconCircle.visible + property int fontSize: config.fontSize readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software signal clicked activeFocusOnTab: true property int iconSize: units.gridUnit * 3 implicitWidth: Math.max(iconSize + units.largeSpacing * 2, label.contentWidth) implicitHeight: iconSize + units.smallSpacing + label.implicitHeight opacity: activeFocus || containsMouse ? 1 : 0.85 Behavior on opacity { PropertyAnimation { // OpacityAnimator makes it turn black at random intervals duration: units.longDuration * 2 easing.type: Easing.InOutQuad } } Rectangle { id: iconCircle anchors.centerIn: icon width: iconSize + units.smallSpacing height: width radius: width / 2 color: softwareRendering ? PlasmaCore.ColorScope.backgroundColor : PlasmaCore.ColorScope.textColor opacity: activeFocus || containsMouse ? (softwareRendering ? 0.8 : 0.15) : (softwareRendering ? 0.6 : 0) Behavior on opacity { PropertyAnimation { // OpacityAnimator makes it turn black at random intervals duration: units.longDuration * 3 easing.type: Easing.InOutQuad } } } PlasmaCore.IconItem { id: icon anchors { top: parent.top horizontalCenter: parent.horizontalCenter } width: iconSize height: iconSize colorGroup: PlasmaCore.ColorScope.colorGroup active: mouseArea.containsMouse || root.activeFocus } PlasmaComponents.Label { id: label - font.pointSize: theme.defaultFont.pointSize + 1 + font.pointSize: Math.max(fontSize + 1,theme.defaultFont.pointSize + 1) anchors { top: icon.bottom topMargin: (softwareRendering ? 1.5 : 1) * units.smallSpacing left: parent.left right: parent.right } style: softwareRendering ? Text.Outline : Text.Normal styleColor: softwareRendering ? PlasmaCore.ColorScope.backgroundColor : "transparent" //no outline, doesn't matter horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignTop wrapMode: Text.WordWrap font.underline: root.activeFocus } MouseArea { id: mouseArea hoverEnabled: true onClicked: root.clicked() anchors.fill: parent } Keys.onEnterPressed: clicked() Keys.onReturnPressed: clicked() Keys.onSpacePressed: clicked() Accessible.onPressAction: clicked() Accessible.role: Accessible.Button Accessible.name: label.text } diff --git a/lookandfeel/contents/components/Battery.qml b/lookandfeel/contents/components/Battery.qml index 1adb46f21..2351c4dee 100644 --- a/lookandfeel/contents/components/Battery.qml +++ b/lookandfeel/contents/components/Battery.qml @@ -1,52 +1,53 @@ /* * Copyright 2016 Kai Uwe Broulik * * 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 org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.workspace.components 2.0 as PW Row { spacing: units.smallSpacing visible: pmSource.data["Battery"]["Has Cumulative"] PlasmaCore.DataSource { id: pmSource engine: "powermanagement" connectedSources: ["Battery", "AC Adapter"] } PW.BatteryIcon { id: battery hasBattery: pmSource.data["Battery"]["Has Battery"] || false percent: pmSource.data["Battery"]["Percent"] || 0 pluggedIn: pmSource.data["AC Adapter"] ? pmSource.data["AC Adapter"]["Plugged in"] : false height: batteryLabel.height width: height } PlasmaComponents.Label { id: batteryLabel + font.pointSize: config.fontSize height: undefined text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","%1%", battery.percent) Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Battery at %1%", battery.percent) } } diff --git a/lookandfeel/contents/components/KeyboardLayoutButton.qml b/lookandfeel/contents/components/KeyboardLayoutButton.qml index 89d5ac2f9..32edb5284 100644 --- a/lookandfeel/contents/components/KeyboardLayoutButton.qml +++ b/lookandfeel/contents/components/KeyboardLayoutButton.qml @@ -1,48 +1,52 @@ /*************************************************************************** * Copyright (C) 2014 by Daniel Vrátil * * * * 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ import QtQuick 2.1 import QtQuick.Controls 1.1 as QQC import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.workspace.keyboardlayout 1.0 PlasmaComponents.ToolButton { + + property int fontSize: config.fontSize + id: kbLayoutButton iconName: "input-keyboard" implicitWidth: minimumWidth text: layout.currentLayoutDisplayName + font.pointSize: Math.max(fontSize,theme.defaultFont.pointSize) Accessible.name: i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "Button to change keyboard layout", "Switch layout") visible: layout.layouts.length > 1 onClicked: layout.nextLayout() KeyboardLayout { id: layout function nextLayout() { var layouts = layout.layouts; var index = (layouts.indexOf(layout.currentLayout)+1) % layouts.length; layout.currentLayout = layouts[index]; } } } diff --git a/lookandfeel/contents/components/SessionManagementScreen.qml b/lookandfeel/contents/components/SessionManagementScreen.qml index e078140b4..572e954a3 100644 --- a/lookandfeel/contents/components/SessionManagementScreen.qml +++ b/lookandfeel/contents/components/SessionManagementScreen.qml @@ -1,118 +1,121 @@ /* * 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 Item { id: root /* * Any message to be displayed to the user, visible above the text fields */ property alias notificationMessage: notificationsLabel.text /* * A list of Items (typically ActionButtons) to be shown in a Row beneath the prompts */ property alias actionItems: actionItemsLayout.children /* * A model with a list of users to show in the view * The following roles should exist: * - name * - iconSource * * The following are also handled: * - vtNumber * - displayNumber * - session * - isTty */ property alias userListModel: userListView.model /* * Self explanatory */ property alias userListCurrentIndex: userListView.currentIndex property var userListCurrentModelData: userListView.currentItem === null ? [] : userListView.currentItem.m property bool showUserList: true property alias userList: userListView + property int fontSize: config.fontSize + default property alias _children: innerLayout.children UserList { id: userListView visible: showUserList && y > 0 anchors { bottom: parent.verticalCenter left: parent.left right: parent.right } } //goal is to show the prompts, in ~16 grid units high, then the action buttons //but collapse the space between the prompts and actions if there's no room //ui is constrained to 16 grid units wide, or the screen ColumnLayout { id: prompts anchors.top: parent.verticalCenter anchors.topMargin: units.gridUnit * 0.5 anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom PlasmaComponents.Label { id: notificationsLabel + font.pointSize: Math.max(fontSize + 1,theme.defaultFont.pointSize + 1) Layout.maximumWidth: units.gridUnit * 16 Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap font.italic: true } ColumnLayout { Layout.minimumHeight: implicitHeight Layout.maximumHeight: units.gridUnit * 10 Layout.maximumWidth: units.gridUnit * 16 Layout.alignment: Qt.AlignHCenter ColumnLayout { id: innerLayout Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true } Item { Layout.fillHeight: true } } Row { //deliberately not rowlayout as I'm not trying to resize child items id: actionItemsLayout spacing: units.smallSpacing Layout.alignment: Qt.AlignHCenter } Item { Layout.fillHeight: true } } } diff --git a/lookandfeel/contents/components/UserDelegate.qml b/lookandfeel/contents/components/UserDelegate.qml index 817e3887c..960315dff 100644 --- a/lookandfeel/contents/components/UserDelegate.qml +++ b/lookandfeel/contents/components/UserDelegate.qml @@ -1,188 +1,190 @@ /* * Copyright 2014 David Edmundson * Copyright 2014 Aleix Pol Gonzalez * * 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.8 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents Item { id: wrapper // If we're using software rendering, draw outlines instead of shadows // See https://bugs.kde.org/show_bug.cgi?id=398317 readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software property bool isCurrent: true readonly property var m: model property string name property string userName property string avatarPath property string iconSource property bool constrainText: true + property alias nameFontSize: usernameDelegate.font.pointSize + property int fontSize: config.fontSize signal clicked() property real faceSize: Math.min(width, height - usernameDelegate.height - units.smallSpacing) opacity: isCurrent ? 1.0 : 0.5 Behavior on opacity { OpacityAnimator { duration: units.longDuration } } // Draw a translucent background circle under the user picture Rectangle { anchors.centerIn: imageSource width: imageSource.width - 2 // Subtract to prevent fringing height: width radius: width / 2 color: PlasmaCore.ColorScope.backgroundColor opacity: 0.6 } Item { id: imageSource anchors { bottom: usernameDelegate.top bottomMargin: units.largeSpacing horizontalCenter: parent.horizontalCenter } Behavior on width { PropertyAnimation { from: faceSize duration: units.longDuration * 2; } } width: isCurrent ? faceSize : faceSize - units.largeSpacing height: width //Image takes priority, taking a full path to a file, if that doesn't exist we show an icon Image { id: face source: wrapper.avatarPath sourceSize: Qt.size(faceSize, faceSize) fillMode: Image.PreserveAspectCrop anchors.fill: parent } PlasmaCore.IconItem { id: faceIcon source: iconSource visible: (face.status == Image.Error || face.status == Image.Null) anchors.fill: parent anchors.margins: units.gridUnit * 0.5 // because mockup says so... colorGroup: PlasmaCore.ColorScope.colorGroup } } ShaderEffect { anchors { bottom: usernameDelegate.top bottomMargin: units.largeSpacing horizontalCenter: parent.horizontalCenter } width: imageSource.width height: imageSource.height supportsAtlasTextures: true property var source: ShaderEffectSource { sourceItem: imageSource // software rendering is just a fallback so we can accept not having a rounded avatar here hideSource: wrapper.GraphicsInfo.api !== GraphicsInfo.Software live: true // otherwise the user in focus will show a blurred avatar } property var colorBorder: PlasmaCore.ColorScope.textColor //draw a circle with an antialised border //innerRadius = size of the inner circle with contents //outerRadius = size of the border //blend = area to blend between two colours //all sizes are normalised so 0.5 == half the width of the texture //if copying into another project don't forget to connect themeChanged to update() //but in SDDM that's a bit pointless fragmentShader: " varying highp vec2 qt_TexCoord0; uniform highp float qt_Opacity; uniform lowp sampler2D source; uniform lowp vec4 colorBorder; highp float blend = 0.01; highp float innerRadius = 0.47; highp float outerRadius = 0.49; lowp vec4 colorEmpty = vec4(0.0, 0.0, 0.0, 0.0); void main() { lowp vec4 colorSource = texture2D(source, qt_TexCoord0.st); highp vec2 m = qt_TexCoord0 - vec2(0.5, 0.5); highp float dist = sqrt(m.x * m.x + m.y * m.y); if (dist < innerRadius) gl_FragColor = colorSource; else if (dist < innerRadius + blend) gl_FragColor = mix(colorSource, colorBorder, ((dist - innerRadius) / blend)); else if (dist < outerRadius) gl_FragColor = colorBorder; else if (dist < outerRadius + blend) gl_FragColor = mix(colorBorder, colorEmpty, ((dist - outerRadius) / blend)); else gl_FragColor = colorEmpty ; gl_FragColor = gl_FragColor * qt_Opacity; } " } PlasmaComponents.Label { id: usernameDelegate - font.pointSize: theme.defaultFont.pointSize + 2 + font.pointSize: Math.max(fontSize + 2,theme.defaultFont.pointSize + 2) anchors { bottom: parent.bottom horizontalCenter: parent.horizontalCenter } height: implicitHeight // work around stupid bug in Plasma Components that sets the height width: constrainText ? parent.width : implicitWidth text: wrapper.name style: softwareRendering ? Text.Outline : Text.Normal styleColor: softwareRendering ? PlasmaCore.ColorScope.backgroundColor : "transparent" //no outline, doesn't matter elide: Text.ElideRight horizontalAlignment: Text.AlignHCenter //make an indication that this has active focus, this only happens when reached with keyboard navigation font.underline: wrapper.activeFocus } MouseArea { anchors.fill: parent hoverEnabled: true onClicked: wrapper.clicked(); } Accessible.name: name Accessible.role: Accessible.Button function accessiblePressAction() { wrapper.clicked() } } diff --git a/lookandfeel/contents/lockscreen/LockScreenUi.qml b/lookandfeel/contents/lockscreen/LockScreenUi.qml index 11df77234..798529055 100644 --- a/lookandfeel/contents/lockscreen/LockScreenUi.qml +++ b/lookandfeel/contents/lockscreen/LockScreenUi.qml @@ -1,480 +1,481 @@ /******************************************************************** 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.8 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 import QtGraphicalEffects 1.0 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 { // If we're using software rendering, draw outlines instead of shadows // See https://bugs.kde.org/show_bug.cgi?id=398317 readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software 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 } MouseArea { id: lockScreenRoot property bool uiVisible: false property bool blockUI: mainStack.depth > 1 || mainBlock.mainPasswordBox.text.length > 0 || inputPanel.keyboardActive x: parent.x y: parent.y width: parent.width height: parent.height hoverEnabled: true drag.filterChildren: true onPressed: uiVisible = true; onPositionChanged: uiVisible = true; onUiVisibleChanged: { if (blockUI) { fadeoutTimer.running = false; } else if (uiVisible) { fadeoutTimer.restart(); } } onBlockUIChanged: { if (blockUI) { fadeoutTimer.running = false; uiVisible = true; } else { fadeoutTimer.restart(); } } Keys.onEscapePressed: { uiVisible = !uiVisible; if (inputPanel.keyboardActive) { inputPanel.showHide(); } if (!uiVisible) { mainBlock.mainPasswordBox.text = ""; } } Keys.onPressed: { uiVisible = true; event.accepted = false; } Timer { id: fadeoutTimer interval: 10000 onTriggered: { if (!lockScreenRoot.blockUI) { lockScreenRoot.uiVisible = false; } } } 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.type: 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() } } } WallpaperFader { anchors.fill: parent state: lockScreenRoot.uiVisible ? "on" : "off" source: wallpaper mainStack: mainStack footer: footer clock: clock } DropShadow { id: clockShadow anchors.fill: clock source: clock visible: !softwareRendering horizontalOffset: 1 verticalOffset: 1 radius: 6 samples: 14 spread: 0.3 color: "black" // matches Breeze window decoration and desktopcontainment Behavior on opacity { OpacityAnimator { duration: 1000 easing.type: Easing.InOutQuad } } } Clock { id: clock property Item shadow: clockShadow anchors.horizontalCenter: parent.horizontalCenter 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, }) } } 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 initialItem: MainBlock { id: mainBlock lockScreenUiVisible: lockScreenRoot.uiVisible 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 += " • " } } text += root.notification return text } 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 } ] Loader { Layout.fillWidth: true Layout.preferredHeight: item ? item.implicitHeight : 0 active: config.showMediaControls source: "MediaControls.qml" } } Component.onCompleted: { if (defaultToSwitchUser) { //context property mainStack.push({ item: switchSessionPage, immediate: true}); } } } 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 = "../components/VirtualKeyboard.qml" onKeyboardActiveChanged: { if (keyboardActive) { state = "visible"; } else { state = "hidden"; } } 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 } } ] 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 { 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 + font.pointSize: theme.defaultFont.pointSize + 1 // 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 bottomMargin: units.largeSpacing } } 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/MediaControls.qml b/lookandfeel/contents/lockscreen/MediaControls.qml index 674020478..56935f391 100644 --- a/lookandfeel/contents/lockscreen/MediaControls.qml +++ b/lookandfeel/contents/lockscreen/MediaControls.qml @@ -1,159 +1,161 @@ /******************************************************************** This file is part of the KDE project. Copyright (C) 2016 Kai Uwe Broulik 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.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.extras 2.0 as PlasmaExtras Item { visible: mpris2Source.hasPlayer implicitHeight: controlsRow.height + controlsRow.y RowLayout { id: controlsRow anchors.bottom: parent.bottom y: units.smallSpacing // some distance to the password field width: parent.width height: units.gridUnit * 3 spacing: 0 enabled: mpris2Source.canControl PlasmaCore.DataSource { id: mpris2Source readonly property string source: "@multiplex" readonly property var playerData: data[source] readonly property bool hasPlayer: sources.length > 1 && !!playerData readonly property string identity: hasPlayer ? playerData.Identity : "" readonly property bool playing: hasPlayer && playerData.PlaybackStatus === "Playing" readonly property bool canControl: hasPlayer && playerData.CanControl readonly property bool canGoBack: hasPlayer && playerData.CanGoPrevious readonly property bool canGoNext: hasPlayer && playerData.CanGoNext readonly property var currentMetadata: hasPlayer ? playerData.Metadata : ({}) readonly property string track: { var xesamTitle = currentMetadata["xesam:title"] if (xesamTitle) { return xesamTitle } // if no track title is given, print out the file name var xesamUrl = currentMetadata["xesam:url"] ? currentMetadata["xesam:url"].toString() : "" if (!xesamUrl) { return "" } var lastSlashPos = xesamUrl.lastIndexOf('/') if (lastSlashPos < 0) { return "" } var lastUrlPart = xesamUrl.substring(lastSlashPos + 1) return decodeURIComponent(lastUrlPart) } readonly property string artist: currentMetadata["xesam:artist"] || "" readonly property string albumArt: currentMetadata["mpris:artUrl"] || "" engine: "mpris2" connectedSources: [source] function startOperation(op) { var service = serviceForSource(source) var operation = service.operationDescription(op) return service.startOperationCall(operation) } function goPrevious() { startOperation("Previous"); } function goNext() { startOperation("Next"); } function playPause(source) { startOperation("PlayPause"); } } Image { id: albumArt Layout.preferredWidth: height Layout.fillHeight: true asynchronous: true source: mpris2Source.albumArt sourceSize: Qt.size(width, height) visible: status === Image.Loading || status === Image.Ready } Item { // spacer width: units.smallSpacing height: 1 } ColumnLayout { Layout.fillWidth: true spacing: 0 PlasmaExtras.Heading { Layout.fillWidth: true - level: 4 + level: 5 wrapMode: Text.NoWrap elide: Text.ElideRight text: mpris2Source.track || i18n("No media playing") textFormat: Text.PlainText + font.weight: Font.Bold } PlasmaExtras.Heading { Layout.fillWidth: true level: 5 wrapMode: Text.NoWrap elide: Text.ElideRight // if no artist is given, show player name instead text: mpris2Source.artist || mpris2Source.identity || "" textFormat: Text.PlainText + opacity: 0.75 } } PlasmaComponents.ToolButton { enabled: mpris2Source.canGoBack iconName: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward" onClicked: mpris2Source.goPrevious() visible: mpris2Source.canGoBack || mpris2Source.canGoNext Accessible.name: i18n("Previous track") } PlasmaComponents.ToolButton { Layout.fillHeight: true Layout.preferredWidth: height // make this button bigger iconName: mpris2Source.playing ? "media-playback-pause" : "media-playback-start" onClicked: mpris2Source.playPause() Accessible.name: i18n("Play or Pause media") } PlasmaComponents.ToolButton { enabled: mpris2Source.canGoNext iconName: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward" onClicked: mpris2Source.goNext() visible: mpris2Source.canGoBack || mpris2Source.canGoNext Accessible.name: i18n("Next track") } } } diff --git a/lookandfeel/contents/logout/Logout.qml b/lookandfeel/contents/logout/Logout.qml index fd3a8584e..a63ee909e 100644 --- a/lookandfeel/contents/logout/Logout.qml +++ b/lookandfeel/contents/logout/Logout.qml @@ -1,261 +1,262 @@ /*************************************************************************** * Copyright (C) 2014 by 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, 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.2 import QtQuick.Controls 1.1 as Controls import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.kcoreaddons 1.0 as KCoreAddons import "../components" import "timer.js" as AutoTriggerTimer import org.kde.plasma.private.sessions 2.0 PlasmaCore.ColorScope { id: root colorGroup: PlasmaCore.Theme.ComplementaryColorGroup height: screenGeometry.height width: screenGeometry.width signal logoutRequested() signal haltRequested() signal suspendRequested(int spdMethod) signal rebootRequested() signal rebootRequested2(int opt) signal cancelRequested() signal lockScreenRequested() property alias backgroundColor: backgroundRect.color function sleepRequested() { root.suspendRequested(2); } function hibernateRequested() { root.suspendRequested(4); } property real timeout: 30 property real remainingTime: root.timeout property var currentAction: { switch (sdtype) { case ShutdownType.ShutdownTypeReboot: return root.rebootRequested; case ShutdownType.ShutdownTypeHalt: return root.haltRequested; default: return root.logoutRequested; } } KCoreAddons.KUser { id: kuser } // For showing a "other users are logged in" hint SessionsModel { id: sessionsModel includeUnusedSessions: false } Controls.Action { onTriggered: root.cancelRequested() shortcut: "Escape" } onRemainingTimeChanged: { if (remainingTime <= 0) { root.currentAction(); } } Timer { id: countDownTimer running: true repeat: true interval: 1000 onTriggered: remainingTime-- Component.onCompleted: { AutoTriggerTimer.addCancelAutoTriggerCallback(function() { countDownTimer.running = false; }); } } function isLightColor(color) { return Math.max(color.r, color.g, color.b) > 0.5 } Rectangle { id: backgroundRect anchors.fill: parent //use "black" because this is intended to look like a general darkening of the scene. a dark gray as normal background would just look too "washed out" color: root.isLightColor(PlasmaCore.ColorScope.backgroundColor) ? PlasmaCore.ColorScope.backgroundColor : "black" opacity: 0.5 } MouseArea { anchors.fill: parent onClicked: root.cancelRequested() } UserDelegate { width: units.iconSizes.enormous height: width + nameFontSize: theme.defaultFont.pointSize + 2 anchors { horizontalCenter: parent.horizontalCenter bottom: parent.verticalCenter } constrainText: false avatarPath: kuser.faceIconUrl iconSource: "user-identity" isCurrent: true name: kuser.fullName } ColumnLayout { anchors { top: parent.verticalCenter topMargin: units.gridUnit * 2 horizontalCenter: parent.horizontalCenter } spacing: units.largeSpacing height: Math.max(implicitHeight, units.gridUnit * 10) width: Math.max(implicitWidth, units.gridUnit * 16) PlasmaComponents.Label { font.pointSize: theme.defaultFont.pointSize + 1 Layout.maximumWidth: units.gridUnit * 16 Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap font.italic: true text: i18ndp("plasma_lookandfeel_org.kde.lookandfeel", "One other user is currently logged in. If the computer is shut down or restarted, that user may lose work.", "%1 other users are currently logged in. If the computer is shut down or restarted, those users may lose work.", sessionsModel.count) visible: sessionsModel.count > 1 } PlasmaComponents.Label { font.pointSize: theme.defaultFont.pointSize + 1 Layout.maximumWidth: units.gridUnit * 16 Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap font.italic: true text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "When restarted, the computer will enter the firmware setup screen.") visible: rebootToFirmwareSetup } RowLayout { spacing: units.largeSpacing * 2 Layout.alignment: Qt.AlignHCenter LogoutButton { id: suspendButton iconSource: "system-suspend" text: i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "Suspend to RAM", "Sleep") action: root.sleepRequested KeyNavigation.left: logoutButton KeyNavigation.right: hibernateButton visible: spdMethods.SuspendState } LogoutButton { id: hibernateButton iconSource: "system-suspend-hibernate" text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Hibernate") action: root.hibernateRequested KeyNavigation.left: suspendButton KeyNavigation.right: rebootButton visible: spdMethods.HibernateState } LogoutButton { id: rebootButton iconSource: "system-reboot" text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Restart") action: root.rebootRequested KeyNavigation.left: hibernateButton KeyNavigation.right: shutdownButton focus: sdtype === ShutdownType.ShutdownTypeReboot visible: maysd } LogoutButton { id: shutdownButton iconSource: "system-shutdown" text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Shut Down") action: root.haltRequested KeyNavigation.left: rebootButton KeyNavigation.right: logoutButton focus: sdtype === ShutdownType.ShutdownTypeHalt visible: maysd } LogoutButton { id: logoutButton iconSource: "system-log-out" text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Log Out") action: root.logoutRequested KeyNavigation.left: shutdownButton KeyNavigation.right: suspendButton focus: sdtype === ShutdownType.ShutdownTypeNone visible: canLogout } } PlasmaComponents.Label { font.pointSize: theme.defaultFont.pointSize + 1 Layout.alignment: Qt.AlignHCenter //opacity, as visible would re-layout opacity: countDownTimer.running ? 1 : 0 Behavior on opacity { OpacityAnimator { duration: units.longDuration easing.type: Easing.InOutQuad } } text: { switch (sdtype) { case ShutdownType.ShutdownTypeReboot: return i18ndp("plasma_lookandfeel_org.kde.lookandfeel", "Restarting in 1 second", "Restarting in %1 seconds", root.remainingTime); case ShutdownType.ShutdownTypeHalt: return i18ndp("plasma_lookandfeel_org.kde.lookandfeel", "Shutting down in 1 second", "Shutting down in %1 seconds", root.remainingTime); default: return i18ndp("plasma_lookandfeel_org.kde.lookandfeel", "Logging out in 1 second", "Logging out in %1 seconds", root.remainingTime); } } } RowLayout { Layout.alignment: Qt.AlignHCenter PlasmaComponents.Button { font.pointSize: theme.defaultFont.pointSize + 1 enabled: root.currentAction !== null text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "OK") onClicked: root.currentAction() } PlasmaComponents.Button { font.pointSize: theme.defaultFont.pointSize + 1 text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Cancel") onClicked: root.cancelRequested() } } } } diff --git a/lookandfeel/contents/logout/LogoutButton.qml b/lookandfeel/contents/logout/LogoutButton.qml index 210ec8254..316ab9329 100644 --- a/lookandfeel/contents/logout/LogoutButton.qml +++ b/lookandfeel/contents/logout/LogoutButton.qml @@ -1,45 +1,46 @@ /*************************************************************************** * Copyright (C) 2016 Marco Martin * * * * 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, 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.2 import org.kde.plasma.core 2.0 as PlasmaCore import "../components" import "timer.js" as AutoTriggerTimer ActionButton { property var action onClicked: action() Layout.alignment: Qt.AlignTop iconSize: units.iconSizes.huge circleVisiblity: activeFocus || containsMouse circleOpacity: 0.15 // Selected option's circle is instantly visible opacity: activeFocus || containsMouse ? 1 : 0.5 labelRendering: Text.QtRendering // Remove once we've solved Qt bug: https://bugreports.qt.io/browse/QTBUG-70138 (KDE bug: https://bugs.kde.org/show_bug.cgi?id=401644) font.underline: false + font.pointSize: theme.defaultFont.pointSize + 1 Behavior on opacity { OpacityAnimator { duration: units.longDuration easing.type: Easing.InOutQuad } } Keys.onPressed: AutoTriggerTimer.cancelAutoTrigger(); } diff --git a/sddm-theme/KeyboardButton.qml b/sddm-theme/KeyboardButton.qml index b35cb7852..13d66c2c4 100644 --- a/sddm-theme/KeyboardButton.qml +++ b/sddm-theme/KeyboardButton.qml @@ -1,36 +1,37 @@ import QtQuick 2.2 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import QtQuick.Controls 1.3 as QQC PlasmaComponents.ToolButton { id: keyboardButton property int currentIndex: -1 text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Keyboard Layout: %1", instantiator.objectAt(currentIndex).shortName) implicitWidth: minimumWidth + font.pointSize: config.fontSize visible: menu.items.length > 1 Component.onCompleted: currentIndex = Qt.binding(function() {return keyboard.currentLayout}); menu: QQC.Menu { id: keyboardMenu Instantiator { id: instantiator model: keyboard.layouts onObjectAdded: keyboardMenu.insertItem(index, object) onObjectRemoved: keyboardMenu.removeItem( object ) delegate: QQC.MenuItem { text: modelData.longName property string shortName: modelData.shortName onTriggered: { keyboard.currentLayout = model.index } } } } } diff --git a/sddm-theme/Login.qml b/sddm-theme/Login.qml index 758b11999..ca881e0ab 100644 --- a/sddm-theme/Login.qml +++ b/sddm-theme/Login.qml @@ -1,120 +1,122 @@ import "components" import QtQuick 2.0 import QtQuick.Layouts 1.2 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents SessionManagementScreen { id: root property Item mainPasswordBox: passwordBox property bool showUsernamePrompt: !showUserList property string lastUserName property bool loginScreenUiVisible: false //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 + property int fontSize: config.fontSize + signal loginRequest(string username, string password) onShowUsernamePromptChanged: { if (!showUsernamePrompt) { lastUserName = "" } } /* * 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 */ function startLogin() { var username = showUsernamePrompt ? userNameInput.text : userList.selectedUser 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 focused //DAVE REPORT THE FRICKING THING AND PUT A LINK loginButton.forceActiveFocus(); loginRequest(username, password); } PlasmaComponents.TextField { id: userNameInput - font.pointSize: theme.defaultFont.pointSize + 1 + font.pointSize: fontSize + 1 Layout.fillWidth: true text: lastUserName visible: showUsernamePrompt focus: showUsernamePrompt && !lastUserName //if there's a username prompt it gets focus first, otherwise password does placeholderText: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Username") onAccepted: if (root.loginScreenUiVisible) { passwordBox.forceActiveFocus() } } RowLayout { Layout.fillWidth: true PlasmaComponents.TextField { id: passwordBox - font.pointSize: theme.defaultFont.pointSize + 1 + font.pointSize: fontSize + 1 Layout.fillWidth: true placeholderText: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Password") focus: !showUsernamePrompt || lastUserName echoMode: TextInput.Password revealPasswordButtonShown: true onAccepted: { if (root.loginScreenUiVisible) { startLogin(); } } Keys.onEscapePressed: { mainStack.currentItem.forceActiveFocus(); } //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: sddm onLoginFailed: { passwordBox.selectAll() passwordBox.forceActiveFocus() } } } PlasmaComponents.Button { id: loginButton Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Log In") implicitHeight: passwordBox.height - units.smallSpacing * 0.5 // otherwise it comes out taller than the password field Layout.rightMargin: 1 // prevents it from extending beyond the username field PlasmaCore.IconItem { // no iconSource because if you take away half a unit (implicitHeight), "go-next" gets cut off anchors.fill: parent anchors.margins: units.smallSpacing source: "go-next" } onClicked: startLogin(); } } } diff --git a/sddm-theme/SessionButton.qml b/sddm-theme/SessionButton.qml index f5466cea7..5965af6e0 100644 --- a/sddm-theme/SessionButton.qml +++ b/sddm-theme/SessionButton.qml @@ -1,56 +1,58 @@ /* * 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 org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import QtQuick.Controls 1.3 as QQC PlasmaComponents.ToolButton { id: root property int currentIndex: -1 implicitWidth: minimumWidth visible: menu.items.length > 1 text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Desktop Session: %1", instantiator.objectAt(currentIndex).text || "") + font.pointSize: config.fontSize + Component.onCompleted: { currentIndex = sessionModel.lastIndex } menu: QQC.Menu { id: menu Instantiator { id: instantiator model: sessionModel onObjectAdded: menu.insertItem(index, object) onObjectRemoved: menu.removeItem( object ) delegate: QQC.MenuItem { text: model.name onTriggered: { root.currentIndex = model.index } } } } } diff --git a/sddm-theme/theme.conf.cmake b/sddm-theme/theme.conf.cmake index 69d307052..d21cd81c8 100644 --- a/sddm-theme/theme.conf.cmake +++ b/sddm-theme/theme.conf.cmake @@ -1,4 +1,5 @@ [General] type=image color=#1d99f3 +fontSize=10 background=${CMAKE_INSTALL_PREFIX}/${WALLPAPER_INSTALL_DIR}/Next/contents/images/3200x2000.png