summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components
diff options
context:
space:
mode:
authorNippy <nippy@rp1.hu>2026-06-22 10:34:04 +0200
committerNippy <nippy@rp1.hu>2026-06-22 10:34:04 +0200
commit171cde2891791b8710071458f1b9436a3fdff905 (patch)
tree694e33fd0f58282de0b0aa0d7ebc78d5f8992ad4 /raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components
parent9259296e50a4e4bf59583bad03c9360c9c918204 (diff)
downloadRaveOS-PKGBUILD-171cde2891791b8710071458f1b9436a3fdff905.tar.gz
RaveOS-PKGBUILD-171cde2891791b8710071458f1b9436a3fdff905.zip
új sddm config
Diffstat (limited to 'raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components')
-rw-r--r--raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/BatteryIndicator.qml257
-rw-r--r--raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/Clock.qml81
-rw-r--r--raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/Input.qml754
-rw-r--r--raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/KeyboardLayoutIndicator.qml237
-rw-r--r--raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/LoginForm.qml89
-rw-r--r--raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/NetworkIndicator.qml243
-rw-r--r--raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/ParticleOverlay.qml90
-rw-r--r--raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/SessionButton.qml158
-rw-r--r--raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/ToastNotification.qml83
-rw-r--r--raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/UserSwitcher.qml332
-rw-r--r--raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/VirtualKeyboard.qml20
-rw-r--r--raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/VirtualKeyboardButton.qml79
12 files changed, 2423 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/BatteryIndicator.qml b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/BatteryIndicator.qml
new file mode 100644
index 0000000..5497911
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/BatteryIndicator.qml
@@ -0,0 +1,257 @@
+import QtQuick 2.15
+import Qt.labs.folderlistmodel 2.15
+
+Row {
+ id: batteryIndicator
+
+ height: root.font.pointSize * 1.6
+ spacing: root.font.pointSize * 0.5
+
+ // -1 = nincs akkumulátor / nem laptop
+ property int batteryLevel: -1
+ property bool isCharging: false
+ property bool isFull: false
+ property string batteryPath: ""
+
+ visible: batteryLevel >= 0
+
+ // Ismert akksi elérési utak
+ property var tryPaths: [
+ "/sys/class/power_supply/BAT0/",
+ "/sys/class/power_supply/BAT1/",
+ "/sys/class/power_supply/BAT2/",
+ "/sys/class/power_supply/battery/",
+ "/sys/class/power_supply/BATT/"
+ ]
+
+ // Dinamikus akkumulátor kereső (pl. kontroller, UPS, egyéb külső akkuk észleléséhez)
+ FolderListModel {
+ id: powerSupplyScanner
+ folder: "file:///sys/class/power_supply"
+ showDirs: true
+ showFiles: false
+
+ onCountChanged: scan()
+ onFolderChanged: scan()
+
+ function scan() {
+ var defaults = [
+ "/sys/class/power_supply/BAT0/",
+ "/sys/class/power_supply/BAT1/",
+ "/sys/class/power_supply/BAT2/",
+ "/sys/class/power_supply/battery/",
+ "/sys/class/power_supply/BATT/"
+ ]
+ for (var i = 0; i < count; i++) {
+ var name = get(i, "fileName")
+ if (name && name !== "." && name !== "..") {
+ // Csak a tipikus laptop akkumulátor elnevezések (pl. BAT0, BAT1, BATT, battery)
+ var isLaptopBat = /^bat/i.test(name) || /^battery/i.test(name)
+ if (isLaptopBat) {
+ var fullPath = "/sys/class/power_supply/" + name + "/"
+ if (defaults.indexOf(fullPath) === -1) {
+ defaults.push(fullPath)
+ }
+ }
+ }
+ }
+ tryPaths = defaults
+ detectBattery(0)
+ }
+ }
+
+ // ── Fájl olvasó ──────────────────────────────────────────────────────────
+ function readFile(path, cb) {
+ var xhr = new XMLHttpRequest()
+ xhr.open("GET", "file://" + path, true)
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === XMLHttpRequest.DONE)
+ cb(xhr.responseText && xhr.responseText.trim() !== "" ? xhr.responseText.trim() : null)
+ }
+ xhr.send()
+ }
+
+ // ── Akkumulátor keresés és frissítés ─────────────────────────────────────
+ function detectBattery(idx) {
+ if (idx >= tryPaths.length) { batteryLevel = -1; return }
+ readFile(tryPaths[idx] + "capacity", function(cap) {
+ if (cap !== null) {
+ batteryPath = tryPaths[idx]
+ batteryLevel = Math.max(0, Math.min(100, parseInt(cap) || 0))
+ readFile(batteryPath + "status", function(st) {
+ var s = st ? st.toLowerCase() : ""
+ isFull = (s === "full")
+ isCharging = (s === "charging") || isFull
+ batteryCanvas.requestPaint()
+ })
+ } else {
+ detectBattery(idx + 1)
+ }
+ })
+ }
+
+ // ── Akkumulátor frissítés ────────────────────────────────────────────────
+ function refresh() {
+ if (batteryPath !== "") {
+ readFile(batteryPath + "capacity", function(cap) {
+ if (cap !== null) {
+ batteryLevel = Math.max(0, Math.min(100, parseInt(cap) || 0))
+ readFile(batteryPath + "status", function(st) {
+ var s = st ? st.toLowerCase() : ""
+ isFull = (s === "full")
+ isCharging = (s === "charging") || isFull
+ batteryCanvas.requestPaint()
+ })
+ }
+ })
+ } else {
+ powerSupplyScanner.scan()
+ }
+ }
+
+ Component.onCompleted: powerSupplyScanner.scan()
+
+ Timer {
+ interval: 30000
+ repeat: true
+ running: true
+ onTriggered: batteryIndicator.refresh()
+ }
+
+ // ── Szín a töltési szint alapján ─────────────────────────────────────────
+ function levelColor(lvl, charging) {
+ if (charging) return "#4ade80" // zöld – tölt
+ if (lvl > 60) return "#ffffff" // fehér – ok
+ if (lvl > 30) return "#facc15" // sárga – közepes
+ if (lvl > 15) return "#fb923c" // narancs – alacsony
+ return "#f87171" // piros – kritikus
+ }
+
+ // ── UI ───────────────────────────────────────────────────────────────────
+ Canvas {
+ id: batteryCanvas
+ width: root.font.pointSize * 3.2
+ height: root.font.pointSize * 1.6
+ anchors.verticalCenter: parent.verticalCenter
+
+ onPaint: {
+ var ctx = getContext("2d")
+ ctx.clearRect(0, 0, width, height)
+
+ // Qt Canvas nem támogatja ctx.roundRect() – saját helper
+ function rr(c, x, y, w, h, r) {
+ r = Math.min(Math.max(r, 0), Math.min(w, h) / 2)
+ c.beginPath()
+ c.moveTo(x + r, y)
+ c.arcTo(x + w, y, x + w, y + h, r)
+ c.arcTo(x + w, y + h, x, y + h, r)
+ c.arcTo(x, y + h, x, y, r)
+ c.arcTo(x, y, x + w, y, r)
+ c.closePath()
+ }
+
+ var lvl = batteryIndicator.batteryLevel
+ var charging = batteryIndicator.isCharging
+ var full = batteryIndicator.isFull
+ var col = batteryIndicator.levelColor(lvl, charging)
+
+ var bw = width * 0.84 // akksi test szélessége
+ var bh = height * 0.72 // akksi test magassága
+ var bx = 0
+ var by = (height - bh) / 2
+ var r = bh * 0.22 // sarokkerekítés
+ var tw = width * 0.07 // terminál szélessége
+ var th = bh * 0.44 // terminál magassága
+ var tx = bw // terminál x
+ var ty = (height - th) / 2
+
+ // Terminál (+ pólus)
+ rr(ctx, tx, ty, tw, th, 2)
+ ctx.fillStyle = col
+ ctx.globalAlpha = 0.7
+ ctx.fill()
+ ctx.globalAlpha = 1.0
+
+ // Keret (akksi test)
+ rr(ctx, bx, by, bw, bh, r)
+ ctx.strokeStyle = col
+ ctx.lineWidth = 1.5
+ ctx.stroke()
+
+ // Töltési szint kitöltés
+ var padding = 3
+ var fillW = Math.max(0, (bw - padding * 2) * lvl / 100)
+ if (fillW > 0) {
+ rr(ctx, bx + padding, by + padding,
+ fillW, bh - padding * 2,
+ Math.max(0, r - padding * 0.5))
+ ctx.fillStyle = col
+ ctx.globalAlpha = 0.85
+ ctx.fill()
+ ctx.globalAlpha = 1.0
+ }
+
+ // Villám jel (töltés közben)
+ if (charging && !full) {
+ var cx = bw / 2
+ var cy = height / 2
+ var s = bh * 0.52
+ ctx.beginPath()
+ ctx.moveTo(cx + s * 0.18, cy - s * 0.5)
+ ctx.lineTo(cx - s * 0.08, cy + s * 0.05)
+ ctx.lineTo(cx + s * 0.10, cy + s * 0.05)
+ ctx.lineTo(cx - s * 0.18, cy + s * 0.5)
+ ctx.lineTo(cx + s * 0.08, cy - s * 0.05)
+ ctx.lineTo(cx - s * 0.10, cy - s * 0.05)
+ ctx.closePath()
+ ctx.fillStyle = "#ffffff"
+ ctx.globalAlpha = 0.95
+ ctx.fill()
+ ctx.globalAlpha = 1.0
+ }
+
+ // Tele jel (checkbox-szerű pipával)
+ if (full) {
+ ctx.beginPath()
+ ctx.moveTo(bw * 0.32, by + bh / 2)
+ ctx.lineTo(bw * 0.46, by + bh * 0.68)
+ ctx.lineTo(bw * 0.68, by + bh * 0.28)
+ ctx.strokeStyle = "#ffffff"
+ ctx.lineWidth = 1.8
+ ctx.lineCap = "round"
+ ctx.lineJoin = "round"
+ ctx.globalAlpha = 0.95
+ ctx.stroke()
+ ctx.globalAlpha = 1.0
+ }
+ }
+ }
+
+ // Százalék szöveg
+ Text {
+ id: batteryText
+ anchors.verticalCenter: parent.verticalCenter
+ text: {
+ if (batteryIndicator.isFull) return "100%"
+ if (batteryIndicator.isCharging) return batteryIndicator.batteryLevel + "% ↑"
+ return batteryIndicator.batteryLevel + "%"
+ }
+ color: batteryIndicator.levelColor(batteryIndicator.batteryLevel, batteryIndicator.isCharging)
+ font.pointSize: root.font.pointSize * 0.82
+ font.family: root.font.family
+ font.bold: batteryIndicator.batteryLevel <= 15 && !batteryIndicator.isCharging
+
+ // Pulzáló animáció kritikus töltésnél
+ SequentialAnimation on opacity {
+ running: batteryIndicator.batteryLevel <= 15 && !batteryIndicator.isCharging
+ loops: Animation.Infinite
+ NumberAnimation { to: 0.3; duration: 700; easing.type: Easing.InOutSine }
+ NumberAnimation { to: 1.0; duration: 700; easing.type: Easing.InOutSine }
+ }
+ }
+
+ // Szint változáskor újrafest
+ onBatteryLevelChanged: batteryCanvas.requestPaint()
+ onIsChargingChanged: batteryCanvas.requestPaint()
+ onIsFullChanged: batteryCanvas.requestPaint()
+}
diff --git a/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/Clock.qml b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/Clock.qml
new file mode 100644
index 0000000..e9d1399
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/Clock.qml
@@ -0,0 +1,81 @@
+// Config created by Keyitdev https://git.rp1.hu/gabeszm/sddm-rave-theme
+// Copyright (C) 2022-2025 Keyitdev
+// Based on https://github.com/MarianArlt/sddm-sugar-dark
+// Distributed under the GPLv3+ License https://www.gnu.org/licenses/gpl-3.0.html
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+
+Column {
+ id: clock
+
+ width: parent.width / 2
+ spacing: 8
+
+ Image {
+ id: logoImage
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ source: (config.Logo && config.Logo !== "") ? Qt.resolvedUrl("../" + config.Logo) : ""
+ fillMode: Image.PreserveAspectFit
+ width: root.font.pointSize * 13
+ height: root.font.pointSize * 13
+ visible: config.Logo && config.Logo !== ""
+ }
+
+ Label {
+ id:headerTextLabel
+
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ font.pointSize: root.font.pointSize * 2
+ color: config.HeaderTextColor
+ renderType: Text.QtRendering
+ text: config.HeaderText
+ }
+
+ Label {
+ id: timeLabel
+
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ font.pointSize: root.font.pointSize * 3.5
+ font.bold: true
+ color: config.TimeTextColor
+ renderType: Text.QtRendering
+
+ function updateTime() {
+ text = new Date().toLocaleTimeString(Qt.locale(config.Locale), config.HourFormat == "long" ? Locale.LongFormat : config.HourFormat !== "" ? config.HourFormat : Locale.ShortFormat)
+ }
+ }
+
+ Label {
+ id: dateLabel
+
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ color: config.DateTextColor
+ font.pointSize: root.font.pointSize * 0.95
+ font.bold: true
+ renderType: Text.QtRendering
+
+ function updateTime() {
+ text = new Date().toLocaleDateString(Qt.locale(config.Locale), config.DateFormat == "short" ? Locale.ShortFormat : config.DateFormat !== "" ? config.DateFormat : Locale.LongFormat)
+ }
+ }
+
+ Timer {
+ interval: 1000
+ repeat: true
+ running: true
+ onTriggered: {
+ dateLabel.updateTime()
+ timeLabel.updateTime()
+ }
+ }
+
+ Component.onCompleted: {
+ dateLabel.updateTime()
+ timeLabel.updateTime()
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/Input.qml b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/Input.qml
new file mode 100644
index 0000000..47e6598
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/Input.qml
@@ -0,0 +1,754 @@
+// Config created by Keyitdev https://git.rp1.hu/gabeszm/sddm-rave-theme
+// Copyright (C) 2022-2025 Keyitdev
+// Based on https://github.com/MarianArlt/sddm-sugar-dark
+// Distributed under the GPLv3+ License https://www.gnu.org/licenses/gpl-3.0.html
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15
+import QtQuick.Effects
+
+Column {
+ id: inputContainer
+
+ Layout.fillWidth: true
+
+ property ComboBox exposeSession: sessionSelect.exposeSession
+ property bool failed
+
+ Item {
+ id: errorMessageField
+
+ // change also in selectSession
+ height: root.font.pointSize * 2
+ width: parent.width / 2
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Label {
+ id: errorMessage
+
+ width: parent.width
+ horizontalAlignment: Text.AlignHCenter
+
+ text: failed ? config.TranslateLoginFailedWarning || textConstants.loginFailed + "!" : keyboard.capsLock ? config.TranslateCapslockWarning || textConstants.capslockWarning : null
+ font.pointSize: root.font.pointSize * 0.8
+ font.italic: true
+ color: config.WarningColor
+ opacity: 0
+
+ states: [
+ State {
+ name: "fail"
+ when: failed
+ PropertyChanges {
+ target: errorMessage
+ opacity: 1
+ }
+ },
+ State {
+ name: "capslock"
+ when: keyboard.capsLock
+ PropertyChanges {
+ target: errorMessage
+ opacity: 1
+ }
+ }
+ ]
+ transitions: [
+ Transition {
+ PropertyAnimation {
+ properties: "opacity"
+ duration: 100
+ }
+ }
+ ]
+ }
+ }
+
+ // Profilkép + felhasználóváltó
+ Item {
+ id: avatarField
+
+ height: root.font.pointSize * 12
+ width: parent.width / 2
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ property string userIconPath: {
+ var icon = userModel.data(userModel.index(selectUser.currentIndex, 0), Qt.UserRole + 4)
+ return (icon && icon !== "") ? icon : ""
+ }
+
+ Column {
+ anchors.centerIn: parent
+ spacing: root.font.pointSize * 0.8
+
+ // Avatar kör
+ Rectangle {
+ id: avatarCircle
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: root.font.pointSize * 7
+ height: root.font.pointSize * 7
+ radius: width / 2
+ clip: true
+ color: Qt.rgba(0.10, 0.10, 0.15, 0.65)
+
+ // The source image (invisible)
+ Image {
+ id: avatarPhoto
+ anchors.fill: parent
+ source: avatarField.userIconPath !== "" ? avatarField.userIconPath : ""
+ fillMode: Image.PreserveAspectCrop
+ smooth: true
+ mipmap: true
+ visible: false
+ }
+
+ // The mask source
+ Item {
+ id: avatarMask
+ anchors.fill: parent
+ visible: false
+ layer.enabled: true
+ Rectangle {
+ anchors.fill: parent
+ radius: parent.width / 2
+ color: "black"
+ }
+ }
+
+ // Apply MultiEffect to mask the avatarPhoto
+ MultiEffect {
+ anchors.fill: parent
+ source: avatarPhoto
+ visible: avatarPhoto.status === Image.Ready && avatarField.userIconPath !== ""
+ maskEnabled: true
+ maskSource: avatarMask
+ maskThresholdMin: 0.5
+ maskSpreadAtMin: 1.0
+ }
+
+ Image {
+ id: avatarFallback
+ anchors.centerIn: parent
+ width: parent.width * 0.55
+ height: parent.height * 0.55
+ source: Qt.resolvedUrl("../Assets/User.svg")
+ fillMode: Image.PreserveAspectFit
+ smooth: true
+ visible: avatarField.userIconPath === "" || avatarPhoto.status !== Image.Ready
+ opacity: 0.75
+ }
+
+ // Draw the border on top of everything!
+ Rectangle {
+ anchors.fill: parent
+ radius: parent.width / 2
+ color: "transparent"
+ border.color: Qt.rgba(1, 1, 1, 0.30)
+ border.width: 2
+ }
+ }
+
+ // Felhasználóváltó gomb (csak ha több user van)
+ UserSwitcher {
+ id: userSwitcher
+ anchors.horizontalCenter: parent.horizontalCenter
+ selectedIndex: selectUser.currentIndex
+
+ onUserSwitched: function(idx, name) {
+ selectUser.currentIndex = idx
+ username.text = name
+ }
+ }
+ }
+ }
+
+
+ Item {
+ id: usernameField
+
+ height: root.font.pointSize * 4.5
+ width: parent.width / 2
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Button {
+ id: staticUserIcon
+
+ width: parent.height
+ height: parent.height
+ anchors.left: parent.left
+ z: 2
+ visible: userModel.count <= 1
+ enabled: false
+
+ icon.height: parent.height * 0.25
+ icon.width: parent.height * 0.25
+ icon.color: config.UserIconColor
+ icon.source: Qt.resolvedUrl("../Assets/User.svg")
+
+ background: Rectangle {
+ color: "transparent"
+ border.color: "transparent"
+ }
+ }
+
+ ComboBox {
+ id: selectUser
+
+ width: parent.height
+ height: parent.height
+ anchors.left: parent.left
+ z: 2
+ visible: userModel.count > 1
+
+ model: userModel
+ currentIndex: model.lastIndex
+ textRole: "name"
+ hoverEnabled: true
+ onActivated: {
+ username.text = currentText;
+ }
+
+ property var popkey: config.RightToLeftLayout == "true" ? Qt.Key_Right : Qt.Key_Left
+ Keys.onPressed: function (event) {
+ if (event.key == Qt.Key_Down && !popup.opened)
+ username.forceActiveFocus();
+ if ((event.key == Qt.Key_Up || event.key == popkey) && !popup.opened)
+ popup.open();
+ }
+ KeyNavigation.down: username
+ KeyNavigation.right: username
+
+ delegate: ItemDelegate {
+ // minus padding
+ width: popupHandler.width - 20
+ anchors.horizontalCenter: popupHandler.horizontalCenter
+
+ contentItem: Text {
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+
+ text: model.name
+ font.pointSize: root.font.pointSize * 0.8
+ font.capitalization: Font.AllLowercase
+ font.family: root.font.family
+ color: config.DropdownTextColor
+ }
+
+ background: Rectangle {
+ color: selectUser.highlightedIndex === index ? Qt.rgba(1, 1, 1, 0.15) : "transparent"
+ radius: config.RoundCorners !== "" ? parseInt(config.RoundCorners) / 4 : 5
+ }
+ }
+
+ indicator: Button {
+ id: usernameIcon
+
+ width: selectUser.height * 1
+ height: parent.height
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.leftMargin: selectUser.height * 0
+
+ icon.height: parent.height * 0.25
+ icon.width: parent.height * 0.25
+ enabled: false
+ icon.color: config.UserIconColor
+ icon.source: Qt.resolvedUrl("../Assets/User.svg")
+
+ background: Rectangle {
+ color: "transparent"
+ border.color: "transparent"
+ }
+ }
+
+ background: Rectangle {
+ color: "transparent"
+ border.color: "transparent"
+ }
+
+ popup: Popup {
+ id: popupHandler
+
+ implicitHeight: contentItem.implicitHeight
+ width: usernameField.width
+ y: parent.height - username.height / 3
+ x: config.RightToLeftLayout == "true" ? -loginButton.width + selectUser.width : 0
+ rightMargin: config.RightToLeftLayout == "true" ? root.padding + usernameField.width / 2 : undefined
+ padding: 10
+
+ contentItem: ListView {
+ implicitHeight: contentHeight + 20
+
+ clip: true
+ model: selectUser.popup.visible ? selectUser.delegateModel : null
+ currentIndex: selectUser.highlightedIndex
+ ScrollIndicator.vertical: ScrollIndicator {}
+ }
+
+ background: Rectangle {
+ radius: config.RoundCorners !== "" ? parseInt(config.RoundCorners) / 2 : 10
+ color: Qt.rgba(1, 1, 1, 0.08)
+ border.color: Qt.rgba(1, 1, 1, 0.18)
+ border.width: 1
+ layer.enabled: true
+ }
+
+ enter: Transition {
+ NumberAnimation {
+ property: "opacity"
+ from: 0
+ to: 1
+ }
+ }
+ }
+
+ states: [
+ State {
+ name: "pressed"
+ when: selectUser.down
+ PropertyChanges {
+ target: usernameIcon
+ icon.color: Qt.lighter(config.HoverUserIconColor, 1.1)
+ }
+ },
+ State {
+ name: "hovered"
+ when: selectUser.hovered
+ PropertyChanges {
+ target: usernameIcon
+ icon.color: Qt.lighter(config.HoverUserIconColor, 1.2)
+ }
+ },
+ State {
+ name: "focused"
+ when: selectUser.activeFocus
+ PropertyChanges {
+ target: usernameIcon
+ icon.color: config.HoverUserIconColor
+ }
+ }
+ ]
+ transitions: [
+ Transition {
+ PropertyAnimation {
+ properties: "color, border.color, icon.color"
+ duration: 150
+ }
+ }
+ ]
+ }
+
+ TextField {
+ id: username
+
+ anchors.centerIn: parent
+ height: root.font.pointSize * 3
+ width: parent.width
+ horizontalAlignment: TextInput.AlignHCenter
+ z: 1
+
+ text: config.ForceLastUser == "true" ? selectUser.currentText : null
+ color: config.LoginFieldTextColor
+ font.bold: true
+ font.capitalization: config.AllowUppercaseLettersInUsernames == "false" ? Font.AllLowercase : Font.MixedCase
+ placeholderText: config.TranslatePlaceholderUsername || textConstants.userName
+ placeholderTextColor: config.PlaceholderTextColor
+ selectByMouse: true
+ renderType: Text.QtRendering
+
+ onFocusChanged: {
+ if (focus)
+ selectAll();
+ }
+
+ background: Rectangle {
+ color: username.activeFocus ? Qt.rgba(0, 0, 0, 0.35) : Qt.rgba(0, 0, 0, 0.2)
+ border.color: username.activeFocus ? Qt.rgba(255, 255, 255, 0.3) : Qt.rgba(255, 255, 255, 0.12)
+ border.width: username.activeFocus ? 1.5 : 1
+ radius: config.RoundCorners !== "" ? parseInt(config.RoundCorners) : 10
+ Behavior on color {
+ ColorAnimation {
+ duration: 150
+ }
+ }
+ Behavior on border.color {
+ ColorAnimation {
+ duration: 150
+ }
+ }
+ }
+
+ onAccepted: config.AllowUppercaseLettersInUsernames == "false" ? sddm.login(username.text.toLowerCase(), password.text, sessionSelect.selectedSession) : sddm.login(username.text, password.text, sessionSelect.selectedSession)
+ KeyNavigation.down: passwordIcon
+
+ states: [
+ State {
+ name: "focused"
+ when: username.activeFocus
+ PropertyChanges {
+ target: username
+ color: Qt.lighter(config.LoginFieldTextColor, 1.15)
+ }
+ }
+ ]
+ }
+ }
+
+ Item {
+ id: passwordField
+
+ height: root.font.pointSize * 4.5
+ width: parent.width / 2
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Button {
+ id: passwordIcon
+
+ height: parent.height
+ width: selectUser.height * 1
+ anchors.left: parent.left
+ anchors.leftMargin: selectUser.height * 0
+ anchors.verticalCenter: parent.verticalCenter
+ z: 2
+
+ icon.height: parent.height * 0.25
+ icon.width: parent.height * 0.25
+ icon.color: config.PasswordIconColor
+ icon.source: Qt.resolvedUrl("../Assets/Password2.svg")
+
+ background: Rectangle {
+ color: "transparent"
+ border.color: "transparent"
+ }
+
+ states: [
+ State {
+ name: "visiblePasswordFocused"
+ when: passwordIcon.checked && passwordIcon.activeFocus
+ PropertyChanges {
+ target: passwordIcon
+ icon.source: Qt.resolvedUrl("../Assets/Password.svg")
+ icon.color: config.HoverPasswordIconColor
+ }
+ },
+ State {
+ name: "visiblePasswordHovered"
+ when: passwordIcon.checked && passwordIcon.hovered
+ PropertyChanges {
+ target: passwordIcon
+ icon.source: Qt.resolvedUrl("../Assets/Password.svg")
+ icon.color: config.HoverPasswordIconColor
+ }
+ },
+ State {
+ name: "visiblePassword"
+ when: passwordIcon.checked
+ PropertyChanges {
+ target: passwordIcon
+ icon.source: Qt.resolvedUrl("../Assets/Password.svg")
+ }
+ },
+ State {
+ name: "hiddenPasswordFocused"
+ when: passwordIcon.enabled && passwordIcon.activeFocus
+ PropertyChanges {
+ target: passwordIcon
+ icon.source: Qt.resolvedUrl("../Assets/Password2.svg")
+ icon.color: config.HoverPasswordIconColor
+ }
+ },
+ State {
+ name: "hiddenPasswordHovered"
+ when: passwordIcon.hovered
+ PropertyChanges {
+ target: passwordIcon
+ icon.source: Qt.resolvedUrl("../Assets/Password2.svg")
+ icon.color: config.HoverPasswordIconColor
+ }
+ }
+ ]
+
+ onClicked: toggle()
+ Keys.onReturnPressed: toggle()
+ Keys.onEnterPressed: toggle()
+ KeyNavigation.down: password
+ }
+
+ TextField {
+ id: password
+
+ height: root.font.pointSize * 3
+ width: parent.width
+ anchors.centerIn: parent
+ horizontalAlignment: TextInput.AlignHCenter
+
+ font.bold: true
+ color: config.PasswordFieldTextColor
+ focus: config.PasswordFocus == "true" ? true : false
+ echoMode: passwordIcon.checked ? TextInput.Normal : TextInput.Password
+ placeholderText: config.TranslatePlaceholderPassword || textConstants.password
+ placeholderTextColor: config.PlaceholderTextColor
+ passwordCharacter: "•"
+ passwordMaskDelay: config.HideCompletePassword == "true" ? undefined : 1000
+ renderType: Text.QtRendering
+ selectByMouse: true
+
+ background: Rectangle {
+ color: password.activeFocus ? Qt.rgba(0, 0, 0, 0.35) : Qt.rgba(0, 0, 0, 0.2)
+ border.color: password.activeFocus ? Qt.rgba(255, 255, 255, 0.3) : Qt.rgba(255, 255, 255, 0.12)
+ border.width: password.activeFocus ? 1.5 : 1
+ radius: config.RoundCorners !== "" ? parseInt(config.RoundCorners) : 10
+ Behavior on color {
+ ColorAnimation {
+ duration: 150
+ }
+ }
+ Behavior on border.color {
+ ColorAnimation {
+ duration: 150
+ }
+ }
+ }
+ onAccepted: config.AllowUppercaseLettersInUsernames == "false" ? sddm.login(username.text.toLowerCase(), password.text, sessionSelect.selectedSession) : sddm.login(username.text, password.text, sessionSelect.selectedSession)
+ KeyNavigation.down: loginButton
+ }
+
+ states: [
+ State {
+ name: "focused"
+ when: password.activeFocus
+ PropertyChanges {
+ target: password
+ color: Qt.lighter(config.LoginFieldTextColor, 1.15)
+ }
+ }
+ ]
+ transitions: [
+ Transition {
+ PropertyAnimation {
+ properties: "color, border.color"
+ duration: 150
+ }
+ }
+ ]
+ }
+
+ Item {
+ id: login
+
+ // important
+ // try 4 or 9 ...
+ height: root.font.pointSize * 9
+ width: parent.width / 2
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ visible: config.HideLoginButton == "true" ? false : true
+
+ Item {
+ id: loginButton
+
+ height: root.font.pointSize * 3
+ width: parent.width
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+
+ focus: true
+ activeFocusOnTab: true
+
+ property bool hovered: hoverArea.containsMouse
+ property bool down: hoverArea.pressed
+
+ signal clicked()
+
+ onClicked: config.AllowUppercaseLettersInUsernames == "false" ? sddm.login(username.text.toLowerCase(), password.text, sessionSelect.selectedSession) : sddm.login(username.text, password.text, sessionSelect.selectedSession)
+
+ Keys.onReturnPressed: clicked()
+ Keys.onEnterPressed: clicked()
+
+ KeyNavigation.down: rebootBtn
+
+ Rectangle {
+ id: buttonBackground
+ anchors.fill: parent
+
+ radius: config.RoundCorners !== "" ? parseInt(config.RoundCorners) : 10
+ border.width: loginButton.activeFocus ? 1.5 : 1
+
+ border.color: loginButton.down ? Qt.rgba(21/255, 128/255, 61/255, 0.95) : (loginButton.hovered ? Qt.rgba(34/255, 197/255, 94/255, 0.85) : Qt.rgba(74/255, 222/255, 128/255, 0.65))
+
+ color: loginButton.down ? Qt.rgba(21/255, 128/255, 61/255, 0.80) : (loginButton.hovered ? Qt.rgba(34/255, 197/255, 94/255, 0.65) : Qt.rgba(74/255, 222/255, 128/255, 0.40))
+
+ Behavior on border.color {
+ ColorAnimation {
+ duration: 150
+ }
+ }
+
+ Behavior on color {
+ ColorAnimation {
+ duration: 150
+ }
+ }
+ }
+
+ Text {
+ id: loginButtonText
+ anchors.centerIn: parent
+
+ font.bold: true
+ font.pointSize: root.font.pointSize
+ font.family: root.font.family
+ color: "#ffffff"
+ text: config.TranslateLogin || textConstants.login
+ opacity: 1.0
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 150
+ }
+ }
+ }
+
+ MouseArea {
+ id: hoverArea
+ anchors.fill: parent
+ hoverEnabled: true
+ onClicked: parent.clicked()
+ }
+ }
+ }
+
+ Item {
+ id: powerButtonsRow
+
+ height: root.font.pointSize * 10
+ width: parent.width / 2
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Row {
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: root.font.pointSize * 3
+
+ // Reboot gomb
+ Column {
+ spacing: root.font.pointSize * 0.6
+
+ RoundButton {
+ id: rebootBtn
+
+ width: root.font.pointSize * 6
+ height: root.font.pointSize * 6
+
+ activeFocusOnTab: true
+ Keys.onReturnPressed: sddm.reboot()
+ Keys.onEnterPressed: sddm.reboot()
+ KeyNavigation.right: shutdownBtn
+ hoverEnabled: true
+
+ icon.source: Qt.resolvedUrl("../Assets/Reboot.svg")
+ icon.width: root.font.pointSize * 3
+ icon.height: root.font.pointSize * 3
+ icon.color: "#ffffff"
+
+ opacity: rebootBtn.hovered || rebootBtn.activeFocus ? 1.0 : 0.75
+ Behavior on opacity { NumberAnimation { duration: 150 } }
+
+ scale: rebootBtn.pressed ? 0.88 : 1.0
+ Behavior on scale { NumberAnimation { duration: 100 } }
+
+ background: Rectangle {
+ anchors.fill: parent
+ radius: width / 2
+ color: rebootBtn.pressed ? Qt.rgba(1, 0.45, 0.0, 0.35) : (rebootBtn.hovered ? Qt.rgba(1, 0.55, 0.0, 0.22) : "transparent")
+ border.color: "transparent"
+ border.width: 0
+ Behavior on color { ColorAnimation { duration: 150 } }
+ }
+
+ onClicked: sddm.reboot()
+ }
+
+ Text {
+ anchors.horizontalCenter: parent.children[0].horizontalCenter
+ text: config.TranslateReboot || textConstants.reboot
+ color: "#ffffff"
+ font.pointSize: root.font.pointSize * 0.8
+ font.family: root.font.family
+ horizontalAlignment: Text.AlignHCenter
+ opacity: rebootBtn.hovered || rebootBtn.activeFocus ? 1.0 : 0.75
+ Behavior on opacity { NumberAnimation { duration: 150 } }
+ }
+ }
+
+ // Shutdown gomb
+ Column {
+ spacing: root.font.pointSize * 0.6
+
+ RoundButton {
+ id: shutdownBtn
+
+ width: root.font.pointSize * 6
+ height: root.font.pointSize * 6
+
+ activeFocusOnTab: true
+ Keys.onReturnPressed: sddm.powerOff()
+ Keys.onEnterPressed: sddm.powerOff()
+ KeyNavigation.left: rebootBtn
+ hoverEnabled: true
+
+ icon.source: Qt.resolvedUrl("../Assets/Shutdown.svg")
+ icon.width: root.font.pointSize * 3
+ icon.height: root.font.pointSize * 3
+ icon.color: "#ffffff"
+
+ opacity: shutdownBtn.hovered || shutdownBtn.activeFocus ? 1.0 : 0.75
+ Behavior on opacity { NumberAnimation { duration: 150 } }
+
+ scale: shutdownBtn.pressed ? 0.88 : 1.0
+ Behavior on scale { NumberAnimation { duration: 100 } }
+
+ background: Rectangle {
+ anchors.fill: parent
+ radius: width / 2
+ color: shutdownBtn.pressed ? Qt.rgba(0.8, 0.1, 0.1, 0.40) : (shutdownBtn.hovered ? Qt.rgba(0.8, 0.1, 0.1, 0.25) : "transparent")
+ border.color: "transparent"
+ border.width: 0
+ Behavior on color { ColorAnimation { duration: 150 } }
+ }
+
+ onClicked: sddm.powerOff()
+ }
+
+ Text {
+ anchors.horizontalCenter: parent.children[0].horizontalCenter
+ text: config.TranslateShutdown || textConstants.shutdown
+ color: "#ffffff"
+ font.pointSize: root.font.pointSize * 0.8
+ font.family: root.font.family
+ horizontalAlignment: Text.AlignHCenter
+ opacity: shutdownBtn.hovered || shutdownBtn.activeFocus ? 1.0 : 0.75
+ Behavior on opacity { NumberAnimation { duration: 150 } }
+ }
+ }
+ }
+ }
+
+ Connections {
+ target: sddm
+ function onLoginSucceeded() {
+ }
+ function onLoginFailed() {
+ failed = true;
+ resetError.running ? resetError.stop() && resetError.start() : resetError.start();
+ }
+ }
+
+ Timer {
+ id: resetError
+ interval: 2000
+ onTriggered: failed = false
+ running: false
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/KeyboardLayoutIndicator.qml b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/KeyboardLayoutIndicator.qml
new file mode 100644
index 0000000..0fb03be
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/KeyboardLayoutIndicator.qml
@@ -0,0 +1,237 @@
+// Keyboard Layout Indicator component
+// Displays the current locale/layout and switches between them when clicked (if multiple are available)
+
+import QtQuick 2.15
+import QtQml 2.15
+import QtQuick.Controls 2.15
+
+Item {
+ id: layoutIndicator
+
+ implicitWidth: layoutRow.implicitWidth
+ implicitHeight: layoutRow.implicitHeight
+ width: implicitWidth
+ height: implicitHeight
+
+ // Number of available layouts
+ property int layoutsCount: (typeof keyboard !== "undefined" && keyboard && keyboard.layouts) ? keyboard.layouts.length : 0
+
+ // Show if we have layout info or a valid locale fallback
+ visible: currentShortName !== ""
+
+ // Get current layout shortname (e.g. "US", "HU")
+ property string currentShortName: ""
+
+ function updateCurrentShortName() {
+ if (layoutsCount > 0) {
+ var idx = keyboard.currentLayout
+ if (idx >= 0 && idx < layoutsCount) {
+ var layoutObj = keyboard.layouts[idx]
+ if (layoutObj && layoutObj.shortName) {
+ currentShortName = layoutObj.shortName.toUpperCase()
+ return
+ }
+ }
+ }
+
+ // Fallback to config locale or system locale
+ var loc = config.Locale || Qt.locale().name
+ if (loc) {
+ var parts = loc.split("_")
+ if (parts.length > 0 && parts[0]) {
+ currentShortName = parts[0].toUpperCase()
+ return
+ }
+ }
+ currentShortName = ""
+ }
+
+ Component.onCompleted: updateCurrentShortName()
+
+ // Colors matched to the theme config
+ property color textColor: config.UserIconColor || "#ffffff"
+ property color hoverColor: config.HoverUserIconColor || "#b7cef1"
+
+ Row {
+ id: layoutRow
+ height: root.font.pointSize * 1.1
+ spacing: root.font.pointSize * 0.4
+ // Removed anchors.centerIn to prevent circular binding loop with parent width/height
+
+ // Keyboard icon
+ Canvas {
+ id: kbdCanvas
+ width: root.font.pointSize * 1.5
+ height: root.font.pointSize * 1.1
+ anchors.verticalCenter: parent.verticalCenter
+
+ onPaint: {
+ var ctx = getContext("2d")
+ ctx.clearRect(0, 0, width, height)
+
+ var canHover = layoutsCount >= 1
+ var col = (layoutMouseArea.containsMouse && canHover) ? layoutIndicator.hoverColor : layoutIndicator.textColor
+
+ ctx.strokeStyle = col
+ ctx.fillStyle = col
+ ctx.lineWidth = height * 0.08
+ ctx.lineCap = "round"
+ ctx.lineJoin = "round"
+ ctx.globalAlpha = 0.85
+
+ // Draw keyboard outline
+ var r = height * 0.15
+ var bx = ctx.lineWidth / 2
+ var by = ctx.lineWidth / 2
+ var bw = width - ctx.lineWidth
+ var bh = height - ctx.lineWidth
+
+ ctx.beginPath()
+ ctx.moveTo(bx + r, by)
+ ctx.arcTo(bx + bw, by, bx + bw, by + bh, r)
+ ctx.arcTo(bx + bw, by + bh, bx, by + bh, r)
+ ctx.arcTo(bx, by + bh, bx, by, r)
+ ctx.arcTo(bx, by, bx + bw, by, r)
+ ctx.closePath()
+ ctx.stroke()
+
+ // Draw simple key grid
+ ctx.lineWidth = height * 0.06
+ var kw = bw / 6
+ var kh = bh / 4
+ // Row 1 & 2 keys
+ for (var i = 0; i < 4; i++) {
+ ctx.fillRect(bx + kw * (i + 1), by + kh, kw * 0.6, kh * 0.6)
+ ctx.fillRect(bx + kw * (i + 0.5), by + kh * 2, kw * 0.6, kh * 0.6)
+ }
+ // Spacebar
+ ctx.fillRect(bx + kw * 1.5, by + kh * 3, kw * 3, kh * 0.6)
+
+ ctx.globalAlpha = 1.0
+ }
+
+ Connections {
+ target: layoutMouseArea
+ function onContainsMouseChanged() { kbdCanvas.requestPaint() }
+ }
+ }
+
+ // Layout Short Name (e.g. US, HU)
+ Text {
+ id: layoutText
+ anchors.verticalCenter: parent.verticalCenter
+ text: layoutIndicator.currentShortName
+
+ color: {
+ var canHover = layoutsCount >= 1
+ return (layoutMouseArea.containsMouse && canHover) ? layoutIndicator.hoverColor : layoutIndicator.textColor
+ }
+
+ font.pointSize: root.font.pointSize * 0.82
+ font.family: root.font.family
+ font.bold: true
+ }
+ }
+
+ MouseArea {
+ id: layoutMouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: (layoutsCount >= 1) ? Qt.PointingHandCursor : Qt.ArrowCursor
+
+ onClicked: {
+ if (layoutsCount >= 1) {
+ layoutPopup.open()
+ }
+ }
+ }
+
+ Popup {
+ id: layoutPopup
+ y: parent.height + 5
+ x: -width / 2 + parent.width / 2
+ width: Math.max(160, layoutRow.width * 1.5)
+ padding: 5
+
+ background: Rectangle {
+ radius: config.RoundCorners !== "" ? parseInt(config.RoundCorners) / 2 : 10
+ color: config.BackgroundColor || "#1a1a1a"
+ border.color: Qt.rgba(1, 1, 1, 0.20)
+ border.width: 1
+ }
+
+ contentItem: ListView {
+ id: layoutListView
+ implicitHeight: Math.min(200, contentHeight)
+ clip: true
+ model: layoutsCount
+ ScrollIndicator.vertical: ScrollIndicator { }
+
+ delegate: Rectangle {
+ id: delegateItem
+ width: parent.width
+ height: root.font.pointSize * 2
+ color: delegateMouse.containsMouse ? Qt.rgba(1, 1, 1, 0.15) : "transparent"
+ radius: config.RoundCorners !== "" ? parseInt(config.RoundCorners) / 4 : 5
+
+ // Get layout object safely
+ property var layoutObj: (typeof keyboard !== "undefined" && keyboard && keyboard.layouts) ? keyboard.layouts[index] : null
+ property string shortName: layoutObj && layoutObj.shortName ? layoutObj.shortName.toUpperCase() : ""
+ property string longName: layoutObj && layoutObj.longName ? layoutObj.longName : ""
+
+ Row {
+ spacing: 8
+ anchors.fill: parent
+ anchors.leftMargin: 10
+ anchors.rightMargin: 10
+
+ Text {
+ text: delegateItem.shortName
+ color: index === keyboard.currentLayout ? (config.HoverUserIconColor || "#b7cef1") : (config.UserIconColor || "#ffffff")
+ font.pointSize: root.font.pointSize * 0.8
+ font.family: root.font.family
+ font.bold: true
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Text {
+ text: delegateItem.longName
+ color: index === keyboard.currentLayout ? (config.HoverUserIconColor || "#b7cef1") : (config.UserIconColor || "#ffffff")
+ font.pointSize: root.font.pointSize * 0.8
+ font.family: root.font.family
+ elide: Text.ElideRight
+ width: parent.width - x - 10
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ MouseArea {
+ id: delegateMouse
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ keyboard.currentLayout = index
+ layoutPopup.close()
+ }
+ }
+ }
+ }
+
+ enter: Transition {
+ NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 150 }
+ }
+ exit: Transition {
+ NumberAnimation { property: "opacity"; from: 1; to: 0; duration: 150 }
+ }
+ }
+
+ Connections {
+ target: keyboard
+ ignoreUnknownSignals: true
+ function onCurrentLayoutChanged() {
+ kbdCanvas.requestPaint()
+ updateCurrentShortName()
+ }
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/LoginForm.qml b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/LoginForm.qml
new file mode 100644
index 0000000..515d6b3
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/LoginForm.qml
@@ -0,0 +1,89 @@
+// Config created by Keyitdev https://git.rp1.hu/gabeszm/sddm-rave-theme
+// Copyright (C) 2022-2025 Keyitdev
+// Based on https://github.com/MarianArlt/sddm-sugar-dark
+// Distributed under the GPLv3+ License https://www.gnu.org/licenses/gpl-3.0.html
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import SddmComponents 2.0 as SDDM
+
+ColumnLayout {
+ id: formContainer
+ SDDM.TextConstants { id: textConstants }
+
+ property int p: config.ScreenPadding == "" ? 0 : config.ScreenPadding
+ property string a: config.FormPosition
+
+ spacing: 15
+
+ Clock {
+ id: clock
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.preferredHeight: clock.implicitHeight
+ Layout.leftMargin: p != "0" ? a == "left" ? -p : a == "right" ? p : 0 : 0
+ }
+
+ // Akkumulátor + Hálózat jelzők
+ RowLayout {
+ id: statusRow
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.leftMargin: p != "0" ? a == "left" ? -p : a == "right" ? p : 0 : 0
+ Layout.bottomMargin: visible ? 4 : 0
+ visible: (batteryIndicator.batteryLevel >= 0) || networkIndicator.isConnected
+
+ spacing: root.font.pointSize * 1.2
+
+ BatteryIndicator {
+ id: batteryIndicator
+ Layout.alignment: Qt.AlignVCenter
+ Layout.preferredWidth: batteryIndicator.implicitWidth
+ Layout.preferredHeight: batteryIndicator.implicitHeight
+ visible: batteryIndicator.batteryLevel >= 0
+ }
+
+ NetworkIndicator {
+ id: networkIndicator
+ Layout.alignment: Qt.AlignVCenter
+ Layout.preferredWidth: networkIndicator.implicitWidth
+ Layout.preferredHeight: networkIndicator.implicitHeight
+ visible: networkIndicator.isConnected
+ }
+ }
+
+ // Billentyűzet kiosztás jelző (külön sorban, hogy akksi/wifi hiányában is látszódjon)
+ KeyboardLayoutIndicator {
+ id: keyboardLayoutIndicator
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.preferredHeight: keyboardLayoutIndicator.implicitHeight
+ Layout.leftMargin: p != "0" ? a == "left" ? -p : a == "right" ? p : 0 : 0
+ Layout.bottomMargin: visible ? 4 : 0
+ }
+
+ Input {
+ id: input
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.preferredHeight: input.implicitHeight
+ Layout.leftMargin: p != "0" ? a == "left" ? -p : a == "right" ? p : 0 : 0
+ Layout.topMargin: 0
+ }
+
+ SessionButton {
+ id: sessionSelect
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.preferredHeight: sessionSelect.height
+ Layout.leftMargin: p != "0" ? a == "left" ? -p : a == "right" ? p : 0 : 0
+ }
+
+ VirtualKeyboardButton {
+ id: virtualKeyboardButton
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.preferredHeight: root.font.pointSize * 2
+ Layout.leftMargin: p != "0" ? a == "left" ? -p : a == "right" ? p : 0 : 0
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/NetworkIndicator.qml b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/NetworkIndicator.qml
new file mode 100644
index 0000000..bed97c8
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/NetworkIndicator.qml
@@ -0,0 +1,243 @@
+// Config created by Keyitdev https://git.rp1.hu/gabeszm/sddm-rave-theme
+// Copyright (C) 2022-2025 Keyitdev
+// Distributed under the GPLv3+ License https://www.gnu.org/licenses/gpl-3.0.html
+
+import QtQuick 2.15
+
+Row {
+ id: networkIndicator
+
+ height: root.font.pointSize * 1.6
+ spacing: root.font.pointSize * 0.5
+
+ // "none" | "wifi" | "ethernet"
+ property string netType: "none"
+ property int wifiQuality: 0 // 0-100
+ property string activeIface: ""
+ property bool isConnected: netType !== "none"
+
+ visible: isConnected
+
+ property var wifiIfaces: [
+ "wlan0","wlan1","wlp1s0","wlp2s0","wlp3s0",
+ "wlp4s0","wlp5s0","wlp6s0","wlp0s20f3"
+ ]
+ property var ethIfaces: [
+ "eth0","eth1","eno1","eno2",
+ "enp1s0","enp2s0","enp3s0","enp4s0",
+ "enp0s3","enp0s31f6","ens33","ens3","ens18"
+ ]
+
+ // ── Fájl olvasó ──────────────────────────────────────────────────────────
+ function readFile(path, cb) {
+ var xhr = new XMLHttpRequest()
+ xhr.open("GET", "file://" + path, true)
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === XMLHttpRequest.DONE) {
+ var res = xhr.responseText && xhr.responseText.trim() !== ""
+ ? xhr.responseText.trim() : null
+ cb(res)
+ }
+ }
+ xhr.send()
+ }
+
+ // ── WiFi jelerősség /proc/net/wireless-ből ───────────────────────────────
+ function readWifiSignal(iface) {
+ readFile("/proc/net/wireless", function(content) {
+ if (!content) { wifiQuality = 60; netCanvas.requestPaint(); return }
+ var lines = content.split("\n")
+ for (var i = 0; i < lines.length; i++) {
+ if (lines[i].indexOf(iface + ":") !== -1) {
+ // formátum: "wlan0: 0000 61. -49. -256 ..."
+ var parts = lines[i].trim().replace(/\./g,"").split(/\s+/)
+ // parts[2] = link quality (0–70 tartomány általában)
+ var q = parseFloat(parts[2]) || 0
+ wifiQuality = Math.min(100, Math.round(q / 70 * 100))
+ break
+ }
+ }
+ netCanvas.requestPaint()
+ })
+ }
+
+ // ── Interfész keresők ────────────────────────────────────────────────────
+ function checkWifi(idx) {
+ if (idx >= wifiIfaces.length) { checkEth(0); return }
+ readFile("/sys/class/net/" + wifiIfaces[idx] + "/operstate", function(st) {
+ if (st === "up") {
+ activeIface = wifiIfaces[idx]
+ netType = "wifi"
+ readWifiSignal(wifiIfaces[idx])
+ } else {
+ checkWifi(idx + 1)
+ }
+ })
+ }
+
+ // ── Ethernet keresés ─────────────────────────────────────────────────────
+ function checkEth(idx) {
+ if (idx >= ethIfaces.length) { netType = "none"; return }
+ readFile("/sys/class/net/" + ethIfaces[idx] + "/operstate", function(st) {
+ if (st === "up") {
+ activeIface = ethIfaces[idx]
+ netType = "ethernet"
+ netCanvas.requestPaint()
+ } else {
+ checkEth(idx + 1)
+ }
+ })
+ }
+
+ function refresh() { checkWifi(0) }
+
+ Component.onCompleted: refresh()
+
+ Timer {
+ interval: 15000
+ repeat: true
+ running: true
+ onTriggered: networkIndicator.refresh()
+ }
+
+ // ── Szín ─────────────────────────────────────────────────────────────────
+ function signalColor(q) {
+ if (q >= 65) return "#4ade80" // zöld – erős
+ if (q >= 40) return "#facc15" // sárga – közepes
+ return "#fb923c" // narancs – gyenge
+ }
+
+ // ── UI ───────────────────────────────────────────────────────────────────
+ Canvas {
+ id: netCanvas
+ width: root.font.pointSize * 2.0
+ height: root.font.pointSize * 1.6
+ anchors.verticalCenter: parent.verticalCenter
+
+ // Qt Canvas nem támogatja ctx.roundRect() – saját helper
+ function rr(c, x, y, w, h, r) {
+ r = Math.min(Math.max(r, 0), Math.min(w, h) / 2)
+ c.beginPath()
+ c.moveTo(x + r, y)
+ c.arcTo(x + w, y, x + w, y + h, r)
+ c.arcTo(x + w, y + h, x, y + h, r)
+ c.arcTo(x, y + h, x, y, r)
+ c.arcTo(x, y, x + w, y, r)
+ c.closePath()
+ }
+
+ onPaint: {
+ var ctx = getContext("2d")
+ ctx.clearRect(0, 0, width, height)
+
+ if (networkIndicator.netType === "wifi")
+ drawWifi(ctx)
+ else if (networkIndicator.netType === "ethernet")
+ drawEthernet(ctx)
+ }
+
+ // ── WiFi antenna ikon (4 ívelt sáv) ─────────────────────────────
+ function drawWifi(ctx) {
+ var q = networkIndicator.wifiQuality
+ var cx = width / 2
+ var base = height * 0.96
+ var dot = height * 0.08
+
+ // Sávok száma a minőség alapján
+ var bars = q >= 70 ? 4 : q >= 45 ? 3 : q >= 20 ? 2 : 1
+ var col = networkIndicator.signalColor(q)
+
+ var radii = [height*0.14, height*0.32, height*0.54, height*0.76]
+ var arcStart = Math.PI * 1.25
+ var arcEnd = Math.PI * 1.75
+
+ for (var i = 0; i < 4; i++) {
+ ctx.beginPath()
+ ctx.arc(cx, base, radii[i], arcStart, arcEnd)
+ ctx.strokeStyle = col
+ ctx.globalAlpha = i < bars ? 0.90 : 0.22
+ ctx.lineWidth = height * 0.10
+ ctx.lineCap = "round"
+ ctx.stroke()
+ }
+
+ // Középső pont (antenna)
+ ctx.beginPath()
+ ctx.arc(cx, base, dot, 0, Math.PI * 2)
+ ctx.fillStyle = col
+ ctx.globalAlpha = 0.95
+ ctx.fill()
+ ctx.globalAlpha = 1.0
+ }
+
+ // ── Ethernet dugó ikon ───────────────────────────────────────────
+ function drawEthernet(ctx) {
+ var col = "#4ade80"
+ var lw = height * 0.09
+ ctx.strokeStyle = col
+ ctx.fillStyle = col
+ ctx.lineWidth = lw
+ ctx.lineCap = "round"
+ ctx.lineJoin = "round"
+ ctx.globalAlpha = 0.90
+
+ var cx = width / 2
+ var cy = height / 2
+
+ // Kábel test (felső téglalap)
+ var bw = width * 0.70
+ var bh = height * 0.38
+ var bx = cx - bw / 2
+ var by = cy - height * 0.30
+ rr(ctx, bx, by, bw, bh, height * 0.06)
+ ctx.stroke()
+
+ // 3 érintkező pin a téglalapban
+ var pinW = width * 0.08
+ var pinH = height * 0.22
+ var pinY = by - pinH + lw
+ var gap = bw / 4
+ for (var p = 0; p < 3; p++) {
+ rr(ctx, bx + gap * (p + 0.5) - pinW/2, pinY, pinW, pinH, 2)
+ ctx.fill()
+ }
+
+ // Kábel vonala lefelé
+ ctx.beginPath()
+ ctx.moveTo(cx, by + bh)
+ ctx.lineTo(cx, cy + height * 0.38)
+ ctx.stroke()
+
+ // Dugó alap vonal
+ ctx.lineWidth = lw * 1.5
+ ctx.beginPath()
+ ctx.moveTo(cx - width * 0.22, cy + height * 0.38)
+ ctx.lineTo(cx + width * 0.22, cy + height * 0.38)
+ ctx.stroke()
+
+ ctx.globalAlpha = 1.0
+ }
+ }
+
+ // Felirat
+ Text {
+ id: netLabel
+ anchors.verticalCenter: parent.verticalCenter
+ text: {
+ if (networkIndicator.netType === "ethernet") return "Ethernet"
+ return networkIndicator.wifiQuality + "%"
+ }
+ color: networkIndicator.netType === "ethernet"
+ ? "#4ade80"
+ : networkIndicator.signalColor(networkIndicator.wifiQuality)
+ font.pointSize: root.font.pointSize * 0.82
+ font.family: root.font.family
+ }
+
+ onNetTypeChanged: {
+ netCanvas.requestPaint()
+ }
+ onWifiQualityChanged: {
+ netCanvas.requestPaint()
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/ParticleOverlay.qml b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/ParticleOverlay.qml
new file mode 100644
index 0000000..6fa02f9
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/ParticleOverlay.qml
@@ -0,0 +1,90 @@
+// Config created by Keyitdev https://git.rp1.hu/gabeszm/sddm-rave-theme
+// Copyright (C) 2022-2025 Keyitdev
+// Distributed under the GPLv3+ License https://www.gnu.org/licenses/gpl-3.0.html
+
+import QtQuick 2.15
+
+Canvas {
+ id: particleCanvas
+
+ anchors.fill: parent
+
+ property int particleCount: 55
+ property var particles: []
+ property real time: 0
+
+ function initParticles() {
+ var arr = []
+ for (var i = 0; i < particleCount; i++) {
+ arr.push({
+ x: Math.random() * width,
+ y: Math.random() * height,
+ vx: (Math.random() - 0.5) * 0.35,
+ vy: (Math.random() - 0.5) * 0.35,
+ baseR: Math.random() * 2.0 + 0.8,
+ hue: Math.random() * 360,
+ hueSpeed: (Math.random() - 0.5) * 0.4,
+ phase: Math.random() * Math.PI * 2,
+ opacity: Math.random() * 0.45 + 0.15,
+ glowR: Math.random() * 18 + 8
+ })
+ }
+ particles = arr
+ }
+
+ onPaint: {
+ var ctx = getContext("2d")
+ ctx.clearRect(0, 0, width, height)
+
+ for (var i = 0; i < particles.length; i++) {
+ var p = particles[i]
+ var r = p.baseR * (0.75 + 0.25 * Math.sin(p.phase + time * 2.2))
+
+ // Glow halo
+ var grd = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.glowR)
+ grd.addColorStop(0, "hsla(" + p.hue + ",100%,72%," + (p.opacity * 0.55) + ")")
+ grd.addColorStop(1, "hsla(" + p.hue + ",100%,72%,0)")
+ ctx.beginPath()
+ ctx.arc(p.x, p.y, p.glowR, 0, Math.PI * 2)
+ ctx.fillStyle = grd
+ ctx.fill()
+
+ // Core dot
+ ctx.beginPath()
+ ctx.arc(p.x, p.y, r, 0, Math.PI * 2)
+ ctx.fillStyle = "hsla(" + p.hue + ",100%,88%," + p.opacity + ")"
+ ctx.fill()
+ }
+ }
+
+ Timer {
+ interval: 16
+ repeat: true
+ running: true
+ onTriggered: {
+ particleCanvas.time += 0.016
+ var pts = particleCanvas.particles
+ var W = particleCanvas.width
+ var H = particleCanvas.height
+ var t = particleCanvas.time
+ for (var i = 0; i < pts.length; i++) {
+ var p = pts[i]
+ p.x += p.vx + Math.sin(t * 0.9 + p.phase) * 0.28
+ p.y += p.vy + Math.cos(t * 0.7 + p.phase) * 0.28
+ p.hue += p.hueSpeed
+ p.phase += 0.008
+ var margin = p.glowR
+ if (p.x < -margin) p.x = W + margin
+ if (p.x > W + margin) p.x = -margin
+ if (p.y < -margin) p.y = H + margin
+ if (p.y > H + margin) p.y = -margin
+ }
+ particleCanvas.requestPaint()
+ }
+ }
+
+ Component.onCompleted: {
+ // kis késleltetés hogy a méret biztosan kész legyen
+ Qt.callLater(initParticles)
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/SessionButton.qml b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/SessionButton.qml
new file mode 100644
index 0000000..301256b
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/SessionButton.qml
@@ -0,0 +1,158 @@
+// Config created by Keyitdev https://git.rp1.hu/gabeszm/sddm-rave-theme
+// Copyright (C) 2022-2025 Keyitdev
+// Based on https://github.com/MarianArlt/sddm-sugar-dark
+// Distributed under the GPLv3+ License https://www.gnu.org/licenses/gpl-3.0.html
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+
+Item {
+ id: sessionButton
+
+ height: root.font.pointSize * 2
+ width: parent.width / 2
+
+ property var selectedSession: selectSession.currentIndex
+ property string textConstantSession
+ property int loginButtonWidth
+ property ComboBox exposeSession: selectSession
+
+ ComboBox {
+ id: selectSession
+
+ // important
+ // change also in errorMessage
+ height: root.font.pointSize * 2
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ hoverEnabled: true
+ model: sessionModel
+ currentIndex: model.lastIndex
+ textRole: "name"
+
+ Keys.onPressed: function(event) {
+ if ((event.key == Qt.Key_Left || event.key == Qt.Key_Right) && !popup.opened) {
+ popup.open();
+ }
+ }
+
+ delegate: ItemDelegate {
+ // minus padding
+ width: popupHandler.width - 20
+ anchors.horizontalCenter: popupHandler.horizontalCenter
+
+ contentItem: Text {
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+
+ text: model.name
+ font.pointSize: root.font.pointSize * 0.8
+ font.family: root.font.family
+ color: config.DropdownTextColor
+ }
+
+ background: Rectangle {
+ color: selectSession.highlightedIndex === index ? Qt.rgba(1, 1, 1, 0.15) : "transparent"
+ radius: config.RoundCorners !== "" ? parseInt(config.RoundCorners) / 4 : 5
+ }
+ }
+
+ indicator {
+ visible: false
+ }
+
+ contentItem: Text {
+ id: displayedItem
+
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ leftPadding: root.font.pointSize * 0.8
+ rightPadding: root.font.pointSize * 0.8
+
+ text: (config.TranslateSessionSelection || "Session") + ": " + selectSession.currentText
+ color: config.SessionButtonTextColor
+ font.pointSize: root.font.pointSize * 0.8
+ font.family: root.font.family
+
+ Keys.onReleased: parent.popup.open()
+ }
+
+ background: Rectangle {
+ anchors.fill: parent
+ radius: config.RoundCorners !== "" ? parseInt(config.RoundCorners) : 10
+ color: selectSession.down ? Qt.rgba(1, 1, 1, 0.12) : (selectSession.hovered ? Qt.rgba(1, 1, 1, 0.08) : Qt.rgba(1, 1, 1, 0.04))
+ border.color: selectSession.visualFocus ? Qt.rgba(1, 1, 1, 0.60) : (selectSession.hovered ? Qt.rgba(1, 1, 1, 0.35) : Qt.rgba(1, 1, 1, 0.18))
+ border.width: selectSession.visualFocus ? 1.5 : 1
+
+ Behavior on color { ColorAnimation { duration: 150 } }
+ Behavior on border.color { ColorAnimation { duration: 150 } }
+ }
+
+ popup: Popup {
+ id: popupHandler
+
+ implicitHeight: contentItem.implicitHeight
+ width: sessionButton.width
+ y: parent.height - 1
+ x: -popupHandler.width/2 + displayedItem.width/2
+ padding: 10
+
+ contentItem: ListView {
+ implicitHeight: contentHeight + 20
+
+ clip: true
+ model: selectSession.popup.visible ? selectSession.delegateModel : null
+ currentIndex: selectSession.highlightedIndex
+ ScrollIndicator.vertical: ScrollIndicator { }
+ }
+
+ background: Rectangle {
+ radius: config.RoundCorners !== "" ? parseInt(config.RoundCorners) / 2 : 10
+ color: config.BackgroundColor
+ border.color: Qt.rgba(1, 1, 1, 0.20)
+ border.width: 1
+ }
+
+ enter: Transition {
+ NumberAnimation { property: "opacity"; from: 0; to: 1 }
+ }
+ }
+
+ states: [
+ State {
+ name: "pressed"
+ when: selectSession.down
+ PropertyChanges {
+ target: displayedItem
+ color: Qt.darker(config.HoverSessionButtonTextColor, 1.1)
+ }
+ },
+ State {
+ name: "hovered"
+ when: selectSession.hovered
+ PropertyChanges {
+ target: displayedItem
+ color: Qt.lighter(config.HoverSessionButtonTextColor, 1.1)
+ }
+ },
+ State {
+ name: "focused"
+ when: selectSession.visualFocus
+ PropertyChanges {
+ target: displayedItem
+ color: config.HoverSessionButtonTextColor
+ }
+ }
+ ]
+ transitions: [
+ Transition {
+ PropertyAnimation {
+ properties: "color"
+ duration: 150
+ }
+ }
+ ]
+
+ }
+
+}
diff --git a/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/ToastNotification.qml b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/ToastNotification.qml
new file mode 100644
index 0000000..43817c4
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/ToastNotification.qml
@@ -0,0 +1,83 @@
+// Config created by Keyitdev https://git.rp1.hu/gabeszm/sddm-rave-theme
+// Copyright (C) 2022-2025 Keyitdev
+// Distributed under the GPLv3+ License https://www.gnu.org/licenses/gpl-3.0.html
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+
+Item {
+ id: toastRoot
+
+ anchors.top: parent.top
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: parent.width
+ height: toastRect.height + root.font.pointSize * 2
+ z: 100
+
+ property string message: ""
+ property bool showing: false
+
+ function show(msg) {
+ message = msg
+ showing = true
+ hideTimer.restart()
+ }
+
+ function hide() {
+ showing = false
+ }
+
+ Rectangle {
+ id: toastRect
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ y: toastRoot.showing ? root.font.pointSize * 2 : -height - 10
+
+ Behavior on y {
+ NumberAnimation { duration: 380; easing.type: Easing.OutBack }
+ }
+
+ width: toastRow.implicitWidth + root.font.pointSize * 3
+ height: root.font.pointSize * 3
+ radius: height / 2
+
+ color: Qt.rgba(0.75, 0.08, 0.08, 0.88)
+ border.color: Qt.rgba(1, 0.35, 0.35, 0.65)
+ border.width: 1
+
+ // Pulse animation border
+ SequentialAnimation on border.color {
+ running: toastRoot.showing
+ loops: Animation.Infinite
+ ColorAnimation { to: Qt.rgba(1, 0.5, 0.5, 0.9); duration: 600; easing.type: Easing.InOutSine }
+ ColorAnimation { to: Qt.rgba(1, 0.25, 0.25, 0.5); duration: 600; easing.type: Easing.InOutSine }
+ }
+
+ Row {
+ id: toastRow
+ anchors.centerIn: parent
+ spacing: root.font.pointSize * 0.6
+
+ Text {
+ anchors.verticalCenter: parent.verticalCenter
+ text: "⚠"
+ color: "#ff8888"
+ font.pointSize: root.font.pointSize * 0.9
+ }
+
+ Text {
+ anchors.verticalCenter: parent.verticalCenter
+ text: toastRoot.message
+ color: "#ffffff"
+ font.pointSize: root.font.pointSize * 0.85
+ font.family: root.font.family
+ }
+ }
+ }
+
+ Timer {
+ id: hideTimer
+ interval: 3000
+ onTriggered: toastRoot.hide()
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/UserSwitcher.qml b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/UserSwitcher.qml
new file mode 100644
index 0000000..d16c496
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/UserSwitcher.qml
@@ -0,0 +1,332 @@
+// Config created by Keyitdev https://git.rp1.hu/gabeszm/sddm-rave-theme
+// Copyright (C) 2022-2025 Keyitdev
+// Distributed under the GPLv3+ License https://www.gnu.org/licenses/gpl-3.0.html
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Effects
+
+Item {
+ id: userSwitcher
+
+ // Csak akkor látható ha több mint 1 felhasználó van
+ visible: userModel.count > 1
+
+ width: switchBtn.implicitWidth + root.font.pointSize * 2
+ height: switchBtn.implicitHeight + root.font.pointSize * 0.6
+
+ // Kiválasztott felhasználó indexe (kívülről beállítható)
+ property int selectedIndex: 0
+ property string selectedName: {
+ if (userModel.count < 1) return ""
+ var n = userModel.data(userModel.index(selectedIndex, 0), Qt.UserRole + 1)
+ return n ? n : ""
+ }
+
+ signal userSwitched(int idx, string name)
+
+ // ── Segédfüggvények ───────────────────────────────────────────────────────────────────
+ function isImagePath(p) {
+ return p && p !== ""
+ }
+
+ function getUserIcon(idx) {
+ // Qt.UserRole+4 = IconRole (ebben az SDDM verziban a Home = +3)
+ var icon = userModel.data(userModel.index(idx, 0), Qt.UserRole + 4)
+ return isImagePath(icon) ? icon : ""
+ }
+
+ function getUserName(idx) {
+ return userModel.data(userModel.index(idx, 0), Qt.UserRole + 1) || ""
+ }
+
+ // ── Gomb (chip stílus) ────────────────────────────────────────────────────
+ Rectangle {
+ id: switchBtn
+ anchors.centerIn: parent
+ implicitWidth: btnRow.implicitWidth + root.font.pointSize * 1.6
+ implicitHeight: btnRow.implicitHeight + root.font.pointSize * 0.7
+ radius: implicitHeight / 2
+
+ color: btnArea.pressed
+ ? Qt.rgba(1, 1, 1, 0.18)
+ : btnArea.containsMouse
+ ? Qt.rgba(1, 1, 1, 0.12)
+ : Qt.rgba(1, 1, 1, 0.07)
+ border.color: btnArea.containsMouse
+ ? Qt.rgba(1, 1, 1, 0.45)
+ : Qt.rgba(1, 1, 1, 0.22)
+ border.width: 1
+
+ Behavior on color { ColorAnimation { duration: 150 } }
+ Behavior on border.color{ ColorAnimation { duration: 150 } }
+
+ Row {
+ id: btnRow
+ anchors.centerIn: parent
+ spacing: root.font.pointSize * 0.5
+
+ // Mini avatar kör
+ Rectangle {
+ id: miniAvatarCircle
+ width: root.font.pointSize * 1.6
+ height: root.font.pointSize * 1.6
+ radius: width / 2
+ clip: true
+ color: Qt.rgba(0.10, 0.10, 0.18, 0.70)
+ anchors.verticalCenter: parent.verticalCenter
+
+ Image {
+ id: miniPhoto
+ anchors.fill: parent
+ source: userSwitcher.getUserIcon(userSwitcher.selectedIndex)
+ fillMode: Image.PreserveAspectCrop
+ smooth: true
+ visible: false
+ }
+
+ Item {
+ id: miniMask
+ anchors.fill: parent
+ visible: false
+ layer.enabled: true
+ Rectangle {
+ anchors.fill: parent
+ radius: parent.width / 2
+ color: "black"
+ }
+ }
+
+ MultiEffect {
+ anchors.fill: parent
+ source: miniPhoto
+ visible: miniPhoto.status === Image.Ready && miniPhoto.source !== ""
+ maskEnabled: true
+ maskSource: miniMask
+ maskThresholdMin: 0.5
+ maskSpreadAtMin: 1.0
+ }
+
+ Image {
+ anchors.centerIn: parent
+ width: parent.width * 0.70
+ height: parent.height * 0.70
+ source: Qt.resolvedUrl("../Assets/User.svg")
+ fillMode: Image.PreserveAspectFit
+ visible: miniPhoto.status !== Image.Ready || miniPhoto.source === ""
+ opacity: 0.80
+ }
+ }
+
+ // Felhasználónév
+ Text {
+ anchors.verticalCenter: parent.verticalCenter
+ text: userSwitcher.selectedName
+ color: "#ffffff"
+ font.pointSize: root.font.pointSize * 0.82
+ font.family: root.font.family
+ }
+
+ // Nyíl ikon
+ Text {
+ anchors.verticalCenter: parent.verticalCenter
+ text: "⌄"
+ color: Qt.rgba(1, 1, 1, 0.70)
+ font.pointSize: root.font.pointSize * 0.85
+ font.family: root.font.family
+ // Csak ha több user van, akkor látszik a nyíl
+ visible: userModel.count > 1
+
+ rotation: popup.visible ? 180 : 0
+ Behavior on rotation { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } }
+ }
+ }
+
+ MouseArea {
+ id: btnArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ // Csak akkor nyit popup-ot ha több felhasználó van
+ onClicked: if (userModel.count > 1) { popup.visible ? popup.close() : popup.open() }
+ }
+ }
+
+ // ── Felhasználó-lista popup ───────────────────────────────────────────────
+ Popup {
+ id: popup
+
+ parent: Overlay.overlay
+ x: (parent.width - width) / 2
+ y: (parent.height - height) / 2
+
+ width: root.font.pointSize * 18
+ padding: root.font.pointSize * 0.6
+
+ modal: true
+ focus: true
+ closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
+
+ enter: Transition {
+ NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 200; easing.type: Easing.OutCubic }
+ NumberAnimation { property: "scale"; from: 0.92; to: 1; duration: 200; easing.type: Easing.OutCubic }
+ }
+ exit: Transition {
+ NumberAnimation { property: "opacity"; from: 1; to: 0; duration: 160; easing.type: Easing.InCubic }
+ }
+
+ background: Rectangle {
+ radius: config.RoundCorners !== "" ? parseInt(config.RoundCorners) : 16
+ color: config.BackgroundColor
+ border.color: Qt.rgba(1, 1, 1, 0.18)
+ border.width: 1
+ }
+
+ // Fejléc
+ Column {
+ width: parent.width
+ spacing: root.font.pointSize * 0.4
+
+ Text {
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: "Felhasználó váltás"
+ color: Qt.rgba(1, 1, 1, 0.55)
+ font.pointSize: root.font.pointSize * 0.75
+ font.family: root.font.family
+ bottomPadding: root.font.pointSize * 0.2
+ }
+
+ // Elválasztó vonal
+ Rectangle {
+ width: parent.width
+ height: 1
+ color: Qt.rgba(1, 1, 1, 0.12)
+ }
+
+ // Felhasználók listája
+ ListView {
+ id: userList
+ width: parent.width
+ height: Math.min(contentHeight, root.font.pointSize * 22)
+ model: userModel
+ clip: true
+ spacing: 2
+ boundsBehavior: Flickable.StopAtBounds
+
+ ScrollIndicator.vertical: ScrollIndicator { }
+
+ delegate: Rectangle {
+ id: delegateRect
+ width: userList.width
+ height: root.font.pointSize * 4
+ radius: config.RoundCorners !== "" ? parseInt(config.RoundCorners) / 2 : 8
+
+ // Kiemelés: aktív felhasználó
+ property bool isActive: index === userSwitcher.selectedIndex
+ color: isActive
+ ? Qt.rgba(1, 1, 1, 0.12)
+ : delegateMouse.containsMouse
+ ? Qt.rgba(1, 1, 1, 0.07)
+ : "transparent"
+
+ Behavior on color { ColorAnimation { duration: 120 } }
+
+ Row {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: parent.left
+ anchors.leftMargin: root.font.pointSize * 0.7
+ spacing: root.font.pointSize * 0.8
+
+ // Avatar kör
+ Rectangle {
+ id: delegateAvatarCircle
+ width: root.font.pointSize * 3
+ height: root.font.pointSize * 3
+ radius: width / 2
+ clip: true
+ color: Qt.rgba(0.12, 0.12, 0.20, 0.80)
+ anchors.verticalCenter: parent.verticalCenter
+
+ property string iconPath: userSwitcher.getUserIcon(index)
+
+ Image {
+ id: delegatePhoto
+ anchors.fill: parent
+ source: parent.iconPath
+ fillMode: Image.PreserveAspectCrop
+ smooth: true
+ visible: false
+ }
+
+ Item {
+ id: delegateMask
+ anchors.fill: parent
+ visible: false
+ layer.enabled: true
+ Rectangle {
+ anchors.fill: parent
+ radius: parent.width / 2
+ color: "black"
+ }
+ }
+
+ MultiEffect {
+ anchors.fill: parent
+ source: delegatePhoto
+ visible: delegatePhoto.status === Image.Ready && parent.iconPath !== ""
+ maskEnabled: true
+ maskSource: delegateMask
+ maskThresholdMin: 0.5
+ maskSpreadAtMin: 1.0
+ }
+
+ Image {
+ anchors.centerIn: parent
+ width: parent.width * 0.65
+ height: parent.height * 0.65
+ source: Qt.resolvedUrl("../Assets/User.svg")
+ fillMode: Image.PreserveAspectFit
+ visible: delegatePhoto.status !== Image.Ready || parent.iconPath === ""
+ opacity: 0.75
+ }
+ }
+
+ // Felhasználónév
+ Text {
+ anchors.verticalCenter: parent.verticalCenter
+ text: userSwitcher.getUserName(index)
+ color: delegateRect.isActive ? "#ffffff" : Qt.rgba(1,1,1,0.80)
+ font.pointSize: root.font.pointSize * 0.88
+ font.family: root.font.family
+ font.bold: delegateRect.isActive
+ }
+ }
+
+ // Aktív jelző csík a bal oldalon
+ Rectangle {
+ visible: delegateRect.isActive
+ width: 3
+ height: parent.height * 0.55
+ radius: 2
+ color: Qt.rgba(74/255, 222/255, 128/255, 0.90)
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: parent.left
+ anchors.leftMargin: 2
+ }
+
+ MouseArea {
+ id: delegateMouse
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ userSwitcher.selectedIndex = index
+ userSwitcher.userSwitched(index, userSwitcher.getUserName(index))
+ popup.close()
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/VirtualKeyboard.qml b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/VirtualKeyboard.qml
new file mode 100644
index 0000000..b7b7a0f
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/VirtualKeyboard.qml
@@ -0,0 +1,20 @@
+import QtQuick 2.15
+import QtQuick.VirtualKeyboard
+import QtQuick.VirtualKeyboard.Settings
+
+InputPanel {
+ id: virtualKeyboard
+
+ property bool activated: false
+ visible: true
+
+ Component.onCompleted: {
+ VirtualKeyboardSettings.activeLocales = ["en_US", "hu_HU"]
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ z: -1
+ onClicked: {} // Consume clicks so they don't fall through to the background and steal focus
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/VirtualKeyboardButton.qml b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/VirtualKeyboardButton.qml
new file mode 100644
index 0000000..90a52b8
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/sddm/sddm-rave-theme/Components/VirtualKeyboardButton.qml
@@ -0,0 +1,79 @@
+// Config created by Keyitdev https://git.rp1.hu/gabeszm/sddm-rave-theme
+// Copyright (C) 2022-2025 Keyitdev
+// Distributed under the GPLv3+ License https://www.gnu.org/licenses/gpl-3.0.html
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+
+Item {
+ Button {
+ id: virtualKeyboardButton
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ z: 1
+ focusPolicy: Qt.TabFocus
+
+ visible: virtualKeyboard.status == Loader.Ready && config.HideVirtualKeyboard == "false"
+ checkable: true
+ onClicked: virtualKeyboard.switchState()
+
+ Keys.onReturnPressed: {
+ toggle();
+ virtualKeyboard.switchState();
+ }
+ Keys.onEnterPressed: {
+ toggle();
+ virtualKeyboard.switchState();
+ }
+
+ contentItem: Text {
+ id: virtualKeyboardButtonText
+
+ text: config.TranslateVirtualKeyboardButtonOff || "Virtual Keyboard (off)"
+ font.pointSize: root.font.pointSize * 0.8
+ font.family: root.font.family
+ color: parent.visualFocus ? config.HoverVirtualKeyboardButtonTextColor : config.VirtualKeyboardButtonTextColor
+ }
+
+ background: Rectangle {
+ id: virtualKeyboardButtonBackground
+
+ anchors.fill: parent
+ radius: config.RoundCorners !== "" ? parseInt(config.RoundCorners) : 10
+ color: virtualKeyboardButton.down ? Qt.rgba(1, 1, 1, 0.12) : (virtualKeyboardButton.hovered ? Qt.rgba(1, 1, 1, 0.08) : Qt.rgba(1, 1, 1, 0.04))
+ border.color: virtualKeyboardButton.visualFocus ? Qt.rgba(1, 1, 1, 0.60) : (virtualKeyboardButton.hovered ? Qt.rgba(1, 1, 1, 0.35) : Qt.rgba(1, 1, 1, 0.18))
+ border.width: virtualKeyboardButton.visualFocus ? 1.5 : 1
+
+ Behavior on color { ColorAnimation { duration: 150 } }
+ Behavior on border.color { ColorAnimation { duration: 150 } }
+ }
+ states: [
+ State {
+ name: "HoveredAndChecked"
+ when: virtualKeyboardButton.checked && virtualKeyboardButton.hovered
+ PropertyChanges {
+ target: virtualKeyboardButtonText
+ text: config.TranslateVirtualKeyboardButtonOn || "Virtual Keyboard (on)"
+ color: config.HoverVirtualKeyboardButtonTextColor
+ }
+ },
+ State {
+ name: "checked"
+ when: virtualKeyboardButton.checked
+ PropertyChanges {
+ target: virtualKeyboardButtonText
+ text: config.TranslateVirtualKeyboardButtonOn || "Virtual Keyboard (on)"
+ }
+ },
+ State {
+ name: "hovered"
+ when: virtualKeyboardButton.hovered
+ PropertyChanges {
+ target: virtualKeyboardButtonText
+ text: config.TranslateVirtualKeyboardButtonOff || "Virtual Keyboard (off)"
+ color: config.HoverVirtualKeyboardButtonTextColor
+ }
+ }
+ ]
+ }
+} \ No newline at end of file