diff --git a/.config/quickshell/ii/modules/background/Background.qml b/.config/quickshell/ii/modules/background/Background.qml index 24a1d0b82..fd1ee53dd 100644 --- a/.config/quickshell/ii/modules/background/Background.qml +++ b/.config/quickshell/ii/modules/background/Background.qml @@ -13,14 +13,14 @@ import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland - Variants { id: root - readonly property bool fixedClockPosition: Config.options.background.fixedClockPosition - readonly property real fixedClockX: Config.options.background.clockX - readonly property real fixedClockY: Config.options.background.clockY + readonly property bool fixedClockPosition: Config.options.background.clock.fixedPosition + readonly property real fixedClockX: Config.options.background.clock.x + readonly property real fixedClockY: Config.options.background.clock.y readonly property real clockSizePadding: 20 readonly property real screenSizePadding: 50 + readonly property string clockStyle: Config.options.background.clock.style model: Quickshell.screens PanelWindow { @@ -29,8 +29,8 @@ Variants { required property var modelData // Hide when fullscreen - property list workspacesForMonitor: Hyprland.workspaces.values.filter(workspace=>workspace.monitor && workspace.monitor.name == monitor.name) - property var activeWorkspaceWithFullscreen: workspacesForMonitor.filter(workspace=>((workspace.toplevels.values.filter(window=>window.wayland?.fullscreen)[0] != undefined) && workspace.active))[0] + property list workspacesForMonitor: Hyprland.workspaces.values.filter(workspace => workspace.monitor && workspace.monitor.name == monitor.name) + property var activeWorkspaceWithFullscreen: workspacesForMonitor.filter(workspace => ((workspace.toplevels.values.filter(window => window.wayland?.fullscreen)[0] != undefined) && workspace.active))[0] visible: GlobalStates.screenLocked || (!(activeWorkspaceWithFullscreen != undefined)) || !Config?.options.background.hideWhenFullscreen // Workspaces @@ -39,16 +39,9 @@ Variants { property int firstWorkspaceId: relevantWindows[0]?.workspace.id || 1 property int lastWorkspaceId: relevantWindows[relevantWindows.length - 1]?.workspace.id || 10 // Wallpaper - property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(".mp4") - || Config.options.background.wallpaperPath.endsWith(".webm") - || Config.options.background.wallpaperPath.endsWith(".mkv") - || Config.options.background.wallpaperPath.endsWith(".avi") - || Config.options.background.wallpaperPath.endsWith(".mov") + property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(".mp4") || Config.options.background.wallpaperPath.endsWith(".webm") || Config.options.background.wallpaperPath.endsWith(".mkv") || Config.options.background.wallpaperPath.endsWith(".avi") || Config.options.background.wallpaperPath.endsWith(".mov") property string wallpaperPath: wallpaperIsVideo ? Config.options.background.thumbnailPath : Config.options.background.wallpaperPath - property bool wallpaperSafetyTriggered: Config.options.background.wallpaperSafety.enable && ( - CF.StringUtils.stringListContainsSubstring(wallpaperPath.toLowerCase(), Config.options.background.wallpaperSafety.triggerCondition.wallpaperKeywords) && - CF.StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.background.wallpaperSafety.triggerCondition.networkNameKeywords) - ) + property bool wallpaperSafetyTriggered: Config.options.background.wallpaperSafety.enable && (CF.StringUtils.stringListContainsSubstring(wallpaperPath.toLowerCase(), Config.options.background.wallpaperSafety.triggerCondition.wallpaperKeywords) && CF.StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.background.wallpaperSafety.triggerCondition.networkNameKeywords)) property real wallpaperToScreenRatio: Math.min(wallpaperWidth / screen.width, wallpaperHeight / screen.height) property real preferredWallpaperScale: Config.options.background.parallax.workspaceZoom property real effectiveWallpaperScale: 1 // Some reasonable init value, to be updated @@ -74,60 +67,14 @@ Variants { property color dominantColor: Appearance.colors.colPrimary property bool dominantColorIsDark: dominantColor.hslLightness < 0.5 property color colText: { - if (wallpaperSafetyTriggered) return CF.ColorUtils.mix(Appearance.colors.colOnLayer0, Appearance.colors.colPrimary, 0.75); - return (GlobalStates.screenLocked && shouldBlur) ? Appearance.colors.colOnLayer0 : CF.ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (dominantColorIsDark ? 0.8 : 0.12)) + if (wallpaperSafetyTriggered) + return CF.ColorUtils.mix(Appearance.colors.colOnLayer0, Appearance.colors.colPrimary, 0.75); + return (GlobalStates.screenLocked && shouldBlur) ? Appearance.colors.colOnLayer0 : CF.ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (dominantColorIsDark ? 0.8 : 0.12)); } Behavior on colText { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } - // Components - component ClockText: StyledText { - Layout.fillWidth: true - horizontalAlignment: bgRoot.textHorizontalAlignment - font { - family: Appearance.font.family.expressive - pixelSize: 20 - weight: Font.DemiBold - } - color: bgRoot.colText - style: Text.Raised - styleColor: Appearance.colors.colShadow - animateChange: true - } - component ClockStatusText: RowLayout { - id: statusTextRow - property alias statusIcon: statusIconWidget.text - property alias statusText: statusTextWidget.text - property bool shown: true - opacity: shown ? 1 : 0 - visible: opacity > 0 - Behavior on opacity { - animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) - } - Layout.fillWidth: false - MaterialSymbol { - id: statusIconWidget - Layout.fillWidth: false - iconSize: Appearance.font.pixelSize.huge - color: bgRoot.colText - style: Text.Raised - styleColor: Appearance.colors.colShadow - } - ClockText { - id: statusTextWidget - Layout.fillWidth: false - color: bgRoot.colText - font { - family: Appearance.font.family.main - pixelSize: Appearance.font.pixelSize.large - weight: Font.Normal - } - style: Text.Raised - styleColor: Appearance.colors.colShadow - } - } - // Layer props screen: modelData exclusionMode: ExclusionMode.Ignore @@ -140,45 +87,43 @@ Variants { left: true right: true } - color: CF.ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colPrimary, 0.75); + color: CF.ColorUtils.transparentize(CF.ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colPrimary, 0.75), (bgRoot.wallpaperIsVideo ? 1 : 0)) Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } onWallpaperPathChanged: { - bgRoot.updateZoomScale() + bgRoot.updateZoomScale(); // Clock position gets updated after zoom scale is updated } // Wallpaper zoom scale function updateZoomScale() { - getWallpaperSizeProc.path = bgRoot.wallpaperPath + getWallpaperSizeProc.path = bgRoot.wallpaperPath; getWallpaperSizeProc.running = true; } Process { id: getWallpaperSizeProc property string path: bgRoot.wallpaperPath - command: [ "magick", "identify", "-format", "%w %h", path ] + command: ["magick", "identify", "-format", "%w %h", path] stdout: StdioCollector { id: wallpaperSizeOutputCollector onStreamFinished: { - const output = wallpaperSizeOutputCollector.text + const output = wallpaperSizeOutputCollector.text; const [width, height] = output.split(" ").map(Number); const [screenWidth, screenHeight] = [bgRoot.screen.width, bgRoot.screen.height]; - bgRoot.wallpaperWidth = width - bgRoot.wallpaperHeight = height + bgRoot.wallpaperWidth = width; + bgRoot.wallpaperHeight = height; - if (width <= screenWidth || height <= screenHeight) { // Undersized/perfectly sized wallpapers + if (width <= screenWidth || height <= screenHeight) { + // Undersized/perfectly sized wallpapers bgRoot.effectiveWallpaperScale = Math.max(screenWidth / width, screenHeight / height); - } else { // Oversized = can be zoomed for parallax, yay - bgRoot.effectiveWallpaperScale = Math.min( - bgRoot.preferredWallpaperScale, - width / screenWidth, height / screenHeight - ); + } else { + // Oversized = can be zoomed for parallax, yay + bgRoot.effectiveWallpaperScale = Math.min(bgRoot.preferredWallpaperScale, width / screenWidth, height / screenHeight); } - - bgRoot.updateClockPosition() + bgRoot.updateClockPosition(); } } } @@ -186,11 +131,11 @@ Variants { // Clock positioning function updateClockPosition() { // Somehow all this manual setting is needed to make the proc correctly use the new values - leastBusyRegionProc.path = bgRoot.wallpaperPath - leastBusyRegionProc.contentWidth = clockLoader.implicitWidth + root.clockSizePadding * 2 - leastBusyRegionProc.contentHeight = clockLoader.implicitHeight + root.clockSizePadding * 2 - leastBusyRegionProc.horizontalPadding = bgRoot.movableXSpace + root.screenSizePadding * 2 - leastBusyRegionProc.verticalPadding = bgRoot.movableYSpace + root.screenSizePadding * 2 + leastBusyRegionProc.path = bgRoot.wallpaperPath; + leastBusyRegionProc.contentWidth = clockLoader.implicitWidth + root.clockSizePadding * 2; + leastBusyRegionProc.contentHeight = clockLoader.implicitHeight + root.clockSizePadding * 2; + leastBusyRegionProc.horizontalPadding = bgRoot.movableXSpace + root.screenSizePadding * 2; + leastBusyRegionProc.verticalPadding = bgRoot.movableYSpace + root.screenSizePadding * 2; leastBusyRegionProc.running = false; leastBusyRegionProc.running = true; } @@ -201,220 +146,310 @@ Variants { property int contentHeight: 300 property int horizontalPadding: bgRoot.movableXSpace property int verticalPadding: bgRoot.movableYSpace - command: [Quickshell.shellPath("scripts/images/least_busy_region.py"), - "--screen-width", Math.round(bgRoot.screen.width / bgRoot.effectiveWallpaperScale), - "--screen-height", Math.round(bgRoot.screen.height / bgRoot.effectiveWallpaperScale), - "--width", contentWidth, - "--height", contentHeight, - "--horizontal-padding", horizontalPadding, - "--vertical-padding", verticalPadding, - path, + command: [Quickshell.shellPath("scripts/images/least_busy_region.py"), "--screen-width", Math.round(bgRoot.screen.width / bgRoot.effectiveWallpaperScale), "--screen-height", Math.round(bgRoot.screen.height / bgRoot.effectiveWallpaperScale), "--width", contentWidth, "--height", contentHeight, "--horizontal-padding", horizontalPadding, "--vertical-padding", verticalPadding, path // "--visual-output", - ] + ,] stdout: StdioCollector { id: leastBusyRegionOutputCollector onStreamFinished: { - const output = leastBusyRegionOutputCollector.text + const output = leastBusyRegionOutputCollector.text; // console.log("[Background] Least busy region output:", output) - if (output.length === 0) return; - const parsedContent = JSON.parse(output) - bgRoot.clockX = parsedContent.center_x * bgRoot.effectiveWallpaperScale - bgRoot.clockY = parsedContent.center_y * bgRoot.effectiveWallpaperScale - bgRoot.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary + if (output.length === 0) + return; + const parsedContent = JSON.parse(output); + bgRoot.clockX = parsedContent.center_x * bgRoot.effectiveWallpaperScale; + bgRoot.clockY = parsedContent.center_y * bgRoot.effectiveWallpaperScale; + bgRoot.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary; } } } // Wallpaper - Image { - id: wallpaper - visible: opacity > 0 && !blurLoader.active - opacity: (status === Image.Ready && !bgRoot.wallpaperIsVideo) ? 1 : 0 - Behavior on opacity { - animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) - } - cache: false - asynchronous: true - retainWhileLoading: true - smooth: false - // Range = groups that workspaces span on - property int chunkSize: Config?.options.bar.workspaces.shown ?? 10; - property int lower: Math.floor(bgRoot.firstWorkspaceId / chunkSize) * chunkSize; - property int upper: Math.ceil(bgRoot.lastWorkspaceId / chunkSize) * chunkSize; - property int range: upper - lower; - property real valueX: { - let result = 0.5; - if (Config.options.background.parallax.enableWorkspace && !bgRoot.verticalParallax) { - result = ((bgRoot.monitor.activeWorkspace?.id - lower) / range); - } - if (Config.options.background.parallax.enableSidebar) { - result += (0.15 * GlobalStates.sidebarRightOpen - 0.15 * GlobalStates.sidebarLeftOpen); - } - return result; - } - property real valueY: { - let result = 0.5; - if (Config.options.background.parallax.enableWorkspace && bgRoot.verticalParallax) { - result = ((bgRoot.monitor.activeWorkspace?.id - lower) / range); - } - return result; - } - property real effectiveValueX: Math.max(0, Math.min(1, valueX)) - property real effectiveValueY: Math.max(0, Math.min(1, valueY)) - x: -(bgRoot.movableXSpace) - (effectiveValueX - 0.5) * 2 * bgRoot.movableXSpace - y: -(bgRoot.movableYSpace) - (effectiveValueY - 0.5) * 2 * bgRoot.movableYSpace - source: bgRoot.wallpaperSafetyTriggered ? "" : bgRoot.wallpaperPath - fillMode: Image.PreserveAspectCrop - Behavior on x { - NumberAnimation { - duration: 600 - easing.type: Easing.OutCubic - } - } - Behavior on y { - NumberAnimation { - duration: 600 - easing.type: Easing.OutCubic - } - } - sourceSize { - width: bgRoot.screen.width * bgRoot.effectiveWallpaperScale * bgRoot.monitor.scale - height: bgRoot.screen.height * bgRoot.effectiveWallpaperScale * bgRoot.monitor.scale - } - width: bgRoot.wallpaperWidth / bgRoot.wallpaperToScreenRatio * bgRoot.effectiveWallpaperScale - height: bgRoot.wallpaperHeight / bgRoot.wallpaperToScreenRatio * bgRoot.effectiveWallpaperScale - } + Item { + anchors.fill: parent + clip: true - Loader { - id: blurLoader - active: Config.options.background.lockBlur.enable && (GlobalStates.screenLocked || scaleAnim.running) - anchors.fill: wallpaper - scale: GlobalStates.screenLocked ? Config.options.background.lockBlur.extraZoom : 1 - Behavior on scale { - NumberAnimation { - id: scaleAnim - duration: 400 - easing.type: Easing.BezierSpline - easing.bezierCurve: Appearance.animationCurves.expressiveDefaultSpatial + Image { + id: wallpaper + visible: opacity > 0 && !blurLoader.active + opacity: (status === Image.Ready && !bgRoot.wallpaperIsVideo) ? 1 : 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } + cache: false + asynchronous: true + retainWhileLoading: true + smooth: false + // Range = groups that workspaces span on + property int chunkSize: Config?.options.bar.workspaces.shown ?? 10 + property int lower: Math.floor(bgRoot.firstWorkspaceId / chunkSize) * chunkSize + property int upper: Math.ceil(bgRoot.lastWorkspaceId / chunkSize) * chunkSize + property int range: upper - lower + property real valueX: { + let result = 0.5; + if (Config.options.background.parallax.enableWorkspace && !bgRoot.verticalParallax) { + result = ((bgRoot.monitor.activeWorkspace?.id - lower) / range); + } + if (Config.options.background.parallax.enableSidebar) { + result += (0.15 * GlobalStates.sidebarRightOpen - 0.15 * GlobalStates.sidebarLeftOpen); + } + return result; + } + property real valueY: { + let result = 0.5; + if (Config.options.background.parallax.enableWorkspace && bgRoot.verticalParallax) { + result = ((bgRoot.monitor.activeWorkspace?.id - lower) / range); + } + return result; + } + property real effectiveValueX: Math.max(0, Math.min(1, valueX)) + property real effectiveValueY: Math.max(0, Math.min(1, valueY)) + x: -(bgRoot.movableXSpace) - (effectiveValueX - 0.5) * 2 * bgRoot.movableXSpace + y: -(bgRoot.movableYSpace) - (effectiveValueY - 0.5) * 2 * bgRoot.movableYSpace + source: bgRoot.wallpaperSafetyTriggered ? "" : bgRoot.wallpaperPath + fillMode: Image.PreserveAspectCrop + Behavior on x { + NumberAnimation { + duration: 600 + easing.type: Easing.OutCubic + } + } + Behavior on y { + NumberAnimation { + duration: 600 + easing.type: Easing.OutCubic + } + } + sourceSize { + width: bgRoot.screen.width * bgRoot.effectiveWallpaperScale * bgRoot.monitor.scale + height: bgRoot.screen.height * bgRoot.effectiveWallpaperScale * bgRoot.monitor.scale + } + width: bgRoot.wallpaperWidth / bgRoot.wallpaperToScreenRatio * bgRoot.effectiveWallpaperScale + height: bgRoot.wallpaperHeight / bgRoot.wallpaperToScreenRatio * bgRoot.effectiveWallpaperScale } - sourceComponent: GaussianBlur { - source: wallpaper - radius: GlobalStates.screenLocked ? Config.options.background.lockBlur.radius : 0 - samples: radius * 2 + 1 - Rectangle { - opacity: GlobalStates.screenLocked ? 1 : 0 - anchors.fill: parent - color: CF.ColorUtils.transparentize(Appearance.colors.colLayer0, 0.7) + Loader { + id: blurLoader + active: Config.options.background.lockBlur.enable && (GlobalStates.screenLocked || scaleAnim.running) + anchors.fill: wallpaper + scale: GlobalStates.screenLocked ? Config.options.background.lockBlur.extraZoom : 1 + Behavior on scale { + NumberAnimation { + id: scaleAnim + duration: 400 + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.animationCurves.expressiveDefaultSpatial + } } - } - } + sourceComponent: GaussianBlur { + source: wallpaper + radius: GlobalStates.screenLocked ? Config.options.background.lockBlur.radius : 0 + samples: radius * 2 + 1 - // The clock - Loader { - id: clockLoader - active: Config.options.background.showClock - anchors { - left: wallpaper.left - top: wallpaper.top - horizontalCenter: undefined - verticalCenter: undefined - leftMargin: bgRoot.movableXSpace + ((root.fixedClockPosition ? root.fixedClockX : bgRoot.clockX * bgRoot.effectiveWallpaperScale) - implicitWidth / 2) - topMargin: { - if (bgRoot.shouldBlur) - return bgRoot.modelData.height / 3 - return bgRoot.movableYSpace + ((root.fixedClockPosition ? root.fixedClockY : bgRoot.clockY * bgRoot.effectiveWallpaperScale) - implicitHeight / 2) - } - Behavior on leftMargin { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } - Behavior on topMargin { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } - } - states: State { - name: "centered" - when: (bgRoot.shouldBlur && Config.options.background.lockBlur.centerClock) || bgRoot.wallpaperSafetyTriggered - AnchorChanges { - target: clockLoader - anchors { - left: undefined - right: undefined - top: undefined - verticalCenter: parent.verticalCenter - horizontalCenter: parent.horizontalCenter + Rectangle { + opacity: GlobalStates.screenLocked ? 1 : 0 + anchors.fill: parent + color: CF.ColorUtils.transparentize(Appearance.colors.colLayer0, 0.7) } } } - transitions: Transition { - AnchorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + + // The clock + Loader { + id: clockLoader + active: Config.options.background.clock.show + anchors { + left: wallpaper.left + top: wallpaper.top + horizontalCenter: undefined + verticalCenter: undefined + leftMargin: bgRoot.movableXSpace + ((root.fixedClockPosition ? root.fixedClockX : bgRoot.clockX * bgRoot.effectiveWallpaperScale) - implicitWidth / 2) + topMargin: { + if (bgRoot.shouldBlur) + return bgRoot.modelData.height / 3; + return bgRoot.movableYSpace + ((root.fixedClockPosition ? root.fixedClockY : bgRoot.clockY * bgRoot.effectiveWallpaperScale) - implicitHeight / 2); + } + Behavior on leftMargin { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + Behavior on topMargin { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } } - } - sourceComponent: Item { - id: clock - implicitWidth: clockColumn.implicitWidth - implicitHeight: clockColumn.implicitHeight - - ColumnLayout { - id: clockColumn - anchors.centerIn: parent - spacing: 6 - - ClockText { - font.pixelSize: 90 - text: DateTime.time - } - ClockText { - Layout.topMargin: -5 - text: DateTime.date - } - StyledText { // Somehow gets fucked up if made a ClockText??? - visible: Config.options.background.quote.length > 0 - Layout.fillWidth: true - horizontalAlignment: bgRoot.textHorizontalAlignment - font { - family: Appearance.font.family.main - pixelSize: Appearance.font.pixelSize.normal - weight: 350 - italic: true + states: State { + name: "centered" + when: (bgRoot.shouldBlur && Config.options.background.lockBlur.centerClock) || bgRoot.wallpaperSafetyTriggered + AnchorChanges { + target: clockLoader + anchors { + left: undefined + right: undefined + top: undefined + verticalCenter: parent.verticalCenter + horizontalCenter: parent.horizontalCenter } - color: bgRoot.colText - style: Text.Raised - styleColor: Appearance.colors.colShadow - text: Config.options.background.quote } } + transitions: Transition { + AnchorAnimation { + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + } + } + sourceComponent: ColumnLayout { + id: clock + spacing: 8 - RowLayout { - anchors { - top: clockColumn.bottom - left: bgRoot.textHorizontalAlignment === Text.AlignLeft ? clockColumn.left : undefined - right: bgRoot.textHorizontalAlignment === Text.AlignRight ? clockColumn.right : undefined - horizontalCenter: bgRoot.textHorizontalAlignment === Text.AlignHCenter ? clockColumn.horizontalCenter : undefined - topMargin: 14 - leftMargin: -6 - rightMargin: -6 - } - spacing: 16 - Item { Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignLeft; implicitWidth: 1 } - ClockStatusText { - shown: bgRoot.wallpaperSafetyTriggered - statusIcon: "hide_image" - statusText: qsTr("Wallpaper safety enforced") - } - ClockStatusText { - shown: GlobalStates.screenLocked && (!Config.options.background.lockBlur.enable || Config.options.background.lockBlur.showLockedText) - statusIcon: "lock" - statusText: qsTr("Locked") - } - Item { Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignRight; implicitWidth: 1 } + Loader { + id: digitalClockLoader + visible: root.clockStyle === "digital" + active: visible + sourceComponent: ColumnLayout { + id: clockColumn + spacing: 6 + ClockText { + font.pixelSize: 90 + text: DateTime.time + } + ClockText { + Layout.topMargin: -5 + text: DateTime.date + } + StyledText { + // Somehow gets fucked up if made a ClockText??? + visible: Config.options.background.quote.length > 0 + Layout.fillWidth: true + horizontalAlignment: bgRoot.textHorizontalAlignment + font { + family: Appearance.font.family.main + pixelSize: Appearance.font.pixelSize.normal + weight: 350 + italic: true + } + color: bgRoot.colText + style: Text.Raised + styleColor: Appearance.colors.colShadow + text: Config.options.background.quote + } + } + } + + Loader { + id: cookieClockLoader + visible: root.clockStyle === "cookie" + active: visible + sourceComponent: CookieClock {} + } + + Item { + Layout.alignment: Qt.AlignHCenter + implicitWidth: statusTextBg.implicitWidth + implicitHeight: statusTextBg.implicitHeight + + StyledRectangularShadow { + target: statusTextBg + visible: statusTextBg.visible + opacity: statusTextBg.opacity + } + + Rectangle { + id: statusTextBg + opacity: (safetyStatusText.shown || lockStatusText.shown) ? 1 : 0 + visible: opacity > 0 + implicitHeight: statusTextRow.implicitHeight + 5 * 2 + implicitWidth: statusTextRow.implicitWidth + 10 * 2 + radius: Appearance.rounding.small + color: CF.ColorUtils.transparentize(Appearance.colors.colSecondaryContainer, cookieClockLoader.active ? 0 : 1) + + Behavior on implicitWidth { + animation: Appearance.animation.elementResize.numberAnimation.createObject(this) + } + Behavior on implicitHeight { + animation: Appearance.animation.elementResize.numberAnimation.createObject(this) + } + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + + RowLayout { + id: statusTextRow + anchors.centerIn: parent + spacing: 16 + Item { + Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignLeft + implicitWidth: 1 + } + ClockStatusText { + id: safetyStatusText + shown: bgRoot.wallpaperSafetyTriggered + statusIcon: "hide_image" + statusText: qsTr("Wallpaper safety enforced") + } + ClockStatusText { + id: lockStatusText + shown: GlobalStates.screenLocked && (!Config.options.background.lockBlur.enable || Config.options.background.lockBlur.showLockedText) + statusIcon: "lock" + statusText: qsTr("Locked") + } + Item { + Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignRight + implicitWidth: 1 + } + } + } + } } } } } + + // Components + component ClockText: StyledText { + Layout.fillWidth: true + horizontalAlignment: bgRoot.textHorizontalAlignment + font { + family: Appearance.font.family.expressive + pixelSize: 20 + weight: Font.DemiBold + } + color: bgRoot.colText + style: Text.Raised + styleColor: Appearance.colors.colShadow + animateChange: true + } + component ClockStatusText: RowLayout { + id: statusTextRow + property alias statusIcon: statusIconWidget.text + property alias statusText: statusTextWidget.text + property bool shown: true + property color textColor: root.clockStyle === "cookie" ? Appearance.colors.colOnSecondaryContainer : bgRoot.colText + opacity: shown ? 1 : 0 + visible: opacity > 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Layout.fillWidth: false + MaterialSymbol { + id: statusIconWidget + Layout.fillWidth: false + iconSize: Appearance.font.pixelSize.huge + color: statusTextRow.textColor + style: Text.Raised + styleColor: Appearance.colors.colShadow + } + ClockText { + id: statusTextWidget + Layout.fillWidth: false + color: statusTextRow.textColor + font { + family: Appearance.font.family.main + pixelSize: Appearance.font.pixelSize.large + weight: Font.Normal + } + style: Text.Raised + styleColor: Appearance.colors.colShadow + } + } } diff --git a/.config/quickshell/ii/modules/background/CookieClock.qml b/.config/quickshell/ii/modules/background/CookieClock.qml new file mode 100644 index 000000000..5a91ce649 --- /dev/null +++ b/.config/quickshell/ii/modules/background/CookieClock.qml @@ -0,0 +1,136 @@ +pragma ComponentBehavior: Bound + +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.common.functions +import QtQuick +import QtQuick.Layouts +import Quickshell +import Qt5Compat.GraphicalEffects + +Item { + id: root + + property real implicitSize: 230 + property real hourHandLength: 72 + property real hourHandWidth: 16 + property real minuteHandLength: 95 + property real minuteHandWidth: 8 + property real centerDotSize: 10 + property real hourDotSize: minuteHandWidth + property color colOnBackground: ColorUtils.mix(Appearance.colors.colPrimary, Appearance.colors.colSecondaryContainer, 0.5) + + property list clockNumbers: DateTime.time.split(/[: ]/) + property int clockHour: parseInt(clockNumbers[0]) % 12 + property int clockMinute: parseInt(clockNumbers[1]) + implicitWidth: implicitSize + implicitHeight: implicitSize + + DropShadow { + source: cookie + anchors.fill: source + horizontalOffset: 0 + verticalOffset: 2 + radius: 8 + samples: radius * 2 + 1 + color: Appearance.colors.colShadow + transparentBorder: true + } + + MaterialCookie { + id: cookie + z: 0 + implicitSize: root.implicitSize + amplitude: implicitSize / 70 + sides: 12 + color: Appearance.colors.colSecondaryContainer + + // 12 dots around the cookie + Repeater { + model: 12 + Item { + required property int index + rotation: 360 / 12 * index + anchors.fill: parent + Rectangle { + anchors { + left: parent.left + verticalCenter: parent.verticalCenter + leftMargin: 10 + } + implicitWidth: root.hourDotSize + implicitHeight: implicitWidth + radius: implicitWidth / 2 + color: root.colOnBackground + opacity: 0.5 + } + } + } + } + + Column { + id: timeIndicators + z: 1 + anchors.centerIn: cookie + spacing: -16 + + // Numbers + Repeater { + model: root.clockNumbers + delegate: StyledText { + required property string modelData + + anchors.horizontalCenter: parent.horizontalCenter + font { + pixelSize: modelData.match(/am|pm/i) ? 26 : 68 + family: Appearance.font.family.expressive + weight: Font.Bold + } + color: root.colOnBackground + text: modelData.padStart(2, "0") + } + } + } + + // Hour hand + Item { + anchors.fill: parent + z: 2 + rotation: -90 + (360 / 12) * (root.clockHour + root.clockMinute / 60) + Rectangle { + anchors.verticalCenter: parent.verticalCenter + x: parent.width / 2 - hourHandWidth / 2 + width: hourHandLength + height: hourHandWidth + radius: hourHandWidth / 2 + color: Appearance.colors.colPrimary + } + } + + // Minute hand + Item { + anchors.fill: parent + z: 3 + rotation: -90 + (360 / 60) * root.clockMinute + Rectangle { + anchors.verticalCenter: parent.verticalCenter + x: parent.width / 2 - minuteHandWidth / 2 + width: minuteHandLength + height: minuteHandWidth + radius: minuteHandWidth / 2 + color: Appearance.colors.colSecondary + } + } + + // Center dot + Rectangle { + z: 4 + color: Appearance.colors.colOnPrimary + anchors.centerIn: parent + implicitWidth: centerDotSize + implicitHeight: implicitWidth + radius: implicitWidth / 2 + } +} diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index c3233a722..05949c351 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -122,10 +122,13 @@ Singleton { } property JsonObject background: JsonObject { - property bool fixedClockPosition: false - property real clockX: -500 - property real clockY: -500 - property bool showClock: true + property JsonObject clock: JsonObject { + property bool fixedPosition: false + property real x: -500 + property real y: -500 + property bool show: true + property string style: "cookie" // Options: "cookie", "digital" + } property string wallpaperPath: "" property string thumbnailPath: "" property string quote: "" diff --git a/.config/quickshell/ii/modules/common/widgets/MaterialCookie.qml b/.config/quickshell/ii/modules/common/widgets/MaterialCookie.qml new file mode 100644 index 000000000..3f85cb294 --- /dev/null +++ b/.config/quickshell/ii/modules/common/widgets/MaterialCookie.qml @@ -0,0 +1,50 @@ +import QtQuick +import QtQuick.Shapes +import Quickshell + +Item { + id: root + + property int sides: 12 + property int implicitSize: 100 + property real amplitude: implicitSize / 50 + property int renderPoints: 360 + property color color: "#605790" + property alias strokeWidth: shapePath.strokeWidth + + implicitWidth: implicitSize + implicitHeight: implicitSize + + Shape { + id: shape + anchors.fill: parent + preferredRendererType: Shape.CurveRenderer + + ShapePath { + id: shapePath + strokeWidth: 0 + fillColor: root.color + pathHints: ShapePath.PathSolid & ShapePath.PathNonIntersecting + + PathPolyline { + property var pointsList: { + var points = [] + var cx = shape.width / 2 // center x + var cy = shape.height / 2 // center y + var steps = root.renderPoints + var radius = root.implicitSize / 2 - root.amplitude + for (var i = 0; i <= steps; i++) { + var angle = (i / steps) * 2 * Math.PI + var wave = Math.sin(angle * root.sides + Math.PI/2) * root.amplitude + var x = Math.cos(angle) * (radius + wave) + cx + var y = Math.sin(angle) * (radius + wave) + cy + points.push(Qt.point(x, y)) + } + return points + } + + path: pointsList + } + } + } +} diff --git a/.config/quickshell/ii/modules/common/widgets/StyledRectangularShadow.qml b/.config/quickshell/ii/modules/common/widgets/StyledRectangularShadow.qml index a3c842c4b..a0c9f7ba3 100644 --- a/.config/quickshell/ii/modules/common/widgets/StyledRectangularShadow.qml +++ b/.config/quickshell/ii/modules/common/widgets/StyledRectangularShadow.qml @@ -5,7 +5,7 @@ import qs.modules.common RectangularShadow { required property var target anchors.fill: target - radius: target.radius + radius: 20 blur: 0.9 * Appearance.sizes.elevationMargin offset: Qt.vector2d(0.0, 1.0) spread: 1 diff --git a/.config/quickshell/ii/modules/settings/InterfaceConfig.qml b/.config/quickshell/ii/modules/settings/InterfaceConfig.qml index 9ff65f4b6..253fff396 100644 --- a/.config/quickshell/ii/modules/settings/InterfaceConfig.qml +++ b/.config/quickshell/ii/modules/settings/InterfaceConfig.qml @@ -14,9 +14,31 @@ ContentPage { ConfigSwitch { text: Translation.tr("Show clock") - checked: Config.options.background.showClock + checked: Config.options.background.clock.show onCheckedChanged: { - Config.options.background.showClock = checked; + Config.options.background.clock.show = checked; + } + } + + ContentSubsection { + title: Translation.tr("Clock style") + ConfigSelectionArray { + currentValue: Config.options.background.clock.style + onSelected: newValue => { + Config.options.background.clock.style = newValue; + } + options: [ + { + displayName: Translation.tr("Simple digital"), + icon: "timer_10", + value: "digital" + }, + { + displayName: Translation.tr("Material cookie"), + icon: "cookie", + value: "cookie" + }, + ] } }