mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
welcome: add a first launch welcome page with doctor integration
fixes #760
This commit is contained in:
@@ -676,7 +676,7 @@ func checkOptionalDependencies() []checkResult {
|
|||||||
}{
|
}{
|
||||||
{"matugen", "matugen", "", "Dynamic theming", true},
|
{"matugen", "matugen", "", "Dynamic theming", true},
|
||||||
{"dgop", "dgop", "", "System monitoring", true},
|
{"dgop", "dgop", "", "System monitoring", true},
|
||||||
{"cava", "cava", "", "Audio waveform", false},
|
{"cava", "cava", "", "Audio visualizer", true},
|
||||||
{"khal", "khal", "", "Calendar events", false},
|
{"khal", "khal", "", "Calendar events", false},
|
||||||
{"Network", "nmcli", "iwctl", "Network management", false},
|
{"Network", "nmcli", "iwctl", "Network management", false},
|
||||||
{"danksearch", "dsearch", "", "File search", false},
|
{"danksearch", "dsearch", "", "File search", false},
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ Singleton {
|
|||||||
if (typeof SettingsData !== "undefined" && SettingsData.theme) {
|
if (typeof SettingsData !== "undefined" && SettingsData.theme) {
|
||||||
Theme.switchTheme(SettingsData.theme);
|
Theme.switchTheme(SettingsData.theme);
|
||||||
} else {
|
} else {
|
||||||
Theme.switchTheme("blue");
|
Theme.switchTheme("purple");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ Singleton {
|
|||||||
property alias dankBarCenterWidgetsModel: centerWidgetsModel
|
property alias dankBarCenterWidgetsModel: centerWidgetsModel
|
||||||
property alias dankBarRightWidgetsModel: rightWidgetsModel
|
property alias dankBarRightWidgetsModel: rightWidgetsModel
|
||||||
|
|
||||||
property string currentThemeName: "blue"
|
property string currentThemeName: "purple"
|
||||||
property string currentThemeCategory: "generic"
|
property string currentThemeCategory: "generic"
|
||||||
property string customThemeFile: ""
|
property string customThemeFile: ""
|
||||||
property var registryThemeVariants: ({})
|
property var registryThemeVariants: ({})
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ Singleton {
|
|||||||
return useAuto ? Math.max(4, spacing) : manualValue;
|
return useAuto ? Math.max(4, spacing) : manualValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
property string currentTheme: "blue"
|
property string currentTheme: "purple"
|
||||||
property string currentThemeCategory: "generic"
|
property string currentThemeCategory: "generic"
|
||||||
property bool isLightMode: typeof SessionData !== "undefined" ? SessionData.isLightMode : false
|
property bool isLightMode: typeof SessionData !== "undefined" ? SessionData.isLightMode : false
|
||||||
property bool colorsFileLoadFailed: false
|
property bool colorsFileLoadFailed: false
|
||||||
@@ -196,7 +196,7 @@ Singleton {
|
|||||||
|
|
||||||
readonly property var currentThemeData: {
|
readonly property var currentThemeData: {
|
||||||
if (currentTheme === "custom") {
|
if (currentTheme === "custom") {
|
||||||
return customThemeData || StockThemes.getThemeByName("blue", isLightMode);
|
return customThemeData || StockThemes.getThemeByName("purple", isLightMode);
|
||||||
} else if (currentTheme === dynamic) {
|
} else if (currentTheme === dynamic) {
|
||||||
return {
|
return {
|
||||||
"primary": getMatugenColor("primary", "#42a5f5"),
|
"primary": getMatugenColor("primary", "#42a5f5"),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ function percentToUnit(v) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var SPEC = {
|
var SPEC = {
|
||||||
currentThemeName: { def: "blue", onChange: "applyStoredTheme" },
|
currentThemeName: { def: "purple", onChange: "applyStoredTheme" },
|
||||||
currentThemeCategory: { def: "generic" },
|
currentThemeCategory: { def: "generic" },
|
||||||
customThemeFile: { def: "" },
|
customThemeFile: { def: "" },
|
||||||
registryThemeVariants: { def: {} },
|
registryThemeVariants: { def: {} },
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import Quickshell
|
|||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals
|
import qs.Modals
|
||||||
import qs.Modals.Clipboard
|
import qs.Modals.Clipboard
|
||||||
|
import qs.Modals.Greeter
|
||||||
import qs.Modals.Settings
|
import qs.Modals.Settings
|
||||||
import qs.Modals.Spotlight
|
import qs.Modals.Spotlight
|
||||||
import qs.Modules
|
import qs.Modules
|
||||||
@@ -816,4 +817,20 @@ Item {
|
|||||||
id: niriOverviewOverlay
|
id: niriOverviewOverlay
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: greeterLoader
|
||||||
|
active: false
|
||||||
|
sourceComponent: GreeterModal {
|
||||||
|
onGreeterCompleted: greeterLoader.active = false
|
||||||
|
Component.onCompleted: show()
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: FirstLaunchService
|
||||||
|
function onGreeterRequested() {
|
||||||
|
greeterLoader.active = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,14 +41,14 @@ FloatingWindow {
|
|||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
if (parentModal) {
|
if (parentModal && "shouldHaveFocus" in parentModal) {
|
||||||
parentModal.shouldHaveFocus = false;
|
parentModal.shouldHaveFocus = false;
|
||||||
parentModal.allowFocusOverride = true;
|
parentModal.allowFocusOverride = true;
|
||||||
}
|
}
|
||||||
content.reset();
|
content.reset();
|
||||||
Qt.callLater(() => content.forceActiveFocus());
|
Qt.callLater(() => content.forceActiveFocus());
|
||||||
} else {
|
} else {
|
||||||
if (parentModal) {
|
if (parentModal && "allowFocusOverride" in parentModal) {
|
||||||
parentModal.allowFocusOverride = false;
|
parentModal.allowFocusOverride = false;
|
||||||
parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible);
|
parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible);
|
||||||
}
|
}
|
||||||
|
|||||||
492
quickshell/Modals/Greeter/GreeterCompletePage.qml
Normal file
492
quickshell/Modals/Greeter/GreeterCompletePage.qml
Normal file
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
421
quickshell/Modals/Greeter/GreeterDoctorPage.qml
Normal file
421
quickshell/Modals/Greeter/GreeterDoctorPage.qml
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
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: root.errorCount > 0 ? I18n.tr("%1 issue(s) found", "greeter doctor page error count").arg(root.errorCount) : I18n.tr("All checks passed", "greeter doctor page success")
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
96
quickshell/Modals/Greeter/GreeterDoctorResultItem.qml
Normal file
96
quickshell/Modals/Greeter/GreeterDoctorResultItem.qml
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
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 : parent.right
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
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: parent.right
|
||||||
|
anchors.rightMargin: 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
quickshell/Modals/Greeter/GreeterFeatureCard.qml
Normal file
57
quickshell/Modals/Greeter/GreeterFeatureCard.qml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string iconName: ""
|
||||||
|
property string title: ""
|
||||||
|
property string description: ""
|
||||||
|
|
||||||
|
readonly property real iconContainerSize: Math.round(Theme.iconSize * 1.5)
|
||||||
|
|
||||||
|
height: Math.round(Theme.fontSizeMedium * 6.4)
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
327
quickshell/Modals/Greeter/GreeterModal.qml
Normal file
327
quickshell/Modals/Greeter/GreeterModal.qml
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
FloatingWindow {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
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 = 0;
|
||||||
|
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
|
||||||
|
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 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
60
quickshell/Modals/Greeter/GreeterQuickLink.qml
Normal file
60
quickshell/Modals/Greeter/GreeterQuickLink.qml
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
78
quickshell/Modals/Greeter/GreeterSettingsCard.qml
Normal file
78
quickshell/Modals/Greeter/GreeterSettingsCard.qml
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
75
quickshell/Modals/Greeter/GreeterStatusCard.qml
Normal file
75
quickshell/Modals/Greeter/GreeterStatusCard.qml
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
150
quickshell/Modals/Greeter/GreeterWelcomePage.qml
Normal file
150
quickshell/Modals/Greeter/GreeterWelcomePage.qml
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Effects
|
||||||
|
import qs.Common
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
GreeterFeatureCard {
|
||||||
|
width: (parent.width - Theme.spacingS * 2) / 3
|
||||||
|
iconName: "density_small"
|
||||||
|
title: I18n.tr("System Tray", "greeter feature card title")
|
||||||
|
description: I18n.tr("Background app icons", "greeter feature card description")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtCore
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
@@ -15,7 +14,7 @@ Singleton {
|
|||||||
return greetCfgDir + "/settings.json";
|
return greetCfgDir + "/settings.json";
|
||||||
}
|
}
|
||||||
|
|
||||||
property string currentThemeName: "blue"
|
property string currentThemeName: "purple"
|
||||||
property bool settingsLoaded: false
|
property bool settingsLoaded: false
|
||||||
property string customThemeFile: ""
|
property string customThemeFile: ""
|
||||||
property string matugenScheme: "scheme-tonal-spot"
|
property string matugenScheme: "scheme-tonal-spot"
|
||||||
@@ -48,7 +47,7 @@ Singleton {
|
|||||||
try {
|
try {
|
||||||
if (content && content.trim()) {
|
if (content && content.trim()) {
|
||||||
const settings = JSON.parse(content);
|
const settings = JSON.parse(content);
|
||||||
currentThemeName = settings.currentThemeName !== undefined ? settings.currentThemeName : "blue";
|
currentThemeName = settings.currentThemeName !== undefined ? settings.currentThemeName : "purple";
|
||||||
customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : "";
|
customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : "";
|
||||||
matugenScheme = settings.matugenScheme !== undefined ? settings.matugenScheme : "scheme-tonal-spot";
|
matugenScheme = settings.matugenScheme !== undefined ? settings.matugenScheme : "scheme-tonal-spot";
|
||||||
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true;
|
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ PanelWindow {
|
|||||||
readonly property string clearText: I18n.tr("Dismiss")
|
readonly property string clearText: I18n.tr("Dismiss")
|
||||||
|
|
||||||
signal entered
|
signal entered
|
||||||
|
signal exitStarted
|
||||||
signal exitFinished
|
signal exitFinished
|
||||||
|
|
||||||
function startExit() {
|
function startExit() {
|
||||||
@@ -31,6 +32,7 @@ PanelWindow {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
exiting = true;
|
exiting = true;
|
||||||
|
exitStarted();
|
||||||
exitAnim.restart();
|
exitAnim.restart();
|
||||||
exitWatchdog.restart();
|
exitWatchdog.restart();
|
||||||
if (NotificationService.removeFromVisibleNotifications)
|
if (NotificationService.removeFromVisibleNotifications)
|
||||||
@@ -61,7 +63,7 @@ PanelWindow {
|
|||||||
win.exitFinished();
|
win.exitFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
visible: hasValidData
|
visible: !_finalized
|
||||||
WlrLayershell.layer: {
|
WlrLayershell.layer: {
|
||||||
const envLayer = Quickshell.env("DMS_NOTIFICATION_LAYER");
|
const envLayer = Quickshell.env("DMS_NOTIFICATION_LAYER");
|
||||||
if (envLayer) {
|
if (envLayer) {
|
||||||
@@ -211,7 +213,7 @@ PanelWindow {
|
|||||||
y: Theme.snap((win.height - alignedHeight) / 2, dpr)
|
y: Theme.snap((win.height - alignedHeight) / 2, dpr)
|
||||||
width: alignedWidth
|
width: alignedWidth
|
||||||
height: alignedHeight
|
height: alignedHeight
|
||||||
visible: win.hasValidData
|
visible: !win._finalized
|
||||||
|
|
||||||
property real swipeOffset: 0
|
property real swipeOffset: 0
|
||||||
readonly property real dismissThreshold: isTopCenter ? height * 0.4 : width * 0.35
|
readonly property real dismissThreshold: isTopCenter ? height * 0.4 : width * 0.35
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ QtObject {
|
|||||||
popupComponent: Component {
|
popupComponent: Component {
|
||||||
NotificationPopup {
|
NotificationPopup {
|
||||||
onEntered: manager._onPopupEntered(this)
|
onEntered: manager._onPopupEntered(this)
|
||||||
|
onExitStarted: manager._onPopupExitStarted(this)
|
||||||
onExitFinished: manager._onPopupExitFinished(this)
|
onExitFinished: manager._onPopupExitFinished(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,6 +277,14 @@ QtObject {
|
|||||||
function _onPopupEntered(p) {
|
function _onPopupEntered(p) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _onPopupExitStarted(p) {
|
||||||
|
if (!p)
|
||||||
|
return;
|
||||||
|
const survivors = _active().sort((a, b) => a.screenY - b.screenY);
|
||||||
|
for (let k = 0; k < survivors.length; ++k)
|
||||||
|
survivors[k].screenY = topMargin + k * baseNotificationHeight;
|
||||||
|
}
|
||||||
|
|
||||||
function _onPopupExitFinished(p) {
|
function _onPopupExitFinished(p) {
|
||||||
if (!p) {
|
if (!p) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
94
quickshell/Services/FirstLaunchService.qml
Normal file
94
quickshell/Services/FirstLaunchService.qml
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtCore
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + "/DankMaterialShell"
|
||||||
|
readonly property string settingsPath: configDir + "/settings.json"
|
||||||
|
readonly property string firstLaunchMarkerPath: configDir + "/.firstlaunch"
|
||||||
|
|
||||||
|
property bool isFirstLaunch: false
|
||||||
|
property bool checkComplete: false
|
||||||
|
property bool greeterDismissed: false
|
||||||
|
|
||||||
|
readonly property bool shouldShowGreeter: checkComplete && isFirstLaunch && !greeterDismissed
|
||||||
|
|
||||||
|
signal greeterRequested
|
||||||
|
signal greeterCompleted
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
checkFirstLaunch();
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkFirstLaunch() {
|
||||||
|
firstLaunchCheckProcess.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function markFirstLaunchComplete() {
|
||||||
|
greeterDismissed = true;
|
||||||
|
touchMarkerProcess.running = true;
|
||||||
|
greeterCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
function dismissGreeter() {
|
||||||
|
greeterDismissed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: firstLaunchCheckProcess
|
||||||
|
|
||||||
|
command: ["sh", "-c", `
|
||||||
|
SETTINGS='` + settingsPath + `'
|
||||||
|
MARKER='` + firstLaunchMarkerPath + `'
|
||||||
|
if [ -f "$MARKER" ]; then
|
||||||
|
echo 'skip'
|
||||||
|
elif [ -f "$SETTINGS" ]; then
|
||||||
|
echo 'existing_user'
|
||||||
|
else
|
||||||
|
echo 'first'
|
||||||
|
fi
|
||||||
|
`]
|
||||||
|
running: false
|
||||||
|
|
||||||
|
stdout: SplitParser {
|
||||||
|
onRead: data => {
|
||||||
|
const result = data.trim();
|
||||||
|
root.checkComplete = true;
|
||||||
|
|
||||||
|
if (result === "first") {
|
||||||
|
root.isFirstLaunch = true;
|
||||||
|
console.info("FirstLaunchService: First launch detected, greeter will be shown");
|
||||||
|
root.greeterRequested();
|
||||||
|
} else if (result === "existing_user") {
|
||||||
|
root.isFirstLaunch = false;
|
||||||
|
console.info("FirstLaunchService: Existing user detected, silently creating marker");
|
||||||
|
touchMarkerProcess.running = true;
|
||||||
|
} else {
|
||||||
|
root.isFirstLaunch = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: touchMarkerProcess
|
||||||
|
|
||||||
|
command: ["sh", "-c", "mkdir -p '" + configDir + "' && touch '" + firstLaunchMarkerPath + "'"]
|
||||||
|
running: false
|
||||||
|
|
||||||
|
onExited: exitCode => {
|
||||||
|
if (exitCode === 0) {
|
||||||
|
console.info("FirstLaunchService: First launch marker created");
|
||||||
|
} else {
|
||||||
|
console.warn("FirstLaunchService: Failed to create first launch marker");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user