404 lines
14 KiB
QML
404 lines
14 KiB
QML
/*
|
|
Costruttivista — tema SDDM
|
|
Schermata di accesso in stile costruttivista sovietico per SDDM.
|
|
Autosufficiente: solo QtQuick 2.15 e QtQuick.Controls 2.15, nessuna dipendenza esterna.
|
|
*/
|
|
|
|
import QtQuick 2.15
|
|
import QtQuick.Layouts 1.15
|
|
|
|
Rectangle {
|
|
id: root
|
|
color: "#101012"
|
|
width: 1920
|
|
height: 1080
|
|
|
|
readonly property color red: "#d83a28"
|
|
readonly property color cream: "#f0dfba"
|
|
readonly property color dark: "#181719"
|
|
readonly property color panelBg: "#d9151517"
|
|
readonly property color paper: "#f6f1e5"
|
|
|
|
// Font pseudo-cirillico (Russo One, OFL) incluso nel tema.
|
|
FontLoader {
|
|
id: themeFont
|
|
source: "assets/fonts/RussoOne-Regular.ttf"
|
|
}
|
|
|
|
// Utente selezionato dalla lista (quando il modello espone utenti).
|
|
property string selectedUser: userModel.lastUser || ""
|
|
|
|
// Data in italiano senza dipendere dalla locale del greeter.
|
|
function italianDate(d) {
|
|
var days = ["DOMENICA", "LUNEDÌ", "MARTEDÌ", "MERCOLEDÌ", "GIOVEDÌ", "VENERDÌ", "SABATO"]
|
|
var months = ["GENNAIO", "FEBBRAIO", "MARZO", "APRILE", "MAGGIO", "GIUGNO", "LUGLIO", "AGOSTO", "SETTEMBRE", "OTTOBRE", "NOVEMBRE", "DICEMBRE"]
|
|
return days[d.getDay()] + " " + d.getDate() + " " + months[d.getMonth()] + " " + d.getFullYear()
|
|
}
|
|
|
|
function tryLogin() {
|
|
var username = userModel.count > 0 ? root.selectedUser : userField.text
|
|
if (username.length === 0) {
|
|
errorText.text = "INSERISCI L'UTENTE"
|
|
shake.start()
|
|
return
|
|
}
|
|
errorText.text = ""
|
|
sddm.login(username, passField.text, sessionCombo.currentIndex)
|
|
}
|
|
|
|
Item {
|
|
id: ui
|
|
anchors.fill: parent
|
|
opacity: 0
|
|
|
|
// ---------- sfondo ----------
|
|
Image {
|
|
id: background
|
|
anchors.fill: parent
|
|
source: "assets/sfondo_costruttivista_fhd.png"
|
|
fillMode: Image.PreserveAspectCrop
|
|
asynchronous: true
|
|
cache: true
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
color: "#12000000"
|
|
opacity: overlayFade.value
|
|
}
|
|
|
|
// ---------- accenti geometrici ----------
|
|
Item {
|
|
id: accents
|
|
anchors.fill: parent
|
|
|
|
Rectangle {
|
|
id: redBar
|
|
x: parent.width * 0.07
|
|
y: parent.height * 0.15
|
|
width: Math.max(5, parent.width * 0.008)
|
|
height: parent.height * 0.27
|
|
color: root.red
|
|
opacity: 0.9
|
|
transform: Translate { id: redShift; y: -root.height * 0.10 }
|
|
}
|
|
|
|
Rectangle {
|
|
id: creamLine
|
|
x: parent.width * 0.07
|
|
y: parent.height * 0.44
|
|
width: parent.width * 0.20
|
|
height: Math.max(2, parent.height * 0.004)
|
|
color: root.cream
|
|
opacity: 0.85
|
|
transform: Translate { id: lineShift; x: -root.width * 0.12 }
|
|
}
|
|
|
|
Rectangle {
|
|
id: darkBar
|
|
x: parent.width * 0.82
|
|
y: parent.height * 0.60
|
|
width: Math.max(4, parent.width * 0.005)
|
|
height: parent.height * 0.20
|
|
color: root.dark
|
|
opacity: 0.75
|
|
transform: Translate { id: darkShift; y: root.height * 0.09 }
|
|
}
|
|
}
|
|
|
|
// ---------- orologio ----------
|
|
Rectangle {
|
|
id: clockBackdrop
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: parent.top
|
|
anchors.topMargin: Math.max(36, root.height * 0.055)
|
|
width: clockBlock.width + Math.max(36, root.width * 0.02)
|
|
height: clockBlock.height + Math.max(22, root.height * 0.022)
|
|
color: "#66101012"
|
|
border.color: "#33f0dfba"
|
|
border.width: 1
|
|
|
|
Column {
|
|
id: clockBlock
|
|
anchors.centerIn: parent
|
|
spacing: Math.max(2, root.height * 0.004)
|
|
|
|
Text {
|
|
id: clockText
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
text: Qt.formatTime(new Date(), "HH:mm")
|
|
color: root.cream
|
|
font.family: themeFont.name
|
|
font.pixelSize: Math.max(44, Math.min(root.width * 0.05, 88))
|
|
font.bold: true
|
|
font.letterSpacing: 4
|
|
}
|
|
|
|
Text {
|
|
id: dateText
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
text: root.italianDate(new Date())
|
|
color: "#ef6a55"
|
|
font.family: themeFont.name
|
|
font.pixelSize: Math.max(13, Math.min(root.width * 0.014, 22))
|
|
font.bold: true
|
|
font.letterSpacing: 2
|
|
}
|
|
|
|
Timer {
|
|
interval: 1000
|
|
repeat: true
|
|
running: true
|
|
onTriggered: {
|
|
clockText.text = Qt.formatTime(new Date(), "HH:mm")
|
|
dateText.text = root.italianDate(new Date())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------- pannello di accesso ----------
|
|
Item {
|
|
id: panelHost
|
|
anchors.centerIn: parent
|
|
width: panel.width
|
|
height: panel.height
|
|
transform: Translate { id: panelShift; x: 0 }
|
|
|
|
Rectangle {
|
|
id: panel
|
|
width: Math.min(root.width * 0.40, 500)
|
|
height: panelCol.implicitHeight + Math.max(52, root.height * 0.056)
|
|
color: root.panelBg
|
|
border.color: "#66f0dfba"
|
|
border.width: Math.max(1, root.height * 0.002)
|
|
|
|
Rectangle {
|
|
width: Math.max(8, panel.width * 0.02)
|
|
anchors.left: parent.left
|
|
anchors.top: parent.top
|
|
anchors.bottom: parent.bottom
|
|
color: root.red
|
|
}
|
|
|
|
Column {
|
|
id: panelCol
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.top: parent.top
|
|
anchors.leftMargin: Math.max(26, root.height * 0.028)
|
|
anchors.rightMargin: Math.max(26, root.height * 0.028)
|
|
anchors.topMargin: Math.max(26, root.height * 0.028)
|
|
spacing: Math.max(14, root.height * 0.016)
|
|
|
|
// intestazione: marca + titolo
|
|
RowLayout {
|
|
width: parent.width
|
|
spacing: Math.max(16, root.width * 0.011)
|
|
|
|
Mark {
|
|
Layout.preferredWidth: Math.max(58, Math.min(panel.width * 0.15, 96))
|
|
Layout.preferredHeight: Layout.preferredWidth
|
|
Layout.alignment: Qt.AlignVCenter
|
|
}
|
|
|
|
Column {
|
|
Layout.fillWidth: true
|
|
Layout.alignment: Qt.AlignVCenter
|
|
spacing: 2
|
|
|
|
Text {
|
|
text: "ACCESSO"
|
|
color: root.cream
|
|
font.family: themeFont.name
|
|
font.pixelSize: Math.max(20, Math.min(root.width * 0.024, 34))
|
|
font.bold: true
|
|
font.letterSpacing: 3
|
|
}
|
|
|
|
Text {
|
|
text: "INSERISCI LE CREDENZIALI"
|
|
color: "#e85a48"
|
|
font.family: themeFont.name
|
|
font.pixelSize: Math.max(10, Math.min(root.width * 0.011, 15))
|
|
font.bold: true
|
|
font.letterSpacing: 1
|
|
}
|
|
}
|
|
}
|
|
|
|
// lista utenti con avatar (solo se il modello espone utenti)
|
|
Item {
|
|
width: parent.width
|
|
height: userListRow.height
|
|
|
|
UserList {
|
|
id: userListRow
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
onUserSelected: root.selectedUser = name
|
|
}
|
|
}
|
|
|
|
// campo utente (solo se non ci sono utenti da selezionare)
|
|
Field {
|
|
id: userField
|
|
visible: userModel.count === 0
|
|
label: "UTENTE"
|
|
text: userModel.lastUser || ""
|
|
}
|
|
|
|
// campo password
|
|
Field {
|
|
id: passField
|
|
label: "PASSWORD"
|
|
password: true
|
|
onAccepted: root.tryLogin()
|
|
}
|
|
|
|
// selettore sessione
|
|
Combo {
|
|
id: sessionCombo
|
|
label: "SESSIONE"
|
|
model: sessionModel
|
|
textRole: "name"
|
|
currentIndex: sessionModel.lastIndex >= 0 ? sessionModel.lastIndex : 0
|
|
}
|
|
|
|
// messaggio di errore
|
|
Text {
|
|
id: errorText
|
|
width: parent.width
|
|
text: ""
|
|
color: root.red
|
|
font.bold: true
|
|
font.letterSpacing: 1
|
|
font.family: themeFont.name
|
|
font.pixelSize: Math.max(11, Math.min(root.width * 0.012, 16))
|
|
visible: text.length > 0
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
|
|
// pulsante di accesso
|
|
Rectangle {
|
|
id: loginButton
|
|
width: parent.width
|
|
height: Math.max(42, root.height * 0.05)
|
|
color: root.red
|
|
border.color: root.cream
|
|
border.width: 1
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: "ENTRA"
|
|
color: root.paper
|
|
font.bold: true
|
|
font.letterSpacing: 3
|
|
font.family: themeFont.name
|
|
font.pixelSize: Math.max(14, Math.min(root.width * 0.015, 20))
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
onClicked: root.tryLogin()
|
|
onPressed: loginButton.color = Qt.darker(root.red, 1.3)
|
|
onReleased: loginButton.color = root.red
|
|
}
|
|
}
|
|
|
|
// separatore
|
|
Rectangle {
|
|
width: parent.width
|
|
height: 2
|
|
color: "#66f0dfba"
|
|
}
|
|
|
|
// pulsanti di sistema
|
|
RowLayout {
|
|
width: parent.width
|
|
spacing: Math.max(8, root.width * 0.006)
|
|
|
|
PowerButton {
|
|
Layout.fillWidth: true
|
|
label: "SOSPENDI"
|
|
enabled: sddm.canSuspend
|
|
onClicked: sddm.suspend()
|
|
}
|
|
PowerButton {
|
|
Layout.fillWidth: true
|
|
label: "RIAVVIA"
|
|
enabled: sddm.canReboot
|
|
onClicked: sddm.reboot()
|
|
}
|
|
PowerButton {
|
|
Layout.fillWidth: true
|
|
label: "SPEGNI"
|
|
enabled: sddm.canPowerOff
|
|
onClicked: sddm.powerOff()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------- animazioni ----------
|
|
QtObject { id: overlayFade; property real value: 0.0 }
|
|
|
|
ParallelAnimation {
|
|
id: intro
|
|
NumberAnimation { target: ui; property: "opacity"; from: 0; to: 1; duration: 700; easing.type: Easing.OutCubic }
|
|
NumberAnimation { target: overlayFade; property: "value"; from: 0; to: 1; duration: 900 }
|
|
NumberAnimation { target: redShift; property: "y"; to: 0; duration: 850; easing.type: Easing.OutCubic }
|
|
NumberAnimation { target: lineShift; property: "x"; to: 0; duration: 950; easing.type: Easing.OutCubic }
|
|
NumberAnimation { target: darkShift; property: "y"; to: 0; duration: 1050; easing.type: Easing.OutCubic }
|
|
}
|
|
|
|
SequentialAnimation {
|
|
id: shake
|
|
NumberAnimation { target: panelShift; property: "x"; to: -14; duration: 45 }
|
|
NumberAnimation { target: panelShift; property: "x"; to: 14; duration: 45 }
|
|
NumberAnimation { target: panelShift; property: "x"; to: -10; duration: 45 }
|
|
NumberAnimation { target: panelShift; property: "x"; to: 10; duration: 45 }
|
|
NumberAnimation { target: panelShift; property: "x"; to: 0; duration: 45 }
|
|
}
|
|
|
|
NumberAnimation {
|
|
id: leave
|
|
target: root
|
|
property: "opacity"
|
|
to: 0
|
|
duration: 500
|
|
easing.type: Easing.InOutQuad
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
intro.start()
|
|
focusTimer.start()
|
|
}
|
|
|
|
Timer {
|
|
id: focusTimer
|
|
interval: 200
|
|
onTriggered: {
|
|
if (userModel.count > 0) {
|
|
passField.input.forceActiveFocus()
|
|
} else {
|
|
userField.input.forceActiveFocus()
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: sddm
|
|
function onLoginFailed() {
|
|
errorText.text = "ACCESSO NEGATO"
|
|
passField.text = ""
|
|
shake.start()
|
|
}
|
|
function onLoginSucceeded() {
|
|
leave.start()
|
|
}
|
|
}
|
|
}
|