diff --git a/core/cmd/dms/commands_doctor.go b/core/cmd/dms/commands_doctor.go index 7b12aa424..1d057044f 100644 --- a/core/cmd/dms/commands_doctor.go +++ b/core/cmd/dms/commands_doctor.go @@ -877,7 +877,7 @@ func checkWindowManagers() []checkResult { results = append(results, checkAqueousConfigHelper()) } - if utils.CommandExists("mango") { + if os.Getenv("MANGO_INSTANCE_SIGNATURE") != "" { results = append(results, checkMangoConfig(mangoconf.Dir(), mangoconf.Detect())...) } diff --git a/core/internal/plugins/registry.go b/core/internal/plugins/registry.go index 7c23bde40..7460cb389 100644 --- a/core/internal/plugins/registry.go +++ b/core/internal/plugins/registry.go @@ -293,6 +293,12 @@ func (r *Registry) resetLegacyCache() { // declaration order (first occurrence of an ID wins). A failing registry is // reported in the joined error but does not block the others. func (r *Registry) Update() error { + unlock, err := registries.LockCache() + if err != nil { + return err + } + defer unlock() + r.resetLegacyCache() r.plugins = []Plugin{} seen := make(map[string]struct{}) diff --git a/core/internal/registries/lock.go b/core/internal/registries/lock.go new file mode 100644 index 000000000..bfb83e812 --- /dev/null +++ b/core/internal/registries/lock.go @@ -0,0 +1,31 @@ +package registries + +import ( + "fmt" + "os" + "path/filepath" + "syscall" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/utils" +) + +// flock is per open file, so concurrent goroutines contend like separate processes. +func LockCache() (unlock func(), err error) { + dir := filepath.Join(utils.RuntimeDir(), "dms") + if err := os.MkdirAll(dir, 0o700); err != nil { + return nil, fmt.Errorf("create registry lock dir: %w", err) + } + + f, err := os.OpenFile(filepath.Join(dir, "registry.lock"), os.O_CREATE|os.O_RDWR, 0o600) + if err != nil { + return nil, fmt.Errorf("open registry lock: %w", err) + } + if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX); err != nil { + f.Close() + return nil, fmt.Errorf("acquire registry lock: %w", err) + } + return func() { + _ = syscall.Flock(int(f.Fd()), syscall.LOCK_UN) + f.Close() + }, nil +} diff --git a/core/internal/themes/registry.go b/core/internal/themes/registry.go index 7630c9077..1460301fa 100644 --- a/core/internal/themes/registry.go +++ b/core/internal/themes/registry.go @@ -318,6 +318,12 @@ func (r *Registry) resetLegacyCache() { // declaration order (first occurrence of an ID wins). A failing registry is // reported in the joined error but does not block the others. func (r *Registry) Update() error { + unlock, err := registries.LockCache() + if err != nil { + return err + } + defer unlock() + r.resetLegacyCache() r.themes = []Theme{} seen := make(map[string]struct{}) diff --git a/dank-qml-common b/dank-qml-common index 51978d3ba..0166a116f 160000 --- a/dank-qml-common +++ b/dank-qml-common @@ -1 +1 @@ -Subproject commit 51978d3ba6be3138ee39bb0c7f07e156a82805b6 +Subproject commit 0166a116f7c9739008b202eee3dfef5bf76d4f0f diff --git a/flake.lock b/flake.lock index 93cf21379..c5633249b 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "dank-qml-common": { "flake": false, "locked": { - "lastModified": 1792374447, - "narHash": "sha256-FlzldJJuGPxyb6IHnzYLi8HQE1Yg222UnY0KcKnKGik=", + "lastModified": 1792424555, + "narHash": "sha256-V1RZ9krNkeXzvyAC8OoGthhq5zsxWKUWq7Qwnpl7awo=", "owner": "AvengeMedia", "repo": "dank-qml-common", - "rev": "51978d3ba6be3138ee39bb0c7f07e156a82805b6", + "rev": "0166a116f7c9739008b202eee3dfef5bf76d4f0f", "type": "github" }, "original": { diff --git a/quickshell/DMSShell.qml b/quickshell/DMSShell.qml index 7eb711e08..5878fd65f 100644 --- a/quickshell/DMSShell.qml +++ b/quickshell/DMSShell.qml @@ -1379,6 +1379,11 @@ Item { Component.onCompleted: show() } + Component.onCompleted: { + if (ChangelogService.shouldShowChangelog) + active = true; + } + Connections { target: ChangelogService function onChangelogRequested() { diff --git a/quickshell/Modals/Changelog/ChangelogContent.qml b/quickshell/Modals/Changelog/ChangelogContent.qml index fb6a9dbc0..d64382a61 100644 --- a/quickshell/Modals/Changelog/ChangelogContent.qml +++ b/quickshell/Modals/Changelog/ChangelogContent.qml @@ -1,5 +1,6 @@ +pragma ComponentBehavior: Bound + import QtQuick -import QtQuick.Effects import qs.Common import qs.Services import qs.DCommon.Widgets @@ -9,296 +10,315 @@ import qs.Modules.Settings.Widgets Column { id: root - readonly property real logoSize: Math.round(Theme.iconSize * 2.8) - readonly property real badgeHeight: Math.round(Theme.fontSizeSmall * 1.7) - readonly property string releaseNotesUrl: Site.web + "/blog/v1-6-release" - readonly property string screenshotDocsUrl: Site.docs + "/dankmaterialshell/cli-screenshot" + readonly property string pluginDocsUrl: Site.docs + "/dms/plugin-development" + readonly property string compositorDocsUrl: Site.docs + "/dms/compositors" - topPadding: Theme.spacingL + readonly property var features: [ + { + "icon": "palette", + "shape": "softBurst", + "hoverShape": "flower", + "accent": "purple", + "heading": "A new design", + "description": "Material 3 Expressive across Settings, Control Center, Dash, and every control", + "target": "theme_surfaces" + }, + { + "icon": "space_dashboard", + "shape": "cookie9", + "hoverShape": "sunny", + "accent": "blue", + "heading": "Dash plugins", + "description": "Plugins add full tabs or overview cards, sized and ordered your way", + "target": "dank_dash" + }, + { + "icon": "lock", + "shape": "clover4", + "hoverShape": "cookie4", + "accent": "green", + "heading": "Build your lock screen", + "description": "Place the clock, media, status, and any desktop widget where you want", + "target": "lock_screen" + }, + { + "icon": "lyrics", + "shape": "sunny", + "hoverShape": "softBurst", + "accent": "pink", + "heading": "Lyrical Engine", + "description": "Word synced lyrics from six providers, right in the media player", + "target": "media_player" + }, + { + "icon": "wallpaper", + "shape": "gem", + "hoverShape": "cookie6", + "accent": "orange", + "heading": "Material wallpapers", + "description": "Presets from your seed color, plus layouts from wallpaper.danklinux.com", + "target": "personalization" + }, + { + "icon": "digital_wellbeing", + "shape": "puffy", + "hoverShape": "clover8", + "accent": "teal", + "heading": "Digital wellbeing", + "description": "Screen time, daily and app limits, with its own Dash tab", + "target": "wellbeing" + } + ] + + readonly property var additions: [ + { + "icon": "view_in_ar", + "accent": "purple", + "title": "Island", + "subtitle": "Notch mode, workspace switcher, tray slot, clipboard, and shadows", + "target": "dankbar_settings" + }, + { + "icon": "grid_view", + "accent": "green", + "title": "Launcher", + "subtitle": "Custom keybindings, dmenu mode, Open with, and app editing", + "target": "launcher" + }, + { + "icon": "dock_to_bottom", + "accent": "orange", + "title": "Dock", + "subtitle": "Proximity magnification and a focused app pill", + "target": "dock_appearance" + }, + { + "icon": "notifications", + "accent": "pink", + "title": "Notifications", + "subtitle": "Rule conditions, timed app mutes, and two finger swipe to dismiss", + "target": "notification_rules" + }, + { + "icon": "extension", + "accent": "teal", + "title": "Plugin browser", + "subtitle": "Discover shelves, quick filters, upvotes, and typo tolerant search", + "target": "plugins" + }, + { + "icon": "format_paint", + "accent": "yellow", + "title": "Themes", + "subtitle": "Bold palettes, injected palettes, fixed radius, and terminal opacity", + "target": "theme" + }, + { + "icon": "monitor", + "accent": "red", + "title": "Displays", + "subtitle": "Per-output ICC profiles and color temperature", + "target": "display_config" + }, + { + "icon": "system_update_alt", + "accent": "blue", + "title": "Software updates", + "subtitle": "Update dms from Settings and switch between stable and beta", + "target": "updater" + }, + { + "icon": "devices", + "accent": "green", + "title": "More platforms", + "subtitle": "FreeBSD, Enterprise Linux 10, Mango, Hyprland 0.56, Umbriel, and Aqueous", + "target": root.compositorDocsUrl + } + ] + + readonly property var featurePairs: { + const pairs = []; + for (let i = 0; i < features.length; i += 2) + pairs.push(features.slice(i, i + 2)); + return pairs; + } + + function open(target) { + if (target.startsWith("https://")) { + Qt.openUrlExternally(target); + return; + } + PopoutService.openSettingsWithTab(target); + } + + topPadding: Theme.spacingS spacing: Theme.spacingL + ChangelogHero { + width: parent.width + symbol: "He" + atomicNumber: 2 + atomicMass: "4.0026" + remainder: "isenberg" + } + Column { width: parent.width - spacing: Theme.spacingM + spacing: Theme.spacingS + + Repeater { + model: root.featurePairs + + delegate: Row { + id: pair + + required property var modelData + required property int index + + readonly property real cardWidth: (width - spacing) / 2 + readonly property real rowHeight: Math.max(first.implicitHeight, second.implicitHeight) + + width: parent.width + spacing: Theme.spacingS + LayoutMirroring.enabled: I18n.isRtl + + FeatureCard { + id: first + width: pair.cardWidth + height: pair.rowHeight + iconName: pair.modelData[0].icon + shape: pair.modelData[0].shape + hoverShape: pair.modelData[0].hoverShape + accent: pair.modelData[0].accent + heading: pair.modelData[0].heading + description: pair.modelData[0].description + onClicked: root.open(pair.modelData[0].target) + } + + FeatureCard { + id: second + width: pair.cardWidth + height: pair.rowHeight + iconName: pair.modelData[1].icon + shape: pair.modelData[1].shape + hoverShape: pair.modelData[1].hoverShape + accent: pair.modelData[1].accent + heading: pair.modelData[1].heading + description: pair.modelData[1].description + onClicked: root.open(pair.modelData[1].target) + } + } + } + } + + SettingsCard { + title: "More in " + ChangelogService.currentVersion + + Repeater { + model: root.additions + + delegate: SettingsRow { + id: addition + + required property var modelData + + readonly property var accentPair: Theme.accent(modelData.accent) + + title: modelData.title + subtitle: modelData.subtitle + resetKeys: [] + clickable: modelData.target !== "" + showChevron: clickable + onClicked: root.open(modelData.target) + + leading: Rectangle { + width: SettingsMetrics.navIconSize + height: width + radius: Theme.fullRadius(width, height) + color: addition.accentPair?.container ?? Theme.primaryContainer + + DIcon { + anchors.centerIn: parent + name: addition.modelData.icon + size: Theme.iconSizeMedium + color: addition.accentPair?.onContainer ?? Theme.onPrimaryContainer + } + } + } + } + } + + DCard { + width: parent.width + height: pluginNote.implicitHeight + pad * 2 + tone: "tertiary" + pad: Theme.spacingL + restRadius: Theme.groupedListOuterRadius Row { - anchors.horizontalCenter: parent.horizontalCenter - spacing: Theme.spacingM + id: pluginNote + width: parent.width + spacing: Theme.spacingL + LayoutMirroring.enabled: I18n.isRtl - 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 + DMaterialShape { + width: Theme.buttonHeightM + height: width + shape: "cookie6" + color: Theme.onTertiaryContainer + + DIcon { + anchors.centerIn: parent + name: "code" + size: Theme.iconSize + color: Theme.tertiaryContainer } } Column { + width: parent.width - Theme.buttonHeightM - docsButton.width - parent.spacing * 2 anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingXS + spacing: Theme.spacingXXS - Row { - spacing: Theme.spacingS - - StyledText { - text: "DMS " + ChangelogService.currentVersion - font.pixelSize: Theme.fontSizeXLarge + 2 - color: Theme.surfaceText - anchors.verticalCenter: parent.verticalCenter - } - - Rectangle { - width: codenameText.implicitWidth + Theme.spacingM * 2 - height: root.badgeHeight - radius: Theme.fullRadius(width, height) - color: Theme.primaryContainer - anchors.verticalCenter: parent.verticalCenter - - StyledText { - id: codenameText - anchors.centerIn: parent - text: "Marble Tabby" - font.pixelSize: Theme.fontSizeSmall - font.weight: Theme.fontWeightMedium - color: Theme.accentOnPrimaryContainer - } - } + StyledText { + width: parent.width + text: "Plugin authors" + font.pixelSize: Theme.fontSizeLarge + font.weight: Theme.fontWeightBold + color: Theme.onTertiaryContainer + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignLeft } StyledText { - text: "Dank Island, spring motion, performance & resource optimizations" + width: parent.width + text: "The Dank* widget wrappers are deprecated. Import qs.DCommon.Widgets and use the D names, DankButton becomes DButton. Each old type logs its replacement." 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: Theme.fontWeightMedium - color: Theme.surfaceText - } - - Grid { - width: parent.width - columns: 2 - rowSpacing: Theme.spacingS - columnSpacing: Theme.spacingS - - ChangelogFeatureCard { - width: (parent.width - Theme.spacingS) / 2 - iconName: "view_in_ar" - title: "Dank Island" - description: "A bar that reacts to you" - onClicked: { - SettingsSearchService.navigateToSection("barLayout"); - PopoutService.openSettingsWithTab("dankbar"); + color: Theme.onTertiaryContainer + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignLeft } } - ChangelogFeatureCard { - width: (parent.width - Theme.spacingS) / 2 - iconName: "animation" - title: "Spring Motion" - description: "New motion physics" - onClicked: { - SettingsSearchService.navigateToSection("springBounce"); - PopoutService.openSettingsWithTab("typography"); - } - } - - ChangelogFeatureCard { - width: (parent.width - Theme.spacingS) / 2 - iconName: "speed" - title: "Performance" - description: "Big speedups, less memory" - onClicked: Qt.openUrlExternally(root.releaseNotesUrl) - } - - ChangelogFeatureCard { - width: (parent.width - Theme.spacingS) / 2 - iconName: "wifi_tethering" - title: "Network" - description: "Hotspot, cellular, and OWE" - onClicked: { - SettingsSearchService.navigateToSection("networkHotspot"); - PopoutService.openSettingsWithTab("network_wifi"); - } - } - - ChangelogFeatureCard { - width: (parent.width - Theme.spacingS) / 2 - iconName: "extension" - title: "Plugin Registries" - description: "Add your own sources" - onClicked: PopoutService.openSettingsWithTab("plugins") - } - - ChangelogFeatureCard { - width: (parent.width - Theme.spacingS) / 2 - iconName: "photo_camera" - title: "Capture" - description: "Grab regions across outputs" - onClicked: Qt.openUrlExternally(root.screenshotDocsUrl) - } - - ChangelogFeatureCard { - width: (parent.width - Theme.spacingS) / 2 - iconName: "login" - title: "Standalone Greeter" - description: "Now its own app" - onClicked: { - SettingsSearchService.navigateToSection("greeterStatus"); - PopoutService.openSettingsWithTab("greeter"); - } - } - - ChangelogFeatureCard { - width: (parent.width - Theme.spacingS) / 2 - iconName: "dock_to_bottom" - title: "Dock" - description: "Pinned apps, running apps" - onClicked: { - SettingsSearchService.navigateToSection("dockSeparatePinnedAndRunningApps"); - PopoutService.openSettingsWithTab("dock_general"); - } - } - - ChangelogFeatureCard { - width: (parent.width - Theme.spacingS) / 2 - iconName: "content_paste" - title: "Clipboard" - description: "Drop in anything to store it" - onClicked: PopoutService.openSettingsWithTab("clipboard") - } - - ChangelogFeatureCard { - width: (parent.width - Theme.spacingS) / 2 - iconName: "language" - title: "Translations" - description: "22 languages, plugin translations" - onClicked: { - SettingsSearchService.navigateToSection("locale"); - PopoutService.openSettingsWithTab("locale"); - } - } - } - } - - Rectangle { - width: parent.width - height: 1 - color: Theme.outlineMedium - opacity: 0.3 - } - - Column { - width: parent.width - spacing: Theme.spacingS - - Row { - spacing: Theme.spacingS - - DIcon { - name: "warning" - size: Theme.iconSizeSmall - color: Theme.warning - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: "Upgrade Notes" - font.pixelSize: Theme.fontSizeMedium - font.weight: Theme.fontWeightMedium - color: Theme.surfaceText + DButton { + id: docsButton anchors.verticalCenter: parent.verticalCenter + text: I18n.tr("Docs") + iconName: "open_in_new" + backgroundColor: Theme.onTertiaryContainer + textColor: Theme.tertiaryContainer + onClicked: Qt.openUrlExternally(root.pluginDocsUrl) } } - - 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: "The shell is embedded in the dms binary. ~/.config/quickshell/dms is no longer auto-discovered, pass -c or set DMS_SHELL_DIR to override it" - } - - ChangelogUpgradeNote { - width: parent.width - text: "The greeter is now a standalone application. dms greeter commands become dms-greeter" - } - - ChangelogUpgradeNote { - width: parent.width - text: "settings.json only stores what differs from the defaults, and machine-specific state moved to session.json" - } - } - } - } - - SettingsDivider { - visible: ChangelogService.release !== null } Column { width: parent.width visible: ChangelogService.release !== null - spacing: Theme.spacingS + spacing: 0 - Row { - spacing: Theme.spacingS - - DIcon { - name: "auto_awesome" - size: Theme.iconSizeSmall - color: Theme.primary - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: I18n.tr("What changed", "changelog section heading listing the changes in a release") - font.pixelSize: Theme.fontSizeMedium - font.weight: Theme.fontWeightMedium - color: Theme.surfaceText - anchors.verticalCenter: parent.verticalCenter - } + SettingsSectionLabel { + text: I18n.tr("What changed", "changelog section heading listing the changes in a release") } ReleaseNotesCard { diff --git a/quickshell/Modals/Changelog/ChangelogFeatureCard.qml b/quickshell/Modals/Changelog/ChangelogFeatureCard.qml deleted file mode 100644 index fc017d50c..000000000 --- a/quickshell/Modals/Changelog/ChangelogFeatureCard.qml +++ /dev/null @@ -1,80 +0,0 @@ -import QtQuick -import qs.Common -import qs.DCommon.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.floatingWindowNestedSurface - border.width: Theme.layerOutlineWidth - border.color: Theme.outlineMedium - - 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 - - DIcon { - anchors.centerIn: parent - name: root.iconName - size: Theme.iconSize - 6 - color: Theme.accentOnPrimaryContainer - } - } - - Column { - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingXXS - width: parent.width - root.iconContainerSize - Theme.spacingS - - StyledText { - text: root.title - font.pixelSize: Theme.fontSizeSmall - font.weight: Theme.fontWeightMedium - 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/ChangelogHero.qml b/quickshell/Modals/Changelog/ChangelogHero.qml new file mode 100644 index 000000000..0d14570d0 --- /dev/null +++ b/quickshell/Modals/Changelog/ChangelogHero.qml @@ -0,0 +1,96 @@ +import QtQuick +import qs.Common +import qs.Services +import qs.DCommon.Widgets +import qs.Widgets + +ShapeHero { + id: root + + property string symbol: "" + property int atomicNumber: 0 + property string atomicMass: "" + property string remainder: "" + + readonly property real lockupBaseSize: Theme.fontSizeDisplayLarge * 0.8 + readonly property real lockupScale: Math.min(1, availableWidth / Math.max(1, lockupBaseSize * 1.6 + Theme.spacingS + remainderMetrics.width)) + readonly property real lockupSize: lockupBaseSize * lockupScale + readonly property real tileSize: lockupSize * 1.6 + + accentSize: tileSize + paddingV: Theme.spacingXL + + StyledTextMetrics { + id: remainderMetrics + text: root.remainder + font.pixelSize: root.lockupBaseSize + font.weight: Theme.fontWeightBold + } + + Row { + anchors.horizontalCenter: parent.horizontalCenter + spacing: Theme.spacingS + + Rectangle { + id: tile + width: root.tileSize + height: width + radius: Theme.cornerRadiusS + color: Theme.primary + anchors.verticalCenter: parent.verticalCenter + + StyledText { + anchors.left: parent.left + anchors.top: parent.top + anchors.margins: tile.width * 0.07 + text: root.atomicNumber + font.pixelSize: tile.width * 0.15 + font.weight: Theme.fontWeightMedium + color: Theme.onPrimary + } + + StyledText { + anchors.centerIn: parent + anchors.verticalCenterOffset: tile.width * 0.03 + text: root.symbol + font.pixelSize: root.lockupSize * 0.9 + font.weight: Theme.fontWeightBold + color: Theme.onPrimary + } + + StyledText { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: tile.width * 0.05 + text: root.atomicMass + font.pixelSize: tile.width * 0.11 + color: Theme.onPrimary + } + } + + StyledText { + anchors.verticalCenter: parent.verticalCenter + text: root.remainder + font.pixelSize: root.lockupSize + font.weight: Theme.fontWeightBold + color: Theme.onPrimaryContainer + } + } + + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + width: versionText.implicitWidth + Theme.spacingL * 2 + height: Theme.buttonHeightXXS + radius: Theme.fullRadius(width, height) + color: Theme.primary + + StyledText { + id: versionText + anchors.centerIn: parent + text: "dms " + ChangelogService.currentVersion + font.pixelSize: Theme.fontSizeMedium + font.weight: Theme.fontWeightBold + color: Theme.onPrimary + } + } +} diff --git a/quickshell/Modals/Changelog/ChangelogModal.qml b/quickshell/Modals/Changelog/ChangelogModal.qml index b4a392662..de028c137 100644 --- a/quickshell/Modals/Changelog/ChangelogModal.qml +++ b/quickshell/Modals/Changelog/ChangelogModal.qml @@ -7,8 +7,8 @@ import qs.Widgets DFloatingWindow { id: root - readonly property int modalWidth: 680 - readonly property int modalHeight: screen ? Math.min(720, screen.height - 80) : 720 + readonly property int modalWidth: 720 + readonly property int modalHeight: screen ? Math.min(800, screen.height - 80) : 800 signal changelogDismissed @@ -16,6 +16,12 @@ DFloatingWindow { visible = true; } + function dismiss() { + ChangelogService.dismissChangelog(); + changelogDismissed(); + visible = false; + } + objectName: "changelogModal" title: "What's New" minimumSize: Qt.size(modalWidth, modalHeight) @@ -57,58 +63,55 @@ DFloatingWindow { anchors.left: parent.left anchors.right: parent.right anchors.top: headerRow.bottom - anchors.bottom: footerRow.top + anchors.bottom: parent.bottom clip: true - contentHeight: mainColumn.height + Theme.spacingL * 2 + contentHeight: mainColumn.height + fabBar.height + Theme.spacingL * 3 contentWidth: width ChangelogContent { id: mainColumn anchors.horizontalCenter: parent.horizontalCenter - width: Math.min(600, parent.width - Theme.spacingXL * 2) + width: Math.min(640, parent.width - Theme.spacingXL * 2) } } - Rectangle { - id: footerRow - anchors.left: parent.left - anchors.right: parent.right + Row { + id: fabBar + anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom - height: Math.round(Theme.fontSizeMedium * 4.5) - color: Theme.floatingWindowNestedSurface - border.width: Theme.layerOutlineWidth - border.color: Theme.outlineMedium + anchors.bottomMargin: Theme.spacingL + z: 2 + spacing: Theme.spacingM + LayoutMirroring.enabled: I18n.isRtl - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: Theme.outlineMedium - opacity: 0.5 - } + DButton { + id: notesButton + anchors.verticalCenter: parent.verticalCenter + text: I18n.tr("Release notes", "settings page and card title, notes for a DMS release") + iconName: "auto_awesome" + buttonHeight: Theme.buttonHeightM + backgroundColor: Theme.secondaryContainer + textColor: Theme.onSecondaryContainer + onClicked: PopoutService.openSettingsWithTab("updater_changelog") - Row { - anchors.centerIn: parent - spacing: Theme.spacingM - - DButton { - text: I18n.tr("Release notes", "settings page and card title, notes for a DMS release") - iconName: "auto_awesome" - backgroundColor: Theme.chipSurface - textColor: Theme.surfaceText - onClicked: { - root.dismiss(); - PopoutService.openSettingsWithTab("updater_changelog"); + Loader { + anchors.fill: parent + z: -1 + active: Theme.elevationEnabled + sourceComponent: ElevationShadow { + level: Theme.elevationLevel3 + targetColor: notesButton.color + targetRadius: notesButton.radius } } + } - DButton { - text: I18n.tr("OK") - iconName: "check" - backgroundColor: Theme.primary - textColor: Theme.primaryText - onClicked: root.dismiss() - } + DFab { + anchors.verticalCenter: parent.verticalCenter + text: "Let's go" + iconName: "rocket_launch" + colorRole: "primary" + onClicked: root.dismiss() } } } @@ -117,10 +120,4 @@ DFloatingWindow { 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 deleted file mode 100644 index 9c7922892..000000000 --- a/quickshell/Modals/Changelog/ChangelogUpgradeNote.qml +++ /dev/null @@ -1,27 +0,0 @@ -import QtQuick -import qs.Common -import qs.DCommon.Widgets - -Row { - id: root - - property alias text: noteText.text - - spacing: Theme.spacingS - - DIcon { - 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/Modals/Greeter/GreeterFeatureCard.qml b/quickshell/Modals/Greeter/GreeterFeatureCard.qml deleted file mode 100644 index 98675bcaa..000000000 --- a/quickshell/Modals/Greeter/GreeterFeatureCard.qml +++ /dev/null @@ -1,76 +0,0 @@ -import QtQuick -import qs.Common -import qs.DCommon.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.floatingWindowNestedSurface - border.width: Theme.layerOutlineWidth - border.color: Theme.outlineMedium - - 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 - - DIcon { - anchors.centerIn: parent - name: root.iconName - size: Theme.iconSize - 4 - color: Theme.accentOnPrimaryContainer - } - } - - Column { - anchors.horizontalCenter: parent.horizontalCenter - spacing: Theme.spacingXXS - - StyledText { - text: root.title - font.pixelSize: Theme.fontSizeSmall - font.weight: Theme.fontWeightMedium - 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/quickshell/Modals/Greeter/GreeterModal.qml b/quickshell/Modals/Greeter/GreeterModal.qml index 9fc5fe868..63aba4398 100644 --- a/quickshell/Modals/Greeter/GreeterModal.qml +++ b/quickshell/Modals/Greeter/GreeterModal.qml @@ -154,54 +154,6 @@ DFloatingWindow { title: root.title closeTooltipText: I18n.tr("Skip setup", "greeter skip button tooltip") onCloseRequested: root.skip() - - Rectangle { - id: pageIndicatorContainer - readonly property real indicatorHeight: Math.round(Theme.fontSizeMedium * 2) - - anchors.verticalCenter: parent.verticalCenter - width: pageIndicatorRow.width + Theme.spacingM * 2 - height: indicatorHeight - radius: Theme.fullRadius(width, height) - color: Theme.floatingWindowNestedSurface - border.width: Theme.layerOutlineWidth - border.color: Theme.outlineMedium - - 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: dotColor.value - anchors.verticalCenter: parent.verticalCenter - - DColorAnimation { - id: dotColor - to: isActive ? Theme.primary : Theme.surfaceTextAlpha - duration: Theme.shortDuration - } - - Behavior on width { - NumberAnimation { - duration: Theme.shortDuration - easing.type: Theme.emphasizedEasing - } - } - } - } - } - } } Item { @@ -219,66 +171,93 @@ DFloatingWindow { } } - Rectangle { + Item { id: footerRow anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom - height: Math.round(Theme.fontSizeMedium * 4.5) - color: Theme.floatingWindowNestedSurface - border.width: Theme.layerOutlineWidth - border.color: Theme.outlineMedium - - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: Theme.outlineMedium - opacity: 0.5 - } + height: Theme.buttonHeightM + Theme.spacingL * 2 + LayoutMirroring.enabled: I18n.isRtl + LayoutMirroring.childrenInherit: true Row { - anchors.right: parent.right - anchors.rightMargin: Theme.windowInset + anchors.left: parent.left + anchors.leftMargin: Theme.spacingL anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingM - - DButton { - visible: root.currentPage < root.totalPages - 1 - text: I18n.tr("Skip", "greeter skip button") - backgroundColor: "transparent" - textColor: Theme.surfaceVariantText - onClicked: root.currentPage === 1 ? root.nextPage() : root.skip() - } + spacing: Theme.spacingS DButton { visible: root.currentPage > 0 text: I18n.tr("Back", "greeter back button") iconName: "arrow_back" - backgroundColor: Theme.chipSurface - textColor: Theme.surfaceText + buttonHeight: Theme.buttonHeightM + backgroundColor: Theme.secondaryContainer + textColor: Theme.onSecondaryContainer onClicked: root.prevPage() } DButton { 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() + text: I18n.tr("Skip", "greeter skip button") + buttonHeight: Theme.buttonHeightM + backgroundColor: "transparent" + textColor: Theme.primary + onClicked: root.currentPage === 1 ? root.nextPage() : root.skip() } + } - DButton { - visible: root.currentPage === root.totalPages - 1 - text: I18n.tr("Finish", "greeter finish button") - iconName: "check" - backgroundColor: Theme.primary - textColor: Theme.primaryText - onClicked: root.finish() + Row { + anchors.centerIn: parent + spacing: Theme.spacingS + + Repeater { + model: root.totalPages + + Rectangle { + required property int index + readonly property bool isActive: index === root.currentPage + + width: isActive ? Theme.spacingXL : Theme.spacingS + height: Theme.spacingS + radius: Theme.fullRadius(width, height) + color: isActive ? Theme.primary : Theme.outlineVariant + anchors.verticalCenter: parent.verticalCenter + + Behavior on width { + enabled: Theme.currentAnimationSpeed !== SettingsData.AnimationSpeed.None + NumberAnimation { + duration: Theme.expressiveDurations.expressiveFastSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.expressiveFastSpatial + } + } + + Behavior on color { + enabled: Theme.currentAnimationSpeed !== SettingsData.AnimationSpeed.None + ColorAnimation { + duration: Theme.expressiveDurations.expressiveEffects + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.expressiveEffects + } + } + } } } + + DButton { + readonly property bool lastPage: root.currentPage === root.totalPages - 1 + + anchors.right: parent.right + anchors.rightMargin: Theme.spacingL + anchors.verticalCenter: parent.verticalCenter + enabled: !(root.currentPage === 1 && pageLoader.item && pageLoader.item.isRunning) + text: lastPage ? I18n.tr("Finish", "greeter finish button") : root.currentPage === 0 ? I18n.tr("Get Started", "greeter first page button") : I18n.tr("Next", "greeter next button") + iconName: lastPage ? "check" : "arrow_forward" + buttonHeight: Theme.buttonHeightM + backgroundColor: Theme.primary + textColor: Theme.onPrimary + onClicked: lastPage ? root.finish() : root.nextPage() + } } } diff --git a/quickshell/Modals/Greeter/GreeterPluginsPage.qml b/quickshell/Modals/Greeter/GreeterPluginsPage.qml index 4c117f3cf..bf5aaeffe 100644 --- a/quickshell/Modals/Greeter/GreeterPluginsPage.qml +++ b/quickshell/Modals/Greeter/GreeterPluginsPage.qml @@ -9,7 +9,7 @@ Item { property var greeterRoot: parent ? parent.greeterRoot : null - readonly property var curatedPluginIds: ["quickCapture", "dankKDEConnect", "wallpaperCarousel", "calculator", "emojiLauncher", "dockerManager", "amdGpuMonitor", "bongoCat"] + readonly property var curatedPluginIds: ["quickCapture", "dankKDEConnect", "wallpaperCarousel", "calculator", "emojiLauncher", "dockerManager", "dankSoftwareDepot", "homeAssistantMonitor"] readonly property real headerIconContainerSize: Math.round(Theme.iconSize * 2) diff --git a/quickshell/Modals/Greeter/GreeterWelcomePage.qml b/quickshell/Modals/Greeter/GreeterWelcomePage.qml index 4cfa3333d..e46d45e07 100644 --- a/quickshell/Modals/Greeter/GreeterWelcomePage.qml +++ b/quickshell/Modals/Greeter/GreeterWelcomePage.qml @@ -1,135 +1,229 @@ +pragma ComponentBehavior: Bound + import QtQuick import QtQuick.Effects import qs.Common import qs.Services import qs.DCommon.Widgets +import qs.Widgets Item { id: root - readonly property real logoSize: Math.round(Theme.iconSize * 5.3) + readonly property int columns: 3 + readonly property real cardWidth: (content.width - Theme.spacingS * (columns - 1)) / columns + readonly property var features: [ + { + "icon": "auto_awesome", + "shape": "softBurst", + "hoverShape": "flower", + "accent": "purple", + "heading": I18n.tr("Dynamic Theming", "greeter feature card title"), + "description": I18n.tr("Colors from wallpaper", "greeter feature card description"), + "target": "theme" + }, + { + "icon": "toolbar", + "shape": "pill", + "hoverShape": "cookie4", + "accent": "blue", + "heading": I18n.tr("Bar"), + "description": I18n.tr("Modular widget bar", "greeter feature card description"), + "target": "dankbar_settings" + }, + { + "icon": "extension", + "shape": "puffy", + "hoverShape": "clover8", + "accent": "green", + "heading": I18n.tr("Plugins", "greeter feature card title"), + "description": I18n.tr("Extensible architecture", "greeter feature card description"), + "target": "plugins" + }, + { + "icon": "monitor", + "shape": "gem", + "hoverShape": "cookie6", + "accent": "orange", + "heading": I18n.tr("Multi-Monitor", "greeter feature card title"), + "description": I18n.tr("Per-screen config", "greeter feature card description"), + "target": CompositorService.supportsDisplayConfig ? "display_config" : "display_widgets" + }, + { + "icon": "nightlight", + "shape": "sunny", + "hoverShape": "softBurst", + "accent": "pink", + "heading": I18n.tr("Display Control", "greeter feature card title"), + "description": I18n.tr("Night mode & gamma", "greeter feature card description"), + "target": "display_gamma" + }, + { + "icon": "tune", + "shape": "cookie9", + "hoverShape": "sunny", + "accent": "teal", + "heading": I18n.tr("Control Center", "greeter feature card title"), + "description": I18n.tr("Quick system toggles", "greeter feature card description"), + "target": "control_center" + } + ] - Column { - id: mainColumn - anchors.centerIn: parent - width: Math.min(Math.round(Theme.fontSizeMedium * 43), parent.width - Theme.spacingXL * 2) - spacing: Theme.spacingXL + function open(target) { + switch (target) { + case "control_center": + BarWidgetService.getBarWindowOnFocusedScreen()?.triggerControlCenter(); + return; + } + PopoutService.openSettingsWithTab(target); + } + + DFlickable { + anchors.fill: parent + clip: true + contentWidth: width + contentHeight: Math.max(height, content.height + Theme.spacingL * 2) Column { - width: parent.width - spacing: Theme.spacingM + id: content + anchors.horizontalCenter: parent.horizontalCenter + y: Math.max(Theme.spacingS, (parent.height - height) / 2) + width: Math.min(640, parent.width - Theme.spacingXL * 2) + spacing: Theme.spacingL - 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 { + ShapeHero { width: parent.width - spacing: Theme.spacingXS + paddingV: Theme.spacingXL + backdrop: Item { + DMaterialShape { + width: parent.height * 1.6 + height: width + x: parent.width - width * 0.72 + y: -height * 0.38 + shape: "cookie12" + respectThemeShape: false + color: Theme.withAlpha(Theme.primary, Theme.tonalTintAlpha) + rotation: 10 + } - StyledText { - text: I18n.tr("Welcome to DankMaterialShell", "greeter welcome page title") - font.pixelSize: Theme.fontSizeXLarge + 4 - color: Theme.surfaceText - anchors.horizontalCenter: parent.horizontalCenter - } + DMaterialShape { + width: parent.height * 1.1 + height: width + x: parent.width - width * 1.35 + y: parent.height - height * 0.55 + shape: "softBurst" + respectThemeShape: false + color: Theme.withAlpha(Theme.tertiary, Theme.tonalTintAlpha) + rotation: -12 + } - 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: Theme.fontWeightMedium - 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: "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: { - PopoutService.openSettingsWithTab(CompositorService.supportsDisplayConfig ? "display_config" : "display_widgets"); + DMaterialShape { + width: parent.height * 0.75 + height: width + x: parent.width * 0.6 + y: -height * 0.2 + shape: "pill" + respectThemeShape: false + color: Theme.withAlpha(Theme.secondary, Theme.tonalTintAlpha) + rotation: -35 } } - 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") + Row { + width: parent.width + spacing: Theme.spacingXL + LayoutMirroring.enabled: I18n.isRtl + LayoutMirroring.childrenInherit: true + + Item { + id: logo + width: Theme.buttonHeightM * 1.6 + height: width + anchors.verticalCenter: parent.verticalCenter + + Image { + readonly property real dpr: Screen.devicePixelRatio + + anchors.centerIn: parent + height: parent.height + width: height + // 2x raster plus mipmap keeps the thin rings smooth at fractional scales + sourceSize.height: Math.ceil(height * dpr * 2) + fillMode: Image.PreserveAspectFit + smooth: true + mipmap: true + asynchronous: true + source: "file://" + Theme.shellDir + "/assets/danklogo.svg" + layer.enabled: true + layer.smooth: true + layer.textureSize: Qt.size(Math.ceil(width * dpr), Math.ceil(height * dpr)) + layer.effect: MultiEffect { + saturation: 0 + colorization: 1 + colorizationColor: Theme.primary + } + } + } + + Column { + width: parent.width - logo.width - parent.spacing + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + StyledText { + width: parent.width + text: I18n.tr("Welcome to DankMaterialShell", "greeter welcome page title") + font.pixelSize: Theme.fontSizeXXLarge + font.weight: Theme.fontWeightBold + color: Theme.onPrimaryContainer + horizontalAlignment: Text.AlignLeft + wrapMode: Text.WordWrap + } + + StyledText { + width: parent.width + text: I18n.tr("A modern desktop shell for Wayland compositors", "greeter welcome page tagline") + font.pixelSize: Theme.fontSizeLarge + color: Theme.onPrimaryContainer + horizontalAlignment: Text.AlignLeft + wrapMode: Text.WordWrap + } + } + } + } + + Grid { + id: grid + + readonly property real cellHeight: { + let tallest = 0; + for (const card of children) + tallest = Math.max(tallest, card.implicitHeight ?? 0); + return tallest; } - 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") - onClicked: BarWidgetService.getBarWindowOnFocusedScreen()?.triggerControlCenter() + width: parent.width + columns: root.columns + rowSpacing: Theme.spacingS + columnSpacing: Theme.spacingS + layoutDirection: I18n.isRtl ? Qt.RightToLeft : Qt.LeftToRight + + Repeater { + model: root.features + + delegate: FeatureCard { + required property var modelData + + width: root.cardWidth + height: grid.cellHeight + iconName: modelData.icon + shape: modelData.shape + hoverShape: modelData.hoverShape + accent: modelData.accent + heading: modelData.heading + description: modelData.description + onClicked: root.open(modelData.target) + } } } } diff --git a/quickshell/Modules/Settings/ThemeColorsTab.qml b/quickshell/Modules/Settings/ThemeColorsTab.qml index f736d1c6e..ae2b71a07 100644 --- a/quickshell/Modules/Settings/ThemeColorsTab.qml +++ b/quickshell/Modules/Settings/ThemeColorsTab.qml @@ -323,7 +323,7 @@ Item { settingKey: "matugenSpec" text: I18n.tr("Material palette", "theme settings card title, matugen palette options") enabled: Theme.matugenAvailable - model: ["2021", "2025", "DMS"] + model: ["2021", "2025", "dms"] currentIndex: SettingsData.matugenSpec === "dms" ? 2 : SettingsData.matugenSpec === "2025" ? 1 : 0 onSelectionChanged: (index, selected) => { if (!selected) diff --git a/quickshell/Modules/Settings/Widgets/SettingsHeroCard.qml b/quickshell/Modules/Settings/Widgets/SettingsHeroCard.qml index 82a5ee5cd..041413eb9 100644 --- a/quickshell/Modules/Settings/Widgets/SettingsHeroCard.qml +++ b/quickshell/Modules/Settings/Widgets/SettingsHeroCard.qml @@ -4,12 +4,14 @@ import qs.Services import qs.DCommon.Widgets DHeroCard { - brand: "DMS" + brand: "dms" accent: { const semver = ShellVersionService.semverVersion.replace(/^v/, ""); const base = semver.match(/^\d+\.\d+/); return base ? base[0] : semver || SystemUpdateService.shellRunning.replace(/^v/, ""); } - caption: ShellVersionService.shellCodename.toUpperCase() - color: SettingsMetrics.rowColor + caption: ShellVersionService.shellCodename + symbol: "He" + atomicNumber: 2 + atomicMass: "4.0026" } diff --git a/quickshell/Services/AppSearchService.qml b/quickshell/Services/AppSearchService.qml index 40395e2a0..fefe3a3d8 100644 --- a/quickshell/Services/AppSearchService.qml +++ b/quickshell/Services/AppSearchService.qml @@ -195,7 +195,7 @@ Singleton { name: I18n.tr("Settings", "settings window title"), icon: "svg+corner:" + dmsLogoPath + "|settings", cornerIcon: "settings", - comment: "DMS", + comment: "dms", action: "ipc:settings", categories: ["Settings", "System"], defaultTrigger: "", @@ -206,7 +206,7 @@ Singleton { name: I18n.tr("Notepad", "Notepad"), icon: "svg+corner:" + dmsLogoPath + "|description", cornerIcon: "description", - comment: "DMS", + comment: "dms", action: "ipc:notepad", categories: ["Office", "Utility"], defaultTrigger: "", @@ -217,7 +217,7 @@ Singleton { name: I18n.tr("System Monitor", "sysmon window title"), icon: "svg+corner:" + dmsLogoPath + "|monitor_heart", cornerIcon: "monitor_heart", - comment: "DMS", + comment: "dms", action: "ipc:processlist", categories: ["System", "Monitor"], defaultTrigger: "", @@ -228,7 +228,7 @@ Singleton { name: I18n.tr("Color Picker"), icon: "svg+corner:" + dmsLogoPath + "|palette", cornerIcon: "palette", - comment: "DMS", + comment: "dms", action: "ipc:color-picker", categories: ["Graphics", "Utility"], defaultTrigger: "", @@ -238,7 +238,7 @@ Singleton { id: "dms_power", name: I18n.tr("Power"), cornerIcon: "power_settings_new", - comment: "DMS", + comment: "dms", defaultTrigger: "pw", isLauncher: true, viewMode: "list", @@ -249,7 +249,7 @@ Singleton { id: "dms_vpn", name: I18n.tr("VPN", "virtual private network, widget and page title"), cornerIcon: "vpn_key", - comment: "DMS", + comment: "dms", defaultTrigger: "", isLauncher: true, viewMode: "list", @@ -261,7 +261,7 @@ Singleton { name: I18n.tr("QR Generator"), icon: "svg+corner:" + dmsLogoPath + "|qr_code", cornerIcon: "qr_code", - comment: "DMS", + comment: "dms", action: "ipc:qr-generator", categories: ["Utility"], defaultTrigger: "qrg", @@ -281,7 +281,7 @@ Singleton { id: "dms_clipboard_search", name: I18n.tr("Clipboard"), cornerIcon: "content_paste", - comment: "DMS", + comment: "dms", defaultTrigger: "cb", isLauncher: true, viewMode: "list", diff --git a/quickshell/Services/ChangelogService.qml b/quickshell/Services/ChangelogService.qml index 57de93e24..619b017cf 100644 --- a/quickshell/Services/ChangelogService.qml +++ b/quickshell/Services/ChangelogService.qml @@ -17,7 +17,7 @@ Singleton { const m = v.match(/^\d+\.\d+/); return m ? m[0] : ""; } - readonly property bool changelogEnabled: false + readonly property bool changelogEnabled: true readonly property bool ready: SessionData._hasLoaded && !SessionData.isGreeterMode && FirstLaunchService.checkComplete && currentVersion !== "" property bool changelogDismissed: false diff --git a/quickshell/Services/NightModeService.qml b/quickshell/Services/NightModeService.qml index 21d803eaf..792fbec48 100644 --- a/quickshell/Services/NightModeService.qml +++ b/quickshell/Services/NightModeService.qml @@ -615,7 +615,7 @@ Singleton { function gamma(value: string): string { if (!root.gammaAdjustAvailable) - return "Gamma adjustment not available (requires DMS API v34+)"; + return "Gamma adjustment not available (requires dms API v34+)"; if (!value) return SessionData.displayGamma.toString(); @@ -629,7 +629,7 @@ Singleton { function contrast(value: string): string { if (!root.gammaAdjustAvailable) - return "Contrast adjustment not available (requires DMS API v34+)"; + return "Contrast adjustment not available (requires dms API v34+)"; if (!value) return SessionData.displayContrast.toString(); diff --git a/quickshell/Services/NotificationService.qml b/quickshell/Services/NotificationService.qml index 9991871f0..a05b42d04 100644 --- a/quickshell/Services/NotificationService.qml +++ b/quickshell/Services/NotificationService.qml @@ -1088,7 +1088,7 @@ Singleton { } function sendTestNotification(index) { - const notifications = [["Notification Position Test", "DMS test notification 1 of 3 ~ Hi there!", "preferences-system"], ["Second Test", "DMS Notification 2 of 3 ~ Check it out!", "applications-graphics"], ["Third Test", "DMS notification 3 of 3 ~ Enjoy!", "face-smile"]]; + const notifications = [["Notification Position Test", "dms test notification 1 of 3 ~ Hi there!", "preferences-system"], ["Second Test", "dms notification 2 of 3 ~ Check it out!", "applications-graphics"], ["Third Test", "DMS notification 3 of 3 ~ Enjoy!", "face-smile"]]; if (index < 0 || index >= notifications.length) { return; diff --git a/quickshell/Services/WellbeingService.qml b/quickshell/Services/WellbeingService.qml index 2233a9dd1..5cacc3c9b 100644 --- a/quickshell/Services/WellbeingService.qml +++ b/quickshell/Services/WellbeingService.qml @@ -225,7 +225,7 @@ Singleton { function status(): string { if (!root.available) - return "Screen time tracking: unavailable (DMS server without wellbeing support)"; + return "Screen time tracking: unavailable (dms server without wellbeing support)"; const lines = ["Screen time tracking: " + (root.enabled ? "enabled" : "disabled")]; if (root.tracking) lines.push("Today: " + root.formatDuration(root.today.active ?? 0)); diff --git a/quickshell/Widgets/FeatureCard.qml b/quickshell/Widgets/FeatureCard.qml new file mode 100644 index 000000000..c9150dade --- /dev/null +++ b/quickshell/Widgets/FeatureCard.qml @@ -0,0 +1,95 @@ +import QtQuick +import qs.Common +import qs.DCommon.Widgets + +DCard { + id: root + + property string iconName: "" + property string shape: "circle" + property string hoverShape: "circle" + property string accent: "" + property string heading: "" + property string description: "" + + readonly property var accentPair: Theme.accent(accent) + readonly property bool engaged: hover.hovered || focusTarget.visualFocus + readonly property var morphSpring: Theme.springPreset("default", Theme.expressiveDurations.expressiveDefaultSpatial) + + onEngagedChanged: morphMotion.retarget(engaged ? 1 : 0) + + clickable: true + pad: Theme.spacingL + restRadius: Theme.groupedListOuterRadius + bodyRadius: focusTarget.pressed ? Theme.cornerRadiusM : restRadius + implicitHeight: body.implicitHeight + pad * 2 + Accessible.name: heading + Accessible.description: description + + HoverHandler { + id: hover + } + + SpringMotion { + id: morphMotion + enabled: !Theme.springMotionDisabled + stiffness: root.morphSpring.stiffness + damping: root.morphSpring.damping + } + + Column { + id: body + width: parent.width + spacing: Theme.spacingM + + Item { + width: parent.width + height: Theme.buttonHeightM + + MorphShape { + id: badge + width: parent.height + height: width + fromShape: root.shape + toShape: root.hoverShape + progress: morphMotion.value + color: root.accentPair?.container ?? Theme.primaryContainer + } + + DIcon { + anchors.centerIn: badge + name: root.iconName + size: Theme.iconSize + color: root.accentPair?.onContainer ?? Theme.onPrimaryContainer + } + + DIcon { + anchors.right: parent.right + anchors.top: parent.top + name: "arrow_outward" + size: Theme.iconSizeMedium + color: root.engaged ? Theme.primary : Theme.surfaceVariantText + rotation: I18n.isRtl ? -90 : 0 + } + } + + StyledText { + width: parent.width + text: root.heading + font.pixelSize: Theme.fontSizeLarge + font.weight: Theme.fontWeightBold + color: Theme.surfaceText + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignLeft + } + + StyledText { + width: parent.width + text: root.description + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceVariantText + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignLeft + } + } +} diff --git a/quickshell/Widgets/MorphShape.qml b/quickshell/Widgets/MorphShape.qml new file mode 100644 index 000000000..20cc52808 --- /dev/null +++ b/quickshell/Widgets/MorphShape.qml @@ -0,0 +1,56 @@ +import "../DCommon/Widgets/MaterialShapes.js" as Shapes +import QtQuick +import QtQuick.Shapes +import qs.Common +import qs.DCommon.Widgets + +Item { + id: root + + property string fromShape: "circle" + property string toShape: "circle" + property color color: Theme.primary + property real progress: 0 + + readonly property int samples: 128 + readonly property real clampedProgress: Math.max(0, Math.min(1, progress)) + readonly property bool morphing: clampedProgress > 0 && clampedProgress < 1 && Theme.shapeScale !== 0 + readonly property var fromProfile: Shapes.polarProfile(fromShape, samples) + readonly property var toProfile: Shapes.polarProfile(toShape, samples) + readonly property var outline: { + if (!morphing) + return []; + const size = Math.min(width, height); + const points = []; + for (let i = 0; i <= samples; i++) { + const index = i % samples; + const angle = -Math.PI + 2 * Math.PI * index / samples; + const radius = (fromProfile[index] + (toProfile[index] - fromProfile[index]) * clampedProgress) * size; + points.push(Qt.point(width / 2 + Math.cos(angle) * radius, height / 2 + Math.sin(angle) * radius)); + } + return points; + } + + DMaterialShape { + anchors.fill: parent + shape: root.clampedProgress >= 1 ? root.toShape : root.fromShape + color: root.color + visible: !root.morphing + } + + Shape { + anchors.fill: parent + visible: root.morphing + preferredRendererType: Shape.CurveRenderer + + ShapePath { + strokeColor: "transparent" + strokeWidth: 0 + fillColor: root.color + + PathPolyline { + path: root.outline + } + } + } +} diff --git a/quickshell/Widgets/ShapeHero.qml b/quickshell/Widgets/ShapeHero.qml new file mode 100644 index 000000000..8493ddf8d --- /dev/null +++ b/quickshell/Widgets/ShapeHero.qml @@ -0,0 +1,84 @@ +import QtQuick +import Quickshell.Widgets +import qs.Common +import qs.DCommon.Widgets + +Item { + id: root + + default property alias content: contentColumn.data + property real paddingV: Theme.spacingXL + Theme.spacingS + property real accentSize: Theme.buttonHeightM * 1.6 + property Component backdrop: defaultBackdrop + + readonly property real availableWidth: width - Theme.spacingXL * 2 + + height: contentColumn.implicitHeight + paddingV * 2 + + component Decoration: DMaterialShape { + respectThemeShape: false + } + + Component { + id: defaultBackdrop + + Item { + Decoration { + width: root.height * 1.15 + height: width + x: parent.width - width * 0.62 + y: -height * 0.42 + shape: "cookie12" + color: Theme.withAlpha(Theme.primary, Theme.tonalTintAlpha) + rotation: 12 + } + + Decoration { + width: root.height * 0.8 + height: width + x: -width * 0.38 + y: parent.height - height * 0.58 + shape: "softBurst" + color: Theme.withAlpha(Theme.tertiary, Theme.tonalTintAlpha) + rotation: -8 + } + + Decoration { + width: root.accentSize * 0.6 + height: width + x: parent.width * 0.12 + y: root.paddingV * 0.6 + shape: "flower" + color: Theme.withAlpha(Theme.onPrimaryContainer, Theme.stateLayerHover) + } + + Decoration { + width: root.accentSize * 0.75 + height: width + x: parent.width * 0.8 + y: parent.height - height - root.paddingV * 0.5 + shape: "pill" + color: Theme.withAlpha(Theme.secondary, Theme.tonalTintAlpha) + rotation: -40 + } + } + } + + ClippingRectangle { + anchors.fill: parent + radius: Theme.cornerRadiusXL + color: Theme.primaryContainer + + Loader { + anchors.fill: parent + sourceComponent: root.backdrop + } + } + + Column { + id: contentColumn + anchors.centerIn: parent + width: root.availableWidth + spacing: Theme.spacingM + } +}