diff --git a/quickshell/DMSShell.qml b/quickshell/DMSShell.qml index eaf94fd5..e882ebc2 100644 --- a/quickshell/DMSShell.qml +++ b/quickshell/DMSShell.qml @@ -2,6 +2,7 @@ import QtQuick import Quickshell import qs.Common import qs.Modals +import qs.Modals.Changelog import qs.Modals.Clipboard import qs.Modals.Greeter import qs.Modals.Settings @@ -836,9 +837,29 @@ Item { function onGreeterRequested() { if (greeterLoader.active && greeterLoader.item) { greeterLoader.item.show(); - } else { - greeterLoader.active = true; + return; } + greeterLoader.active = true; + } + } + } + + Loader { + id: changelogLoader + active: false + sourceComponent: ChangelogModal { + onChangelogDismissed: changelogLoader.active = false + Component.onCompleted: show() + } + + Connections { + target: ChangelogService + function onChangelogRequested() { + if (changelogLoader.active && changelogLoader.item) { + changelogLoader.item.show(); + return; + } + changelogLoader.active = true; } } } diff --git a/quickshell/Modals/Changelog/ChangelogContent.qml b/quickshell/Modals/Changelog/ChangelogContent.qml new file mode 100644 index 00000000..d080fe87 --- /dev/null +++ b/quickshell/Modals/Changelog/ChangelogContent.qml @@ -0,0 +1,246 @@ +import QtQuick +import QtQuick.Effects +import qs.Common +import qs.Services +import qs.Widgets + +Column { + id: root + + readonly property real logoSize: Math.round(Theme.iconSize * 2.8) + readonly property real badgeHeight: Math.round(Theme.fontSizeSmall * 1.7) + + topPadding: Theme.spacingL + spacing: Theme.spacingL + + Column { + width: parent.width + spacing: Theme.spacingM + + Row { + anchors.horizontalCenter: parent.horizontalCenter + spacing: Theme.spacingM + + Image { + width: root.logoSize + height: width * (569.94629 / 506.50931) + anchors.verticalCenter: parent.verticalCenter + 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 { + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingXS + + Row { + spacing: Theme.spacingS + + StyledText { + text: "DMS " + ChangelogService.currentVersion + font.pixelSize: Theme.fontSizeXLarge + 2 + font.weight: Font.Bold + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + Rectangle { + width: codenameText.implicitWidth + Theme.spacingM * 2 + height: root.badgeHeight + radius: root.badgeHeight / 2 + color: Theme.primaryContainer + anchors.verticalCenter: parent.verticalCenter + + StyledText { + id: codenameText + anchors.centerIn: parent + text: "Spicy Miso" + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Medium + color: Theme.primary + } + } + } + + StyledText { + text: "Desktop widgets, theme registry, native clipboard & more" + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceVariantText + } + } + } + } + + Rectangle { + width: parent.width + height: 1 + color: Theme.outlineMedium + opacity: 0.3 + } + + Column { + width: parent.width + spacing: Theme.spacingM + + StyledText { + text: "What's New" + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.surfaceText + } + + Grid { + width: parent.width + columns: 2 + rowSpacing: Theme.spacingS + columnSpacing: Theme.spacingS + + ChangelogFeatureCard { + width: (parent.width - Theme.spacingS) / 2 + iconName: "widgets" + title: "Desktop Widgets" + description: "Widgets on your desktop" + onClicked: PopoutService.openSettingsWithTab("desktop_widgets") + } + + ChangelogFeatureCard { + width: (parent.width - Theme.spacingS) / 2 + iconName: "palette" + title: "Theme Registry" + description: "Community themes" + onClicked: PopoutService.openSettingsWithTab("theme") + } + + ChangelogFeatureCard { + width: (parent.width - Theme.spacingS) / 2 + iconName: "content_paste" + title: "Native Clipboard" + description: "Zero-dependency history" + onClicked: PopoutService.openSettingsWithTab("clipboard") + } + + ChangelogFeatureCard { + width: (parent.width - Theme.spacingS) / 2 + iconName: "display_settings" + title: "Monitor Config" + description: "Full display setup" + onClicked: PopoutService.openSettingsWithTab("display_config") + } + + ChangelogFeatureCard { + width: (parent.width - Theme.spacingS) / 2 + iconName: "notifications_active" + title: "Notifications" + description: "History & gestures" + onClicked: PopoutService.openSettingsWithTab("notifications") + } + + ChangelogFeatureCard { + width: (parent.width - Theme.spacingS) / 2 + iconName: "healing" + title: "DMS Doctor" + description: "Diagnose issues" + onClicked: FirstLaunchService.showDoctor() + } + + ChangelogFeatureCard { + width: (parent.width - Theme.spacingS) / 2 + iconName: "keyboard" + title: "Keybinds Editor" + description: "niri, Hyprland, & MangoWC" + visible: KeybindsService.available + onClicked: PopoutService.openSettingsWithTab("keybinds") + } + + ChangelogFeatureCard { + width: (parent.width - Theme.spacingS) / 2 + iconName: "search" + title: "Settings Search" + description: "Find settings fast" + onClicked: PopoutService.openSettingsWithTab("general") + } + } + } + + Rectangle { + width: parent.width + height: 1 + color: Theme.outlineMedium + opacity: 0.3 + } + + Column { + width: parent.width + spacing: Theme.spacingS + + Row { + spacing: Theme.spacingS + + DankIcon { + name: "warning" + size: Theme.iconSizeSmall + color: Theme.warning + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: "Upgrade Notes" + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + } + + Rectangle { + width: parent.width + height: upgradeNotesColumn.height + Theme.spacingM * 2 + radius: Theme.cornerRadius + color: Theme.withAlpha(Theme.warning, 0.08) + border.width: 1 + border.color: Theme.withAlpha(Theme.warning, 0.2) + + Column { + id: upgradeNotesColumn + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Theme.spacingM + spacing: Theme.spacingS + + ChangelogUpgradeNote { + width: parent.width + text: "Ghostty theme path changed to ~/.config/ghostty/themes/danktheme" + } + + ChangelogUpgradeNote { + width: parent.width + text: "VS Code theme reinstall required" + } + + ChangelogUpgradeNote { + width: parent.width + text: "Clipboard history migration available from cliphist" + } + } + } + + StyledText { + text: "See full release notes for migration steps" + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + width: parent.width + } + } +} diff --git a/quickshell/Modals/Changelog/ChangelogFeatureCard.qml b/quickshell/Modals/Changelog/ChangelogFeatureCard.qml new file mode 100644 index 00000000..eab4f701 --- /dev/null +++ b/quickshell/Modals/Changelog/ChangelogFeatureCard.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.3) + + height: Math.round(Theme.fontSizeMedium * 4.2) + 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.spacingS + + Rectangle { + width: root.iconContainerSize + height: root.iconContainerSize + radius: Math.round(root.iconContainerSize * 0.28) + color: Theme.primaryContainer + anchors.verticalCenter: parent.verticalCenter + + DankIcon { + anchors.centerIn: parent + name: root.iconName + size: Theme.iconSize - 6 + color: Theme.primary + } + } + + Column { + anchors.verticalCenter: parent.verticalCenter + spacing: 2 + width: parent.width - root.iconContainerSize - Theme.spacingS + + 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/quickshell/Modals/Changelog/ChangelogModal.qml b/quickshell/Modals/Changelog/ChangelogModal.qml new file mode 100644 index 00000000..03f51d6c --- /dev/null +++ b/quickshell/Modals/Changelog/ChangelogModal.qml @@ -0,0 +1,155 @@ +import QtQuick +import Quickshell +import qs.Common +import qs.Services +import qs.Widgets + +FloatingWindow { + id: root + + readonly property int modalWidth: 680 + readonly property int modalHeight: screen ? Math.min(720, screen.height - 80) : 720 + + signal changelogDismissed + + function show() { + visible = true; + } + + objectName: "changelogModal" + title: "What's New" + minimumSize: Qt.size(modalWidth, modalHeight) + maximumSize: Qt.size(modalWidth, modalHeight) + color: Theme.surfaceContainer + visible: false + + FocusScope { + id: contentFocusScope + anchors.fill: parent + focus: true + + Keys.onEscapePressed: event => { + root.dismiss(); + event.accepted = true; + } + + Keys.onPressed: event => { + switch (event.key) { + case Qt.Key_Return: + case Qt.Key_Enter: + root.dismiss(); + 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) + + 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.dismiss() + + DankTooltip { + text: "Close" + } + } + } + } + + DankFlickable { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: headerRow.bottom + anchors.bottom: footerRow.top + anchors.topMargin: Theme.spacingS + clip: true + contentHeight: mainColumn.height + Theme.spacingL * 2 + contentWidth: width + + ChangelogContent { + id: mainColumn + anchors.horizontalCenter: parent.horizontalCenter + width: Math.min(600, parent.width - Theme.spacingXL * 2) + } + } + + 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.centerIn: parent + spacing: Theme.spacingM + + DankButton { + text: "Read Full Release Notes" + iconName: "open_in_new" + backgroundColor: Theme.surfaceContainerHighest + textColor: Theme.surfaceText + onClicked: Qt.openUrlExternally("https://danklinux.com/blog/dms-1-2-spicy-miso") + } + + DankButton { + text: "Got It" + iconName: "check" + backgroundColor: Theme.primary + textColor: Theme.primaryText + onClicked: root.dismiss() + } + } + } + } + + FloatingWindowControls { + id: windowControls + targetWindow: root + } + + function dismiss() { + ChangelogService.dismissChangelog(); + changelogDismissed(); + visible = false; + } +} diff --git a/quickshell/Modals/Changelog/ChangelogUpgradeNote.qml b/quickshell/Modals/Changelog/ChangelogUpgradeNote.qml new file mode 100644 index 00000000..545eb9a2 --- /dev/null +++ b/quickshell/Modals/Changelog/ChangelogUpgradeNote.qml @@ -0,0 +1,27 @@ +import QtQuick +import qs.Common +import qs.Widgets + +Row { + id: root + + property alias text: noteText.text + + spacing: Theme.spacingS + + DankIcon { + name: "arrow_right" + size: Theme.iconSizeSmall - 2 + color: Theme.surfaceVariantText + anchors.top: parent.top + anchors.topMargin: 2 + } + + StyledText { + id: noteText + width: root.width - Theme.iconSizeSmall - Theme.spacingS + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + wrapMode: Text.WordWrap + } +} diff --git a/quickshell/Services/ChangelogService.qml b/quickshell/Services/ChangelogService.qml new file mode 100644 index 00000000..c1a2f246 --- /dev/null +++ b/quickshell/Services/ChangelogService.qml @@ -0,0 +1,90 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtCore +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Common + +Singleton { + id: root + + readonly property string currentVersion: "1.2" + readonly property bool changelogEnabled: false + + readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + "/DankMaterialShell" + readonly property string changelogMarkerPath: configDir + "/.changelog-" + currentVersion + + property bool checkComplete: false + property bool changelogDismissed: false + + readonly property bool shouldShowChangelog: { + if (!checkComplete) + return false; + if (!changelogEnabled) + return false; + if (changelogDismissed) + return false; + if (typeof FirstLaunchService !== "undefined" && FirstLaunchService.isFirstLaunch) + return false; + return true; + } + + signal changelogRequested + signal changelogCompleted + + Component.onCompleted: { + if (!changelogEnabled) + return; + changelogCheckProcess.running = true; + } + + function showChangelog() { + changelogRequested(); + } + + function dismissChangelog() { + changelogDismissed = true; + touchMarkerProcess.running = true; + changelogCompleted(); + } + + Process { + id: changelogCheckProcess + + command: ["sh", "-c", "[ -f '" + changelogMarkerPath + "' ] && echo 'seen' || echo 'show'"] + running: false + + stdout: SplitParser { + onRead: data => { + const result = data.trim(); + root.checkComplete = true; + + switch (result) { + case "seen": + root.changelogDismissed = true; + break; + case "show": + if (typeof FirstLaunchService === "undefined" || !FirstLaunchService.isFirstLaunch) { + root.changelogRequested(); + } + break; + } + } + } + } + + Process { + id: touchMarkerProcess + + command: ["sh", "-c", "mkdir -p '" + configDir + "' && touch '" + changelogMarkerPath + "'"] + running: false + + onExited: exitCode => { + if (exitCode !== 0) { + console.warn("ChangelogService: Failed to create changelog marker"); + } + } + } +}