qs: move panels into modules/ii

This commit is contained in:
end-4
2025-11-09 08:58:56 +01:00
parent f678a55e6a
commit 53768a6885
194 changed files with 68 additions and 73 deletions
@@ -0,0 +1,298 @@
pragma ComponentBehavior: Bound
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.widgets.widgetCanvas
import qs.modules.common.functions as CF
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
import qs.modules.ii.background.widgets
import qs.modules.ii.background.widgets.clock
import qs.modules.ii.background.widgets.weather
Variants {
id: root
model: Quickshell.screens
PanelWindow {
id: bgRoot
required property var modelData
// Hide when fullscreen
property list<HyprlandWorkspace> 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
property HyprlandMonitor monitor: Hyprland.monitorFor(modelData)
property list<var> relevantWindows: HyprlandData.windowList.filter(win => win.monitor == monitor?.id && win.workspace.id >= 0).sort((a, b) => a.workspace.id - b.workspace.id)
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 string wallpaperPath: wallpaperIsVideo ? Config.options.background.thumbnailPath : Config.options.background.wallpaperPath
property bool wallpaperSafetyTriggered: {
const enabled = Config.options.workSafety.enable.wallpaper;
const sensitiveWallpaper = (CF.StringUtils.stringListContainsSubstring(wallpaperPath.toLowerCase(), Config.options.workSafety.triggerCondition.fileKeywords));
const sensitiveNetwork = (CF.StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords));
return enabled && sensitiveWallpaper && sensitiveNetwork;
}
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
property int wallpaperWidth: modelData.width // Some reasonable init value, to be updated
property int wallpaperHeight: modelData.height // Some reasonable init value, to be updated
property real movableXSpace: ((wallpaperWidth / wallpaperToScreenRatio * effectiveWallpaperScale) - screen.width) / 2
property real movableYSpace: ((wallpaperHeight / wallpaperToScreenRatio * effectiveWallpaperScale) - screen.height) / 2
readonly property bool verticalParallax: (Config.options.background.parallax.autoVertical && wallpaperHeight > wallpaperWidth) || Config.options.background.parallax.vertical
// Colors
property bool shouldBlur: (GlobalStates.screenLocked && Config.options.lock.blur.enable)
property color dominantColor: Appearance.colors.colPrimary // Default, to be changed
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));
}
Behavior on colText {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
// Layer props
screen: modelData
exclusionMode: ExclusionMode.Ignore
WlrLayershell.layer: (GlobalStates.screenLocked && !scaleAnim.running) ? WlrLayer.Overlay : WlrLayer.Bottom
// WlrLayershell.layer: WlrLayer.Bottom
WlrLayershell.namespace: "quickshell:background"
anchors {
top: true
bottom: true
left: true
right: true
}
color: {
if (!bgRoot.wallpaperSafetyTriggered || bgRoot.wallpaperIsVideo)
return "transparent";
return CF.ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colPrimary, 0.75);
}
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
onWallpaperPathChanged: {
bgRoot.updateZoomScale();
// Clock position gets updated after zoom scale is updated
}
// Wallpaper zoom scale
function updateZoomScale() {
getWallpaperSizeProc.path = bgRoot.wallpaperPath;
getWallpaperSizeProc.running = true;
}
Process {
id: getWallpaperSizeProc
property string path: bgRoot.wallpaperPath
command: ["magick", "identify", "-format", "%w %h", path]
stdout: StdioCollector {
id: wallpaperSizeOutputCollector
onStreamFinished: {
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;
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);
}
}
}
}
Item {
anchors.fill: parent
clip: true
// Wallpaper
StyledImage {
id: wallpaper
visible: opacity > 0 && !blurLoader.active
opacity: (status === Image.Ready && !bgRoot.wallpaperIsVideo) ? 1 : 0
cache: false
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
}
Loader {
id: blurLoader
active: Config.options.lock.blur.enable && (GlobalStates.screenLocked || scaleAnim.running)
anchors.fill: wallpaper
scale: GlobalStates.screenLocked ? Config.options.lock.blur.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.lock.blur.radius : 0
samples: radius * 2 + 1
Rectangle {
opacity: GlobalStates.screenLocked ? 1 : 0
anchors.fill: parent
color: CF.ColorUtils.transparentize(Appearance.colors.colLayer0, 0.7)
}
}
}
WidgetCanvas {
id: widgetCanvas
anchors {
left: wallpaper.left
right: wallpaper.right
top: wallpaper.top
bottom: wallpaper.bottom
horizontalCenter: undefined
verticalCenter: undefined
readonly property real parallaxFactor: Config.options.background.parallax.widgetsFactor
leftMargin: {
const xOnWallpaper = bgRoot.movableXSpace;
const extraMove = (wallpaper.effectiveValueX * 2 * bgRoot.movableXSpace) * (parallaxFactor - 1);
return xOnWallpaper - extraMove;
}
topMargin: {
const yOnWallpaper = bgRoot.movableYSpace;
const extraMove = (wallpaper.effectiveValueY * 2 * bgRoot.movableYSpace) * (parallaxFactor - 1);
return yOnWallpaper - extraMove;
}
Behavior on leftMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on topMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
}
width: wallpaper.width
height: wallpaper.height
states: State {
name: "centered"
when: GlobalStates.screenLocked || bgRoot.wallpaperSafetyTriggered
PropertyChanges {
target: widgetCanvas
width: parent.width
height: parent.height
}
AnchorChanges {
target: widgetCanvas
anchors {
left: undefined
right: undefined
top: undefined
bottom: undefined
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
}
}
transitions: Transition {
PropertyAnimation {
properties: "width,height"
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
AnchorAnimation {
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
}
FadeLoader {
shown: Config.options.background.widgets.weather.enable
sourceComponent: WeatherWidget {
screenWidth: bgRoot.screen.width
screenHeight: bgRoot.screen.height
scaledScreenWidth: bgRoot.screen.width / bgRoot.effectiveWallpaperScale
scaledScreenHeight: bgRoot.screen.height / bgRoot.effectiveWallpaperScale
wallpaperScale: bgRoot.effectiveWallpaperScale
}
}
FadeLoader {
shown: Config.options.background.widgets.clock.enable
sourceComponent: ClockWidget {
screenWidth: bgRoot.screen.width
screenHeight: bgRoot.screen.height
scaledScreenWidth: bgRoot.screen.width / bgRoot.effectiveWallpaperScale
scaledScreenHeight: bgRoot.screen.height / bgRoot.effectiveWallpaperScale
wallpaperScale: bgRoot.effectiveWallpaperScale
wallpaperSafetyTriggered: bgRoot.wallpaperSafetyTriggered
}
}
}
}
}
}
@@ -0,0 +1,101 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets.widgetCanvas
AbstractWidget {
id: root
required property string configEntryName
required property int screenWidth
required property int screenHeight
required property int scaledScreenWidth
required property int scaledScreenHeight
required property real wallpaperScale
property bool visibleWhenLocked: false
property var configEntry: Config.options.background.widgets[configEntryName]
property string placementStrategy: configEntry.placementStrategy
property real targetX: Math.max(0, Math.min(configEntry.x, scaledScreenWidth - width))
property real targetY : Math.max(0, Math.min(configEntry.y, scaledScreenHeight - height))
x: targetX
y: targetY
visible: opacity > 0
opacity: (GlobalStates.screenLocked && !visibleWhenLocked) ? 0 : 1
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
scale: (draggable && containsPress) ? 1.05 : 1
Behavior on scale {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
draggable: placementStrategy === "free"
onReleased: {
root.targetX = root.x;
root.targetY = root.y;
configEntry.x = root.targetX;
configEntry.y = root.targetY;
}
property bool needsColText: false
property color dominantColor: Appearance.colors.colPrimary
property bool dominantColorIsDark: dominantColor.hslLightness < 0.5
property color colText: {
const onNormalBackground = (GlobalStates.screenLocked && Config.options.lock.blur.enable)
const adaptiveColor = ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (dominantColorIsDark ? 0.8 : 0.12))
return onNormalBackground ? Appearance.colors.colOnLayer0 : adaptiveColor;
}
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
onWallpaperPathChanged: refreshPlacementIfNeeded()
onPlacementStrategyChanged: refreshPlacementIfNeeded()
Connections {
target: Config
function onReadyChanged() { refreshPlacementIfNeeded() }
}
function refreshPlacementIfNeeded() {
if (!Config.ready || (root.placementStrategy === "free" && root.needsColText)) return;
leastBusyRegionProc.wallpaperPath = root.wallpaperPath;
leastBusyRegionProc.running = false;
leastBusyRegionProc.running = true;
}
Process {
id: leastBusyRegionProc
property string wallpaperPath: root.wallpaperPath
// TODO: make these less arbitrary
property int contentWidth: 300
property int contentHeight: 300
property int horizontalPadding: 200
property int verticalPadding: 200
command: [Quickshell.shellPath("scripts/images/least-busy-region-venv.sh") // Comments to force the formatter to break lines
, "--screen-width", Math.round(root.scaledScreenWidth) //
, "--screen-height", Math.round(root.scaledScreenHeight) //
, "--width", contentWidth //
, "--height", contentHeight //
, "--horizontal-padding", horizontalPadding //
, "--vertical-padding", verticalPadding //
, wallpaperPath //
, ...(root.placementStrategy === "mostBusy" ? ["--busiest"] : [])
// "--visual-output",
]
stdout: StdioCollector {
id: leastBusyRegionOutputCollector
onStreamFinished: {
const output = leastBusyRegionOutputCollector.text;
// console.log("[Background] Least busy region output:", output)
if (output.length === 0) return;
const parsedContent = JSON.parse(output);
root.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary;
if (root.placementStrategy === "free") return;
root.targetX = parsedContent.center_x * root.wallpaperScale - root.width / 2;
root.targetY = parsedContent.center_y * root.wallpaperScale - root.height / 2;
}
}
}
}
@@ -0,0 +1,195 @@
import QtQuick
import QtQuick.Layouts
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.common.widgets.widgetCanvas
import qs.modules.ii.background.widgets
AbstractBackgroundWidget {
id: root
configEntryName: "clock"
implicitHeight: contentColumn.implicitHeight
implicitWidth: contentColumn.implicitWidth
property string clockStyle: Config.options.background.widgets.clock.style
property bool forceCenter: (GlobalStates.screenLocked && Config.options.lock.centerClock)
property bool wallpaperSafetyTriggered: false
needsColText: clockStyle === "digital"
x: forceCenter ? ((root.screenWidth - root.width) / 2) : targetX
y: forceCenter ? ((root.screenHeight - root.height) / 2) : targetY
visibleWhenLocked: true
property var textHorizontalAlignment: {
if (root.forceCenter)
return Text.AlignHCenter;
if (root.x < root.scaledScreenWidth / 3)
return Text.AlignLeft;
if (root.x > root.scaledScreenWidth * 2 / 3)
return Text.AlignRight;
return Text.AlignHCenter;
}
Column {
id: contentColumn
anchors.centerIn: parent
spacing: 6
FadeLoader {
id: cookieClockLoader
anchors.horizontalCenter: parent.horizontalCenter
shown: root.clockStyle === "cookie"
sourceComponent: Column {
CookieClock {
anchors.horizontalCenter: parent.horizontalCenter
}
FadeLoader {
anchors.horizontalCenter: parent.horizontalCenter
shown: Config.options.background.widgets.clock.quote.enable && Config.options.background.widgets.clock.quote.text !== ""
sourceComponent: CookieQuote {}
}
}
}
FadeLoader {
id: digitalClockLoader
anchors.horizontalCenter: parent.horizontalCenter
shown: root.clockStyle === "digital"
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.widgets.clock.quote.enable && Config.options.background.widgets.clock.quote.text.length > 0
Layout.fillWidth: true
horizontalAlignment: root.textHorizontalAlignment
font {
pixelSize: Appearance.font.pixelSize.normal
weight: 350
}
color: root.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
text: Config.options.background.widgets.clock.quote.text
}
}
}
Item {
id: statusText
anchors.horizontalCenter: parent.horizontalCenter
implicitHeight: statusTextBg.implicitHeight
implicitWidth: statusTextBg.implicitWidth
StyledRectangularShadow {
target: statusTextBg
visible: statusTextBg.visible && root.clockStyle === "cookie"
opacity: statusTextBg.opacity
}
Rectangle {
id: statusTextBg
anchors.centerIn: parent
clip: true
opacity: (safetyStatusText.shown || lockStatusText.shown) ? 1 : 0
visible: opacity > 0
implicitHeight: statusTextRow.implicitHeight + 5 * 2
implicitWidth: statusTextRow.implicitWidth + 5 * 2
radius: Appearance.rounding.small
color: ColorUtils.transparentize(Appearance.colors.colSecondaryContainer, root.clockStyle === "cookie" ? 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: 14
Item {
Layout.fillWidth: root.textHorizontalAlignment !== Text.AlignLeft
implicitWidth: 1
}
ClockStatusText {
id: safetyStatusText
shown: root.wallpaperSafetyTriggered
statusIcon: "hide_image"
statusText: Translation.tr("Wallpaper safety enforced")
}
ClockStatusText {
id: lockStatusText
shown: GlobalStates.screenLocked && Config.options.lock.showLockedText
statusIcon: "lock"
statusText: Translation.tr("Locked")
}
Item {
Layout.fillWidth: root.textHorizontalAlignment !== Text.AlignRight
implicitWidth: 1
}
}
}
}
}
component ClockText: StyledText {
Layout.fillWidth: true
horizontalAlignment: root.textHorizontalAlignment
font {
family: Appearance.font.family.expressive
pixelSize: 20
weight: Font.DemiBold
}
color: root.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
animateChange: Config.options.background.widgets.clock.digital.animateChange
}
component ClockStatusText: Row {
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 : root.colText
opacity: shown ? 1 : 0
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
spacing: 4
MaterialSymbol {
id: statusIconWidget
anchors.verticalCenter: statusTextRow.verticalCenter
iconSize: Appearance.font.pixelSize.huge
color: statusTextRow.textColor
style: Text.Raised
styleColor: Appearance.colors.colShadow
}
ClockText {
id: statusTextWidget
color: statusTextRow.textColor
anchors.verticalCenter: statusTextRow.verticalCenter
font {
pixelSize: Appearance.font.pixelSize.large
weight: Font.Normal
}
style: Text.Raised
styleColor: Appearance.colors.colShadow
}
}
}
@@ -0,0 +1,218 @@
pragma ComponentBehavior: Bound
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell.Io
import qs.modules.ii.background.widgets.clock.dateIndicator
import qs.modules.ii.background.widgets.clock.minuteMarks
Item {
id: root
readonly property string clockStyle: Config.options.background.widgets.clock.style
property real implicitSize: 230
property color colShadow: Appearance.colors.colShadow
property color colBackground: Appearance.colors.colPrimaryContainer
property color colOnBackground: ColorUtils.mix(Appearance.colors.colSecondary, Appearance.colors.colPrimaryContainer, 0.15)
property color colBackgroundInfo: ColorUtils.mix(Appearance.colors.colPrimary, Appearance.colors.colPrimaryContainer, 0.55)
property color colHourHand: Appearance.colors.colPrimary
property color colMinuteHand: Appearance.colors.colTertiary
property color colSecondHand: Appearance.colors.colPrimary
readonly property list<string> clockNumbers: DateTime.time.split(/[: ]/)
readonly property int clockHour: parseInt(clockNumbers[0]) % 12
readonly property int clockMinute: DateTime.clock.minutes
readonly property int clockSecond: DateTime.clock.seconds
implicitWidth: implicitSize
implicitHeight: implicitSize
function applyStyle(sides, dialStyle, hourHandStyle, minuteHandStyle, secondHandStyle, dateStyle) {
Config.options.background.widgets.clock.cookie.sides = sides
Config.options.background.widgets.clock.cookie.dialNumberStyle = dialStyle
Config.options.background.widgets.clock.cookie.hourHandStyle = hourHandStyle
Config.options.background.widgets.clock.cookie.minuteHandStyle = minuteHandStyle
Config.options.background.widgets.clock.cookie.secondHandStyle = secondHandStyle
Config.options.background.widgets.clock.cookie.dateStyle = dateStyle
}
function setClockPreset(category) {
if (!Config.options.background.widgets.clock.cookie.aiStyling) return;
if (category === "") return;
print("[Cookie clock] Setting clock preset for category: " + category)
// "abstract", "anime", "city", "minimalist", "landscape", "plants", "person", "space"
if (category == "abstract") {
applyStyle(9, "none", "fill", "medium", "dot", "bubble")
} else if (category == "anime") {
applyStyle(7, "none", "fill", "bold", "dot", "bubble")
} else if (category == "city" || category == "space") {
applyStyle(23, "full", "hollow", "thin", "classic", "bubble")
} else if (category == "minimalist") {
applyStyle(6, "none", "fill", "bold", "dot", "hide")
} else if (category == "landscape") {
applyStyle(14, "full", "hollow", "medium", "classic", "bubble")
} else if (category == "plants") {
applyStyle(9, "dots", "fill", "bold", "dot", "border")
} else if (category == "person") {
applyStyle(14, "full", "classic", "classic", "classic", "rect")
}
}
Connections {
target: Config
function onReadyChanged() {
categoryFileView.path = Directories.generatedWallpaperCategoryPath
}
}
FileView {
id: categoryFileView
path: ""
watchChanges: true
onFileChanged: reload()
onLoaded: {
root.setClockPreset(categoryFileView.text().trim())
}
}
property bool useSineCookie: Config.options.background.widgets.clock.cookie.useSineCookie
StyledDropShadow {
target: useSineCookie ? sineCookieLoader : roundedPolygonCookieLoader
RotationAnimation on rotation {
running: Config.options.background.widgets.clock.cookie.constantlyRotate
duration: 30000
easing.type: Easing.Linear
loops: Animation.Infinite
from: 360
to: 0
}
}
Loader {
id: sineCookieLoader
z: 0
visible: false // The DropShadow already draws it
active: useSineCookie
sourceComponent: SineCookie {
implicitSize: root.implicitSize
sides: Config.options.background.widgets.clock.cookie.sides
color: root.colBackground
}
}
Loader {
id: roundedPolygonCookieLoader
z: 0
visible: false // The DropShadow already draws it
active: !useSineCookie
sourceComponent: MaterialCookie {
implicitSize: root.implicitSize
sides: Config.options.background.widgets.clock.cookie.sides
color: root.colBackground
}
}
// Hour/minutes numbers/dots/lines
MinuteMarks {
anchors.fill: parent
color: root.colOnBackground
}
// Stupid extra hour marks in the middle
FadeLoader {
id: hourMarksLoader
anchors.centerIn: parent
shown: Config.options.background.widgets.clock.cookie.hourMarks
sourceComponent: HourMarks {
implicitSize: 135 * (1.75 - 0.75 * hourMarksLoader.opacity)
color: root.colOnBackground
colOnBackground: ColorUtils.mix(root.colBackgroundInfo, root.colOnBackground, 0.5)
}
}
// Number column in the middle
FadeLoader {
id: timeColumnLoader
anchors.centerIn: parent
shown: Config.options.background.widgets.clock.cookie.timeIndicators
scale: 1.4 - 0.4 * timeColumnLoader.shown
Behavior on scale {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
sourceComponent: TimeColumn {
color: root.colBackgroundInfo
}
}
// Minute hand
FadeLoader {
anchors.fill: parent
z: 1
shown: Config.options.background.widgets.clock.cookie.minuteHandStyle !== "hide"
sourceComponent: MinuteHand {
anchors.fill: parent
clockMinute: root.clockMinute
style: Config.options.background.widgets.clock.cookie.minuteHandStyle
color: root.colMinuteHand
}
}
// Hour hand
FadeLoader {
anchors.fill: parent
z: item?.style === "hollow" ? 0 : 2
shown: Config.options.background.widgets.clock.cookie.hourHandStyle !== "hide"
sourceComponent: HourHand {
clockHour: root.clockHour
clockMinute: root.clockMinute
style: Config.options.background.widgets.clock.cookie.hourHandStyle
color: root.colHourHand
}
}
// Second hand
FadeLoader {
id: secondHandLoader
z: (Config.options.background.widgets.clock.cookie.secondHandStyle === "line") ? 2 : 3
shown: Config.options.time.secondPrecision && Config.options.background.widgets.clock.cookie.secondHandStyle !== "hide"
anchors.fill: parent
sourceComponent: SecondHand {
id: secondHand
clockSecond: root.clockSecond
style: Config.options.background.widgets.clock.cookie.secondHandStyle
color: root.colSecondHand
}
}
// Center dot
FadeLoader {
z: 4
anchors.centerIn: parent
shown: Config.options.background.widgets.clock.cookie.minuteHandStyle !== "bold"
sourceComponent: Rectangle {
color: Config.options.background.widgets.clock.cookie.minuteHandStyle === "medium" ? root.colBackground : root.colMinuteHand
implicitWidth: 6
implicitHeight: implicitWidth
radius: width / 2
}
}
// Date
FadeLoader {
anchors.fill: parent
shown: Config.options.background.widgets.clock.cookie.dateStyle !== "hide"
sourceComponent: DateIndicator {
color: root.colBackgroundInfo
style: Config.options.background.widgets.clock.cookie.dateStyle
}
}
}
@@ -0,0 +1,60 @@
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import Qt5Compat.GraphicalEffects
Item {
id: root
readonly property string quoteText: Config.options.background.widgets.clock.quote.text
implicitWidth: quoteBox.implicitWidth
implicitHeight: quoteBox.implicitHeight
anchors.bottom: parent.bottom
anchors.bottomMargin: -24
DropShadow {
source: quoteBox
anchors.fill: quoteBox
horizontalOffset: 0
verticalOffset: 2
radius: 12
samples: radius * 2 + 1
color: Appearance.colors.colShadow
transparentBorder: true
}
Rectangle {
id: quoteBox
implicitWidth: quoteStyledText.width + quoteIcon.width + 16 // for spacing on both sides
implicitHeight: quoteStyledText.height + 8
radius: Appearance.rounding.small
color: Appearance.colors.colSecondaryContainer
Row {
anchors.centerIn: parent
spacing: 4
MaterialSymbol {
id: quoteIcon
anchors.top: parent.top
iconSize: Appearance.font.pixelSize.huge
text: "format_quote"
color: Appearance.colors.colOnSecondaryContainer
}
StyledText {
id: quoteStyledText
horizontalAlignment: Text.AlignLeft
text: Config.options.background.widgets.clock.quote.text
color: Appearance.colors.colOnSecondaryContainer
font {
family: Appearance.font.family.reading
pixelSize: Appearance.font.pixelSize.large
weight: Font.Normal
}
}
}
}
}
@@ -0,0 +1,45 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import QtQuick
Item {
id: root
required property int clockHour
required property int clockMinute
property real handLength: 72
property real handWidth: 20
property string style: "fill"
property color color: Appearance.colors.colPrimary
property real fillColorAlpha: root.style === "hollow" ? 0 : 1
Behavior on fillColorAlpha {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
rotation: -90 + (360 / 12) * (root.clockHour + root.clockMinute / 60)
Behavior on rotation {
animation: RotationAnimation {
direction: RotationAnimation.Clockwise
duration: 300
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.emphasized
}
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
x: (parent.width - root.handWidth) / 2 - 15 * (root.style === "classic")
width: root.handLength
height: root.style === "classic" ? 8 : root.handWidth
radius: root.style === "classic" ? 2 : root.handWidth / 2
color : Qt.rgba(root.color.r, root.color.g, root.color.b, root.fillColorAlpha)
border.color: root.color
border.width: 4
Behavior on x {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
}
}
@@ -0,0 +1,50 @@
pragma ComponentBehavior: Bound
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
Item {
id: root
property real implicitSize: 135
property real markLength: 12
property real markWidth: 4
property color color: Appearance.colors.colOnSecondaryContainer
property color colOnBackground: Appearance.colors.colSecondaryContainer
property real padding: 8
Rectangle {
color: root.color
anchors.centerIn: parent
implicitWidth: root.implicitSize
implicitHeight: root.implicitSize
radius: width / 2
// Hour mark lines
Repeater {
model: 12
Item {
required property int index
anchors.fill: parent
rotation: 360 / 12 * index
Rectangle {
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
leftMargin: root.padding
}
implicitWidth: root.markLength
implicitHeight: root.markWidth
radius: width / 2
color: root.colOnBackground
}
}
}
}
}
@@ -0,0 +1,47 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import QtQuick
Item {
id: root
anchors.fill: parent
required property int clockMinute
property string style: "medium"
property real handLength: 95
property real handWidth: style === "bold" ? 20 : style === "medium" ? 12 : 5
property color color: Appearance.colors.colTertiary
rotation: -90 + (360 / 60) * root.clockMinute
Behavior on rotation {
animation: RotationAnimation {
direction: RotationAnimation.Clockwise
duration: 300
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.emphasized
}
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
x: {
let position = parent.width / 2 - root.handWidth / 2;
if (root.style === "classic") position -= 15;
return position;
}
width: root.handLength
height: root.handWidth
radius: root.style === "classic" ? 2 : root.handWidth / 2
color: root.color
Behavior on height {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
Behavior on x {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
}
}
@@ -0,0 +1,71 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
Item {
id: root
anchors.fill: parent
required property int clockSecond
property real handWidth: 2
property real handLength: 95
property real dotSize: 20
property string style: "hide"
property color color: Appearance.colors.colSecondary
rotation: (360 / 60 * clockSecond) + 90
Behavior on rotation {
enabled: Config.options.background.widgets.clock.cookie.constantlyRotate // Animating every second is expensive...
animation: RotationAnimation {
direction: RotationAnimation.Clockwise
duration: 1000 // 1 second
easing.type: Easing.InOutQuad
}
}
Rectangle {
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
leftMargin: 10 + (root.style === "dot" ? root.dotSize : 0)
}
implicitWidth: root.style === "dot" ? root.dotSize : root.handLength
implicitHeight: root.style === "dot" ? root.dotSize : root.handWidth
radius: Math.min(width, height) / 2
color: root.color
Behavior on implicitHeight {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
Behavior on implicitWidth {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
}
// Classic style dot in the middle of the hand
FadeLoader {
id: classicDotLoader
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
}
shown: root.style === "classic"
Rectangle {
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
leftMargin: 40
}
implicitWidth: root.style === "classic" ? 14 : 0
implicitHeight: implicitWidth
color: root.color
radius: Appearance.rounding.small
Behavior on implicitWidth {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
}
}
}
@@ -0,0 +1,41 @@
pragma ComponentBehavior: Bound
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
Column {
id: root
property list<string> clockNumbers: DateTime.time.split(/[: ]/)
property bool isEnabled: Config.options.background.widgets.clock.cookie.timeIndicators
property color color: Appearance.colors.colOnSecondaryContainer
property bool hourMarksEnabled: Config.options.background.widgets.clock.cookie.hourMarks
spacing: -16
Repeater {
model: root.clockNumbers
delegate: StyledText {
required property string modelData
text: modelData.padStart(2, "0")
property bool isAmPm: !text.match(/\d{2}/i)
property real numberSizeWithoutGlow: isAmPm ? 26 : 68
property real numberSizeWithGlow: isAmPm ? 20 : 40
property real numberSize: root.hourMarksEnabled ? numberSizeWithGlow : numberSizeWithoutGlow
anchors.horizontalCenter: root.horizontalCenter
color: root.color
font {
family: Appearance.font.family.expressive
weight: Font.Bold
pixelSize: numberSize
}
Behavior on numberSize {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
}
}
}
@@ -0,0 +1,37 @@
pragma ComponentBehavior: Bound
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
Item {
id: root
property bool isMonth: false
property real targetSize: 0
property alias text: bubbleText.text
text: Qt.locale().toString(DateTime.clock.date, root.isMonth ? "MM" : "d")
MaterialShape {
id: bubble
z: 5
// sides: root.isMonth ? 1 : 4
shape: root.isMonth ? MaterialShape.Shape.Pill : MaterialShape.Shape.Pentagon
anchors.centerIn: parent
color: root.isMonth ? Appearance.colors.colSecondaryContainer : Appearance.colors.colTertiaryContainer
implicitSize: targetSize
}
StyledText {
id: bubbleText
z: 6
anchors.centerIn: parent
color: root.isMonth ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnTertiaryContainer
font {
family: Appearance.font.family.expressive
pixelSize: 30
weight: Font.Black
}
}
}
@@ -0,0 +1,76 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
Item {
id: root
property string style: "bubble"
property color color: Appearance.colors.colOnSecondaryContainer
property real dateSquareSize: 64
// Rotating date
FadeLoader {
anchors.fill: parent
shown: Config.options.background.widgets.clock.cookie.dateStyle === "border"
sourceComponent: RotatingDate {
color: root.color
}
}
// Rectangle date (only today's number) in right side of the clock
FadeLoader {
id: rectLoader
shown: root.style === "rect"
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: 40 - rectLoader.opacity * 30
}
sourceComponent: RectangleDate {
color: ColorUtils.mix(root.color, Appearance.colors.colSecondaryContainerHover, 0.5)
radius: Appearance.rounding.small
implicitWidth: 45 * rectLoader.opacity
implicitHeight: 30 * rectLoader.opacity
}
}
// Bubble style: day of month
FadeLoader {
id: dayBubbleLoader
shown: root.style === "bubble"
property real targetSize: root.dateSquareSize * opacity
anchors {
left: parent.left
top: parent.top
}
sourceComponent: BubbleDate {
implicitWidth: dayBubbleLoader.targetSize
implicitHeight: dayBubbleLoader.targetSize
isMonth: false
targetSize: dayBubbleLoader.targetSize
}
}
// Bubble style: month
FadeLoader {
id: monthBubbleLoader
shown: root.style === "bubble"
property real targetSize: root.dateSquareSize * opacity
anchors {
right: parent.right
bottom: parent.bottom
}
sourceComponent: BubbleDate {
implicitWidth: monthBubbleLoader.targetSize
implicitHeight: monthBubbleLoader.targetSize
isMonth: true
targetSize: monthBubbleLoader.targetSize
}
}
}
@@ -0,0 +1,21 @@
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
Rectangle {
id: rect
readonly property string dialStyle: Config.options.background.widgets.clock.cookie.dialNumberStyle
StyledText {
anchors.centerIn: parent
color: Appearance.colors.colSecondaryHover
text: Qt.locale().toString(DateTime.clock.date, "dd")
font {
family: Appearance.font.family.expressive
pixelSize: 20
weight: 1000
}
}
}
@@ -0,0 +1,50 @@
pragma ComponentBehavior: Bound
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
Item {
id: root
property string style: Config.options.background.widgets.clock.cookie.dateStyle
property color color: Appearance.colors.colOnSecondaryContainer
property real angleStep: 12 * Math.PI / 180
property string dateText: Qt.locale().toString(DateTime.clock.date, "ddd dd")
readonly property int clockSecond: DateTime.clock.seconds
readonly property string dialStyle: Config.options.background.widgets.clock.cookie.dialNumberStyle
readonly property bool timeIndicators: Config.options.background.widgets.clock.cookie.timeIndicators
property real radius: style === "border" ? 90 : 0
Behavior on radius {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
rotation: {
if (!Config.options.time.secondPrecision) return 0
else return (360 / 60 * clockSecond) + 180 - (angleStep / Math.PI * 180 * dateText.length) / 2
}
Repeater {
model: root.dateText.length
delegate: Text {
required property int index
property real angle: index * root.angleStep - Math.PI / 2
x: root.width / 2 + root.radius * Math.cos(angle) - width / 2
y: root.height / 2 + root.radius * Math.sin(angle) - height / 2
rotation: angle * 180 / Math.PI + 90
color: root.color
font {
family: Appearance.font.family.title
pixelSize: 30
variableAxes: Appearance.font.variableAxes.title
}
text: root.dateText.charAt(index)
}
}
}
@@ -0,0 +1,49 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
Item {
id: root
property real numberSize: 80
property real margins: 10
property color color: Appearance.colors.colOnSecondaryContainer
property int hours: 12
property int numbers: 4
property int fontSize: 80
Repeater {
model: root.numbers
Item {
id: numberItem
required property int index
rotation: 360 / root.numbers * (index + 1)
anchors.fill: parent
Item {
implicitWidth: root.numberSize
implicitHeight: implicitWidth
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
topMargin: root.margins
}
StyledText {
color: root.color
anchors.centerIn: parent
text: root.hours / root.numbers * (numberItem.index + 1)
rotation: -numberItem.rotation
font {
family: Appearance.font.family.reading
pixelSize: root.fontSize
weight: Font.Black
}
}
}
}
}
}
@@ -0,0 +1,34 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
Item {
id: root
property real implicitSize: 12
property real margins: 10
property color color: Appearance.colors.colOnSecondaryContainer
Repeater {
model: 12
Item {
required property int index
anchors.fill: parent // Ensures rotation works properly
rotation: 360 / 12 * index
Rectangle {
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
leftMargin: root.margins
}
implicitWidth: root.implicitSize
implicitHeight: implicitWidth
radius: implicitWidth / 2
color: root.color
}
}
}
}
@@ -0,0 +1,66 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
Item {
id: root
property real numberSize: 80
property real margins: 10
property color color: Appearance.colors.colOnSecondaryContainer
property real hourLineSize: 4
property real minuteLineSize: 2
property real hourLineLength: 18
property real minuteLineLength: 7
property int hours: 12
property int minutes: 60
// Full dial style hour lines
Repeater {
model: root.hours
Item {
required property int index
rotation: 360 / root.hours * index
anchors.fill: parent
Rectangle {
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
leftMargin: root.margins
}
implicitWidth: root.hourLineLength
implicitHeight: root.hourLineSize
radius: implicitWidth / 2
color: root.color
}
}
}
// Minute lines
Repeater {
model: root.minutes
Item {
required property int index
rotation: 360 / root.minutes * index
anchors.fill: parent
Rectangle {
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
leftMargin: root.margins
}
implicitWidth: root.minuteLineLength
implicitHeight: root.minuteLineSize
radius: implicitWidth / 2
color: root.color
}
}
}
}
@@ -0,0 +1,54 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
Item {
id: root
property color color: Appearance.colors.colOnSecondaryContainer
property string style: Config.options.background.widgets.clock.cookie.dialNumberStyle // "dots", "numbers", "full", "hide"
property string dateStyle : Config.options.background.widgets.clock.cookie.dateStyle
// 12 Dots
FadeLoader {
id: dotsLoader
anchors {
fill: parent
margins: 10
}
shown: root.style === "dots"
sourceComponent: Dots {
color: root.color
margins: 46 - dotsLoader.opacity * 34
}
}
// 3-6-9-12 hour numbers (pls don't realize you can have more than 4 numbers)
FadeLoader {
id: bigHourNumbersLoader
anchors.fill: parent
shown: root.style === "numbers"
sourceComponent: BigHourNumbers {
numberSize: 80
color: root.color
margins: 20 - 10 * bigHourNumbersLoader.opacity
}
}
// Lines
FadeLoader {
id: linesLoader
anchors {
fill: parent
margins: 10
}
shown: root.style === "full"
sourceComponent: Lines {
color: root.color
margins: 46 - linesLoader.opacity * 34
}
}
}
@@ -0,0 +1,58 @@
import QtQuick
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.common.widgets.widgetCanvas
import qs.modules.ii.background.widgets
AbstractBackgroundWidget {
id: root
configEntryName: "weather"
implicitHeight: backgroundShape.implicitHeight
implicitWidth: backgroundShape.implicitWidth
StyledDropShadow {
target: backgroundShape
}
MaterialShape {
id: backgroundShape
anchors.fill: parent
shape: MaterialShape.Shape.Pill
color: Appearance.colors.colPrimaryContainer
implicitSize: 200
StyledText {
font {
pixelSize: 80
family: Appearance.font.family.expressive
weight: Font.Medium
}
color: Appearance.colors.colPrimary
text: Weather.data?.temp.substring(0,Weather.data?.temp.length - 1) ?? "--°"
anchors {
right: parent.right
top: parent.top
rightMargin: 16
topMargin: 20
}
}
MaterialSymbol {
iconSize: 80
color: Appearance.colors.colOnPrimaryContainer
text: Icons.getWeatherIcon(Weather.data.wCode) ?? "cloud"
anchors {
left: parent.left
bottom: parent.bottom
leftMargin: 16
bottomMargin: 20
}
}
}
}