summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/dms/Modals/Greeter
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-hyprland-theme/theme-data/dms/Modals/Greeter')
-rw-r--r--raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterCompletePage.qml492
-rw-r--r--raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterDoctorPage.qml427
-rw-r--r--raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterDoctorResultItem.qml109
-rw-r--r--raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterFeatureCard.qml74
-rw-r--r--raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterModal.qml333
-rw-r--r--raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterQuickLink.qml60
-rw-r--r--raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterSettingsCard.qml78
-rw-r--r--raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterStatusCard.qml75
-rw-r--r--raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterWelcomePage.qml165
9 files changed, 1813 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterCompletePage.qml b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterCompletePage.qml
new file mode 100644
index 0000000..8a5ca00
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterCompletePage.qml
@@ -0,0 +1,492 @@
+import QtQuick
+import qs.Common
+import qs.Services
+import qs.Widgets
+
+Item {
+ id: root
+
+ property var greeterRoot: parent ? parent.greeterRoot : null
+
+ readonly property real headerIconContainerSize: Math.round(Theme.iconSize * 2)
+ readonly property real sectionIconSize: Theme.iconSizeSmall + 2
+ readonly property real keybindRowHeight: Math.round(Theme.fontSizeMedium * 2)
+ readonly property real keyBadgeHeight: Math.round(Theme.fontSizeSmall * 1.83)
+
+ readonly property var featureNames: ({
+ "spotlight": "App Launcher",
+ "clipboard": "Clipboard",
+ "processlist": "Task Manager",
+ "settings": "Settings",
+ "notifications": "Notifications",
+ "notepad": "Notepad",
+ "hotkeys": "Keybinds",
+ "lock": "Lock Screen",
+ "dankdash": "Dashboard"
+ })
+
+ function getFeatureDesc(action) {
+ const match = action.match(/dms\s+ipc\s+call\s+(\w+)/);
+ if (match && featureNames[match[1]])
+ return featureNames[match[1]];
+ return null;
+ }
+
+ readonly property var dmsKeybinds: {
+ if (!greeterRoot || !greeterRoot.cheatsheetLoaded || !greeterRoot.cheatsheetData || !greeterRoot.cheatsheetData.binds)
+ return [];
+ const seen = new Set();
+ const binds = [];
+ const allBinds = greeterRoot.cheatsheetData.binds;
+ for (const category in allBinds) {
+ const categoryBinds = allBinds[category];
+ for (let i = 0; i < categoryBinds.length; i++) {
+ const bind = categoryBinds[i];
+ if (!bind.key || !bind.action)
+ continue;
+ if (!bind.action.includes("dms"))
+ continue;
+ if (!(bind.action.includes("spawn") || bind.action.includes("exec")))
+ continue;
+ const feature = getFeatureDesc(bind.action);
+ if (!feature)
+ continue;
+ if (seen.has(feature))
+ continue;
+ seen.add(feature);
+ binds.push({
+ key: bind.key,
+ desc: feature
+ });
+ }
+ }
+ return binds;
+ }
+
+ readonly property bool hasKeybinds: dmsKeybinds.length > 0
+
+ DankFlickable {
+ anchors.fill: parent
+ clip: true
+ contentHeight: mainColumn.height + Theme.spacingL * 2
+ contentWidth: width
+
+ Column {
+ id: mainColumn
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: Math.min(640, parent.width - Theme.spacingXL * 2)
+ topPadding: Theme.spacingL
+ spacing: Theme.spacingL
+
+ Row {
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: Theme.spacingM
+
+ Rectangle {
+ width: root.headerIconContainerSize
+ height: root.headerIconContainerSize
+ radius: Math.round(root.headerIconContainerSize * 0.29)
+ color: Theme.withAlpha(Theme.success, 0.15)
+ anchors.verticalCenter: parent.verticalCenter
+
+ DankIcon {
+ anchors.centerIn: parent
+ name: "check_circle"
+ size: Theme.iconSize + 4
+ color: Theme.success
+ }
+ }
+
+ Column {
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: 2
+
+ StyledText {
+ text: I18n.tr("You're All Set!", "greeter completion page title")
+ font.pixelSize: Theme.fontSizeXLarge
+ font.weight: Font.Bold
+ color: Theme.surfaceText
+ }
+
+ StyledText {
+ text: I18n.tr("DankMaterialShell is ready to use", "greeter completion page subtitle")
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceVariantText
+ }
+ }
+ }
+
+ Column {
+ width: parent.width
+ spacing: Theme.spacingS
+ visible: root.hasKeybinds
+
+ Row {
+ width: parent.width
+ spacing: Theme.spacingS
+
+ DankIcon {
+ name: "keyboard"
+ size: root.sectionIconSize
+ color: Theme.primary
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ StyledText {
+ text: I18n.tr("DMS Shortcuts", "greeter keybinds section header")
+ font.pixelSize: Theme.fontSizeMedium
+ font.weight: Font.Medium
+ color: Theme.surfaceText
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ Rectangle {
+ id: keybindsRect
+ width: parent.width
+ height: keybindsGrid.height + Theme.spacingM * 2
+ radius: Theme.cornerRadius
+ color: Theme.surfaceContainerHigh
+
+ readonly property bool useTwoColumns: width > 500
+ readonly property int columnCount: useTwoColumns ? 2 : 1
+ readonly property real itemWidth: useTwoColumns ? (width - Theme.spacingM * 3) / 2 : width - Theme.spacingM * 2
+ property real maxKeyWidth: 0
+
+ Grid {
+ id: keybindsGrid
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.margins: Theme.spacingM
+ columns: keybindsRect.columnCount
+ rowSpacing: Theme.spacingS
+ columnSpacing: Theme.spacingM
+
+ Repeater {
+ model: root.dmsKeybinds
+
+ Row {
+ width: keybindsRect.itemWidth
+ height: root.keybindRowHeight
+ spacing: Theme.spacingS
+
+ Item {
+ width: keybindsRect.maxKeyWidth
+ height: parent.height
+
+ Row {
+ id: keysRow
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: Theme.spacingXS
+
+ property real naturalWidth: {
+ let w = 0;
+ for (let i = 0; i < children.length; i++) {
+ if (children[i].visible)
+ w += children[i].width + (i > 0 ? Theme.spacingXS : 0);
+ }
+ return w;
+ }
+
+ Component.onCompleted: {
+ Qt.callLater(() => {
+ if (naturalWidth > keybindsRect.maxKeyWidth)
+ keybindsRect.maxKeyWidth = naturalWidth;
+ });
+ }
+
+ Repeater {
+ model: (modelData.key || "").split("+")
+
+ Rectangle {
+ width: singleKeyText.implicitWidth + Theme.spacingM
+ height: root.keyBadgeHeight
+ radius: Theme.spacingXS
+ color: Theme.surfaceContainerHighest
+ border.width: 1
+ border.color: Theme.outline
+
+ StyledText {
+ id: singleKeyText
+ anchors.centerIn: parent
+ color: Theme.secondary
+ text: modelData
+ font.pixelSize: Theme.fontSizeSmall - 1
+ font.weight: Font.Medium
+ isMonospace: true
+ }
+ }
+ }
+ }
+ }
+
+ StyledText {
+ anchors.verticalCenter: parent.verticalCenter
+ width: parent.width - keybindsRect.maxKeyWidth - Theme.spacingS
+ text: modelData.desc || ""
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceText
+ elide: Text.ElideRight
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ width: parent.width
+ height: noKeybindsColumn.height + Theme.spacingM * 2
+ radius: Theme.cornerRadius
+ color: Theme.surfaceContainerHigh
+ visible: !root.hasKeybinds
+
+ Column {
+ id: noKeybindsColumn
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.margins: Theme.spacingM
+ spacing: Theme.spacingS
+
+ Row {
+ spacing: Theme.spacingS
+
+ DankIcon {
+ name: "keyboard"
+ size: root.sectionIconSize
+ color: Theme.surfaceVariantText
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ StyledText {
+ text: I18n.tr("No DMS shortcuts configured", "greeter no keybinds message")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ Rectangle {
+ width: parent.width
+ height: Math.round(Theme.fontSizeMedium * 2.85)
+ radius: Theme.cornerRadius
+ color: Theme.surfaceContainerHighest
+
+ Rectangle {
+ anchors.fill: parent
+ radius: parent.radius
+ color: Theme.primary
+ opacity: noKeybindsLinkMouse.containsMouse ? 0.12 : 0
+ }
+
+ Row {
+ anchors.centerIn: parent
+ spacing: Theme.spacingS
+
+ DankIcon {
+ name: "menu_book"
+ size: root.sectionIconSize
+ color: Theme.primary
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ StyledText {
+ text: I18n.tr("Configure Keybinds", "greeter configure keybinds link")
+ font.pixelSize: Theme.fontSizeSmall
+ font.weight: Font.Medium
+ color: Theme.primary
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ DankIcon {
+ name: "open_in_new"
+ size: Theme.iconSizeSmall - 2
+ color: Theme.surfaceVariantText
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ MouseArea {
+ id: noKeybindsLinkMouse
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ let url = "https://danklinux.com/docs/dankmaterialshell/keybinds-ipc";
+ if (CompositorService.isNiri)
+ url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings";
+ else if (CompositorService.isHyprland)
+ url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings-1";
+ else if (CompositorService.isDwl)
+ url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings-2";
+ Qt.openUrlExternally(url);
+ }
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ width: parent.width
+ height: 1
+ color: Theme.outlineMedium
+ opacity: 0.3
+ visible: root.hasKeybinds
+ }
+
+ Column {
+ width: parent.width
+ spacing: Theme.spacingS
+
+ Row {
+ width: parent.width
+ spacing: Theme.spacingS
+
+ DankIcon {
+ name: "settings"
+ size: root.sectionIconSize
+ color: Theme.primary
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ StyledText {
+ text: I18n.tr("Configure", "greeter settings section header")
+ font.pixelSize: Theme.fontSizeMedium
+ font.weight: Font.Medium
+ color: Theme.surfaceText
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ Grid {
+ width: parent.width
+ columns: 2
+ rowSpacing: Theme.spacingS
+ columnSpacing: Theme.spacingS
+
+ GreeterSettingsCard {
+ width: (parent.width - Theme.spacingS) / 2
+ iconName: "display_settings"
+ title: I18n.tr("Displays", "greeter settings link")
+ description: I18n.tr("Resolution, position, scale", "greeter displays description")
+ onClicked: PopoutService.openSettingsWithTab("display_config")
+ }
+
+ GreeterSettingsCard {
+ width: (parent.width - Theme.spacingS) / 2
+ iconName: "wallpaper"
+ title: I18n.tr("Wallpaper", "greeter settings link")
+ description: I18n.tr("Background image", "greeter wallpaper description")
+ onClicked: PopoutService.openSettingsWithTab("wallpaper")
+ }
+
+ GreeterSettingsCard {
+ width: (parent.width - Theme.spacingS) / 2
+ iconName: "format_paint"
+ title: I18n.tr("Theme & Colors", "greeter settings link")
+ description: I18n.tr("Dynamic colors, presets", "greeter theme description")
+ onClicked: PopoutService.openSettingsWithTab("theme")
+ }
+
+ GreeterSettingsCard {
+ width: (parent.width - Theme.spacingS) / 2
+ iconName: "notifications"
+ title: I18n.tr("Notifications", "greeter settings link")
+ description: I18n.tr("Popup behavior, position", "greeter notifications description")
+ onClicked: PopoutService.openSettingsWithTab("notifications")
+ }
+
+ GreeterSettingsCard {
+ width: (parent.width - Theme.spacingS) / 2
+ iconName: "toolbar"
+ title: I18n.tr("DankBar", "greeter settings link")
+ description: I18n.tr("Widgets, layout, style", "greeter dankbar description")
+ onClicked: PopoutService.openSettingsWithTab("dankbar_settings")
+ }
+
+ GreeterSettingsCard {
+ width: (parent.width - Theme.spacingS) / 2
+ iconName: "keyboard"
+ title: I18n.tr("Keybinds", "greeter settings link")
+ description: I18n.tr("niri shortcuts config", "greeter keybinds niri description")
+ visible: KeybindsService.available
+ onClicked: PopoutService.openSettingsWithTab("keybinds")
+ }
+
+ GreeterSettingsCard {
+ width: (parent.width - Theme.spacingS) / 2
+ iconName: "dock_to_bottom"
+ title: I18n.tr("Dock", "greeter settings link")
+ description: I18n.tr("Position, pinned apps", "greeter dock description")
+ visible: !KeybindsService.available
+ onClicked: PopoutService.openSettingsWithTab("dock")
+ }
+ }
+ }
+
+ Rectangle {
+ width: parent.width
+ height: 1
+ color: Theme.outlineMedium
+ opacity: 0.3
+ }
+
+ Column {
+ width: parent.width
+ spacing: Theme.spacingS
+
+ Row {
+ width: parent.width
+ spacing: Theme.spacingS
+
+ DankIcon {
+ name: "explore"
+ size: root.sectionIconSize
+ color: Theme.primary
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ StyledText {
+ text: I18n.tr("Explore", "greeter explore section header")
+ font.pixelSize: Theme.fontSizeMedium
+ font.weight: Font.Medium
+ color: Theme.surfaceText
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ Row {
+ width: parent.width
+ spacing: Theme.spacingS
+
+ GreeterQuickLink {
+ width: (parent.width - Theme.spacingS * 2) / 3
+ iconName: "menu_book"
+ title: I18n.tr("Docs", "greeter documentation link")
+ isExternal: true
+ onClicked: Qt.openUrlExternally("https://danklinux.com/docs")
+ }
+
+ GreeterQuickLink {
+ width: (parent.width - Theme.spacingS * 2) / 3
+ iconName: "extension"
+ title: I18n.tr("Plugins", "greeter plugins link")
+ isExternal: true
+ onClicked: Qt.openUrlExternally("https://danklinux.com/plugins")
+ }
+
+ GreeterQuickLink {
+ width: (parent.width - Theme.spacingS * 2) / 3
+ iconName: "palette"
+ title: I18n.tr("Themes", "greeter themes link")
+ isExternal: true
+ onClicked: Qt.openUrlExternally("https://danklinux.com/plugins?tab=themes")
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterDoctorPage.qml b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterDoctorPage.qml
new file mode 100644
index 0000000..288cd58
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterDoctorPage.qml
@@ -0,0 +1,427 @@
+import QtQuick
+import Quickshell.Io
+import qs.Common
+import qs.Widgets
+
+Item {
+ id: root
+
+ property bool isRunning: false
+ property bool hasRun: false
+ property var doctorResults: null
+ property int errorCount: 0
+ property int warningCount: 0
+ property int okCount: 0
+ property int infoCount: 0
+ property string selectedFilter: "error"
+
+ readonly property real loadingContainerSize: Math.round(Theme.iconSize * 5)
+ readonly property real pulseRingSize: Math.round(Theme.iconSize * 3.3)
+ readonly property real centerIconContainerSize: Math.round(Theme.iconSize * 2.67)
+ readonly property real headerIconContainerSize: Math.round(Theme.iconSize * 2)
+
+ readonly property var filteredResults: {
+ if (!doctorResults?.results)
+ return [];
+ return doctorResults.results.filter(r => r.status === selectedFilter);
+ }
+
+ function runDoctor() {
+ hasRun = false;
+ isRunning = true;
+ doctorProcess.running = true;
+ }
+
+ Component.onCompleted: runDoctor()
+
+ Item {
+ id: loadingView
+ anchors.fill: parent
+ visible: root.isRunning
+
+ Column {
+ anchors.centerIn: parent
+ spacing: Theme.spacingXL
+
+ Item {
+ width: root.loadingContainerSize
+ height: root.loadingContainerSize
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Rectangle {
+ id: pulseRing1
+ anchors.centerIn: parent
+ width: root.pulseRingSize
+ height: root.pulseRingSize
+ radius: root.pulseRingSize / 2
+ color: "transparent"
+ border.width: Math.round(Theme.spacingXS * 0.75)
+ border.color: Theme.primary
+ opacity: 0
+
+ SequentialAnimation on opacity {
+ running: root.isRunning
+ loops: Animation.Infinite
+ NumberAnimation {
+ from: 0.8
+ to: 0
+ duration: 1500
+ easing.type: Easing.OutQuad
+ }
+ }
+
+ SequentialAnimation on scale {
+ running: root.isRunning
+ loops: Animation.Infinite
+ NumberAnimation {
+ from: 0.5
+ to: 1.5
+ duration: 1500
+ easing.type: Easing.OutQuad
+ }
+ }
+ }
+
+ Rectangle {
+ id: pulseRing2
+ anchors.centerIn: parent
+ width: root.pulseRingSize
+ height: root.pulseRingSize
+ radius: root.pulseRingSize / 2
+ color: "transparent"
+ border.width: Math.round(Theme.spacingXS * 0.75)
+ border.color: Theme.secondary
+ opacity: 0
+
+ SequentialAnimation on opacity {
+ running: root.isRunning
+ loops: Animation.Infinite
+ NumberAnimation {
+ from: 0.8
+ to: 0
+ duration: 1500
+ easing.type: Easing.OutQuad
+ }
+ }
+
+ SequentialAnimation on scale {
+ running: root.isRunning
+ loops: Animation.Infinite
+ NumberAnimation {
+ from: 0.3
+ to: 1.3
+ duration: 1500
+ easing.type: Easing.OutQuad
+ }
+ }
+ }
+
+ Rectangle {
+ anchors.centerIn: parent
+ width: root.centerIconContainerSize
+ height: root.centerIconContainerSize
+ radius: root.centerIconContainerSize / 2
+ color: Theme.primaryContainer
+
+ DankIcon {
+ anchors.centerIn: parent
+ name: "vital_signs"
+ size: Theme.iconSizeLarge
+ color: Theme.primary
+ }
+
+ SequentialAnimation on scale {
+ running: root.isRunning
+ loops: Animation.Infinite
+ NumberAnimation {
+ from: 1
+ to: 1.1
+ duration: 750
+ easing.type: Easing.InOutQuad
+ }
+ NumberAnimation {
+ from: 1.1
+ to: 1
+ duration: 750
+ easing.type: Easing.InOutQuad
+ }
+ }
+ }
+ }
+
+ Column {
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: Theme.spacingS
+
+ StyledText {
+ text: I18n.tr("System Check", "greeter doctor page title")
+ font.pixelSize: Theme.fontSizeXLarge
+ font.weight: Font.Bold
+ color: Theme.surfaceText
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ StyledText {
+ text: I18n.tr("Analyzing configuration...", "greeter doctor page loading text")
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceVariantText
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+ }
+ }
+
+ Item {
+ id: resultsView
+ anchors.fill: parent
+ visible: root.hasRun && !root.isRunning
+ opacity: (root.hasRun && !root.isRunning) ? 1 : 0
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: Theme.mediumDuration
+ easing.type: Theme.emphasizedEasing
+ }
+ }
+
+ Column {
+ id: headerSection
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.topMargin: Theme.spacingL
+ anchors.leftMargin: Theme.spacingXL
+ anchors.rightMargin: Theme.spacingXL
+ spacing: Theme.spacingL
+
+ Row {
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: Theme.spacingM
+
+ Rectangle {
+ width: root.headerIconContainerSize
+ height: root.headerIconContainerSize
+ radius: Math.round(root.headerIconContainerSize * 0.29)
+ color: root.errorCount > 0 ? Theme.errorContainer : Theme.primaryContainer
+ anchors.verticalCenter: parent.verticalCenter
+
+ DankIcon {
+ anchors.centerIn: parent
+ name: root.errorCount > 0 ? "warning" : "check_circle"
+ size: Theme.iconSize + 4
+ color: root.errorCount > 0 ? Theme.error : Theme.primary
+ }
+ }
+
+ Column {
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: 2
+
+ StyledText {
+ text: I18n.tr("System Check", "greeter doctor page title")
+ font.pixelSize: Theme.fontSizeXLarge
+ font.weight: Font.Bold
+ color: Theme.surfaceText
+ }
+
+ StyledText {
+ text: {
+ if (root.errorCount === 0)
+ return I18n.tr("All checks passed", "greeter doctor page success");
+ return root.errorCount === 1
+ ? I18n.tr("%1 issue found", "greeter doctor page error count").arg(root.errorCount)
+ : I18n.tr("%1 issues found", "greeter doctor page error count").arg(root.errorCount);
+ }
+ font.pixelSize: Theme.fontSizeMedium
+ color: root.errorCount > 0 ? Theme.error : Theme.surfaceVariantText
+ }
+ }
+ }
+
+ Row {
+ width: parent.width
+ spacing: Theme.spacingS
+
+ GreeterStatusCard {
+ width: (parent.width - Theme.spacingS * 3) / 4
+ count: root.errorCount
+ label: I18n.tr("Errors", "greeter doctor page status card")
+ iconName: "error"
+ iconColor: Theme.error
+ bgColor: Theme.errorContainer || Theme.withAlpha(Theme.error, 0.15)
+ selected: root.selectedFilter === "error"
+ onClicked: root.selectedFilter = "error"
+ }
+
+ GreeterStatusCard {
+ width: (parent.width - Theme.spacingS * 3) / 4
+ count: root.warningCount
+ label: I18n.tr("Warnings", "greeter doctor page status card")
+ iconName: "warning"
+ iconColor: Theme.warning
+ bgColor: Theme.withAlpha(Theme.warning, 0.15)
+ selected: root.selectedFilter === "warn"
+ onClicked: root.selectedFilter = "warn"
+ }
+
+ GreeterStatusCard {
+ width: (parent.width - Theme.spacingS * 3) / 4
+ count: root.infoCount
+ label: I18n.tr("Info", "greeter doctor page status card")
+ iconName: "info"
+ iconColor: Theme.secondary
+ bgColor: Theme.withAlpha(Theme.secondary, 0.15)
+ selected: root.selectedFilter === "info"
+ onClicked: root.selectedFilter = "info"
+ }
+
+ GreeterStatusCard {
+ width: (parent.width - Theme.spacingS * 3) / 4
+ count: root.okCount
+ label: I18n.tr("OK", "greeter doctor page status card")
+ iconName: "check_circle"
+ iconColor: Theme.success
+ bgColor: Theme.withAlpha(Theme.success, 0.15)
+ selected: root.selectedFilter === "ok"
+ onClicked: root.selectedFilter = "ok"
+ }
+ }
+ }
+
+ Rectangle {
+ id: resultsContainer
+ anchors.top: headerSection.bottom
+ anchors.bottom: footerSection.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.topMargin: Theme.spacingL
+ anchors.bottomMargin: Theme.spacingM
+ anchors.leftMargin: Theme.spacingXL
+ anchors.rightMargin: Theme.spacingXL
+ radius: Theme.cornerRadius
+ color: Theme.surfaceContainerHigh
+ clip: true
+
+ Column {
+ anchors.centerIn: parent
+ spacing: Theme.spacingS
+ visible: root.filteredResults.length === 0
+
+ DankIcon {
+ name: {
+ switch (root.selectedFilter) {
+ case "error":
+ return "check_circle";
+ case "warn":
+ return "thumb_up";
+ case "info":
+ return "info";
+ default:
+ return "verified";
+ }
+ }
+ size: Math.round(Theme.iconSize * 1.67)
+ color: Theme.surfaceVariantText
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ StyledText {
+ text: {
+ switch (root.selectedFilter) {
+ case "error":
+ return I18n.tr("No errors", "greeter doctor page empty state");
+ case "warn":
+ return I18n.tr("No warnings", "greeter doctor page empty state");
+ case "info":
+ return I18n.tr("No info items", "greeter doctor page empty state");
+ default:
+ return I18n.tr("No checks passed", "greeter doctor page empty state");
+ }
+ }
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceVariantText
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+
+ DankFlickable {
+ anchors.fill: parent
+ anchors.margins: Theme.spacingM
+ clip: true
+ contentHeight: resultsColumn.height
+ contentWidth: width
+ visible: root.filteredResults.length > 0
+
+ Column {
+ id: resultsColumn
+ width: parent.width
+ spacing: Theme.spacingS
+
+ Repeater {
+ model: root.filteredResults
+
+ GreeterDoctorResultItem {
+ width: resultsColumn.width
+ resultData: modelData
+ }
+ }
+ }
+ }
+ }
+
+ Row {
+ id: footerSection
+ anchors.bottom: parent.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottomMargin: Theme.spacingL
+ spacing: Theme.spacingM
+
+ DankButton {
+ text: I18n.tr("Run Again", "greeter doctor page button")
+ iconName: "refresh"
+ backgroundColor: Theme.surfaceContainerHighest
+ textColor: Theme.surfaceText
+ onClicked: root.runDoctor()
+ }
+ }
+ }
+
+ Process {
+ id: doctorProcess
+ command: ["dms", "doctor", "--json"]
+ running: false
+
+ stdout: StdioCollector {
+ onStreamFinished: {
+ root.isRunning = false;
+ root.hasRun = true;
+ try {
+ root.doctorResults = JSON.parse(text);
+ if (root.doctorResults?.summary) {
+ root.errorCount = root.doctorResults.summary.errors || 0;
+ root.warningCount = root.doctorResults.summary.warnings || 0;
+ root.okCount = root.doctorResults.summary.ok || 0;
+ root.infoCount = root.doctorResults.summary.info || 0;
+ }
+ if (root.errorCount > 0)
+ root.selectedFilter = "error";
+ else if (root.warningCount > 0)
+ root.selectedFilter = "warn";
+ else if (root.infoCount > 0)
+ root.selectedFilter = "info";
+ else
+ root.selectedFilter = "ok";
+ } catch (e) {
+ console.error("GreeterDoctorPage: Failed to parse doctor output:", e);
+ }
+ }
+ }
+
+ onExited: exitCode => {
+ if (exitCode !== 0) {
+ root.isRunning = false;
+ root.hasRun = true;
+ }
+ }
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterDoctorResultItem.qml b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterDoctorResultItem.qml
new file mode 100644
index 0000000..b15969a
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterDoctorResultItem.qml
@@ -0,0 +1,109 @@
+import QtQuick
+import qs.Common
+import qs.Widgets
+
+Rectangle {
+ id: root
+
+ property var resultData: null
+
+ readonly property string status: resultData?.status || "ok"
+ readonly property string statusIcon: {
+ switch (status) {
+ case "error":
+ return "error";
+ case "warn":
+ return "warning";
+ case "info":
+ return "info";
+ default:
+ return "check_circle";
+ }
+ }
+ readonly property color statusColor: {
+ switch (status) {
+ case "error":
+ return Theme.error;
+ case "warn":
+ return Theme.warning;
+ case "info":
+ return Theme.secondary;
+ default:
+ return Theme.success;
+ }
+ }
+
+ height: Math.round(Theme.fontSizeMedium * 3.4)
+ radius: Theme.cornerRadius
+ color: Theme.withAlpha(statusColor, 0.08)
+
+ DankIcon {
+ id: statusIcon
+ anchors.left: parent.left
+ anchors.leftMargin: Theme.spacingM
+ anchors.verticalCenter: parent.verticalCenter
+ name: root.statusIcon
+ size: Theme.iconSize - 4
+ color: root.statusColor
+ }
+
+ Column {
+ anchors.left: statusIcon.right
+ anchors.leftMargin: Theme.spacingS
+ anchors.right: categoryChip.visible ? categoryChip.left : (urlButton.visible ? urlButton.left : parent.right)
+ anchors.rightMargin: Theme.spacingS
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: 1
+
+ StyledText {
+ width: parent.width
+ text: root.resultData?.name || ""
+ font.pixelSize: Theme.fontSizeSmall
+ font.weight: Font.Medium
+ color: Theme.surfaceText
+ elide: Text.ElideRight
+ }
+
+ StyledText {
+ width: parent.width
+ text: root.resultData?.message || ""
+ font.pixelSize: Theme.fontSizeSmall - 1
+ color: Theme.surfaceVariantText
+ elide: Text.ElideRight
+ visible: text.length > 0
+ }
+ }
+
+ Rectangle {
+ id: categoryChip
+ anchors.right: urlButton.visible ? urlButton.left : parent.right
+ anchors.rightMargin: urlButton.visible ? Theme.spacingXS : Theme.spacingM
+ anchors.verticalCenter: parent.verticalCenter
+ height: Math.round(Theme.fontSizeSmall * 1.67)
+ width: categoryText.implicitWidth + Theme.spacingS
+ radius: Theme.spacingXS
+ color: Theme.surfaceContainerHighest
+ visible: !!(root.resultData?.category)
+
+ StyledText {
+ id: categoryText
+ anchors.centerIn: parent
+ text: root.resultData?.category || ""
+ font.pixelSize: Theme.fontSizeSmall - 2
+ color: Theme.surfaceVariantText
+ }
+ }
+
+ DankActionButton {
+ id: urlButton
+ anchors.right: parent.right
+ anchors.rightMargin: Theme.spacingM
+ anchors.verticalCenter: parent.verticalCenter
+ iconName: "open_in_new"
+ iconSize: Theme.iconSize - 6
+ buttonSize: 24
+ visible: !!(root.resultData?.url)
+ tooltipText: root.resultData?.url || ""
+ onClicked: Qt.openUrlExternally(root.resultData.url)
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterFeatureCard.qml b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterFeatureCard.qml
new file mode 100644
index 0000000..a5bf4f4
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterFeatureCard.qml
@@ -0,0 +1,74 @@
+import QtQuick
+import qs.Common
+import qs.Widgets
+
+Rectangle {
+ id: root
+
+ property string iconName: ""
+ property string title: ""
+ property string description: ""
+
+ signal clicked
+
+ readonly property real iconContainerSize: Math.round(Theme.iconSize * 1.5)
+
+ height: Math.round(Theme.fontSizeMedium * 6.4)
+ radius: Theme.cornerRadius
+ color: Theme.surfaceContainerHigh
+
+ Rectangle {
+ anchors.fill: parent
+ radius: parent.radius
+ color: Theme.primary
+ opacity: mouseArea.containsMouse ? 0.12 : 0
+ }
+
+ Column {
+ anchors.centerIn: parent
+ spacing: Theme.spacingS
+
+ Rectangle {
+ width: root.iconContainerSize
+ height: root.iconContainerSize
+ radius: Math.round(root.iconContainerSize * 0.28)
+ color: Theme.primaryContainer
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ DankIcon {
+ anchors.centerIn: parent
+ name: root.iconName
+ size: Theme.iconSize - 4
+ color: Theme.primary
+ }
+ }
+
+ Column {
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: 2
+
+ StyledText {
+ text: root.title
+ font.pixelSize: Theme.fontSizeSmall
+ font.weight: Font.Medium
+ color: Theme.surfaceText
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ StyledText {
+ text: root.description
+ font.pixelSize: Theme.fontSizeSmall - 1
+ color: Theme.surfaceVariantText
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: root.clicked()
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterModal.qml b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterModal.qml
new file mode 100644
index 0000000..973dab2
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterModal.qml
@@ -0,0 +1,333 @@
+import QtQuick
+import Quickshell
+import Quickshell.Io
+import qs.Common
+import qs.Services
+import qs.Widgets
+
+FloatingWindow {
+ id: root
+
+ property bool disablePopupTransparency: true
+ property int currentPage: 0
+ readonly property int totalPages: 3
+ readonly property var pageComponents: [welcomePage, doctorPage, completePage]
+
+ property var cheatsheetData: ({})
+ property bool cheatsheetLoaded: false
+
+ readonly property int modalWidth: 720
+ readonly property int modalHeight: screen ? Math.min(760, screen.height - 80) : 760
+
+ signal greeterCompleted
+
+ Component.onCompleted: Qt.callLater(loadCheatsheet)
+
+ function loadCheatsheet() {
+ const provider = KeybindsService.cheatsheetProvider;
+ if (KeybindsService.cheatsheetAvailable && provider && !cheatsheetLoaded) {
+ cheatsheetProcess.command = ["dms", "keybinds", "show", provider];
+ cheatsheetProcess.running = true;
+ }
+ }
+
+ Connections {
+ target: KeybindsService
+ function onCheatsheetAvailableChanged() {
+ if (KeybindsService.cheatsheetAvailable && !root.cheatsheetLoaded)
+ loadCheatsheet();
+ }
+ }
+
+ function getKeybind(actionPattern) {
+ if (!cheatsheetLoaded || !cheatsheetData.binds)
+ return "";
+ for (const category in cheatsheetData.binds) {
+ const binds = cheatsheetData.binds[category];
+ for (let i = 0; i < binds.length; i++) {
+ const bind = binds[i];
+ if (bind.action && bind.action.includes(actionPattern))
+ return bind.key || "";
+ }
+ }
+ return "";
+ }
+
+ function show() {
+ currentPage = FirstLaunchService.requestedStartPage || 0;
+ visible = true;
+ }
+
+ function showAtPage(page) {
+ currentPage = page;
+ visible = true;
+ }
+
+ function nextPage() {
+ if (currentPage < totalPages - 1)
+ currentPage++;
+ }
+
+ function prevPage() {
+ if (currentPage > 0)
+ currentPage--;
+ }
+
+ function finish() {
+ FirstLaunchService.markFirstLaunchComplete();
+ greeterCompleted();
+ visible = false;
+ }
+
+ function skip() {
+ FirstLaunchService.markFirstLaunchComplete();
+ greeterCompleted();
+ visible = false;
+ }
+
+ objectName: "greeterModal"
+ title: I18n.tr("Welcome", "greeter modal window title")
+ minimumSize: Qt.size(modalWidth, modalHeight)
+ maximumSize: Qt.size(modalWidth, modalHeight)
+ color: Theme.surfaceContainer
+ visible: false
+
+ Process {
+ id: cheatsheetProcess
+ running: false
+
+ stdout: StdioCollector {
+ onStreamFinished: {
+ const trimmed = text.trim();
+ if (trimmed.length === 0)
+ return;
+ try {
+ root.cheatsheetData = JSON.parse(trimmed);
+ root.cheatsheetLoaded = true;
+ } catch (e) {
+ console.warn("Greeter: Failed to parse cheatsheet:", e);
+ }
+ }
+ }
+ }
+
+ FocusScope {
+ id: contentFocusScope
+ anchors.fill: parent
+ focus: true
+
+ Keys.onEscapePressed: event => {
+ root.skip();
+ event.accepted = true;
+ }
+
+ Keys.onPressed: event => {
+ switch (event.key) {
+ case Qt.Key_Return:
+ case Qt.Key_Enter:
+ if (root.currentPage < root.totalPages - 1)
+ root.nextPage();
+ else
+ root.finish();
+ event.accepted = true;
+ break;
+ case Qt.Key_Left:
+ if (root.currentPage > 0)
+ root.prevPage();
+ event.accepted = true;
+ break;
+ case Qt.Key_Right:
+ if (root.currentPage < root.totalPages - 1)
+ root.nextPage();
+ event.accepted = true;
+ break;
+ }
+ }
+
+ MouseArea {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ height: headerRow.height + Theme.spacingM
+ onPressed: windowControls.tryStartMove()
+ onDoubleClicked: windowControls.tryToggleMaximize()
+ }
+
+ Item {
+ id: headerRow
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.margins: Theme.spacingM
+ height: Math.round(Theme.fontSizeMedium * 2.85)
+
+ Rectangle {
+ id: pageIndicatorContainer
+ readonly property real indicatorHeight: Math.round(Theme.fontSizeMedium * 2)
+
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ width: pageIndicatorRow.width + Theme.spacingM * 2
+ height: indicatorHeight
+ radius: indicatorHeight / 2
+ color: Theme.surfaceContainerHigh
+
+ Row {
+ id: pageIndicatorRow
+ anchors.centerIn: parent
+ spacing: Theme.spacingS
+
+ Repeater {
+ model: root.totalPages
+
+ Rectangle {
+ required property int index
+ property bool isActive: index === root.currentPage
+ readonly property real dotSize: Math.round(Theme.spacingS * 1.3)
+
+ width: isActive ? dotSize * 3 : dotSize
+ height: dotSize
+ radius: dotSize / 2
+ color: isActive ? Theme.primary : Theme.surfaceTextAlpha
+ anchors.verticalCenter: parent.verticalCenter
+
+ Behavior on width {
+ NumberAnimation {
+ duration: Theme.shortDuration
+ easing.type: Theme.emphasizedEasing
+ }
+ }
+
+ Behavior on color {
+ ColorAnimation {
+ duration: Theme.shortDuration
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Row {
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: Theme.spacingXS
+
+ DankActionButton {
+ visible: windowControls.supported && windowControls.canMaximize
+ iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
+ iconSize: Theme.iconSize - 4
+ iconColor: Theme.surfaceText
+ onClicked: windowControls.tryToggleMaximize()
+ }
+
+ DankActionButton {
+ iconName: "close"
+ iconSize: Theme.iconSize - 4
+ iconColor: Theme.surfaceText
+ onClicked: root.skip()
+
+ DankTooltip {
+ text: I18n.tr("Skip setup", "greeter skip button tooltip")
+ }
+ }
+ }
+ }
+
+ Item {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: headerRow.bottom
+ anchors.bottom: footerRow.top
+ anchors.topMargin: Theme.spacingS
+
+ Loader {
+ id: pageLoader
+ anchors.fill: parent
+ sourceComponent: root.pageComponents[root.currentPage]
+
+ property var greeterRoot: root
+ }
+ }
+
+ Rectangle {
+ id: footerRow
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ height: Math.round(Theme.fontSizeMedium * 4.5)
+ color: Theme.surfaceContainerHigh
+
+ Rectangle {
+ anchors.top: parent.top
+ width: parent.width
+ height: 1
+ color: Theme.outlineMedium
+ opacity: 0.5
+ }
+
+ Row {
+ anchors.right: parent.right
+ anchors.rightMargin: Theme.spacingL
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: Theme.spacingM
+
+ DankButton {
+ visible: root.currentPage < root.totalPages - 1
+ text: I18n.tr("Skip", "greeter skip button")
+ backgroundColor: "transparent"
+ textColor: Theme.surfaceVariantText
+ onClicked: root.skip()
+ }
+
+ DankButton {
+ visible: root.currentPage > 0
+ text: I18n.tr("Back", "greeter back button")
+ iconName: "arrow_back"
+ backgroundColor: Theme.surfaceContainerHighest
+ textColor: Theme.surfaceText
+ onClicked: root.prevPage()
+ }
+
+ DankButton {
+ visible: root.currentPage < root.totalPages - 1
+ enabled: !(root.currentPage === 1 && pageLoader.item && pageLoader.item.isRunning)
+ text: root.currentPage === 0 ? I18n.tr("Get Started", "greeter first page button") : I18n.tr("Next", "greeter next button")
+ iconName: "arrow_forward"
+ backgroundColor: Theme.primary
+ textColor: Theme.primaryText
+ onClicked: root.nextPage()
+ }
+
+ DankButton {
+ visible: root.currentPage === root.totalPages - 1
+ text: I18n.tr("Finish", "greeter finish button")
+ iconName: "check"
+ backgroundColor: Theme.primary
+ textColor: Theme.primaryText
+ onClicked: root.finish()
+ }
+ }
+ }
+ }
+
+ FloatingWindowControls {
+ id: windowControls
+ targetWindow: root
+ }
+
+ Component {
+ id: welcomePage
+ GreeterWelcomePage {}
+ }
+
+ Component {
+ id: doctorPage
+ GreeterDoctorPage {}
+ }
+
+ Component {
+ id: completePage
+ GreeterCompletePage {}
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterQuickLink.qml b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterQuickLink.qml
new file mode 100644
index 0000000..d064275
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterQuickLink.qml
@@ -0,0 +1,60 @@
+import QtQuick
+import qs.Common
+import qs.Widgets
+
+Rectangle {
+ id: root
+
+ property string iconName: ""
+ property string title: ""
+ property bool isExternal: false
+
+ signal clicked
+
+ height: Math.round(Theme.fontSizeMedium * 3.1)
+ radius: Theme.cornerRadius
+ color: Theme.surfaceContainerHigh
+
+ Rectangle {
+ anchors.fill: parent
+ radius: parent.radius
+ color: Theme.primary
+ opacity: mouseArea.containsMouse ? 0.12 : 0
+ }
+
+ Row {
+ anchors.centerIn: parent
+ spacing: Theme.spacingS
+
+ DankIcon {
+ name: root.iconName
+ size: Theme.iconSizeSmall + 2
+ color: Theme.primary
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ StyledText {
+ text: root.title
+ font.pixelSize: Theme.fontSizeSmall
+ font.weight: Font.Medium
+ color: Theme.surfaceText
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ DankIcon {
+ visible: root.isExternal
+ name: "open_in_new"
+ size: Theme.iconSizeSmall - 2
+ color: Theme.surfaceVariantText
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: root.clicked()
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterSettingsCard.qml b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterSettingsCard.qml
new file mode 100644
index 0000000..85e6b1c
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterSettingsCard.qml
@@ -0,0 +1,78 @@
+import QtQuick
+import qs.Common
+import qs.Widgets
+
+Rectangle {
+ id: root
+
+ property string iconName: ""
+ property string title: ""
+ property string description: ""
+
+ signal clicked
+
+ readonly property real iconContainerSize: Math.round(Theme.iconSize * 1.5)
+
+ height: Math.round(Theme.fontSizeMedium * 4.5)
+ radius: Theme.cornerRadius
+ color: Theme.surfaceContainerHigh
+
+ Rectangle {
+ anchors.fill: parent
+ radius: parent.radius
+ color: Theme.primary
+ opacity: mouseArea.containsMouse ? 0.12 : 0
+ }
+
+ Row {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.margins: Theme.spacingM
+ spacing: Theme.spacingM
+
+ Rectangle {
+ width: root.iconContainerSize
+ height: root.iconContainerSize
+ radius: Math.round(root.iconContainerSize * 0.28)
+ color: Theme.primary
+ anchors.verticalCenter: parent.verticalCenter
+
+ DankIcon {
+ anchors.centerIn: parent
+ name: root.iconName
+ size: Theme.iconSize - 4
+ color: Theme.primaryText
+ }
+ }
+
+ Column {
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: 2
+ width: parent.width - root.iconContainerSize - Theme.spacingM
+
+ StyledText {
+ text: root.title
+ font.pixelSize: Theme.fontSizeSmall
+ font.weight: Font.Medium
+ color: Theme.surfaceText
+ }
+
+ StyledText {
+ text: root.description
+ font.pixelSize: Theme.fontSizeSmall - 1
+ color: Theme.surfaceVariantText
+ width: parent.width
+ elide: Text.ElideRight
+ }
+ }
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: root.clicked()
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterStatusCard.qml b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterStatusCard.qml
new file mode 100644
index 0000000..af0d5d6
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterStatusCard.qml
@@ -0,0 +1,75 @@
+import QtQuick
+import qs.Common
+import qs.Widgets
+
+Rectangle {
+ id: root
+
+ property int count: 0
+ property string label: ""
+ property string iconName: ""
+ property color iconColor: Theme.surfaceText
+ property color bgColor: Theme.surfaceContainerHigh
+ property bool selected: false
+
+ signal clicked
+
+ height: Math.round(Theme.fontSizeMedium * 5)
+ radius: Theme.cornerRadius
+ color: bgColor
+ border.width: selected ? 2 : 0
+ border.color: selected ? iconColor : "transparent"
+ scale: mouseArea.pressed ? 0.97 : 1
+
+ Behavior on scale {
+ NumberAnimation {
+ duration: Theme.shortDuration
+ easing.type: Theme.emphasizedEasing
+ }
+ }
+
+ Behavior on border.width {
+ NumberAnimation {
+ duration: Theme.shortDuration
+ }
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ onClicked: root.clicked()
+ }
+
+ Column {
+ anchors.centerIn: parent
+ spacing: Theme.spacingXS
+
+ Row {
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: Theme.spacingS
+
+ DankIcon {
+ name: root.iconName
+ size: Theme.iconSize - 4
+ color: root.iconColor
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ StyledText {
+ text: root.count.toString()
+ font.pixelSize: Theme.fontSizeXLarge
+ font.weight: Font.Bold
+ color: root.iconColor
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ StyledText {
+ text: root.label
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceText
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterWelcomePage.qml b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterWelcomePage.qml
new file mode 100644
index 0000000..fcb4fc5
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/dms/Modals/Greeter/GreeterWelcomePage.qml
@@ -0,0 +1,165 @@
+import QtQuick
+import QtQuick.Effects
+import Quickshell
+import qs.Common
+import qs.Services
+import qs.Widgets
+
+Item {
+ id: root
+
+ readonly property real logoSize: Math.round(Theme.iconSize * 5.3)
+
+ Column {
+ id: mainColumn
+ anchors.centerIn: parent
+ width: Math.min(Math.round(Theme.fontSizeMedium * 43), parent.width - Theme.spacingXL * 2)
+ spacing: Theme.spacingXL
+
+ Column {
+ width: parent.width
+ spacing: Theme.spacingM
+
+ Image {
+ width: root.logoSize
+ height: width * (569.94629 / 506.50931)
+ anchors.horizontalCenter: parent.horizontalCenter
+ fillMode: Image.PreserveAspectFit
+ smooth: true
+ mipmap: true
+ asynchronous: true
+ source: "file://" + Theme.shellDir + "/assets/danklogonormal.svg"
+ layer.enabled: true
+ layer.smooth: true
+ layer.mipmap: true
+ layer.effect: MultiEffect {
+ saturation: 0
+ colorization: 1
+ colorizationColor: Theme.primary
+ }
+ }
+
+ Column {
+ width: parent.width
+ spacing: Theme.spacingXS
+
+ StyledText {
+ text: I18n.tr("Welcome to DankMaterialShell", "greeter welcome page title")
+ font.pixelSize: Theme.fontSizeXLarge + 4
+ font.weight: Font.Bold
+ color: Theme.surfaceText
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ StyledText {
+ text: I18n.tr("A modern desktop shell for Wayland compositors", "greeter welcome page tagline")
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceVariantText
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+ }
+
+ Rectangle {
+ width: parent.width
+ height: 1
+ color: Theme.outlineMedium
+ opacity: 0.3
+ }
+
+ Column {
+ width: parent.width
+ spacing: Theme.spacingM
+
+ StyledText {
+ text: I18n.tr("Features", "greeter welcome page section header")
+ font.pixelSize: Theme.fontSizeMedium
+ font.weight: Font.Medium
+ color: Theme.surfaceText
+ }
+
+ Grid {
+ width: parent.width
+ columns: 3
+ rowSpacing: Theme.spacingS
+ columnSpacing: Theme.spacingS
+
+ GreeterFeatureCard {
+ width: (parent.width - Theme.spacingS * 2) / 3
+ iconName: "auto_awesome"
+ title: I18n.tr("Dynamic Theming", "greeter feature card title")
+ description: I18n.tr("Colors from wallpaper", "greeter feature card description")
+ onClicked: PopoutService.openSettingsWithTab("theme")
+ }
+
+ GreeterFeatureCard {
+ width: (parent.width - Theme.spacingS * 2) / 3
+ iconName: "format_paint"
+ title: I18n.tr("App Theming", "greeter feature card title")
+ description: I18n.tr("GTK, Qt, IDEs, more", "greeter feature card description")
+ onClicked: PopoutService.openSettingsWithTab("theme")
+ }
+
+ GreeterFeatureCard {
+ width: (parent.width - Theme.spacingS * 2) / 3
+ iconName: "download"
+ title: I18n.tr("Theme Registry", "greeter feature card title")
+ description: I18n.tr("Community themes", "greeter feature card description")
+ onClicked: PopoutService.openSettingsWithTab("theme")
+ }
+
+ GreeterFeatureCard {
+ width: (parent.width - Theme.spacingS * 2) / 3
+ iconName: "view_carousel"
+ title: I18n.tr("DankBar", "greeter feature card title")
+ description: I18n.tr("Modular widget bar", "greeter feature card description")
+ onClicked: PopoutService.openSettingsWithTab("dankbar_settings")
+ }
+
+ GreeterFeatureCard {
+ width: (parent.width - Theme.spacingS * 2) / 3
+ iconName: "extension"
+ title: I18n.tr("Plugins", "greeter feature card title")
+ description: I18n.tr("Extensible architecture", "greeter feature card description")
+ onClicked: PopoutService.openSettingsWithTab("plugins")
+ }
+
+ GreeterFeatureCard {
+ width: (parent.width - Theme.spacingS * 2) / 3
+ iconName: "layers"
+ title: I18n.tr("Multi-Monitor", "greeter feature card title")
+ description: I18n.tr("Per-screen config", "greeter feature card description")
+ onClicked: {
+ const hasDisplayConfig = CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl;
+ PopoutService.openSettingsWithTab(hasDisplayConfig ? "display_config" : "display_widgets");
+ }
+ }
+
+ GreeterFeatureCard {
+ width: (parent.width - Theme.spacingS * 2) / 3
+ iconName: "nightlight"
+ title: I18n.tr("Display Control", "greeter feature card title")
+ description: I18n.tr("Night mode & gamma", "greeter feature card description")
+ onClicked: PopoutService.openSettingsWithTab("display_gamma")
+ }
+
+ GreeterFeatureCard {
+ width: (parent.width - Theme.spacingS * 2) / 3
+ iconName: "tune"
+ title: I18n.tr("Control Center", "greeter feature card title")
+ description: I18n.tr("Quick system toggles", "greeter feature card description")
+ // This is doing an IPC since its just easier and lazier to access the bar ref
+ onClicked: Quickshell.execDetached(["dms", "ipc", "call", "control-center", "open"])
+ }
+
+ GreeterFeatureCard {
+ width: (parent.width - Theme.spacingS * 2) / 3
+ iconName: "lock"
+ title: I18n.tr("Lock Screen", "greeter feature card title")
+ description: I18n.tr("Security & privacy", "greeter feature card description")
+ onClicked: PopoutService.openSettingsWithTab("lock_screen")
+ }
+ }
+ }
+ }
+}