First working Version

This commit is contained in:
Ansible User 2026-01-22 13:37:02 +01:00
commit a08e6d68d7
18 changed files with 2166 additions and 0 deletions

View file

@ -0,0 +1,63 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import "common"
ComboBox {
id: container
property int fontSize: root.font.pointSize
property int screenPadding: parent.Layout.margins
background: null
property bool vkbd_installed: false
function keyboardStatusChanged(state: bool) {
vkbd_installed = state
}
indicator: Button {
anchors.fill: parent
Text {
anchors.centerIn: parent
renderType: Text.QtRendering
text: ""
font.family: iconFont
color: container.focus ? root.palette.accent : root.palette.text
font.pointSize: fontSize * 1.5
}
background: Rectangle {
color: "transparent"
}
onPressed: {
container.popup.open()
}
}
function actionPressed(id: int) {
if (id == 0) root.activateVirtualKeyboard = !root.activateVirtualKeyboard;
}
model: [
{'value': root.activateVirtualKeyboard, 'icon': "", 'label': config.virtualKeyboard || "Virtual keyboard", 'enabled': vkbd_installed},
]
delegate: Toggle {
required property var modelData;
enabled: modelData['enabled']
checked: modelData['value']
icon: modelData['icon']
text: modelData['label']
Component.onCompleted: toggled.connect(container.actionPressed);
}
popup: PopupPanel {
x: root.LayoutMirroring.enabled ? -parent.width : (2 * parent.width - width + parent.parent.spacing)
interactive: false
model: container.delegateModel
}
}

81
components/Clock.qml Normal file
View file

@ -0,0 +1,81 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
Column {
property int fontSize: root.font.pointSize * config.realValue("fontSizeMultiplier")
property date value: new Date()
spacing: -fontSize / 2
readonly property var position_str: config.dateTimePosition.toLowerCase().split(/[.,\/#!$%\^&\*;:{}=\-_`~()\s]/)
readonly property bool is_left: position_str[position_str.length-1] == "left"
readonly property bool is_right: position_str[position_str.length-1] == "right"
readonly property bool is_top: position_str[0] == "top"
readonly property bool is_bottom: position_str[0] == "bottomLogin fehlgeschlagen"
readonly property bool is_center_center: !(is_left || is_right) && !(is_top || is_bottom)
anchors {
left: is_left ? parent.left : undefined
horizontalCenter: !(is_left || is_right) ? parent.horizontalCenter : undefined
right: is_right ? parent.right : undefined
top: is_top ? parent.top: undefined
verticalCenter: !(is_top || is_bottom) ? parent.verticalCenter : undefined
bottom: is_bottom ? parent.bottom: undefined
}
Label {
id: time_label
visible: true
anchors {
left: is_left ? parent.left : undefined
horizontalCenter: !(is_left || is_right) ? parent.horizontalCenter : undefined
right: is_right ? parent.right : undefined
}
renderType: Text.QtRendering
color: config.clockStyle == "outline" ? "transparent" : root.palette.text
style: config.clockStyle == "outline" ? Text.Outline : Text.Normal
styleColor: root.palette.text
font.pointSize: fontSize * 3
function update() {
text = Qt.formatTime(value, config.clockFormat)
}
}
Label {
id: date_label
anchors {
left: is_left ? parent.left : undefined
horizontalCenter: !(is_left || is_right) ? parent.horizontalCenter : undefined
right: is_right ? parent.right : undefined
}
renderType: Text.QtRendering
color: root.palette.text
font.pointSize: Math.min(1.4 * (time_label.width / text.length), fontSize * 2)
function update() {
text = value.toLocaleDateString(Qt.locale(config.locale), config.dateFormat)
}
}
Timer {
interval: 1000
repeat: true
running: true
onTriggered: {
value = new Date()
time_label.update()
date_label.update()
}
}
Component.onCompleted: {
time_label.update()
date_label.update()
}
}

View file

@ -0,0 +1,90 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import "common"
ComboBox {
id: container
property int fontSize: root.font.pointSize
background: null
indicator: Button {
anchors.fill: parent
Text {
id: button
anchors.centerIn: parent
renderType: Text.QtRendering
text: keyboard.layouts[keyboard.currentLayout].shortName
font.family: iconFont
color: container.focus ? root.palette.accent : root.palette.text
font.pointSize: fontSize * 1.5
}
background: Rectangle {
color: "transparent"
}
onPressed: {
container.popup.open()
}
}
model: keyboard.layouts
onActivated: keyboard.currentLayout = highlightedIndex
delegate: ItemDelegate {
id: keyboard_layout
highlighted: keyboard.currentLayout == index
implicitHeight: fontSize * 3
implicitWidth: label.width
Layout.fillWidth: true
Text {
id: label
leftPadding: 10
rightPadding: 10
anchors.verticalCenter: keyboard_layout.verticalCenter
renderType: Text.QtRendering
text: modelData.longName
font.family: root.font.family
font.pointSize: fontSize
color: root.palette.buttonText
}
background: Rectangle {
color: "transparent"
}
states: [
State {
name: "selected"
when: keyboard_layout.highlighted
PropertyChanges {
target: keyboard_layout.background
color: root.palette.accent
}
},
State {
name: "highlighted"
when: index === container.highlightedIndex
PropertyChanges {
target: keyboard_layout.background
color: "#777777"
opacity: 0.4
}
}
]
}
popup: PopupPanel {
x: (parent.width - width) * 0.5
model: container.delegateModel
}
}

221
components/LoginForm.qml Normal file
View file

@ -0,0 +1,221 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import "common"
Column {
id: container
property int fontSize: root.font.pointSize
property int p: config.ScreenPadding
signal loginRequest()
function login(sessionID: int) {
if (sessionID < 0 || sessionID >= sessionModel.count) { // turns out SDDM doesn't check session index is in bounds before sending it to the daemon
console.error(`Could not find session "${session.currentText}" with ID ${sessionID}`);
notificationMessageOverride = `Invalid session "${session.currentText}"`;
loginFailed();
}
sddm.login(username.text, password.text, sessionID);
}
function loginFailed() {
notification.opacity = 1;
password.text = ""
if (notification_timeout.running) notification_timeout.stop();
notification_timeout.start();
password.forceActiveFocus();
}
width: parent.width
// temporary override of notification message to display custom error messages
property string notificationMessageOverride: ""
TextField {
id: username
width: parent.width * 0.5
anchors.horizontalCenter: parent.horizontalCenter
text: ""
background: Rectangle {
color: "transparent"
radius: 8
border.color: parent.focus ? root.palette.accent : root.palette.text
border.width: 2
}
placeholderText: config.username || text_const.userName
horizontalAlignment: Text.AlignHCenter
selectByMouse: true
renderType: Text.QtRendering
font.pointSize: fontSize * 1.2
//font.bold: true
font.capitalization: config.boolValue("capitaliseUsername") ? Font.Capitalize : Font.MixedCase
color: root.palette.text
onAccepted: password.forceActiveFocus()
KeyNavigation.down: password
KeyNavigation.tab: password
}
RowLayout {
anchors.horizontalCenter: username.horizontalCenter
width: username.width
TextField {
id: password
Layout.fillWidth: true
onVisibleChanged: { if (visible && username.text.length > 0) forceActiveFocus(); }
placeholderText: config.password || text_const.password
horizontalAlignment: Text.AlignHCenter
selectByMouse: true
echoMode: reveal_passwd.checked ? TextInput.Normal : (config.passwordEchoStyle == "off" ? TextInput.NoEcho : TextInput.Password)
renderType: Text.QtRendering
font.pointSize: fontSize
color: root.palette.text
passwordCharacter: "•"
onAccepted: {focus = false; loginRequest()}
KeyNavigation.right: login_button
background: Rectangle {
color: "transparent"
radius: 8
border.color: parent.focus ? root.palette.accent : root.palette.text
border.width: 2
}
Label {
anchors.left: parent.left
height: parent.height
width: height
opacity: keyboard.capsLock
Text {
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
renderType: Text.QtRendering
text: "󰪛"
font.family: iconFont
color: root.palette.text
font.pointSize: fontSize * 1.3
}
Behavior on opacity {
NumberAnimation { duration: 75 * root.animationsEnabled }
}
}
CheckBox {
id: reveal_passwd
anchors.right: parent.right
height: parent.height
width: height
indicator: Text {
height: parent.height
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
renderType: Text.QtRendering
text: parent.checked ? "󰈉": "󰈈"
font.family: iconFont
color: root.palette.text
opacity: parent.hovered ? 1 : 0.66
font.pointSize: fontSize * 1.2
Behavior on opacity {
NumberAnimation { duration: 75 * root.animationsEnabled }
}
}
onPressed: password.forceActiveFocus()
}
}
Button {
id: login_button
visible: username.text.length > 0 && (config.boolValue("allowEmptyPassword") || (password.text.length > 0))
implicitHeight: password.height
Layout.fillWidth: false
Layout.preferredWidth: implicitHeight
Text {
id: icon
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
renderType: Text.QtRendering
font.pointSize: fontSize * 1.4
font.family: iconFont
color: login_button.focus ? root.palette.accent : root.palette.text
text: LayoutMirroring.enabled ? "" : ""
}
onClicked: loginRequest()
KeyNavigation.left: password
background: Rectangle {
color: "transparent"
radius: 8
border.color: parent.focus? root.palette.accent : root.palette.text
border.width: 1
}
states: [
State {
name: "selected"
when: login_button.pressed
PropertyChanges {
target: login_button.background
color: root.palette.accent
border.color: root.palette.text
}
PropertyChanges {
target: icon
color: root.palette.text
}
}
]
}
}
Text {
id: notification
text: notificationMessageOverride || config.loginFailed || text_const.loginFailed
opacity: 0
renderType: Text.QtRendering
color: root.palette.accent
font.pointSize: fontSize * 0.825
height: fontSize * 3
verticalAlignment: Qt.AlignVCenter
anchors.horizontalCenter: parent.horizontalCenter
Behavior on opacity {
NumberAnimation { duration: 100 * root.animationsEnabled }
}
Timer {
id: notification_timeout
running: false
interval: 2500
onTriggered: { notification.opacity = 0; notificationMessageOverride = "" }
}
}
}

121
components/PowerMenu.qml Normal file
View file

@ -0,0 +1,121 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import "common"
ComboBox {
id: container
property int fontSize: root.font.pointSize
background: null
indicator: Button {
anchors.fill: parent
Text {
anchors.centerIn: parent
renderType: Text.QtRendering
text: "󰐥"
font.family: iconFont
color: container.focus ? root.palette.accent : root.palette.text
font.pointSize: fontSize * 1.5
}
background: Rectangle {
color: "transparent"
}
onPressed: {
container.popup.open()
}
}
function actionPressed() {
if (currentIndex == 0) sddm.suspend();
else if (currentIndex == 1) sddm.hibernate();
else if (currentIndex == 2) sddm.reboot();
else if (currentIndex == 3) sddm.powerOff();
}
property bool forcePowerOptions: false
model: [
{'icon': "", 'label': config.suspend || text_const.suspend , 'enabled': sddm.canSuspend },
{'icon': "󰍷", 'label': config.hibernate || text_const.hibernate, 'enabled': sddm.canHibernate},
{'icon': "", 'label': config.reboot || text_const.reboot , 'enabled': sddm.canReboot },
{'icon': "", 'label': config.poweroff || text_const.shutdown , 'enabled': sddm.canPowerOff },
]
onActivated: {
currentIndex = highlightedIndex;
actionPressed();
}
delegate: ItemDelegate {
id: power_option
visible: forcePowerOptions || modelData['enabled']
implicitHeight: fontSize * 3
implicitWidth: content_item.width
Layout.fillWidth: true
Row {
id: content_item
spacing: fontSize
anchors.verticalCenter: power_option.verticalCenter
leftPadding: 10
rightPadding: 10
Text {
visible: config.boolValue("iconsInMenus")
renderType: Text.QtRendering
text: modelData['icon']
font.family: iconFont
font.pointSize: fontSize
color: root.palette.buttonText
}
Text {
id: label
renderType: Text.QtRendering
text: modelData['label']
font.family: root.font.family
font.pointSize: fontSize
color: root.palette.buttonText
}
}
onClicked: {
currentIndex = index;
}
background: Rectangle {
color: "transparent"
}
states: [
State {
name: "selected"
when: power_option.activeFocus
PropertyChanges {
target: power_option.background
color: root.palette.accent
}
},
State {
name: "highlighted"
when: container.highlightedIndex === index
PropertyChanges {
target: power_option.background
color: "#777777"
opacity: 0.4
}
}
]
}
popup: PopupPanel {
x: (parent.width - width) * !(root.LayoutMirroring.enabled)
interactive: false
model: container.delegateModel
}
}

View file

@ -0,0 +1,103 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import "common"
ComboBox {
id: container
property int fontSize: root.font.pointSize
background: null
indicator: Button {
anchors.fill: parent
Text {
anchors.centerIn: parent
renderType: Text.QtRendering
text: "󰍹"
font.family: iconFont
color: container.focus ? root.palette.accent : root.palette.text
font.pointSize: fontSize * 1.5
Text {
visible: config.boolValue("displaySession")
anchors {
leftMargin: fontSize
left: parent.right
verticalCenter: parent.verticalCenter
}
renderType: Text.QtRendering
text: container.currentText
font.family: root.font.family
color: root.palette.text
font.pointSize: fontSize
}
}
background: Rectangle {
color: "transparent"
}
onPressed: {
container.popup.open()
}
}
model: sessionModel
currentIndex: model.lastIndex
textRole: "name"
onActivated: currentIndex = highlightedIndex
delegate: ItemDelegate {
id: session_item
highlighted: container.currentIndex === index
implicitHeight: fontSize * 3
implicitWidth: label.width
Layout.fillWidth: true
Text {
id: label
padding: 10
anchors.verticalCenter: session_item.verticalCenter
renderType: Text.QtRendering
text: name
font.family: root.font.family
font.pointSize: fontSize
color: root.palette.buttonText
}
background: Rectangle {
color: "transparent"
}
states: [
State {
name: "selected"
when: session_item.highlighted
PropertyChanges {
target: session_item.background
color: root.palette.accent
}
},
State {
name: "highlighted"
when: container.highlightedIndex === index
PropertyChanges {
target: session_item.background
color: "#777777"
opacity: 0.4
}
}
]
}
popup: PopupPanel {
x: (parent.width - width) * root.LayoutMirroring.enabled
model: container.delegateModel
}
}

86
components/UserList.qml Normal file
View file

@ -0,0 +1,86 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import "common"
ComboBox {
id: container
property int fontSize: root.font.pointSize
property int optionHeight: fontSize * 3
background: null
signal userSelected()
indicator: Button {
anchors.fill: parent
Text {
anchors.centerIn: parent
renderType: Text.QtRendering
text: ""
font.family: iconFont
color: container.focus ? root.palette.accent : root.palette.text
font.pointSize: fontSize * 1.4
}
background: Rectangle {
color: "transparent"
}
onPressed: {
container.popup.open()
}
}
model: userModel
currentIndex: model.lastIndex
textRole: "name"
onActivated: userSelected()
delegate: ItemDelegate {
id: user_item
implicitHeight: optionHeight
implicitWidth: container.popup.width
highlighted: container.currentIndex === index
Text {
id: label
width: parent.width
padding: 10
anchors.verticalCenter: user_item.verticalCenter
horizontalAlignment: Text.AlignHCenter
renderType: Text.QtRendering
text: model.realName != "" ? model.realName : model.name
font.family: root.font.family
font.pointSize: fontSize
color: root.palette.buttonText
}
background: Rectangle {
color: "transparent"
}
states: [
State {
name: "highlighted"
when: index === container.highlightedIndex
PropertyChanges {
target: user_item.background
color: "#777777"
opacity: 0.4
}
}
]
}
popup: PopupPanel {
width: container.parent.width
y: -height
model: container.delegateModel
}
}

View file

@ -0,0 +1,26 @@
import QtQuick 2.15
import QtQuick.VirtualKeyboard 2.15
import QtQuick.VirtualKeyboard.Settings 2.15
Item {
id: keyboard_container
height: input_panel.active ? input_panel.height : 0
InputPanel {
id: input_panel
active: root.activateVirtualKeyboard && (Qt.inputMethod.visible || config.boolValue("forceKeyboardVisible"))
visible: active
z: 1
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
Binding {
target: VirtualKeyboardSettings
property: "wordCandidateList.autoHideDelay"
value: 1000
}
}
}

View file

@ -0,0 +1,54 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
Popup {
id: menu
y: -(height + root.font.pointSize / 2)
property var model
property var delegate
property alias interactive: flickable.interactive
property real delegateHeight: fontSize * 3
// maximum number of options to show at once, scroll/flick to see the rest
// adding .5 works as a visual hint there's more options by displaying half of the next button
property real max_size: 5.5
padding: 0
topPadding: background.radius
bottomPadding: background.radius
background: Rectangle {
color: root.palette.button
radius: 8
}
contentItem: Flickable {
id: flickable
clip: true
implicitWidth: contentWidth
implicitHeight: interactive ? Math.min(contentHeight, menu.max_size * (menu.delegateHeight + delegate_container.spacing)) : contentHeight
contentWidth: delegate_container.width
contentHeight: delegate_container.height
flickableDirection: Flickable.VerticalFlick
ScrollBar.vertical: ScrollBar { }
ColumnLayout {
id: delegate_container
spacing: 4
Repeater {
model: menu.model
}
}
}
enter: Transition {
NumberAnimation { property: "height"; from: 0.0; to: implicitHeight; duration: 50 * root.animationsEnabled }
}
exit: Transition {
NumberAnimation { property: "height"; from: implicitHeight; to: 0.0; duration: 50 * root.animationsEnabled }
}
}

View file

@ -0,0 +1,109 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
RowLayout {
id: item
Layout.fillWidth: true
Layout.preferredHeight: fontSize * 3
spacing: fontSize
Layout.leftMargin: 10
Layout.rightMargin: 10
required property int index;
property alias text: label.text
property alias icon: icon.text
property alias enabled: toggle.enabled
property alias checked: toggle.checked
signal toggled(int id)
Text {
id: icon
visible: config.boolValue("iconsInMenus")
renderType: Text.QtRendering
font.pointSize: fontSize
font.family: config.iconFont
color: root.palette.buttonText
}
Text {
id: label
renderType: Text.QtRendering
font.family: root.font.family
font.pointSize: fontSize
color: root.palette.buttonText
Layout.fillWidth: true
}
Switch {
id: toggle
Layout.preferredHeight: indicator.implicitHeight + 10
Layout.preferredWidth: indicator.implicitWidth + 10
Layout.fillWidth: false
onClicked: item.toggled(index)
background: Rectangle {
color: "transparent"
radius: 8
}
indicator: Rectangle {
anchors.centerIn: parent
implicitHeight: fontSize * 1.5
implicitWidth: fontSize * 3
radius: implicitHeight / 2
color: parent.checked ? root.palette.accent : Qt.darker(root.palette.button, 1.3)
border.color: parent.checked ? root.palette.accent : "#cccccc"
Text {
visible: true
height: parent.height
width: height
x: checked ? 0 : parent.width - width
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
renderType: Text.QtRendering
text: checked ? "" : ""
font.family: root.iconFont
font.pointSize: fontSize * 0.7
color: root.palette.buttonText
}
Rectangle {
id: knob
x: checked ? parent.width - width : 0
height: parent.implicitHeight
width: height
radius: height / 2
color: toggle.down ? "#cccccc" : root.palette.buttonText
border.color: checked ? root.palette.accent : "#999999"
}
}
states: [
State {
when: !enabled
PropertyChanges {
target: toggle.indicator
color: "#555555"
border.color: "#333333"
opacity: 0.3
}
PropertyChanges {
target: toggle
checked: false
}
PropertyChanges {
target: knob
color: "#cccccc"
border.color: Qt.lighter(toggle.indicator.color, 1.5)
}
}
]
}
}