Files
illogical-impulse/dots/.config/quickshell/ii/modules/background/Background.qml
T

299 lines
15 KiB
QML

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.background.widgets
import qs.modules.background.widgets.clock
import qs.modules.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
}
}
}
}
}
}