mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
More wallpaper effects + per-monitor auto cycling
This commit is contained in:
@@ -43,6 +43,7 @@ Singleton {
|
||||
property string wallpaperCyclingMode: "interval" // "interval" or "time"
|
||||
property int wallpaperCyclingInterval: 300 // seconds (5 minutes)
|
||||
property string wallpaperCyclingTime: "06:00" // HH:mm format
|
||||
property var monitorCyclingSettings: ({})
|
||||
property string lastBrightnessDevice: ""
|
||||
property string launchPrefix: ""
|
||||
property string wallpaperTransition: "fade"
|
||||
@@ -113,6 +114,7 @@ Singleton {
|
||||
wallpaperCyclingMode = settings.wallpaperCyclingMode !== undefined ? settings.wallpaperCyclingMode : "interval"
|
||||
wallpaperCyclingInterval = settings.wallpaperCyclingInterval !== undefined ? settings.wallpaperCyclingInterval : 300
|
||||
wallpaperCyclingTime = settings.wallpaperCyclingTime !== undefined ? settings.wallpaperCyclingTime : "06:00"
|
||||
monitorCyclingSettings = settings.monitorCyclingSettings !== undefined ? settings.monitorCyclingSettings : {}
|
||||
lastBrightnessDevice = settings.lastBrightnessDevice !== undefined ? settings.lastBrightnessDevice : ""
|
||||
launchPrefix = settings.launchPrefix !== undefined ? settings.launchPrefix : ""
|
||||
wallpaperTransition = settings.wallpaperTransition !== undefined ? settings.wallpaperTransition : "fade"
|
||||
@@ -166,6 +168,7 @@ Singleton {
|
||||
"wallpaperCyclingMode": wallpaperCyclingMode,
|
||||
"wallpaperCyclingInterval": wallpaperCyclingInterval,
|
||||
"wallpaperCyclingTime": wallpaperCyclingTime,
|
||||
"monitorCyclingSettings": monitorCyclingSettings,
|
||||
"lastBrightnessDevice": lastBrightnessDevice,
|
||||
"launchPrefix": launchPrefix,
|
||||
"wallpaperTransition": wallpaperTransition,
|
||||
@@ -367,18 +370,64 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function getMonitorCyclingSettings(screenName) {
|
||||
return monitorCyclingSettings[screenName] || {
|
||||
enabled: false,
|
||||
mode: "interval",
|
||||
interval: 300,
|
||||
time: "06:00"
|
||||
}
|
||||
}
|
||||
|
||||
function setMonitorCyclingEnabled(screenName, enabled) {
|
||||
var newSettings = Object.assign({}, monitorCyclingSettings)
|
||||
if (!newSettings[screenName]) {
|
||||
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
|
||||
}
|
||||
newSettings[screenName].enabled = enabled
|
||||
monitorCyclingSettings = newSettings
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setMonitorCyclingMode(screenName, mode) {
|
||||
var newSettings = Object.assign({}, monitorCyclingSettings)
|
||||
if (!newSettings[screenName]) {
|
||||
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
|
||||
}
|
||||
newSettings[screenName].mode = mode
|
||||
monitorCyclingSettings = newSettings
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setMonitorCyclingInterval(screenName, interval) {
|
||||
var newSettings = Object.assign({}, monitorCyclingSettings)
|
||||
if (!newSettings[screenName]) {
|
||||
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
|
||||
}
|
||||
newSettings[screenName].interval = interval
|
||||
monitorCyclingSettings = newSettings
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setMonitorCyclingTime(screenName, time) {
|
||||
var newSettings = Object.assign({}, monitorCyclingSettings)
|
||||
if (!newSettings[screenName]) {
|
||||
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
|
||||
}
|
||||
newSettings[screenName].time = time
|
||||
monitorCyclingSettings = newSettings
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setPerMonitorWallpaper(enabled) {
|
||||
perMonitorWallpaper = enabled
|
||||
|
||||
// Disable automatic cycling when per-monitor mode is enabled
|
||||
if (enabled && wallpaperCyclingEnabled) {
|
||||
wallpaperCyclingEnabled = false
|
||||
}
|
||||
|
||||
saveSettings()
|
||||
|
||||
// Refresh dynamic theming when per-monitor mode changes
|
||||
if (typeof Theme !== "undefined") {
|
||||
if (Theme.currentTheme === Theme.dynamic) {
|
||||
Theme.extractColors()
|
||||
}
|
||||
Theme.generateSystemThemesFromCurrentTheme()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,13 +511,13 @@ Item {
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.2
|
||||
visible: SessionData.wallpaperPath !== "" && !SessionData.perMonitorWallpaper
|
||||
visible: SessionData.wallpaperPath !== "" || SessionData.perMonitorWallpaper
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: SessionData.wallpaperPath !== "" && !SessionData.perMonitorWallpaper
|
||||
visible: SessionData.wallpaperPath !== "" || SessionData.perMonitorWallpaper
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
@@ -554,11 +554,23 @@ Item {
|
||||
id: cyclingToggle
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SessionData.wallpaperCyclingEnabled
|
||||
enabled: !SessionData.perMonitorWallpaper
|
||||
checked: SessionData.perMonitorWallpaper ? SessionData.getMonitorCyclingSettings(selectedMonitorName).enabled : SessionData.wallpaperCyclingEnabled
|
||||
onToggled: toggled => {
|
||||
return SessionData.setWallpaperCyclingEnabled(toggled)
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
return SessionData.setMonitorCyclingEnabled(selectedMonitorName, toggled)
|
||||
} else {
|
||||
return SessionData.setWallpaperCyclingEnabled(toggled)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: personalizationTab
|
||||
function onSelectedMonitorNameChanged() {
|
||||
cyclingToggle.checked = Qt.binding(() => {
|
||||
return SessionData.perMonitorWallpaper ? SessionData.getMonitorCyclingSettings(selectedMonitorName).enabled : SessionData.wallpaperCyclingEnabled
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,7 +578,7 @@ Item {
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: SessionData.wallpaperCyclingEnabled
|
||||
visible: SessionData.perMonitorWallpaper ? SessionData.getMonitorCyclingSettings(selectedMonitorName).enabled : SessionData.wallpaperCyclingEnabled
|
||||
leftPadding: Theme.iconSize + Theme.spacingM
|
||||
|
||||
Row {
|
||||
@@ -596,40 +608,104 @@ Item {
|
||||
"text": "Time",
|
||||
"icon": "access_time"
|
||||
}]
|
||||
currentIndex: SessionData.wallpaperCyclingMode === "time" ? 1 : 0
|
||||
currentIndex: {
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
return SessionData.getMonitorCyclingSettings(selectedMonitorName).mode === "time" ? 1 : 0
|
||||
} else {
|
||||
return SessionData.wallpaperCyclingMode === "time" ? 1 : 0
|
||||
}
|
||||
}
|
||||
onTabClicked: index => {
|
||||
SessionData.setWallpaperCyclingMode(index === 1 ? "time" : "interval")
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
SessionData.setMonitorCyclingMode(selectedMonitorName, index === 1 ? "time" : "interval")
|
||||
} else {
|
||||
SessionData.setWallpaperCyclingMode(index === 1 ? "time" : "interval")
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: personalizationTab
|
||||
function onSelectedMonitorNameChanged() {
|
||||
modeTabBar.currentIndex = Qt.binding(() => {
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
return SessionData.getMonitorCyclingSettings(selectedMonitorName).mode === "time" ? 1 : 0
|
||||
} else {
|
||||
return SessionData.wallpaperCyclingMode === "time" ? 1 : 0
|
||||
}
|
||||
})
|
||||
Qt.callLater(modeTabBar.updateIndicator)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interval settings
|
||||
DankDropdown {
|
||||
id: intervalDropdown
|
||||
property var intervalOptions: ["1 minute", "5 minutes", "15 minutes", "30 minutes", "1 hour", "1.5 hours", "2 hours", "3 hours", "4 hours", "6 hours", "8 hours", "12 hours"]
|
||||
property var intervalValues: [60, 300, 900, 1800, 3600, 5400, 7200, 10800, 14400, 21600, 28800, 43200]
|
||||
|
||||
width: parent.width - parent.leftPadding
|
||||
visible: SessionData.wallpaperCyclingMode === "interval"
|
||||
visible: {
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
return SessionData.getMonitorCyclingSettings(selectedMonitorName).mode === "interval"
|
||||
} else {
|
||||
return SessionData.wallpaperCyclingMode === "interval"
|
||||
}
|
||||
}
|
||||
text: "Interval"
|
||||
description: "How often to change wallpaper"
|
||||
options: intervalOptions
|
||||
currentValue: {
|
||||
const currentSeconds = SessionData.wallpaperCyclingInterval
|
||||
var currentSeconds
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
currentSeconds = SessionData.getMonitorCyclingSettings(selectedMonitorName).interval
|
||||
} else {
|
||||
currentSeconds = SessionData.wallpaperCyclingInterval
|
||||
}
|
||||
const index = intervalValues.indexOf(currentSeconds)
|
||||
return index >= 0 ? intervalOptions[index] : "5 minutes"
|
||||
}
|
||||
onValueChanged: value => {
|
||||
const index = intervalOptions.indexOf(value)
|
||||
if (index >= 0)
|
||||
SessionData.setWallpaperCyclingInterval(intervalValues[index])
|
||||
if (index >= 0) {
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
SessionData.setMonitorCyclingInterval(selectedMonitorName, intervalValues[index])
|
||||
} else {
|
||||
SessionData.setWallpaperCyclingInterval(intervalValues[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: personalizationTab
|
||||
function onSelectedMonitorNameChanged() {
|
||||
// Force dropdown to refresh its currentValue
|
||||
Qt.callLater(() => {
|
||||
var currentSeconds
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
currentSeconds = SessionData.getMonitorCyclingSettings(selectedMonitorName).interval
|
||||
} else {
|
||||
currentSeconds = SessionData.wallpaperCyclingInterval
|
||||
}
|
||||
const index = intervalDropdown.intervalValues.indexOf(currentSeconds)
|
||||
intervalDropdown.currentValue = index >= 0 ? intervalDropdown.intervalOptions[index] : "5 minutes"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Time settings
|
||||
Row {
|
||||
spacing: Theme.spacingM
|
||||
visible: SessionData.wallpaperCyclingMode === "time"
|
||||
visible: {
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
return SessionData.getMonitorCyclingSettings(selectedMonitorName).mode === "time"
|
||||
} else {
|
||||
return SessionData.wallpaperCyclingMode === "time"
|
||||
}
|
||||
}
|
||||
width: parent.width - parent.leftPadding
|
||||
|
||||
StyledText {
|
||||
@@ -640,32 +716,71 @@ Item {
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: timeTextField
|
||||
width: 100
|
||||
height: 40
|
||||
text: SessionData.wallpaperCyclingTime
|
||||
text: {
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
return SessionData.getMonitorCyclingSettings(selectedMonitorName).time
|
||||
} else {
|
||||
return SessionData.wallpaperCyclingTime
|
||||
}
|
||||
}
|
||||
placeholderText: "00:00"
|
||||
maximumLength: 5
|
||||
topPadding: Theme.spacingS
|
||||
bottomPadding: Theme.spacingS
|
||||
onAccepted: {
|
||||
var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test(text)
|
||||
if (isValid)
|
||||
SessionData.setWallpaperCyclingTime(text)
|
||||
else
|
||||
text = SessionData.wallpaperCyclingTime
|
||||
if (isValid) {
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
SessionData.setMonitorCyclingTime(selectedMonitorName, text)
|
||||
} else {
|
||||
SessionData.setWallpaperCyclingTime(text)
|
||||
}
|
||||
} else {
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
text = SessionData.getMonitorCyclingSettings(selectedMonitorName).time
|
||||
} else {
|
||||
text = SessionData.wallpaperCyclingTime
|
||||
}
|
||||
}
|
||||
}
|
||||
onEditingFinished: {
|
||||
var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test(text)
|
||||
if (isValid)
|
||||
SessionData.setWallpaperCyclingTime(text)
|
||||
else
|
||||
text = SessionData.wallpaperCyclingTime
|
||||
if (isValid) {
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
SessionData.setMonitorCyclingTime(selectedMonitorName, text)
|
||||
} else {
|
||||
SessionData.setWallpaperCyclingTime(text)
|
||||
}
|
||||
} else {
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
text = SessionData.getMonitorCyclingSettings(selectedMonitorName).time
|
||||
} else {
|
||||
text = SessionData.wallpaperCyclingTime
|
||||
}
|
||||
}
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: personalizationTab
|
||||
function onSelectedMonitorNameChanged() {
|
||||
// Force text field to refresh its value
|
||||
Qt.callLater(() => {
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
timeTextField.text = SessionData.getMonitorCyclingSettings(selectedMonitorName).time
|
||||
} else {
|
||||
timeTextField.text = SessionData.wallpaperCyclingTime
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
@@ -695,10 +810,13 @@ Item {
|
||||
case "wipe": return "Wipe"
|
||||
case "disc": return "Disc"
|
||||
case "stripes": return "Stripes"
|
||||
case "iris bloom": return "Iris Bloom"
|
||||
case "pixelate": return "Pixelate"
|
||||
case "portal": return "Portal"
|
||||
default: return "Fade"
|
||||
}
|
||||
}
|
||||
options: ["Fade", "Wipe", "Disc", "Stripes"]
|
||||
options: ["Fade", "Wipe", "Disc", "Stripes", "Iris Bloom", "Pixelate", "Portal"]
|
||||
onValueChanged: value => {
|
||||
var transition = value.toLowerCase()
|
||||
SessionData.setWallpaperTransition(transition)
|
||||
|
||||
@@ -282,6 +282,78 @@ LazyLoader {
|
||||
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/wp_stripes.frag.qsb")
|
||||
}
|
||||
|
||||
ShaderEffect {
|
||||
id: irisBloomShader
|
||||
anchors.fill: parent
|
||||
visible: (root.transitionType === "iris bloom" || root.transitionType === "none") && (root.hasCurrent || root.booting)
|
||||
|
||||
property variant source1: root.hasCurrent ? currentWallpaper : transparentSource
|
||||
property variant source2: nextWallpaper
|
||||
property real progress: root.transitionProgress
|
||||
property real smoothness: root.edgeSmoothness
|
||||
property real centerX: root.discCenterX
|
||||
property real centerY: root.discCenterY
|
||||
property real aspectRatio: root.width / root.height
|
||||
property real fillMode: root.fillMode
|
||||
property vector4d fillColor: root.fillColor
|
||||
property real imageWidth1: Math.max(1, root.hasCurrent ? source1.sourceSize.width : width)
|
||||
property real imageHeight1: Math.max(1, root.hasCurrent ? source1.sourceSize.height : height)
|
||||
property real imageWidth2: Math.max(1, source2.sourceSize.width)
|
||||
property real imageHeight2: Math.max(1, source2.sourceSize.height)
|
||||
property real screenWidth: width
|
||||
property real screenHeight: height
|
||||
|
||||
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/wp_iris_bloom.frag.qsb")
|
||||
}
|
||||
|
||||
ShaderEffect {
|
||||
id: pixelateShader
|
||||
anchors.fill: parent
|
||||
visible: root.transitionType === "pixelate" && (root.hasCurrent || root.booting)
|
||||
|
||||
property variant source1: root.hasCurrent ? currentWallpaper : transparentSource
|
||||
property variant source2: nextWallpaper
|
||||
property real progress: root.transitionProgress
|
||||
property real smoothness: root.edgeSmoothness // controls starting block size
|
||||
property real fillMode: root.fillMode
|
||||
property vector4d fillColor: root.fillColor
|
||||
property real imageWidth1: Math.max(1, root.hasCurrent ? source1.sourceSize.width : width)
|
||||
property real imageHeight1: Math.max(1, root.hasCurrent ? source1.sourceSize.height : height)
|
||||
property real imageWidth2: Math.max(1, source2.sourceSize.width)
|
||||
property real imageHeight2: Math.max(1, source2.sourceSize.height)
|
||||
property real screenWidth: width
|
||||
property real screenHeight: height
|
||||
property real centerX: root.discCenterX
|
||||
property real centerY: root.discCenterY
|
||||
property real aspectRatio: root.width / root.height
|
||||
|
||||
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/wp_pixelate.frag.qsb")
|
||||
}
|
||||
|
||||
ShaderEffect {
|
||||
id: portalShader
|
||||
anchors.fill: parent
|
||||
visible: root.transitionType === "portal" && (root.hasCurrent || root.booting)
|
||||
|
||||
property variant source1: root.hasCurrent ? currentWallpaper : transparentSource
|
||||
property variant source2: nextWallpaper
|
||||
property real progress: root.transitionProgress
|
||||
property real smoothness: root.edgeSmoothness
|
||||
property real aspectRatio: root.width / root.height
|
||||
property real centerX: root.discCenterX
|
||||
property real centerY: root.discCenterY
|
||||
property real fillMode: root.fillMode
|
||||
property vector4d fillColor: root.fillColor
|
||||
property real imageWidth1: Math.max(1, root.hasCurrent ? source1.sourceSize.width : width)
|
||||
property real imageHeight1: Math.max(1, root.hasCurrent ? source1.sourceSize.height : height)
|
||||
property real imageWidth2: Math.max(1, source2.sourceSize.width)
|
||||
property real imageHeight2: Math.max(1, source2.sourceSize.height)
|
||||
property real screenWidth: width
|
||||
property real screenHeight: height
|
||||
|
||||
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/wp_portal.frag.qsb")
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: transitionAnimation
|
||||
target: root
|
||||
|
||||
@@ -13,11 +13,63 @@ Singleton {
|
||||
property string cachedCyclingTime: SessionData.wallpaperCyclingTime
|
||||
property int cachedCyclingInterval: SessionData.wallpaperCyclingInterval
|
||||
property string lastTimeCheck: ""
|
||||
|
||||
property var monitorTimers: ({})
|
||||
property var monitorLastTimeChecks: ({})
|
||||
property var monitorProcesses: ({})
|
||||
Component.onCompleted: {
|
||||
updateCyclingState()
|
||||
}
|
||||
|
||||
Component {
|
||||
id: monitorTimerComponent
|
||||
Timer {
|
||||
property string targetScreen: ""
|
||||
running: false
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (typeof WallpaperCyclingService !== "undefined" && targetScreen !== "") {
|
||||
WallpaperCyclingService.cycleNextForMonitor(targetScreen)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: monitorProcessComponent
|
||||
Process {
|
||||
property string targetScreenName: ""
|
||||
property string currentWallpaper: ""
|
||||
property bool goToPrevious: false
|
||||
running: false
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text && text.trim()) {
|
||||
const files = text.trim().split('\n').filter(file => file.length > 0)
|
||||
if (files.length <= 1) return
|
||||
const wallpaperList = files.sort()
|
||||
const currentPath = currentWallpaper
|
||||
let currentIndex = wallpaperList.findIndex(path => path === currentPath)
|
||||
if (currentIndex === -1) currentIndex = 0
|
||||
let targetIndex
|
||||
if (goToPrevious) {
|
||||
targetIndex = currentIndex === 0 ? wallpaperList.length - 1 : currentIndex - 1
|
||||
} else {
|
||||
targetIndex = (currentIndex + 1) % wallpaperList.length
|
||||
}
|
||||
const targetWallpaper = wallpaperList[targetIndex]
|
||||
if (targetWallpaper && targetWallpaper !== currentPath) {
|
||||
if (targetScreenName) {
|
||||
SessionData.setMonitorWallpaper(targetScreenName, targetWallpaper)
|
||||
} else {
|
||||
SessionData.setWallpaper(targetWallpaper)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
|
||||
@@ -46,13 +98,46 @@ Singleton {
|
||||
function onPerMonitorWallpaperChanged() {
|
||||
updateCyclingState()
|
||||
}
|
||||
|
||||
function onMonitorCyclingSettingsChanged() {
|
||||
updateCyclingState()
|
||||
}
|
||||
}
|
||||
|
||||
function updateCyclingState() {
|
||||
if (SessionData.wallpaperCyclingEnabled && SessionData.wallpaperPath && !SessionData.perMonitorWallpaper) {
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
stopCycling()
|
||||
updatePerMonitorCycling()
|
||||
} else if (SessionData.wallpaperCyclingEnabled && SessionData.wallpaperPath) {
|
||||
startCycling()
|
||||
stopAllMonitorCycling()
|
||||
} else {
|
||||
stopCycling()
|
||||
stopAllMonitorCycling()
|
||||
}
|
||||
}
|
||||
|
||||
function updatePerMonitorCycling() {
|
||||
if (typeof Quickshell === "undefined") return
|
||||
|
||||
var screens = Quickshell.screens
|
||||
for (var i = 0; i < screens.length; i++) {
|
||||
var screenName = screens[i].name
|
||||
var settings = SessionData.getMonitorCyclingSettings(screenName)
|
||||
var wallpaper = SessionData.getMonitorWallpaper(screenName)
|
||||
|
||||
if (settings.enabled && wallpaper && !wallpaper.startsWith("#") && !wallpaper.startsWith("we:")) {
|
||||
startMonitorCycling(screenName, settings)
|
||||
} else {
|
||||
stopMonitorCycling(screenName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stopAllMonitorCycling() {
|
||||
var screenNames = Object.keys(monitorTimers)
|
||||
for (var i = 0; i < screenNames.length; i++) {
|
||||
stopMonitorCycling(screenNames[i])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,15 +157,80 @@ Singleton {
|
||||
cyclingActive = false
|
||||
}
|
||||
|
||||
function startMonitorCycling(screenName, settings) {
|
||||
if (settings.mode === "interval") {
|
||||
var timer = monitorTimers[screenName]
|
||||
if (!timer && monitorTimerComponent && monitorTimerComponent.status === Component.Ready) {
|
||||
var newTimers = Object.assign({}, monitorTimers)
|
||||
newTimers[screenName] = monitorTimerComponent.createObject(root)
|
||||
newTimers[screenName].targetScreen = screenName
|
||||
monitorTimers = newTimers
|
||||
timer = monitorTimers[screenName]
|
||||
}
|
||||
if (timer) {
|
||||
timer.interval = settings.interval * 1000
|
||||
timer.start()
|
||||
}
|
||||
} else if (settings.mode === "time") {
|
||||
var newChecks = Object.assign({}, monitorLastTimeChecks)
|
||||
newChecks[screenName] = ""
|
||||
monitorLastTimeChecks = newChecks
|
||||
}
|
||||
}
|
||||
|
||||
function stopMonitorCycling(screenName) {
|
||||
var timer = monitorTimers[screenName]
|
||||
if (timer) {
|
||||
timer.stop()
|
||||
timer.destroy()
|
||||
var newTimers = Object.assign({}, monitorTimers)
|
||||
delete newTimers[screenName]
|
||||
monitorTimers = newTimers
|
||||
}
|
||||
|
||||
var process = monitorProcesses[screenName]
|
||||
if (process) {
|
||||
process.destroy()
|
||||
var newProcesses = Object.assign({}, monitorProcesses)
|
||||
delete newProcesses[screenName]
|
||||
monitorProcesses = newProcesses
|
||||
}
|
||||
|
||||
var newChecks = Object.assign({}, monitorLastTimeChecks)
|
||||
delete newChecks[screenName]
|
||||
monitorLastTimeChecks = newChecks
|
||||
}
|
||||
|
||||
function cycleToNextWallpaper(screenName, wallpaperPath) {
|
||||
const currentWallpaper = wallpaperPath || SessionData.wallpaperPath
|
||||
if (!currentWallpaper) return
|
||||
|
||||
const wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'))
|
||||
cyclingProcess.command = ["sh", "-c", `ls -1 "${wallpaperDir}"/*.jpg "${wallpaperDir}"/*.jpeg "${wallpaperDir}"/*.png "${wallpaperDir}"/*.bmp "${wallpaperDir}"/*.gif "${wallpaperDir}"/*.webp 2>/dev/null | sort`]
|
||||
cyclingProcess.targetScreenName = screenName || ""
|
||||
cyclingProcess.currentWallpaper = currentWallpaper
|
||||
cyclingProcess.running = true
|
||||
|
||||
if (screenName && monitorProcessComponent && monitorProcessComponent.status === Component.Ready) {
|
||||
// Use per-monitor process
|
||||
var process = monitorProcesses[screenName]
|
||||
if (!process) {
|
||||
var newProcesses = Object.assign({}, monitorProcesses)
|
||||
newProcesses[screenName] = monitorProcessComponent.createObject(root)
|
||||
monitorProcesses = newProcesses
|
||||
process = monitorProcesses[screenName]
|
||||
}
|
||||
|
||||
if (process) {
|
||||
process.command = ["sh", "-c", `ls -1 "${wallpaperDir}"/*.jpg "${wallpaperDir}"/*.jpeg "${wallpaperDir}"/*.png "${wallpaperDir}"/*.bmp "${wallpaperDir}"/*.gif "${wallpaperDir}"/*.webp 2>/dev/null | sort`]
|
||||
process.targetScreenName = screenName
|
||||
process.currentWallpaper = currentWallpaper
|
||||
process.goToPrevious = false
|
||||
process.running = true
|
||||
}
|
||||
} else {
|
||||
// Use global process for fallback
|
||||
cyclingProcess.command = ["sh", "-c", `ls -1 "${wallpaperDir}"/*.jpg "${wallpaperDir}"/*.jpeg "${wallpaperDir}"/*.png "${wallpaperDir}"/*.bmp "${wallpaperDir}"/*.gif "${wallpaperDir}"/*.webp 2>/dev/null | sort`]
|
||||
cyclingProcess.targetScreenName = screenName || ""
|
||||
cyclingProcess.currentWallpaper = currentWallpaper
|
||||
cyclingProcess.running = true
|
||||
}
|
||||
}
|
||||
|
||||
function cycleToPrevWallpaper(screenName, wallpaperPath) {
|
||||
@@ -88,10 +238,31 @@ Singleton {
|
||||
if (!currentWallpaper) return
|
||||
|
||||
const wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'))
|
||||
prevCyclingProcess.command = ["sh", "-c", `ls -1 "${wallpaperDir}"/*.jpg "${wallpaperDir}"/*.jpeg "${wallpaperDir}"/*.png "${wallpaperDir}"/*.bmp "${wallpaperDir}"/*.gif "${wallpaperDir}"/*.webp 2>/dev/null | sort`]
|
||||
prevCyclingProcess.targetScreenName = screenName || ""
|
||||
prevCyclingProcess.currentWallpaper = currentWallpaper
|
||||
prevCyclingProcess.running = true
|
||||
|
||||
if (screenName && monitorProcessComponent && monitorProcessComponent.status === Component.Ready) {
|
||||
// Use per-monitor process (same as next, but with prev flag)
|
||||
var process = monitorProcesses[screenName]
|
||||
if (!process) {
|
||||
var newProcesses = Object.assign({}, monitorProcesses)
|
||||
newProcesses[screenName] = monitorProcessComponent.createObject(root)
|
||||
monitorProcesses = newProcesses
|
||||
process = monitorProcesses[screenName]
|
||||
}
|
||||
|
||||
if (process) {
|
||||
process.command = ["sh", "-c", `ls -1 "${wallpaperDir}"/*.jpg "${wallpaperDir}"/*.jpeg "${wallpaperDir}"/*.png "${wallpaperDir}"/*.bmp "${wallpaperDir}"/*.gif "${wallpaperDir}"/*.webp 2>/dev/null | sort`]
|
||||
process.targetScreenName = screenName
|
||||
process.currentWallpaper = currentWallpaper
|
||||
process.goToPrevious = true
|
||||
process.running = true
|
||||
}
|
||||
} else {
|
||||
// Use global process for fallback
|
||||
prevCyclingProcess.command = ["sh", "-c", `ls -1 "${wallpaperDir}"/*.jpg "${wallpaperDir}"/*.jpeg "${wallpaperDir}"/*.png "${wallpaperDir}"/*.bmp "${wallpaperDir}"/*.gif "${wallpaperDir}"/*.webp 2>/dev/null | sort`]
|
||||
prevCyclingProcess.targetScreenName = screenName || ""
|
||||
prevCyclingProcess.currentWallpaper = currentWallpaper
|
||||
prevCyclingProcess.running = true
|
||||
}
|
||||
}
|
||||
|
||||
function cycleNextManually() {
|
||||
@@ -122,7 +293,7 @@ Singleton {
|
||||
|
||||
function cycleNextForMonitor(screenName) {
|
||||
if (!screenName) return
|
||||
|
||||
|
||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName)
|
||||
if (currentWallpaper) {
|
||||
cycleToNextWallpaper(screenName, currentWallpaper)
|
||||
@@ -141,12 +312,41 @@ Singleton {
|
||||
function checkTimeBasedCycling() {
|
||||
const currentTime = Qt.formatTime(systemClock.date, "hh:mm")
|
||||
|
||||
if (currentTime === cachedCyclingTime
|
||||
&& currentTime !== lastTimeCheck) {
|
||||
lastTimeCheck = currentTime
|
||||
cycleToNextWallpaper()
|
||||
} else if (currentTime !== cachedCyclingTime) {
|
||||
lastTimeCheck = ""
|
||||
if (!SessionData.perMonitorWallpaper) {
|
||||
if (currentTime === cachedCyclingTime && currentTime !== lastTimeCheck) {
|
||||
lastTimeCheck = currentTime
|
||||
cycleToNextWallpaper()
|
||||
} else if (currentTime !== cachedCyclingTime) {
|
||||
lastTimeCheck = ""
|
||||
}
|
||||
} else {
|
||||
checkPerMonitorTimeBasedCycling(currentTime)
|
||||
}
|
||||
}
|
||||
|
||||
function checkPerMonitorTimeBasedCycling(currentTime) {
|
||||
if (typeof Quickshell === "undefined") return
|
||||
|
||||
var screens = Quickshell.screens
|
||||
for (var i = 0; i < screens.length; i++) {
|
||||
var screenName = screens[i].name
|
||||
var settings = SessionData.getMonitorCyclingSettings(screenName)
|
||||
var wallpaper = SessionData.getMonitorWallpaper(screenName)
|
||||
|
||||
if (settings.enabled && settings.mode === "time" && wallpaper && !wallpaper.startsWith("#") && !wallpaper.startsWith("we:")) {
|
||||
var lastCheck = monitorLastTimeChecks[screenName] || ""
|
||||
|
||||
if (currentTime === settings.time && currentTime !== lastCheck) {
|
||||
var newChecks = Object.assign({}, monitorLastTimeChecks)
|
||||
newChecks[screenName] = currentTime
|
||||
monitorLastTimeChecks = newChecks
|
||||
cycleNextForMonitor(screenName)
|
||||
} else if (currentTime !== settings.time) {
|
||||
var newChecks = Object.assign({}, monitorLastTimeChecks)
|
||||
newChecks[screenName] = ""
|
||||
monitorLastTimeChecks = newChecks
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +362,7 @@ Singleton {
|
||||
id: systemClock
|
||||
precision: SystemClock.Minutes
|
||||
onDateChanged: {
|
||||
if (SessionData.wallpaperCyclingMode === "time" && cyclingActive) {
|
||||
if ((SessionData.wallpaperCyclingMode === "time" && cyclingActive) || SessionData.perMonitorWallpaper) {
|
||||
checkTimeBasedCycling()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 qt_TexCoord0;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(binding = 1) uniform sampler2D source;
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 qt_Matrix;
|
||||
float qt_Opacity;
|
||||
float imageOpacity;
|
||||
} ubuf;
|
||||
|
||||
void main() {
|
||||
// Center coordinates around (0, 0)
|
||||
vec2 uv = qt_TexCoord0 - 0.5;
|
||||
|
||||
// Calculate distance from center
|
||||
float distance = length(uv);
|
||||
|
||||
// Create circular mask - anything beyond radius 0.5 is transparent
|
||||
float mask = 1.0 - smoothstep(0.48, 0.52, distance);
|
||||
|
||||
// Sample the texture
|
||||
vec4 color = texture(source, qt_TexCoord0);
|
||||
|
||||
// Apply the circular mask and opacity
|
||||
float finalAlpha = color.a * mask * ubuf.imageOpacity * ubuf.qt_Opacity;
|
||||
fragColor = vec4(color.rgb * finalAlpha, finalAlpha);
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 qt_TexCoord0;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(binding = 1) uniform sampler2D source;
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 qt_Matrix;
|
||||
float qt_Opacity;
|
||||
// Custom properties with non-conflicting names
|
||||
float itemWidth;
|
||||
float itemHeight;
|
||||
float cornerRadius;
|
||||
float imageOpacity;
|
||||
} ubuf;
|
||||
|
||||
// Function to calculate the signed distance from a point to a rounded box
|
||||
float roundedBoxSDF(vec2 centerPos, vec2 boxSize, float radius) {
|
||||
vec2 d = abs(centerPos) - boxSize + radius;
|
||||
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius;
|
||||
}
|
||||
|
||||
void main() {
|
||||
// Get size from uniforms
|
||||
vec2 itemSize = vec2(ubuf.itemWidth, ubuf.itemHeight);
|
||||
float cornerRadius = ubuf.cornerRadius;
|
||||
float itemOpacity = ubuf.imageOpacity;
|
||||
|
||||
// Normalize coordinates to [-0.5, 0.5] range
|
||||
vec2 uv = qt_TexCoord0 - 0.5;
|
||||
|
||||
// Scale by aspect ratio to maintain uniform rounding
|
||||
vec2 aspectRatio = itemSize / max(itemSize.x, itemSize.y);
|
||||
uv *= aspectRatio;
|
||||
|
||||
// Calculate half size in normalized space
|
||||
vec2 halfSize = 0.5 * aspectRatio;
|
||||
|
||||
// Normalize the corner radius
|
||||
float normalizedRadius = cornerRadius / max(itemSize.x, itemSize.y);
|
||||
|
||||
// Calculate distance to rounded rectangle
|
||||
float distance = roundedBoxSDF(uv, halfSize, normalizedRadius);
|
||||
|
||||
// Create smooth alpha mask
|
||||
float smoothedAlpha = 1.0 - smoothstep(0.0, fwidth(distance), distance);
|
||||
|
||||
// Sample the texture
|
||||
vec4 color = texture(source, qt_TexCoord0);
|
||||
|
||||
// Apply the rounded mask and opacity
|
||||
// Make sure areas outside the rounded rect are completely transparent
|
||||
float finalAlpha = color.a * smoothedAlpha * itemOpacity * ubuf.qt_Opacity;
|
||||
fragColor = vec4(color.rgb * finalAlpha, finalAlpha);
|
||||
}
|
||||
100
Shaders/frag/wp_iris_bloom.frag
Normal file
100
Shaders/frag/wp_iris_bloom.frag
Normal file
@@ -0,0 +1,100 @@
|
||||
// ===== wp_iris_bloom.frag =====
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 qt_TexCoord0;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(binding = 1) uniform sampler2D source1; // Current wallpaper
|
||||
layout(binding = 2) uniform sampler2D source2; // Next wallpaper
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 qt_Matrix;
|
||||
float qt_Opacity;
|
||||
float progress; // 0.0 -> 1.0
|
||||
float centerX; // 0..1
|
||||
float centerY; // 0..1
|
||||
float smoothness; // 0..1 (edge softness)
|
||||
float aspectRatio; // width / height
|
||||
|
||||
// Fill mode parameters
|
||||
float fillMode; // 0=no(center), 1=crop(fill), 2=fit(contain), 3=stretch
|
||||
float imageWidth1;
|
||||
float imageHeight1;
|
||||
float imageWidth2;
|
||||
float imageHeight2;
|
||||
float screenWidth;
|
||||
float screenHeight;
|
||||
vec4 fillColor;
|
||||
} ubuf;
|
||||
|
||||
vec2 calculateUV(vec2 uv, float imgWidth, float imgHeight) {
|
||||
vec2 transformedUV = uv;
|
||||
|
||||
if (ubuf.fillMode < 0.5) {
|
||||
vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight);
|
||||
vec2 imageOffset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - vec2(imgWidth, imgHeight)) * 0.5;
|
||||
vec2 imagePixel = screenPixel - imageOffset;
|
||||
transformedUV = imagePixel / vec2(imgWidth, imgHeight);
|
||||
}
|
||||
else if (ubuf.fillMode < 1.5) {
|
||||
float scale = max(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight);
|
||||
vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale;
|
||||
vec2 offset = (scaledImageSize - vec2(ubuf.screenWidth, ubuf.screenHeight)) / scaledImageSize;
|
||||
transformedUV = uv * (vec2(1.0) - offset) + offset * 0.5;
|
||||
}
|
||||
else if (ubuf.fillMode < 2.5) {
|
||||
float scale = min(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight);
|
||||
vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale;
|
||||
vec2 offset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - vec2(scaledImageSize)) * 0.5;
|
||||
vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight);
|
||||
vec2 imagePixel = (screenPixel - offset) / scale;
|
||||
transformedUV = imagePixel / vec2(imgWidth, imgHeight);
|
||||
}
|
||||
// else stretch
|
||||
|
||||
return transformedUV;
|
||||
}
|
||||
|
||||
vec4 sampleWithFillMode(sampler2D tex, vec2 uv, float imgWidth, float imgHeight) {
|
||||
vec2 tuv = calculateUV(uv, imgWidth, imgHeight);
|
||||
if (tuv.x < 0.0 || tuv.x > 1.0 || tuv.y < 0.0 || tuv.y > 1.0) {
|
||||
return ubuf.fillColor;
|
||||
}
|
||||
return texture(tex, tuv);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = qt_TexCoord0;
|
||||
|
||||
vec4 color1 = sampleWithFillMode(source1, uv, ubuf.imageWidth1, ubuf.imageHeight1);
|
||||
vec4 color2 = sampleWithFillMode(source2, uv, ubuf.imageWidth2, ubuf.imageHeight2);
|
||||
|
||||
// Edge softness mapping
|
||||
float edgeSoft = mix(0.001, 0.45, ubuf.smoothness * ubuf.smoothness);
|
||||
|
||||
// Aspect-corrected coordinates so the iris stays circular
|
||||
vec2 center = vec2(ubuf.centerX, ubuf.centerY);
|
||||
vec2 acUv = vec2(uv.x * ubuf.aspectRatio, uv.y);
|
||||
vec2 acCenter = vec2(center.x * ubuf.aspectRatio, center.y);
|
||||
float dist = length(acUv - acCenter);
|
||||
|
||||
// Max radius needed to cover the screen from the chosen center
|
||||
float maxDistX = max(center.x * ubuf.aspectRatio, (1.0 - center.x) * ubuf.aspectRatio);
|
||||
float maxDistY = max(center.y, 1.0 - center.y);
|
||||
float maxDist = length(vec2(maxDistX, maxDistY));
|
||||
|
||||
float p = ubuf.progress;
|
||||
p = p * p * (3.0 - 2.0 * p);
|
||||
|
||||
float radius = p * maxDist - edgeSoft;
|
||||
|
||||
// Soft circular edge: inside -> color2 (new), outside -> color1 (old)
|
||||
float t = smoothstep(radius - edgeSoft, radius + edgeSoft, dist);
|
||||
vec4 col = mix(color2, color1, t);
|
||||
|
||||
// Exact snaps at ends
|
||||
if (ubuf.progress <= 0.0) col = color1;
|
||||
if (ubuf.progress >= 1.0) col = color2;
|
||||
|
||||
fragColor = col * ubuf.qt_Opacity;
|
||||
}
|
||||
99
Shaders/frag/wp_pixelate.frag
Normal file
99
Shaders/frag/wp_pixelate.frag
Normal file
@@ -0,0 +1,99 @@
|
||||
// ===== wp_pixelate.frag =====
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 qt_TexCoord0;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(binding = 1) uniform sampler2D source1; // Current wallpaper (underlay)
|
||||
layout(binding = 2) uniform sampler2D source2; // Next wallpaper (pixelated overlay → sharp)
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 qt_Matrix;
|
||||
float qt_Opacity;
|
||||
float progress; // 0..1
|
||||
float centerX; // (unused, API compat)
|
||||
float centerY; // (unused)
|
||||
float smoothness; // controls starting block size (0..1)
|
||||
float aspectRatio; // (unused)
|
||||
|
||||
// Fill mode parameters
|
||||
float fillMode; // 0=no(center), 1=crop, 2=fit, 3=stretch
|
||||
float imageWidth1;
|
||||
float imageHeight1;
|
||||
float imageWidth2;
|
||||
float imageHeight2;
|
||||
float screenWidth;
|
||||
float screenHeight;
|
||||
vec4 fillColor;
|
||||
} ubuf;
|
||||
|
||||
vec2 calculateUV(vec2 uv, float imgWidth, float imgHeight) {
|
||||
vec2 transformedUV = uv;
|
||||
if (ubuf.fillMode < 0.5) {
|
||||
vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight);
|
||||
vec2 imageOffset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - vec2(imgWidth, imgHeight)) * 0.5;
|
||||
vec2 imagePixel = screenPixel - imageOffset;
|
||||
transformedUV = imagePixel / vec2(imgWidth, imgHeight);
|
||||
} else if (ubuf.fillMode < 1.5) {
|
||||
float scale = max(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight);
|
||||
vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale;
|
||||
vec2 offset = (scaledImageSize - vec2(ubuf.screenWidth, ubuf.screenHeight)) / scaledImageSize;
|
||||
transformedUV = uv * (vec2(1.0) - offset) + offset * 0.5;
|
||||
} else if (ubuf.fillMode < 2.5) {
|
||||
float scale = min(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight);
|
||||
vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale;
|
||||
vec2 offset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - scaledImageSize) * 0.5;
|
||||
vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight);
|
||||
vec2 imagePixel = (screenPixel - offset) / scale;
|
||||
transformedUV = imagePixel / vec2(imgWidth, imgHeight);
|
||||
}
|
||||
return transformedUV;
|
||||
}
|
||||
|
||||
vec4 sampleWithFillMode(sampler2D tex, vec2 uv, float w, float h) {
|
||||
vec2 tuv = calculateUV(uv, w, h);
|
||||
if (tuv.x < 0.0 || tuv.x > 1.0 || tuv.y < 0.0 || tuv.y > 1.0) return ubuf.fillColor;
|
||||
return texture(tex, tuv);
|
||||
}
|
||||
|
||||
vec2 quantizeUV(vec2 uv, float cellPx) {
|
||||
vec2 screenSize = vec2(max(1.0, ubuf.screenWidth), max(1.0, ubuf.screenHeight));
|
||||
float cell = max(1.0, ceil(cellPx)); // integer pixel cells
|
||||
vec2 grid = floor(uv * screenSize / cell) * cell + 0.5 * cell;
|
||||
return grid / screenSize;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = qt_TexCoord0;
|
||||
|
||||
vec4 oldCol = sampleWithFillMode(source1, uv, ubuf.imageWidth1, ubuf.imageHeight1);
|
||||
|
||||
float p = clamp(ubuf.progress, 0.0, 1.0);
|
||||
float pe = p * p * (3.0 - 2.0 * p); // smootherstep for opacity
|
||||
|
||||
// Screen-relative starting cell size:
|
||||
// smoothness=0 → ~10% of min(screen), smoothness=1 → ~80% of min(screen)
|
||||
float s = clamp(ubuf.smoothness, 0.0, 1.0);
|
||||
float minSide = min(max(1.0, ubuf.screenWidth), max(1.0, ubuf.screenHeight));
|
||||
float startPx = mix(minSide * 0.10, minSide * 0.80, s); // big and obvious even on small screens
|
||||
|
||||
// Cell size shrinks continuously from startPx → 1 as p grows
|
||||
float cellPx = mix(startPx, 1.0, p);
|
||||
|
||||
// Sample next as pixelated overlay
|
||||
vec2 uvq = quantizeUV(uv, cellPx);
|
||||
vec4 newPix = sampleWithFillMode(source2, uvq, ubuf.imageWidth2, ubuf.imageHeight2);
|
||||
|
||||
// As we approach the end, sharpen the next from pixelated → full-res
|
||||
float sharpen = smoothstep(0.75, 1.0, p); // only near the end
|
||||
vec4 newFull = sampleWithFillMode(source2, uv, ubuf.imageWidth2, ubuf.imageHeight2);
|
||||
vec4 newCol = mix(newPix, newFull, sharpen);
|
||||
|
||||
vec4 outColor = mix(oldCol, newCol, pe);
|
||||
|
||||
// Snaps
|
||||
if (p <= 0.0) outColor = oldCol;
|
||||
if (p >= 1.0) outColor = newFull;
|
||||
|
||||
fragColor = outColor * ubuf.qt_Opacity;
|
||||
}
|
||||
103
Shaders/frag/wp_portal.frag
Normal file
103
Shaders/frag/wp_portal.frag
Normal file
@@ -0,0 +1,103 @@
|
||||
// ===== wp_portal.frag =====
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 qt_TexCoord0;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(binding = 1) uniform sampler2D source1; // Current wallpaper (shrinks away)
|
||||
layout(binding = 2) uniform sampler2D source2; // Next wallpaper (underneath)
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 qt_Matrix;
|
||||
float qt_Opacity;
|
||||
float progress; // 0..1
|
||||
float centerX; // 0..1
|
||||
float centerY; // 0..1
|
||||
float smoothness; // 0..1 (edge softness)
|
||||
float aspectRatio; // width / height
|
||||
|
||||
// Fill mode parameters
|
||||
float fillMode; // 0=no(center), 1=crop(fill), 2=fit(contain), 3=stretch
|
||||
float imageWidth1;
|
||||
float imageHeight1;
|
||||
float imageWidth2;
|
||||
float imageHeight2;
|
||||
float screenWidth;
|
||||
float screenHeight;
|
||||
vec4 fillColor;
|
||||
} ubuf;
|
||||
|
||||
vec2 calculateUV(vec2 uv, float imgWidth, float imgHeight) {
|
||||
vec2 transformedUV = uv;
|
||||
|
||||
if (ubuf.fillMode < 0.5) {
|
||||
vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight);
|
||||
vec2 imageOffset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - vec2(imgWidth, imgHeight)) * 0.5;
|
||||
vec2 imagePixel = screenPixel - imageOffset;
|
||||
transformedUV = imagePixel / vec2(imgWidth, imgHeight);
|
||||
}
|
||||
else if (ubuf.fillMode < 1.5) {
|
||||
float scale = max(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight);
|
||||
vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale;
|
||||
vec2 offset = (scaledImageSize - vec2(ubuf.screenWidth, ubuf.screenHeight)) / scaledImageSize;
|
||||
transformedUV = uv * (vec2(1.0) - offset) + offset * 0.5;
|
||||
}
|
||||
else if (ubuf.fillMode < 2.5) {
|
||||
float scale = min(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight);
|
||||
vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale;
|
||||
vec2 offset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - scaledImageSize) * 0.5;
|
||||
vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight);
|
||||
vec2 imagePixel = (screenPixel - offset) / scale;
|
||||
transformedUV = imagePixel / vec2(imgWidth, imgHeight);
|
||||
}
|
||||
// else: stretch
|
||||
|
||||
return transformedUV;
|
||||
}
|
||||
|
||||
vec4 sampleWithFillMode(sampler2D tex, vec2 uv, float w, float h) {
|
||||
vec2 tuv = calculateUV(uv, w, h);
|
||||
if (tuv.x < 0.0 || tuv.x > 1.0 || tuv.y < 0.0 || tuv.y > 1.0) return ubuf.fillColor;
|
||||
return texture(tex, tuv);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = qt_TexCoord0;
|
||||
|
||||
vec4 oldCol = sampleWithFillMode(source1, uv, ubuf.imageWidth1, ubuf.imageHeight1);
|
||||
vec4 newCol = sampleWithFillMode(source2, uv, ubuf.imageWidth2, ubuf.imageHeight2);
|
||||
|
||||
// Edge softness
|
||||
float edgeSoft = mix(0.001, 0.45, ubuf.smoothness * ubuf.smoothness);
|
||||
|
||||
// Aspect-corrected distance from center (keep circle round)
|
||||
vec2 center = vec2(ubuf.centerX, ubuf.centerY);
|
||||
vec2 acUv = vec2(uv.x * ubuf.aspectRatio, uv.y);
|
||||
vec2 acCenter = vec2(center.x * ubuf.aspectRatio, center.y);
|
||||
float dist = length(acUv - acCenter);
|
||||
|
||||
// Max radius from center to cover screen
|
||||
float maxDistX = max(center.x * ubuf.aspectRatio, (1.0 - center.x) * ubuf.aspectRatio);
|
||||
float maxDistY = max(center.y, 1.0 - center.y);
|
||||
float maxDist = length(vec2(maxDistX, maxDistY));
|
||||
|
||||
// Smooth easing for a friendly feel
|
||||
float p = ubuf.progress;
|
||||
p = p * p * (3.0 - 2.0 * p);
|
||||
|
||||
// Portal radius shrinks from full to zero (bias by edgeSoft so it vanishes cleanly)
|
||||
float radius = (1.0 - p) * (maxDist + edgeSoft) - edgeSoft;
|
||||
|
||||
// Inside circle = old wallpaper; outside = new wallpaper
|
||||
float t = smoothstep(radius - edgeSoft, radius + edgeSoft, dist);
|
||||
// When radius is large: t ~ 0 inside (old), ~1 outside (new)
|
||||
// As radius shrinks, old area collapses to center.
|
||||
|
||||
vec4 col = mix(oldCol, newCol, t);
|
||||
|
||||
// Snaps
|
||||
if (ubuf.progress <= 0.0) col = oldCol; // full old at start
|
||||
if (ubuf.progress >= 1.0) col = newCol; // full new at end
|
||||
|
||||
fragColor = col * ubuf.qt_Opacity;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
BIN
Shaders/qsb/wp_iris_bloom.frag.qsb
Normal file
BIN
Shaders/qsb/wp_iris_bloom.frag.qsb
Normal file
Binary file not shown.
BIN
Shaders/qsb/wp_pixelate.frag.qsb
Normal file
BIN
Shaders/qsb/wp_pixelate.frag.qsb
Normal file
Binary file not shown.
BIN
Shaders/qsb/wp_portal.frag.qsb
Normal file
BIN
Shaders/qsb/wp_portal.frag.qsb
Normal file
Binary file not shown.
@@ -159,8 +159,8 @@ Item {
|
||||
|
||||
imageSource: artUrl || lastValidArtUrl || ""
|
||||
fallbackIcon: "album"
|
||||
borderColor: Theme.primary
|
||||
borderWidth: 2
|
||||
border.color: Theme.primary
|
||||
border.width: 2
|
||||
|
||||
onImageSourceChanged: {
|
||||
if (imageSource && imageStatus !== Image.Error) {
|
||||
|
||||
@@ -1,92 +1,84 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string imageSource: ""
|
||||
property string fallbackIcon: "notifications"
|
||||
property string fallbackText: ""
|
||||
property bool hasImage: imageSource !== ""
|
||||
property alias imageStatus: sourceImage.status
|
||||
property color borderColor: "transparent"
|
||||
property real borderWidth: 0
|
||||
property real imageOpacity: 1.0
|
||||
property alias imageStatus: internalImage.status
|
||||
|
||||
width: 64
|
||||
height: 64
|
||||
radius: width / 2
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
|
||||
border.color: "transparent"
|
||||
border.width: 0
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
Image {
|
||||
id: internalImage
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
|
||||
radius: width * 0.5
|
||||
anchors.margins: 2
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
smooth: true
|
||||
mipmap: true
|
||||
cache: true
|
||||
visible: false
|
||||
source: root.imageSource
|
||||
|
||||
Image {
|
||||
id: sourceImage
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
source: root.imageSource
|
||||
visible: false
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
smooth: true
|
||||
mipmap: true
|
||||
asynchronous: true
|
||||
antialiasing: true
|
||||
cache: true
|
||||
|
||||
Component.onCompleted: {
|
||||
sourceSize.width = 128
|
||||
sourceSize.height = 128
|
||||
}
|
||||
Component.onCompleted: {
|
||||
sourceSize.width = 128
|
||||
sourceSize.height = 128
|
||||
}
|
||||
}
|
||||
|
||||
ShaderEffect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
visible: sourceImage.status === Image.Ready && root.imageSource !== ""
|
||||
MultiEffect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
source: internalImage
|
||||
maskEnabled: true
|
||||
maskSource: circularMask
|
||||
visible: internalImage.status === Image.Ready && root.imageSource !== ""
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1
|
||||
}
|
||||
|
||||
property var source: ShaderEffectSource {
|
||||
sourceItem: sourceImage
|
||||
hideSource: true
|
||||
live: true
|
||||
recursive: false
|
||||
format: ShaderEffectSource.RGBA
|
||||
}
|
||||
|
||||
property real imageOpacity: root.imageOpacity
|
||||
|
||||
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/circled_image.frag.qsb")
|
||||
supportsAtlasTextures: false
|
||||
blending: true
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: root.fallbackIcon
|
||||
size: parent.width * 0.5
|
||||
color: Theme.surfaceVariantText
|
||||
visible: sourceImage.status !== Image.Ready && root.imageSource === "" && root.fallbackIcon !== ""
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
visible: root.imageSource === "" && root.fallbackIcon === "" && root.fallbackText !== ""
|
||||
text: root.fallbackText
|
||||
font.pixelSize: Math.max(12, parent.width * 0.36)
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primaryText
|
||||
}
|
||||
Item {
|
||||
id: circularMask
|
||||
width: parent.width - 4
|
||||
height: parent.height - 4
|
||||
anchors.centerIn: parent
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
color: "transparent"
|
||||
border.color: root.borderColor !== "transparent" ? root.borderColor : Theme.popupBackground()
|
||||
border.width: root.hasImage && sourceImage.status === Image.Ready ? (root.borderWidth > 0 ? root.borderWidth : 3) : 0
|
||||
color: "black"
|
||||
antialiasing: true
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: root.fallbackIcon
|
||||
size: parent.width * 0.5
|
||||
color: Theme.surfaceVariantText
|
||||
visible: internalImage.status !== Image.Ready && root.imageSource === "" && root.fallbackIcon !== ""
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
visible: root.imageSource === "" && root.fallbackIcon === "" && root.fallbackText !== ""
|
||||
text: root.fallbackText
|
||||
font.pixelSize: Math.max(12, parent.width * 0.36)
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primaryText
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user