From 41c5c2a99b09c7cb62af7dad15a8ecc4a268e039 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 15 Jun 2025 16:22:47 +0200 Subject: [PATCH] first run experience: welcome app --- .../quickshell/modules/common/Appearance.qml | 1 + .../modules/common/widgets/GroupButton.qml | 4 +- .../common/widgets/StyledProgressBar.qml | 3 +- .../modules/common/widgets/StyledSwitch.qml | 2 +- .../modules/sidebarLeft/ApiCommandButton.qml | 1 + .config/quickshell/services/Ai.qml | 2 +- .../services/FirstRunExperience.qml | 10 +- .config/quickshell/welcome.qml | 301 ++++++++++++++++++ 8 files changed, 318 insertions(+), 6 deletions(-) create mode 100644 .config/quickshell/welcome.qml diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 7622b29a4..694c3af8e 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -148,6 +148,7 @@ Singleton { rounding: QtObject { property int unsharpen: 2 + property int unsharpenmore: 6 property int verysmall: 8 property int small: 12 property int normal: 17 diff --git a/.config/quickshell/modules/common/widgets/GroupButton.qml b/.config/quickshell/modules/common/widgets/GroupButton.qml index 5d4d9d199..bfa887b68 100644 --- a/.config/quickshell/modules/common/widgets/GroupButton.qml +++ b/.config/quickshell/modules/common/widgets/GroupButton.qml @@ -23,8 +23,8 @@ Button { property var altAction // When right clicking property var middleClickAction // When middle clicking property bool bounce: true - property real baseWidth: contentItem.implicitWidth + padding * 2 - property real baseHeight: contentItem.implicitHeight + padding * 2 + property real baseWidth: contentItem.implicitWidth + horizontalPadding * 2 + property real baseHeight: contentItem.implicitHeight + verticalPadding * 2 property real clickedWidth: baseWidth + 20 property real clickedHeight: baseHeight property var parentGroup: root.parent diff --git a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml index 31bce5915..28f517bc8 100644 --- a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml +++ b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml @@ -19,6 +19,7 @@ ProgressBar { property color highlightColor: Appearance?.colors.colPrimary ?? "#685496" property color trackColor: Appearance?.m3colors.m3secondaryContainer ?? "#F1D3F9" property bool sperm: false // If true, the progress bar will have a wavy fill effect + property bool animateSperm: true property real spermAmplitudeMultiplier: sperm ? 0.5 : 0 property real spermFrequency: 6 property real spermFps: 60 @@ -82,7 +83,7 @@ ProgressBar { } Timer { interval: 1000 / root.spermFps - running: root.sperm + running: root.animateSperm repeat: root.sperm onTriggered: wavyFill.requestPaint() } diff --git a/.config/quickshell/modules/common/widgets/StyledSwitch.qml b/.config/quickshell/modules/common/widgets/StyledSwitch.qml index 217a2f7e4..e980d7f88 100644 --- a/.config/quickshell/modules/common/widgets/StyledSwitch.qml +++ b/.config/quickshell/modules/common/widgets/StyledSwitch.qml @@ -9,7 +9,7 @@ import Qt5Compat.GraphicalEffects */ Switch { id: root - property real scale: 1 + property real scale: 0.6 // Default in m3 spec is huge af implicitHeight: 32 * root.scale implicitWidth: 52 * root.scale property color activeColor: Appearance?.colors.colPrimary ?? "#685496" diff --git a/.config/quickshell/modules/sidebarLeft/ApiCommandButton.qml b/.config/quickshell/modules/sidebarLeft/ApiCommandButton.qml index b9fab2961..8741cb6be 100644 --- a/.config/quickshell/modules/sidebarLeft/ApiCommandButton.qml +++ b/.config/quickshell/modules/sidebarLeft/ApiCommandButton.qml @@ -16,6 +16,7 @@ GroupButton { baseWidth: contentItem.implicitWidth + horizontalPadding * 2 clickedWidth: baseWidth + 20 baseHeight: contentItem.implicitHeight + verticalPadding * 2 + buttonRadius: down ? Appearance.rounding.small : baseHeight / 2 colBackground: Appearance.colors.colLayer2 colBackgroundHover: Appearance.colors.colLayer2Hover diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index 40b920e04..9a5ef5124 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -300,7 +300,7 @@ Singleton { const model = models[modelId] // See if policy prevents online models if (ConfigOptions.policies.ai === 2 && !model.endpoint.includes("localhost")) { - root.addMessage(StringUtils.format(StringUtils.format("Policy disallows online models\n\nControlled by `policies.ai` config option"), model.name), root.interfaceRole); + root.addMessage(StringUtils.format(StringUtils.format("Online models disallowed\n\nControlled by `policies.ai` config option"), model.name), root.interfaceRole); return; } PersistentStateManager.setState("ai.model", modelId); diff --git a/.config/quickshell/services/FirstRunExperience.qml b/.config/quickshell/services/FirstRunExperience.qml index eee995a55..e28d4b49b 100644 --- a/.config/quickshell/services/FirstRunExperience.qml +++ b/.config/quickshell/services/FirstRunExperience.qml @@ -13,14 +13,22 @@ Singleton { property string firstRunNotifSummary: "Welcome!" property string firstRunNotifBody: "Hit Super+/ for a list of keybinds" property string defaultWallpaperPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/assets/images/default_wallpaper.png`) + property string welcomeQmlPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/welcome.qml`) function load() { firstRunFileView.reload() } + function enableNextTime() { + Hyprland.dispatch(`exec rm -f '${root.firstRunFilePath}'`) + } + function disableNextTime() { + Hyprland.dispatch(`exec echo '${root.firstRunFileContent}' > '${root.firstRunFilePath}'`) + } + function handleFirstRun() { - Hyprland.dispatch(`exec notify-send '${root.firstRunNotifSummary}' '${root.firstRunNotifBody}' -a 'Shell'`) Hyprland.dispatch(`exec '${Directories.wallpaperSwitchScriptPath}' '${root.defaultWallpaperPath}'`) + Hyprland.dispatch(`exec qs -p '${root.welcomeQmlPath}'`) } FileView { diff --git a/.config/quickshell/welcome.qml b/.config/quickshell/welcome.qml new file mode 100644 index 000000000..bf7071550 --- /dev/null +++ b/.config/quickshell/welcome.qml @@ -0,0 +1,301 @@ +//@ pragma UseQApplication +//@ pragma Env QS_NO_RELOAD_POPUP=1 +//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic + +// Adjust this to make the app smaller or larger +//@ pragma Env QT_SCALE_FACTOR=1 + +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import Quickshell +import Quickshell.Hyprland +import "root:/services/" +import "root:/modules/common/" +import "root:/modules/common/widgets/" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import "root:/modules/common/functions/file_utils.js" as FileUtils +import "root:/modules/common/functions/string_utils.js" as StringUtils + +ApplicationWindow { + id: root + property string firstRunFilePath: FileUtils.trimFileProtocol(`${Directories.state}/user/first_run.txt`) + property string firstRunFileContent: "This file is just here to confirm you've been greeted :>" + property real contentPadding: 5 + property bool showNextTime: false + + visible: true + onClosing: Qt.quit() + title: "illogical-impulse Welcome" + + Component.onCompleted: { + MaterialThemeLoader.reapplyTheme() + ConfigLoader.loadConfig() + } + + minimumWidth: 600 + minimumHeight: 400 + width: 800 + height: 600 + color: Appearance.m3colors.m3background + + component Section: ColumnLayout { + id: sectionRoot + property string title + default property alias data: sectionContent.data + + Layout.fillWidth: true + spacing: 10 + StyledText { + text: sectionRoot.title + font.pixelSize: Appearance.font.pixelSize.larger + } + ColumnLayout { + id: sectionContent + spacing: 5 + } + } + + component LightDarkPrefButton: GroupButton { + id: lightDarkButtonRoot + required property bool dark + property color previewBg: dark ? ColorUtils.colorWithHueOf("#3f3838", Appearance.m3colors.m3primary) : + ColorUtils.colorWithHueOf("#f8f8f8", Appearance.m3colors.m3primary) + property color previewFg: dark ? Qt.lighter(previewBg, 2.2) : ColorUtils.mix(previewBg, "#292929", 0.85) + padding: 5 + Layout.fillWidth: true + colBackground: Appearance.colors.colLayer2 + toggled: Appearance.m3colors.darkmode === dark + onClicked: { + Hyprland.dispatch(`exec ${Directories.wallpaperSwitchScriptPath} --mode ${dark ? "dark" : "light"} --noswitch`) + } + contentItem: Item { + anchors.centerIn: parent + implicitWidth: buttonContentLayout.implicitWidth + implicitHeight: buttonContentLayout.implicitHeight + ColumnLayout { + id: buttonContentLayout + anchors.centerIn: parent + Rectangle { + Layout.alignment: Qt.AlignHCenter + implicitWidth: 250 + implicitHeight: skeletonColumnLayout.implicitHeight + 10 * 2 + radius: lightDarkButtonRoot.buttonRadius - lightDarkButtonRoot.padding + color: lightDarkButtonRoot.previewBg + border { + width: 1 + color: Appearance.m3colors.m3outlineVariant + } + + // Some skeleton items + ColumnLayout { + id: skeletonColumnLayout + anchors.fill: parent + anchors.margins: 10 + spacing: 10 + RowLayout { + Rectangle { + radius: Appearance.rounding.full + color: lightDarkButtonRoot.previewFg + implicitWidth: 50 + implicitHeight: 50 + } + ColumnLayout { + spacing: 4 + Rectangle { + radius: Appearance.rounding.unsharpenmore + color: lightDarkButtonRoot.previewFg + Layout.fillWidth: true + implicitHeight: 22 + } + Rectangle { + radius: Appearance.rounding.unsharpenmore + color: lightDarkButtonRoot.previewFg + Layout.fillWidth: true + Layout.rightMargin: 45 + implicitHeight: 18 + } + } + } + StyledProgressBar { + Layout.topMargin: 5 + Layout.bottomMargin: 5 + Layout.fillWidth: true + value: 0.7 + sperm: true + animateSperm: lightDarkButtonRoot.toggled + highlightColor: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3primary : lightDarkButtonRoot.previewFg + trackColor: ColorUtils.mix(lightDarkButtonRoot.previewBg, lightDarkButtonRoot.previewFg, 0.5) + } + RowLayout { + spacing: 2 + Rectangle { + radius: Appearance.rounding.full + color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3primary : lightDarkButtonRoot.previewFg + Layout.fillWidth: true + implicitHeight: 30 + MaterialSymbol { + visible: lightDarkButtonRoot.toggled + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: "check" + iconSize: 20 + color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3onPrimary : lightDarkButtonRoot.previewBg + } + } + Rectangle { + radius: Appearance.rounding.unsharpenmore + color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3secondaryContainer : lightDarkButtonRoot.previewFg + Layout.fillWidth: true + implicitHeight: 30 + } + Rectangle { + topLeftRadius: Appearance.rounding.unsharpenmore + bottomLeftRadius: Appearance.rounding.unsharpenmore + topRightRadius: Appearance.rounding.full + bottomRightRadius: Appearance.rounding.full + color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3secondaryContainer : lightDarkButtonRoot.previewFg + Layout.fillWidth: true + implicitHeight: 30 + } + } + } + } + StyledText { + Layout.fillWidth: true + text: dark ? "Dark" : "Light" + color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2 + horizontalAlignment: Text.AlignHCenter + } + } + } + } + + ColumnLayout { + anchors { + fill: parent + margins: contentPadding + } + Item { + Layout.fillWidth: true + implicitHeight: Math.max(welcomeText.implicitHeight, windowControlsRow.implicitHeight) + StyledText { + id: welcomeText + anchors.centerIn: parent + color: Appearance.colors.colOnLayer0 + text: "Welcome" + font.pixelSize: Appearance.font.pixelSize.hugeass + font.family: Appearance.font.family.title + } + RowLayout { // Window controls row + id: windowControlsRow + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + StyledText { + font.pixelSize: Appearance.font.pixelSize.smaller + text: "Show next time" + } + StyledSwitch { + id: showNextTimeSwitch + checked: root.showNextTime + scale: 0.6 + Layout.alignment: Qt.AlignVCenter + onCheckedChanged: { + if (checked) { + Hyprland.dispatch(`exec rm '${StringUtils.shellSingleQuoteEscape(root.firstRunFilePath)}'`) + } else { + console.log(`exec echo '${StringUtils.shellSingleQuoteEscape(root.firstRunFileContent)}' > '${StringUtils.shellSingleQuoteEscape(root.firstRunFilePath)}'`) + Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(root.firstRunFileContent)}' > '${StringUtils.shellSingleQuoteEscape(root.firstRunFilePath)}'`) + } + } + } + RippleButton { + buttonRadius: Appearance.rounding.full + implicitWidth: 35 + implicitHeight: 35 + onClicked: root.close() + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: "close" + iconSize: 20 + } + } + } + } + Rectangle { + color: Appearance.m3colors.m3surfaceContainerLow + implicitHeight: contentColumn.implicitHeight + implicitWidth: contentColumn.implicitWidth + Layout.fillWidth: true + Layout.fillHeight: true + radius: Appearance.rounding.windowRounding - root.contentPadding + ColumnLayout { + id: contentColumn + anchors { + top: parent.top + bottom: parent.bottom + horizontalCenter: parent.horizontalCenter + margins: 10 + } + spacing: 20 + + Section { + title: "Customize" + + ButtonGroup { + Layout.fillWidth: true + LightDarkPrefButton { + dark: false + } + LightDarkPrefButton { + dark: true + } + } + } + + Section { + title: "Info" + + RippleButton { + implicitHeight: 35 + horizontalPadding: 10 + // buttonRadius: Appearance.rounding.full + colBackground: Appearance.colors.colSecondaryContainer + colBackgroundHover: Appearance.colors.colSecondaryContainerHover + colRipple: Appearance.colors.colSecondaryContainerActive + + onClicked: { + Hyprland.dispatch("global quickshell:cheatsheetOpen") + } + + contentItem: RowLayout { + KeyboardKey { + key: "󰖳" + } + StyledText { + Layout.alignment: Qt.AlignVCenter + text: "+" + } + KeyboardKey { + key: "/" + } + StyledText { + text: "Open keybind cheatsheet" + color: Appearance.colors.colOnSecondaryContainer + } + } + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + + } + } + } +}