405 lines
14 KiB
QML
405 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
|
||
import "fauxcyrillic.js" as Faux
|
||
|
||
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 = Faux.apply("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: Faux.apply(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 = Faux.apply(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: "АССЭSSО"
|
||
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: Faux.apply("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: Faux.apply("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 = Faux.apply("ACCESSO NEGATO")
|
||
passField.text = ""
|
||
shake.start()
|
||
}
|
||
function onLoginSucceeded() {
|
||
leave.start()
|
||
}
|
||
}
|
||
}
|