Initial Commit

This commit is contained in:
Digital Artifex
2025-08-03 18:41:56 -04:00
commit f65399ec53
402 changed files with 4704 additions and 0 deletions

View File

@@ -0,0 +1,207 @@
/*
* Komplex Wallpaper Engine
* Copyright (C) 2025 @DigitalArtifex | github.com/DigitalArtifex
*
* WallpaperModel.qml
*
* This component provides a model for parsing and managing wallpaper configurations from
* a JSON file, specifically designed for the Komplex Wallpaper Engine.
*
* This enables infinite complexity in shader configurations
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>
*/
import QtQuick
import com.github.digitalartifex.komplex 1.0 as Komplex
Item
{
property real pixelRatio: 1 //This will (hopefully) be set to PlasmaCore.Units.devicePixelRatio in onCompleted
property vector3d iResolution: Qt.vector3d(wallpaper.configuration.resolution_x ? wallpaper.configuration.resolution_x : 1920, wallpaper.configuration.resolution_y ? wallpaper.configuration.resolution_y : 1080, 1)//width, height, pixel aspect ratio
property real iTime: 0 //used by most motion shaders
property real iTimeDelta: iTime
property var iChannelTime: [iTime, iTime, iTime, iTime] //individual channel time values
property real iSampleRate: 44100 //used by audio shaders
property int iFrame: 0
property real iFrameRate: wallpaper.configuration.framerate_limit ? wallpaper.configuration.framerate_limit : 60 // Default frame rate for the shader
property vector4d iMouse
property var iDate
property bool running: true
// Individual channel resolutions to customize performance and quality
property var iChannelResolution: [Qt.vector3d(wallpaper.configuration.iChannel0_resolution_x, wallpaper.configuration.iChannel0_resolution_y, pixelRatio),
Qt.vector3d(wallpaper.configuration.iChannel1_resolution_x, wallpaper.configuration.iChannel1_resolution_y, pixelRatio),
Qt.vector3d(wallpaper.configuration.iChannel2_resolution_x, wallpaper.configuration.iChannel2_resolution_y, pixelRatio),
Qt.vector3d(wallpaper.configuration.iChannel3_resolution_x, wallpaper.configuration.iChannel3_resolution_y, pixelRatio)]
property string iChannel0: ""
property string iChannel1: ""
property string iChannel2: ""
property string iChannel3: ""
id: mainItem
Komplex.ShaderPackModel
{
id: shaderPackModel
onJsonChanged:
{
// Handle the JSON change if needed
console.log("Shader pack JSON changed:", shaderPackModel.json);
mainItem.parsePack(shaderPackModel.json);
}
}
// The WindowModel is used to manage the interaction with the desktop environment
WindowModel
{
id: windowModel
screenGeometry: mainItem.parent.screenGeometry
}
Rectangle
{
anchors.fill: parent
color: "black"
// The output channel that combines all the input channels and displays the final shader output
// This channel must be set to a shader source file that has been pre-compiled to a QSB Fragment Shader
ShaderChannel
{
iTime: mainItem.iTime
iMouse: mainItem.iMouse
iResolution: mainItem.iResolution
id: channelOutput
anchors.fill: parent
type: ShaderChannel.Type.ShaderChannel
visible: true // Set to true to display the output
z: 9000
}
// To save on performance, just use one timer for all channels and shaders
// they will need to be bound to the mainItem properties they need to access
Timer
{
id: channelTimer
//Not entirely sure if this will actually limit the frame rate
interval: (1 / mainItem.iFrameRate) * 1000 //fps to ms cycles :: fps = 60 = 1 / 60 = 0.01666 * 1000 = 16
repeat: true
running: true //wallpaper.configuration.running ? mainItem.running : true
triggeredOnStart: true
onTriggered:
{
var date = new Date();
var startOfDay = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
var secondsSinceMidnight = (date - startOfDay) / 1000;
mainItem.iTime += (interval / 1000) * (wallpaper.configuration.shaderSpeed ? wallpaper.configuration.shaderSpeed : 1.0)
mainItem.iDate = Qt.vector4d(date.getFullYear(), date.getMonth() + 1, date.getDate(), secondsSinceMidnight)
}
}
}
// Setup the connection to shader pack model for updates
Connections
{
target: shaderPackModel
function onJsonChanged()
{
// Parse the JSON configuration and set the properties of the ShaderChannel
// mainItem.parsePack(shaderPackModel.json);
}
}
// Load the default shader pack configuration on component completion
Component.onCompleted:
{
if(wallpaper.configuration.shader_package)
shaderPackModel.loadJson(wallpaper.configuration.shader_package);
else
shaderPackModel.loadJson("/home/parametheus/kde/src/komplex/pack.json"); // Load a default shader pack if none is specified
}
// Function to parse the pack.json file and set the properties of the ShaderChannel
function parsePack(json)
{
var pack = JSON.parse(json);
var currentChannel = null;
if (pack.channel0)
channelOutput.iChannel0 = parseChannel(pack.channel0);
if (pack.channel1)
channelOutput.iChannel1 = parseChannel(pack.channel1);
if (pack.channel2)
channelOutput.iChannel2 = parseChannel(pack.channel2);
if (pack.channel3)
channelOutput.iChannel3 = parseChannel(pack.channel3);
var source
// Ensure the source path is correctly resolved for relative paths
if(!pack.source.startsWith("/") && !pack.source.startsWith("file://"))
source = Qt.resolvedUrl(shaderPackModel.shaderPackPath + "/" + pack.source);
else if(!pack.source.startsWith("file://"))
source = Qt.resolvedUrl("file://" + pack.source);
channelOutput.source = "file://" + (source || ""); // Set the shader source file
channelOutput.type = ShaderChannel.Type.ShaderChannel; // Set the shader
}
// Recursive helper function to parse channels
function parseChannel(channel)
{
if (!channel) return;
var source
// Ensure the source path is correctly resolved for relative paths
if(!channel.source.startsWith("/") && !channel.source.startsWith("file://"))
source = Qt.resolvedUrl(shaderPackModel.shaderPackPath + "/" + channel.source);
else if(!channel.source.startsWith("file://"))
source = Qt.resolvedUrl("file://" + channel.source);
var result = Qt.createQmlObject(`ShaderChannel
{
z: 0
iTimeScale: ${channel.time_scale || 1.0}
iResolutionScale: ${channel.resolution_scale || 1.0}
visible: false
anchors.fill: parent
source: "file://" + "${source || ''}"
type: ${channel.type || 0}
iTime: mainItem.iTime * ${channel.time_scale || 1.0}
iMouse: mainItem.iMouse
iResolution: Qt.vector3d(${(channel.resolution_x || mainItem.width) * (channel.resolution_scale || 1.0)}, ${(channel.resolution_y || mainItem.width) * (channel.resolution_scale || 1.0)}, pixelRatio)
}`, mainItem);
if (channel.channel0)
result.iChannel0 = parseChannel(channel.channel0);
if (channel.channel1)
result.iChannel1 = parseChannel(channel.channel1);
if (channel.channel2)
result.iChannel2 = parseChannel(channel.channel2);
if (channel.channel3)
result.iChannel3 = parseChannel(channel.channel3);
return result;
}
}

View File

@@ -0,0 +1,422 @@
/*
* Komplex Wallpaper Engine
* Copyright (C) 2025 @DigitalArtifex | github.com/DigitalArtifex
*
* ShaderChannel.qml
*
* This component provides a configurable channel type to more closely resemble how ShaderToys
* works. It can be configured to be an Image, Video, Shader, Audio, or CubeMap that can be
* used as the input channel of another ShaderChannel, allowing for more complex shader compositions.
*
* It would also appear that in order to support the audio channel, we will need to
* implement a way to capture the desktop audio in C++, as the MediaPlayer component does not support this.
* This will have to come later.
*
* Volumetric shaders do not seem to be widely used in ShaderToys, so it has not been implemented here.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtMultimedia
import QtQuick3D
import com.github.digitalartifex.komplex 1.0 as Komplex
Item
{
enum Type
{
ImageChannel,
VideoChannel,
ShaderChannel,
CubeMapChannel
// AudioChannel
}
property int type: ShaderChannel.Type.ImageChannel
property string source: ""
property int fillMode: Image.PreserveAspectCrop
property var iChannel0: null
property var iChannel1: null
property var iChannel2: null
property var iChannel3: null
property vector3d iResolution
property real iResolutionScale: 1 // This is used to scale the resolution of the shader, allowing for performance optimizations
property real iTime: 0 //used by most motion shaders
property real lastITime: 0
property real iTimeDelta: 0
property var iChannelTime: [iTimeActual, iTimeActual, iTimeActual, iTimeActual] //individual channel time values
property real iSampleRate: 44100 //used by audio shaders
property int iFrame: 0
property var iFrameRate: 60
property vector4d iMouse
property var iDate
property real iTimeActual: 0 // This is used to track the actual time for the shader, according to the time scale
property real iTimeScale: 1 // This is used to scale the time for the shader, allowing for slow motion or fast forward effects per channel
property var iChannelResolution: [Qt.vector3d(channel.width * iResolutionScale, channel.height * iResolutionScale, 1), Qt.vector3d(channel.width * iResolutionScale, channel.height * iResolutionScale, 1), Qt.vector3d(channel.width * iResolutionScale, channel.height * iResolutionScale, 1)]
onITimeChanged:
{
iTimeDelta = iTime - lastITime
lastITime = iTime
iTimeActual += iTimeDelta * iTimeScale
}
id: channel
visible: false // Set to false by default, main shader needs be set to true in MainWindow.qml
// This is used to dynamically load the appropriate channel type based on the `type` property of the channel
Loader
{
id: loader
anchors.fill: parent
sourceComponent: channelImage
states:[
State
{
when: channel.type === ShaderChannel.Type.ImageChannel
PropertyChanges
{
loader.sourceComponent: channelImage
}
},
State
{
when: channel.type === ShaderChannel.Type.VideoChannel
PropertyChanges
{
loader.sourceComponent: channelVideo
}
},
State
{
when: channel.type === ShaderChannel.Type.CubeMapChannel
PropertyChanges
{
loader.sourceComponent: channelCubeMap
}
},
State
{
when: channel.type === ShaderChannel.Type.ShaderChannel
PropertyChanges
{
loader.sourceComponent: channelShader
}
}
// State
// {
// when: channel.type === ShaderChannel.Type.AudioChannel
// PropertyChanges
// {
// loader.sourceComponent: channelAudio
// }
// }
]
}
//The image channel will be the default channel type for backwards compatability
Component
{
id: channelImage
Image
{
anchors.fill: parent
source: Qt.resolvedUrl(channel.source)
fillMode: channel.fillMode
}
}
//Video channel
Component
{
id: channelVideo
VideoOutput
{
property alias duration: mediaPlayer.duration
property alias mediaSource: mediaPlayer.source
property alias metaData: mediaPlayer.metaData
property alias playbackRate: mediaPlayer.playbackRate
property alias position: mediaPlayer.position
property alias seekable: mediaPlayer.seekable
property alias volume: audioOutput.volume
signal sizeChanged
signal fatalError
id: videoOutput
visible: true
anchors.fill: parent
fillMode: channel.fillMode
onHeightChanged: this.sizeChanged()
MediaPlayer
{
id: mediaPlayer
videoOutput: videoOutput
loops: MediaPlayer.Infinite
source: Qt.resolvedUrl(channel.source)
audioOutput: AudioOutput
{
id: audioOutput
volume: 0
}
onErrorOccurred: function(error, errorString)
{
if (MediaPlayer.NoError !== error)
{
console.log("[qmlvideo] VideoItem.onError error " + error + " errorString " + errorString)
videoOutput.fatalError()
}
}
onSourceChanged:
{
if(mediaPlayer.source != "")
mediaPlayer.play()
else
mediaPlayer.stop()
}
}
function start() { mediaPlayer.play() }
function stop() { mediaPlayer.stop() }
function seek(position) { mediaPlayer.setPosition(position); }
}
}
// A shader channel can be a shader
Component
{
id: channelShader
Item
{
// Setup the shader effect sources for each channel
// These are needed to provide the uniform data to the shader channel buffers
ShaderEffectSource
{
id: channelSource0
sourceItem: channel.iChannel0 ? channel.iChannel0 : null
live: true
smooth: true
sourceRect: Qt.rect(0,0, sourceItem ? sourceItem.width : 0, sourceItem ? sourceItem.height : 0)
}
ShaderEffectSource
{
id: channelSource1
sourceItem: channel.iChannel1 ? channel.iChannel1 : null
live: true
smooth: true
sourceRect: Qt.rect(0,0, sourceItem ? sourceItem.width : 0, sourceItem ? sourceItem.height : 0)
}
ShaderEffectSource
{
id: channelSource2
sourceItem: channel.iChannel2 ? channel.iChannel2 : null
live: true
smooth: true
sourceRect: Qt.rect(0,0, sourceItem ? sourceItem.width : 0, sourceItem ? sourceItem.height : 0)
}
ShaderEffectSource
{
id: channelSource3
sourceItem: channel.iChannel3 ? channel.iChannel3 : null
live: true
smooth: true
sourceRect: Qt.rect(0,0, sourceItem ? sourceItem.width : 0, sourceItem ? sourceItem.height : 0)
}
// The shader effect that will be used to render the shader
ShaderEffect
{
property var iChannel0: channelSource0
property var iChannel1: channelSource1
property var iChannel2: channelSource2
property var iChannel3: channelSource3
property var iResolution: channel.iResolution
property var iTime: channel.iTimeActual
property var iTimeDelta: channel.iTimeDelta
property var iChannelTime: channel.iChannelTime
property var iSampleRate: channel.iSampleRate
property var iFrame: channel.iFrame
property var iFrameRate: channel.iFrameRate
property var iMouse: channel.iMouse
property var iDate: channel.iDate
property var iChannelResolution: channel.iResolution
fragmentShader: Qt.resolvedUrl(channel.source)
anchors.fill: parent
}
}
}
//In order to support CubeMaps, we will need to select a folder that contains a series of JPGs, named as described
//in the CubeMapTexture documentation ie "posx.jpg;negx.jpg;posy.jpg;negy.jpg;posz.jpg;negz.jpg"
Component
{
id: channelCubeMap
View3D
{
id: view3d
anchors.fill: parent
property real lastX: 0
property real lastY: 0
property bool mousePressed: false
property real yaw: 0
property real pitch: 0
environment: SceneEnvironment
{
backgroundMode: SceneEnvironment.SkyBoxCubeMap
skyBoxCubeMap: CubeMapTexture
{
source: channel.source !== "" ? Qt.resolvedUrl(channel.source) + "/%p.jpg" : ""
}
}
camera: PerspectiveCamera
{
id: camera
position: Qt.vector3d(0, 0, 10)
}
function updateCamera()
{
yaw -= (channel.iMouse.x - lastX) * 0.5
pitch -= (channel.iMouse.y - lastY) * -0.5
pitch = Math.max(-89, Math.min(89, pitch))
lastX = channel.iMouse.x
lastY = channel.iMouse.y
var radYaw = yaw * Math.PI / 180
var radPitch = pitch * Math.PI / 180
var x = Math.cos(radPitch) * Math.sin(radYaw)
var y = Math.sin(radPitch)
var z = Math.cos(radPitch) * Math.cos(radYaw)
camera.eulerRotation = Qt.vector3d((radPitch * 180 / Math.PI), (radYaw * 180 / Math.PI), 0)
}
Connections
{
target: channel
function onIMouseChanged()
{
view3d.updateCamera()
}
}
}
}
// Supporting this as an mp3 feels kinda silly. Should really figure out how to capture desktop audio with qml
// UPDATE: This is not currently supported in QML, so we will need to implement this in C++ later
// Component
// {
// id: channelAudio
// Rectangle
// {
// anchors.fill: parent
// color: "black"
// property var audioData: new Array(512).fill(0)
// property alias texture: audioTexture
// MediaDevices
// {
// id: devices
// }
// AudioInput
// {
// id: audioInput
// device: devices.defaultAudioInput
// }
// CaptureSession
// {
// id: captureSession
// audioInput: audioInput
// }
// Timer
// {
// interval: 16 // ~60fps update
// running: true
// repeat: true
// onTriggered:
// {
// // Get audio levels and update texture
// // let buffer = audioInput.probe.audioBuffer()
// // if (buffer)
// // {
// // for (let i = 0; i < Math.min(buffer.length, 512); i++) {
// // audioData[i] = Math.abs(buffer[i])
// // }
// // audioTexture.update()
// // }
// }
// }
// ShaderEffectSource
// {
// id: audioTexture
// width: 512
// height: 1
// format: ShaderEffectSource.Alpha
// smooth: false
// hideSource: true
// sourceItem: Row
// {
// Repeater
// {
// model: 512
// Rectangle
// {
// width: 1
// height: 1
// color: Qt.rgba(1, 1, 1, root.audioData[index])
// }
// }
// }
// }
// Component.onCompleted:
// {
// //captureSession.start()
// }
// }
// }
}

View File

@@ -0,0 +1,623 @@
/*
* Komplex Wallpaper Engine
* Copyright (C) 2025 @DigitalArtifex | github.com/DigitalArtifex
*
* ShaderChannelConfiguration.qml
*
* This component is used to configure the shader channels for the output shader.
* It allows the user to select a file or folder for each channel type (Image,
* Video, Shader, Audio, CubeMap) and sets the appropriate properties on the
* ShaderChannel component.
*
* --------------------------------------------------------------------------------------------------------
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>
*/
pragma ComponentBehavior: Bound
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.plasma.core as PlasmaCore
import QtCore
import Qt.labs.folderlistmodel 2.15
import QtQuick.Dialogs as Dialogs
import com.github.digitalartifex.komplex 1.0 as Komplex
Item
{
signal accepted()
signal rejected()
property bool file: true
property string selectionTitle: "Select a file"
property var selectionFilter: ["All Files (*)"]
// The current folder is used to set the initial folder for the file and folder dialogs
property string currentFolder: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
// Individual folders for each channel type
property string shaderFolder: shaderPackModel.shadersPath
property string imageFolder: shaderPackModel.imagesPath
property string videoFolder: shaderPackModel.videosPath
property string cubemapFolder: shaderPackModel.cubeMapsPath
property alias tmp_source: sourceEdit.text
property int tmp_type: 1
property alias tmp_timeScale: speedSlider.value
property alias tmp_resolution_scale: resolutionScaleSlider.value
property alias tmp_resolution_x: resolutionXEdit.value
property alias tmp_resolution_y: resolutionYEdit.value
property bool tmp_enabled: tmp_source !== ""
property Palette palette
property string source
property int type
property real timeScale
property real resolution_scale
property int resolution_x
property int resolution_y
property bool enabled
id: window
Komplex.ShaderPackModel
{
id: shaderPackModel
}
// The selection model contains the list of available shader channel types
// and their respective properties.
ListModel
{
id: selectionModel
// Commented out Audio channel as it is not implemented yet
// ListElement
// {
// file: true
// name: "Audio"
// icon: "qrc:/icons/audio.svg"
// title: "Select an Audio File"
// filter: "MP3 Files (*.mp3):WAV Files (*.wav)"
// type: ShaderChannel.Type.AudioChannel
// }
ListElement
{
file: false
name: "Cubemap"
icon: "./icons/cube.svg"
title: "Select a CubeMap folder"
filter: ""
type: ShaderChannel.Type.CubeMapChannel
}
ListElement
{
file: true
name: "Image"
icon: "./icons/image.svg"
title: "Select an Image File"
filter: "Image Files (*.jpg, *.jpeg, *.png, *.svg)"
type: ShaderChannel.Type.ImageChannel
}
ListElement
{
file: true
name: "Shader"
icon: "./icons/3d-glasses.svg"
title: "Select a Shader File"
filter: "Shader Files (*frag.qsb)"
type: ShaderChannel.Type.ShaderChannel
}
ListElement
{
file: true
name: "Video"
icon: "./icons/video.svg"
title: "Select a Video File"
filter: "Video Files (*.mov, *.avi, *.mkv, *.mp4)"
type: ShaderChannel.Type.VideoChannel
}
function indexOf(type)
{
for(var i = 0; i < count; ++i) if (get(i).type == type)
return i
return -1
}
}
Component
{
id: selectionDelegate
Item
{
required property int index
required property int type
required property string name
required property string icon
required property string title
required property var filter
required property bool file
width: 100
height: 75
ColumnLayout
{
anchors.fill: parent
Image
{
source: icon
Layout.preferredHeight: 50
Layout.preferredWidth: 50
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
}
Text
{
id: label
text: name
color: "white"
horizontalAlignment: Text.AlignHCenter
Layout.fillHeight: false
Layout.fillWidth: true
Layout.alignment: Qt.AlignBottom
}
}
MouseArea
{
hoverEnabled: true
anchors.fill: parent
z: 9000 // this is dumb but I can't get the mouse area to propogate without it
onClicked:
{
list.currentIndex = parent.index
window.tmp_type = parent.type
window.selectionFilter = parent.filter.split(':')
window.selectionTitle = parent.title
window.file = parent.file
switch(parent.type)
{
// case ShaderChannel.Type.AudioChannel:
// break;
case ShaderChannel.Type.CubeMapChannel:
window.currentFolder = window.cubemapFolder
break;
case ShaderChannel.Type.ImageChannel:
window.currentFolder = window.imageFolder
break;
case ShaderChannel.Type.ShaderChannel:
window.currentFolder = window.shaderFolder
break;
case ShaderChannel.Type.VideoChannel:
window.currentFolder = window.videoFolder
break;
}
}
}
}
}
Component
{
id: highlight
Rectangle
{
width: list.currentItem.width; height: list.currentItem.height
color: "lightsteelblue"; radius: 5
y: list.currentItem.y
Behavior on y
{
SpringAnimation
{
spring: 2
damping: 0.1
}
}
}
}
ColumnLayout
{
anchors.fill: parent
spacing: 10
ListView
{
id: list
model: selectionModel
Layout.fillWidth: true
Layout.preferredHeight: 100
Layout.alignment: Qt.AlignTop
delegate: selectionDelegate
orientation: Qt.Horizontal
clip: true
highlight: highlight
highlightFollowsCurrentItem: true
focus: true
}
RowLayout
{
Layout.alignment: Qt.AlignTop
Label
{
color: palette.text
verticalAlignment: Text.AlignVCenter
text: i18nd("@option:shader_source_label", "Source")
Layout.preferredWidth: Kirigami.Units.gridUnit * 6
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
}
TextField
{
id: sourceEdit
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
}
Button
{
icon.name: "folder-symbolic"
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
onClicked:
{
if(window.file === true)
fileDialog.open()
else
folderDialog.open()
}
}
}
RowLayout
{
Layout.alignment: Qt.AlignTop
id: speedLayout
Kirigami.FormData.label: i18nd("com.github.digitalartifex.komplex", "Shader speed:")
Label
{
color: palette.text
verticalAlignment: Qt.AlignVCenter
Layout.preferredWidth: Kirigami.Units.gridUnit * 6
text: i18nd("@option:time_scale_label", "Shader Speed")
}
Slider
{
id: speedSlider
Layout.fillWidth: true
from: 0
to: 16
stepSize: 0.1
onValueChanged: shaderSpeedField.text = String(value.toFixed(2));
}
TextField
{
id: shaderSpeedField
inputMethodHints: Qt.ImhFormattedNumbersOnly
horizontalAlignment: Text.AlignRight
Layout.preferredWidth: Kirigami.Units.gridUnit * 4
onEditingFinished: () =>
{
let inputValue = parseFloat(text);
if (isNaN(inputValue) || inputValue < speedSlider.from)
inputValue = speedSlider.from;
else if (inputValue > speedSlider.to)
inputValue = speedSlider.to;
text = inputValue.toFixed(2);
speedSlider.value = inputValue;
}
Keys.onPressed: (event) =>
{
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter)
{
shaderSpeedField.focus = false; // Unfocus the TextField
event.accepted = true; // Prevent further propagation of the key event
}
}
background: Rectangle
{
color: shaderSpeedField.activeFocus ? palette.base : "transparent"
border.color: shaderSpeedField.activeFocus ? palette.highlight : "transparent"
border.width: 1
radius: 4
anchors.fill: shaderSpeedField
anchors.margins: -2
}
}
}
RowLayout
{
Layout.alignment: Qt.AlignTop
id: resolutionScaleLayout
Kirigami.FormData.label: i18nd("com.github.digitalartifex.komplex", "Shader speed:")
Label
{
color: palette.text
verticalAlignment: Qt.AlignVCenter
text: i18nd("@option:time_scale_label", "Resolution Scale")
Layout.preferredWidth: Kirigami.Units.gridUnit * 6
}
Slider
{
id: resolutionScaleSlider
Layout.fillWidth: true
from: 0.125
to: 1
stepSize: 0.01
onValueChanged: resolutionScaleField.text = String(value.toFixed(3));
}
TextField
{
id: resolutionScaleField
inputMethodHints: Qt.ImhFormattedNumbersOnly
horizontalAlignment: Text.AlignRight
Layout.preferredWidth: Kirigami.Units.gridUnit * 4
onEditingFinished: () =>
{
let inputValue = parseFloat(text);
if (isNaN(inputValue) || inputValue < resolutionScaleSlider.from)
inputValue = resolutionScaleSlider.from;
else if (inputValue > resolutionScaleSlider.to)
inputValue = resolutionScaleSlider.to;
text = inputValue.toFixed(3);
resolutionScaleSlider.value = inputValue;
}
Keys.onPressed: (event) =>
{
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter)
{
resolutionScaleField.focus = false; // Unfocus the TextField
event.accepted = true; // Prevent further propagation of the key event
}
}
background: Rectangle
{
color: resolutionScaleField.activeFocus ? palette.base : "transparent"
border.color: resolutionScaleField.activeFocus ? palette.highlight : "transparent"
border.width: 1
radius: 4
anchors.fill: resolutionScaleField
anchors.margins: -2
}
}
}
RowLayout
{
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
Label
{
color: palette.text
text: i18nd("@option:resolution_label_x", "Resolution X")
Layout.preferredWidth: Kirigami.Units.gridUnit * 6
}
TextField
{
property int value
Layout.preferredHeight: 35
Layout.fillWidth: true
id: resolutionXEdit
text: value
onEditingFinished: () =>
{
var inputValue = parseInt(text);
if (isNaN(inputValue) || inputValue < 0)
inputValue = 0
value = inputValue;
}
Keys.onPressed: (event) =>
{
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter)
{
resolutionScaleField.focus = false; // Unfocus the TextField
event.accepted = true; // Prevent further propagation of the key event
}
}
}
Label
{
color: palette.text
text: i18nd("@option:resolution_label_y", "Resolution Y")
Layout.preferredWidth: Kirigami.Units.gridUnit * 6
}
TextField
{
property int value
Layout.preferredHeight: 35
Layout.fillWidth: true
id: resolutionYEdit
text: value
onEditingFinished: () =>
{
var inputValue = parseInt(text);
if (isNaN(inputValue) || inputValue < 0)
inputValue = 0
value = inputValue;
}
Keys.onPressed: (event) =>
{
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter)
{
resolutionScaleField.focus = false; // Unfocus the TextField
event.accepted = true; // Prevent further propagation of the key event
}
}
}
}
RowLayout
{
Layout.fillWidth: true
Layout.alignment: Qt.AlignBottom | Qt.AlignRight
Layout.bottomMargin: 5
Button
{
id: okayButton
text: "Okay"
onClicked: window.accept()
Layout.alignment: Qt.AlignRight
}
Button
{
id: cancelButton
text: "Cancel"
onClicked: window.reject()
Layout.alignment: Qt.AlignRight
}
}
}
// FileDialog is used to select a file for the Image, Shader, and Video channels
Dialogs.FileDialog
{
id: fileDialog
currentFolder: "file://" + window.currentFolder
nameFilters: window.selectionFilter
title: window.selectionTitle
onAccepted: window.tmp_source = selectedFile
}
// FolderDialog is used to select a folder for the CubeMap channel
Dialogs.FolderDialog
{
id: folderDialog
currentFolder: window.cubemapFolder
title: window.selectionTitle
onAccepted: window.tmp_source = selectedFolder
}
function accept()
{
// copy over temp values
source = tmp_source
type = tmp_type
timeScale = tmp_timeScale
resolution_scale = tmp_resolution_scale
resolution_x = tmp_resolution_x
resolution_y = tmp_resolution_y
enabled = tmp_enabled
// Emit the accepted signal and reset the selection
window.accepted()
}
function reject()
{
// Emit the rejected signal and reset the selection
resetSelection()
window.rejected()
}
function configureChannel()
{
resetSelection()
}
// Function to update the current selection based on the channel type
function updateCurrentSelection()
{
// Set the dialog properties based on the channel properties
switch(type)
{
case ShaderChannel.Type.CubeMapChannel:
window.currentFolder = window.cubemapFolder
break;
case ShaderChannel.Type.ImageChannel:
window.currentFolder = window.imageFolder
break;
case ShaderChannel.Type.ShaderChannel:
window.currentFolder = window.shaderFolder
break;
case ShaderChannel.Type.VideoChannel:
window.currentFolder = window.videoFolder
break;
}
// Set the current selection index
list.currentIndex = selectionModel.indexOf(type)
}
// Function to reset the selection to default values
function resetSelection()
{
tmp_source = source
tmp_timeScale = timeScale
tmp_resolution_scale = resolution_scale
tmp_resolution_x = resolution_x
tmp_resolution_y = resolution_y
tmp_type = type
updateCurrentSelection()
}
}

View File

@@ -0,0 +1,226 @@
/*
* Komplex Wallpaper Engine
* Copyright (C) 2025 @DigitalArtifex | github.com/DigitalArtifex
*
* This was originally part of the KDE Shader Wallpaper Project, which was the inspiration for this project.
* It has been modified to use the new channel structure and is being used to support
* ShaderToy imports.
*
* --------------------------------------------------------------------------------------------------------
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software uses some of the QML code from JaredTao/jared2020@163.com's ToyShader for Android.
* See: https://github.com/jaredtao/TaoShaderToy/
*/
pragma ComponentBehavior: Bound
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs as Dialogs
import org.kde.plasma.core as PlasmaCore
Item
{
property real pixelRatio: 1 //This will (hopefully) be set to PlasmaCore.Units.devicePixelRatio in onCompleted
property vector3d iResolution: Qt.vector3d(wallpaper.configuration.resolution_x,wallpaper.configuration.resolution_y,1)//width, height, pixel aspect ratio
property real iTime: 0 //used by most motion shaders
property real iTimeDelta: iTime
property var iChannelTime: [iTime, iTime, iTime, iTime] //individual channel time values
property real iSampleRate: 44100 //used by audio shaders
property int iFrame: 0
property real iFrameRate: wallpaper.configuration.framerate_limit // Default frame rate for the shader
property vector4d iMouse
property var iDate
property bool running: windowModel.runShader // Controls whether the wallpaper is running or paused
// Individual channel resolutions to customize performance and quality
property var iChannelResolution: [Qt.vector3d(wallpaper.configuration.iChannel0_resolution_x, wallpaper.configuration.iChannel0_resolution_y, pixelRatio),
Qt.vector3d(wallpaper.configuration.iChannel1_resolution_x, wallpaper.configuration.iChannel1_resolution_y, pixelRatio),
Qt.vector3d(wallpaper.configuration.iChannel2_resolution_x, wallpaper.configuration.iChannel2_resolution_y, pixelRatio),
Qt.vector3d(wallpaper.configuration.iChannel3_resolution_x, wallpaper.configuration.iChannel3_resolution_y, pixelRatio)]
id: mainItem
// The WindowModel is used to manage the interaction with the desktop environment
WindowModel
{
id: windowModel
screenGeometry: mainItem.parent.screenGeometry
}
Rectangle
{
anchors.fill: parent
color: "black"
Text
{
color: "white"
text: "<h1>" + wallpaper.configuration.selectedShaderPath + "</h1>"
}
// Setup the shader channels (iChannel0, iChannel1, iChannel2, iChannel3)
// These channels are used to pass channel data to the shader sources and can
// be configured to use different types of media (Image, Video, Shader, Audio, CubeMap)
ShaderChannel
{
//Fallback to a channel if the output channel is not set or there was an error loading the shader
visible: wallpaper.configuration.iChannel0_flag && (channelOutput.source === "" || channelOutput.source === undefined)
iTime: mainItem.iTime
iMouse: mainItem.iMouse
iResolution: mainItem.iChannelResolution[0]
id: channel0
anchors.fill: parent
type: wallpaper.configuration.iChannel0_flag ? wallpaper.configuration.iChannel0_type : 0
source: wallpaper.configuration.iChannel0_flag ? Qt.resolvedUrl(wallpaper.configuration.iChannel0) : ""
}
ShaderChannel
{
//Fallback to a channel if the output channel is not set or there was an error loading the shader
visible: wallpaper.configuration.iChannel1_flag && (channelOutput.source === "" || channelOutput.source === undefined)
iTime: mainItem.iTime
iResolution: mainItem.iChannelResolution[1]
id: channel1
anchors.fill: parent
type: wallpaper.configuration.iChannel1_flag ? wallpaper.configuration.iChannel1_type : 0
source: wallpaper.configuration.iChannel1_flag ? Qt.resolvedUrl(wallpaper.configuration.iChannel1) : ""
}
ShaderChannel
{
//Fallback to a channel if the output channel is not set or there was an error loading the shader
visible: wallpaper.configuration.iChannel2_flag && (channelOutput.source === "" || channelOutput.source === undefined)
iTime: mainItem.iTime
iResolution: mainItem.iChannelResolution[2]
id: channel2
anchors.fill: parent
type: wallpaper.configuration.iChannel2_flag ? wallpaper.configuration.iChannel2_type : 0
source: wallpaper.configuration.iChannel2_flag ? Qt.resolvedUrl(wallpaper.configuration.iChannel2) : ""
}
ShaderChannel
{
//Fallback to a channel if the output channel is not set or there was an error loading the shader
visible: wallpaper.configuration.iChannel3_flag && (channelOutput.source === "" || channelOutput.source === undefined)
iTime: mainItem.iTime
iChannelTime: [mainItem.iTime, mainItem.iTime, mainItem.iTime, mainItem.iTime]
iResolution: mainItem.iChannelResolution[3]
iDate: mainItem.iDate
id: channel3
anchors.fill: parent
type: wallpaper.configuration.iChannel3_flag ? wallpaper.configuration.iChannel3_type : 0
source: wallpaper.configuration.iChannel3_flag ? Qt.resolvedUrl(wallpaper.configuration.iChannel3) : ""
}
// The output channel that combines all the input channels and displays the final shader output
// This channel must be set to a shader source file that has been pre-compiled to a QSB Fragment Shader
ShaderChannel
{
iTime: mainItem.iTime
iMouse: mainItem.iMouse
iResolution: mainItem.iResolution
id: channelOutput
anchors.fill: parent
type: ShaderChannel.Type.ShaderChannel
source: wallpaper.configuration.selectedShaderPath ? wallpaper.configuration.selectedShaderPath : ""
iChannel0: channel0
iChannel1: channel1
iChannel2: channel2
iChannel3: channel3
visible: true // Set to true to display the output
}
// To save on performance, just use one timer for all channels
Timer
{
id: channelTimer
//Not entirely sure if this will actually limit the frame rate
interval: (1 / mainItem.iFrameRate) * 1000 //fps to ms cycles :: fps = 60 = 1 / 60 = 0.01666 * 1000 = 16
repeat: true
running: mainItem.running
triggeredOnStart: true
onTriggered:
{
var date = new Date();
var startOfDay = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
var secondsSinceMidnight = (date - startOfDay) / 1000;
mainItem.iTime += (interval / 1000) * (wallpaper.configuration.shaderSpeed ? wallpaper.configuration.shaderSpeed : 1.0)
mainItem.iDate = Qt.vector4d(date.getFullYear(), date.getMonth() + 1, date.getDate(), secondsSinceMidnight)
}
}
}
Component.onCompleted:
{
Qt.createQmlObject(`import QtQuick
MouseArea
{
id: mouseTrackingArea
propagateComposedEvents: true
preventStealing: false
enabled: wallpaper.configuration.mouseAllowed
anchors.fill: parent
hoverEnabled: true
onPositionChanged: (mouse) => {
mouse.accepted = false
mainItem.iMouse.x = mouse.x * wallpaper.configuration.mouseSpeedBias
mainItem.iMouse.y = -mouse.y * wallpaper.configuration.mouseSpeedBias
}
onClicked:(mouse) => {
mouse.accepted = false
mainItem.iMouse.z = mouse.x
mainItem.iMouse.w = mouse.y
}
// this still doesnt work... guess a C++ wrapper is all that can be done?
onPressed:(mouse) => {
mouse.accepted = false
}
onPressAndHold:(mouse) => {
mouse.accepted = false
}
onDoubleClicked:(mouse) => {
mouse.accepted = false
}
//cancelled, entered, and exited do not pass mouse events, so we can remove them
onReleased:(mouse) => {
mouse.accepted = false
}
onWheel: (mouse) => {
mouse.accepted = false
}
}`, parent.parent, "mouseTrackerArea");
// Initialize pixelRatio for shader use
pixelRatio = 1 //PlasmaCore.Units.devicePixelRatio was removed in Plasma6
}
}

View File

@@ -0,0 +1,178 @@
/*
* Komplex Wallpaper Engine
* Copyright (C) 2025 @DigitalArtifex | github.com/DigitalArtifex
*
* WindowModel.qml
*
* Copyright 2018 Rog131 <samrog131@hotmail.com>
* Copyright 2019 adhe <adhemarks2@gmail.com>
* Copyright 2024 Luis Bocanegra <luisbocanegra17b@gmail.com>
*
* This file was part of the KDE Shader Wallpaper project and is used to track the
* desktop state.
*
* --------------------------------------------------------------------------------------------------------
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software uses some of the QML code from JaredTao/jared2020@163.com's ToyShader for Android.
* See: https://github.com/jaredtao/TaoShaderToy/
*/
pragma ComponentBehavior: Bound
import QtQuick
import org.kde.taskmanager 0.1 as TaskManager
import org.kde.kwindowsystem
import com.github.digitalartifex.komplex as Komplex
Item
{
id: wModel
property var screenGeometry
property int pauseMode: wallpaper.configuration.pauseMode
property bool runShader: false
property bool maximizedExists: false
property bool visibleExists: false
property bool activeExists: false
property var abstractTasksModel: TaskManager.AbstractTasksModel
property var appId: abstractTasksModel.AppId
property var isWindow: abstractTasksModel.IsWindow
property var isMinimized: abstractTasksModel.IsMinimized
property var isMaximized: abstractTasksModel.IsMaximized
property var isFullScreen: abstractTasksModel.IsFullScreen
property var isActive: abstractTasksModel.IsActive
property var isHidden: abstractTasksModel.IsHidden
property bool activeScreenOnly: wallpaper.configuration.checkActiveScreen
property var excludeWindows: wallpaper.configuration.excludeWindows
Komplex.ShaderPackModel
{
id: shaderPackModel
}
Connections
{
target: wallpaper.configuration
function onValueChanged()
{
wModel.updateWindowsInfo();
}
}
Connections
{
target: KWindowSystem
function onShowingDesktopChanged()
{
wModel.updateWindowsInfo();
}
}
onPauseModeChanged:
{
updateWindowsInfo();
}
function updateRun()
{
let shouldRun = true;
switch (pauseMode)
{
case 0:
shouldRun = !maximizedExists;
break;
case 1:
shouldRun = !activeExists;
break;
case 2:
shouldRun = !visibleExists;
break;
case 3:
shouldRun = true;
}
runShader = shouldRun;
}
TaskManager.VirtualDesktopInfo
{
id: virtualDesktopInfo
}
TaskManager.ActivityInfo
{
id: activityInfo
readonly property string nullUuid: "00000000-0000-0000-0000-000000000000"
}
TaskManager.TasksModel
{
id: tasksModel
sortMode: TaskManager.TasksModel.SortVirtualDesktop
groupMode: TaskManager.TasksModel.GroupDisabled
virtualDesktop: virtualDesktopInfo.currentDesktop
activity: activityInfo.currentActivity
screenGeometry: wModel.screenGeometry
filterByVirtualDesktop: true
filterByScreen: parent.activeScreenOnly
filterByActivity: true
filterMinimized: true
onActiveTaskChanged:
{
updateWindowsInfo();
}
onDataChanged:
{
updateWindowsInfo();
}
onCountChanged:
{
updateWindowsInfo();
}
}
function updateWindowsInfo()
{
let activeCount = 0;
let visibleCount = 0;
let maximizedCount = 0;
if (!KWindowSystem.showingDesktop)
{
for (var i = 0; i < tasksModel.count; i++)
{
const currentTask = tasksModel.index(i, 0);
// Long line
if (currentTask === undefined || excludeWindows.includes(tasksModel.data(currentTask, appId).replace(/\.desktop$/, "")) || tasksModel.data(currentTask, isHidden) || !tasksModel.data(currentTask, isWindow) || tasksModel.data(currentTask, isMinimized))
continue;
visibleCount += 1;
if (tasksModel.data(currentTask, isMaximized) || tasksModel.data(currentTask, isFullScreen))
maximizedCount += 1;
if (tasksModel.data(currentTask, isActive))
activeCount += 1;
}
}
visibleExists = visibleCount > 0;
maximizedExists = maximizedCount > 0;
activeExists = activeCount > 0;
updateRun();
}
}

View File

@@ -0,0 +1,740 @@
/*
* Komplex Wallpaper Engine
* Copyright (C) 2025 @DigitalArtifex | github.com/DigitalArtifex
*
* config.qml
*
* This component provides a configuration interface for the Komplex Wallpaper Engine,
* allowing users to customize shader settings, channel configurations, and other
* parameters related to the wallpaper engine.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>
*/
// pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.plasma.core as PlasmaCore
import QtCore
import Qt.labs.folderlistmodel 2.15
import com.github.digitalartifex.komplex 1.0 as Komplex
Kirigami.FormLayout
{
id: root
twinFormLayouts: parentLayout // required by parent
property alias formLayout: root // required by parent
property alias cfg_pauseMode: pauseModeCombo.currentIndex
property alias cfg_isPaused: runningCombo.checked
property alias cfg_selectedShaderIndex: selectedShader.currentIndex
property alias cfg_selectedShaderPath: selectedShader.shader
property alias cfg_iChannel0: shaderChannelConfig0.source
property alias cfg_iChannel1: shaderChannelConfig1.source
property alias cfg_iChannel2: shaderChannelConfig2.source
property alias cfg_iChannel3: shaderChannelConfig3.source
property alias cfg_iChannel0_flag: shaderChannelConfig0.enabled
property alias cfg_iChannel1_flag: shaderChannelConfig1.enabled
property alias cfg_iChannel2_flag: shaderChannelConfig2.enabled
property alias cfg_iChannel3_flag: shaderChannelConfig3.enabled
property alias cfg_iChannel0_type: shaderChannelConfig0.type
property alias cfg_iChannel1_type: shaderChannelConfig1.type
property alias cfg_iChannel2_type: shaderChannelConfig2.type
property alias cfg_iChannel3_type: shaderChannelConfig3.type
property alias cfg_iChannel0_timeScale: shaderChannelConfig0.timeScale
property alias cfg_iChannel1_timeScale: shaderChannelConfig1.timeScale
property alias cfg_iChannel2_timeScale: shaderChannelConfig2.timeScale
property alias cfg_iChannel3_timeScale: shaderChannelConfig3.timeScale
property alias cfg_iChannel0_resolution_scale: shaderChannelConfig0.resolution_scale
property alias cfg_iChannel1_resolution_scale: shaderChannelConfig1.resolution_scale
property alias cfg_iChannel2_resolution_scale: shaderChannelConfig2.resolution_scale
property alias cfg_iChannel3_resolution_scale: shaderChannelConfig3.resolution_scale
property alias cfg_iChannel0_resolution_x: shaderChannelConfig0.resolution_x
property alias cfg_iChannel0_resolution_y: shaderChannelConfig0.resolution_y
property alias cfg_iChannel1_resolution_x: shaderChannelConfig1.resolution_x
property alias cfg_iChannel1_resolution_y: shaderChannelConfig1.resolution_y
property alias cfg_iChannel2_resolution_x: shaderChannelConfig2.resolution_x
property alias cfg_iChannel2_resolution_y: shaderChannelConfig2.resolution_y
property alias cfg_iChannel3_resolution_x: shaderChannelConfig3.resolution_x
property alias cfg_iChannel3_resolution_y: shaderChannelConfig3.resolution_y
property alias cfg_shaderSpeed: speedSlider.value
property alias cfg_mouseSpeedBias: mouseBiasSlider.value
property alias cfg_mouseAllowed: mouseEnableButton.checked
property bool cfg_infoPlasma6Preview_dismissed
property bool cfg_warningResources_dismissed
property bool cfg_emergencyHelp_dismissed
property bool cfg_infoiChannelSettings_dismissed
property alias cfg_checkActiveScreen: activeScreenOnlyCheckbox.checked
property alias cfg_excludeWindows: excludeWindows.windows
property alias cfg_running: runningCombo.checked
property alias cfg_shader_package: selectedShaderPack.shader
property alias cfg_shader_package_index: selectedShaderPack.currentIndex
property alias cfg_komplex_mode: engineModeSelect.currentIndex
Palette
{
id: palette
}
Komplex.ShaderPackModel
{
id: shaderPackModel
onJsonChanged:
{
// Handle the JSON change if needed
console.log("Shader pack JSON changed:", shaderPackModel.json);
root.cfg_shader_package = shaderPackModel.shaderPackPath + "/" + shaderPackModel.shaderPackName + "/pack.json"
}
}
// Engine Mode Selection
RowLayout
{
Kirigami.FormData.label: i18nd("com.github.digitalartifex.komplex", "Engine Mode:")
ComboBox
{
Layout.preferredWidth: Kirigami.Units.gridUnit * 11
id: engineModeSelect
currentIndex: root.cfg_komplex_mode
onCurrentIndexChanged: root.cfg_komplex_mode = currentIndex
textRole: "label"
model: [
{ "label": i18nd("@option:komplex_mode", "ShaderToys") },
{ "label": i18nd("@option:komplex_mode", "Komplex") }
]
}
}
TabBar
{
Layout.fillWidth: true
id: navBar
width: parent.width
TabButton {
text: qsTr("Shader")
}
TabButton {
text: qsTr("Shader Settings")
}
TabButton {
text: qsTr("Extra Settings")
}
}
RowLayout
{
Layout.fillWidth: true
Kirigami.InlineMessage
{
id: warningResources
Layout.fillWidth: true
type: Kirigami.MessageType.Warning
text: qsTr("Some shaders might consume more power and resources than others, beware!")
showCloseButton: true
visible: !root.cfg_warningResources_dismissed
onVisibleChanged: () =>
{
root.cfg_warningResources_dismissed = true;
}
}
}
RowLayout
{
visible: root.cfg_komplex_mode === 0 && navBar.currentIndex === 0
id: shaderOutputLayout
spacing: Kirigami.Units.smallSpacing
Kirigami.FormData.label: i18nd("com.github.digitalartifex.komplex", "Output Shader:")
ComboBox
{
property string shader
id: selectedShader
Layout.preferredWidth: Kirigami.Units.gridUnit * 11
model: FolderListModel
{
id: folderListModel
showDirs: false
showFiles: true
showHidden: true
nameFilters: ["*.frag.qsb"]
folder: "file://" + shaderPackModel.shadersPath + "/generative"
}
delegate: Component
{
id: folderListDelegate
ItemDelegate
{
width: parent ? parent.width : 0
text: fileBaseName.replace("_", " ").charAt(0).toUpperCase() + fileBaseName.replace("_", " ").slice(1)
}
}
textRole: "fileBaseName"
currentIndex: root.cfg_selectedShaderIndex
displayText: currentIndex === -1 ? "Custom File" : currentText.replace("_", " ").charAt(0).toUpperCase() + currentText.replace("_", " ").slice(1)
onCurrentTextChanged:
{
root.cfg_selectedShaderIndex = currentIndex;
if (root.cfg_selectedShaderIndex === -1)
return;
var source = model.get(currentIndex, "fileUrl");
shader = source;
}
}
Button
{
id: shaderFileButton
icon.name: "folder-symbolic"
text: i18nd("@button:toggle_select_shader", "Select File")
Layout.preferredWidth: Kirigami.Units.gridUnit * 9
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
onClicked:
{
fileDialog.currentFolder = "file://" + shaderPackModel.shadersPath + "/generative";
fileDialog.open();
}
}
FileDialog
{
id: fileDialog
fileMode: FileDialog.OpenFile
title: i18nd("@dialog_title:choose_shader", "Choose a shader")
// will accept and auto convert .frag in the near future
nameFilters: ["Shader files (*.frag.qsb)", "All files (*)"]
visible: false
currentFolder: `${StandardPaths.writableLocation(StandardPaths.HomeLocation)}/.local/share/komplex/shaders/`
onAccepted:
{
root.cfg_selectedShaderIndex = -1;
root.cfg_selectedShaderPath = selectedFile;
}
}
}
// iChannel0 Configuration
RowLayout
{
visible: root.cfg_komplex_mode === 0 && navBar.currentIndex === 0
id: channel0ConfigLayout
Kirigami.FormData.label: i18nd("com.github.digitalartifex.komplex", "Channel 1:")
spacing: Kirigami.Units.smallSpacing
Text
{
Layout.preferredWidth: Kirigami.Units.gridUnit * 11
color: palette.text
text: i18nd("@iChannel0_source", root.cfg_iChannel0 ? root.cfg_iChannel0 : "No source selected")
verticalAlignment: Text.AlignVCenter
elide: Text.ElideLeft
}
Button
{
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
icon.name: "configure-symbolic"
onClicked: () =>
{
shaderChannelConfig0.configureChannel();
shaderChannelOverlay0.open();
}
}
}
Kirigami.OverlaySheet
{
id: shaderChannelOverlay0
parent: applicationWindow().overlay
implicitHeight: 400
ShaderChannelConfiguration
{
id: shaderChannelConfig0
palette: palette
height: 350
onAccepted: shaderChannelOverlay0.close(); // Close the overlay after configuration
onRejected: shaderChannelOverlay0.close();
}
}
// iChannel1 Configuration
RowLayout
{
visible: root.cfg_komplex_mode === 0 && navBar.currentIndex === 0
id: channel1ConfigLayout
Kirigami.FormData.label: i18nd("com.github.digitalartifex.komplex", "Channel 2:")
Text
{
Layout.preferredWidth: Kirigami.Units.gridUnit * 11
color: palette.text
text: i18nd("@iChannel1_source", root.cfg_iChannel1 ? root.cfg_iChannel1 : "No source selected")
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
Button
{
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
icon.name: "configure-symbolic"
onClicked: () =>
{
shaderChannelConfig1.configureChannel();
shaderChannelOverlay1.open();
}
}
}
Kirigami.OverlaySheet
{
id: shaderChannelOverlay1
parent: applicationWindow().overlay
implicitHeight: 400
ShaderChannelConfiguration
{
id: shaderChannelConfig1
height: 350
onAccepted: shaderChannelOverlay1.close(); // Close the overlay after configuration
onRejected: shaderChannelOverlay1.close();
}
}
// iChannel2 Configuration
RowLayout
{
visible: root.cfg_komplex_mode === 0 && navBar.currentIndex === 0
id: channel2ConfigLayout
Kirigami.FormData.label: i18nd("com.github.digitalartifex.komplex", "Channel 3:")
Text
{
Layout.preferredWidth: Kirigami.Units.gridUnit * 11
color: palette.text
text: i18nd("@iChannel2_source", root.cfg_iChannel2 ? root.cfg_iChannel2 : "No source selected")
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
Button
{
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
icon.name: "configure-symbolic"
onClicked: () =>
{
shaderChannelConfig2.configureChannel();
shaderChannelOverlay2.open();
}
}
}
Kirigami.OverlaySheet
{
id: shaderChannelOverlay2
parent: applicationWindow().overlay
implicitHeight: 400
ShaderChannelConfiguration
{
id: shaderChannelConfig2
height: 350
onAccepted: shaderChannelOverlay2.close(); // Close the overlay after configuration
onRejected: shaderChannelOverlay2.close();
}
}
// iChannel3 Configuration
RowLayout
{
visible: root.cfg_komplex_mode === 0 && navBar.currentIndex === 0
Layout.fillWidth: true
Kirigami.FormData.label: i18nd("com.github.digitalartifex.komplex", "Channel 4:")
Text
{
Layout.preferredWidth: Kirigami.Units.gridUnit * 11
color: palette.text
text: i18nd("@iChannel0_source", root.cfg_iChannel3 ? root.cfg_iChannel3 : "No source selected")
verticalAlignment: Text.AlignVCenter
elide: Text.ElideLeft
}
Button
{
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
icon.name: "configure-symbolic"
onClicked: () =>
{
shaderChannelConfig3.configureChannel();
shaderChannelOverlay3.open();
}
}
}
Kirigami.OverlaySheet
{
id: shaderChannelOverlay3
parent: applicationWindow().overlay
implicitHeight: 400
ShaderChannelConfiguration
{
id: shaderChannelConfig3
height: 350
onAccepted: shaderChannelOverlay3.close(); // Close the overlay after configuration
onRejected: shaderChannelOverlay3.close();
}
}
RowLayout
{
visible: navBar.currentIndex === 1
id: speedLayout
Layout.fillWidth: true
Kirigami.FormData.label: i18nd("com.github.digitalartifex.komplex", "Shader speed:")
Slider
{
id: speedSlider
Layout.fillWidth: true
from: -10.0
to: 10.0
stepSize: 0.01
onValueChanged: shaderSpeedField.text = String(value.toFixed(2));
}
TextField
{
id: shaderSpeedField
inputMethodHints: Qt.ImhFormattedNumbersOnly
horizontalAlignment: Text.AlignRight
Layout.preferredWidth: Kirigami.Units.gridUnit * 4
onEditingFinished: () =>
{
let inputValue = parseFloat(text);
if (isNaN(inputValue) || inputValue < speedSlider.from)
inputValue = speedSlider.from;
else if (inputValue > speedSlider.to)
inputValue = speedSlider.to;
text = inputValue.toFixed(2);
speedSlider.value = inputValue;
}
Keys.onPressed: (event) =>
{
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter)
{
shaderSpeedField.focus = false; // Unfocus the TextField
event.accepted = true; // Prevent further propagation of the key event
}
}
background: Rectangle
{
color: shaderSpeedField.activeFocus ? palette.base : "transparent"
border.color: shaderSpeedField.activeFocus ? palette.highlight : "transparent"
border.width: 1
radius: 4
anchors.fill: shaderSpeedField
anchors.margins: -2
}
}
}
ComboBox
{
visible: navBar.currentIndex === 1
id: pauseModeCombo
Kirigami.FormData.label: i18nd("@buttonGroup:pause_mode", "Pause:")
model: [
{
'label': i18nd("@option:pause_mode", "Maximized or full-screen windows")
},
{
'label': i18nd("@option:pause_mode", "Active window is present")
},
{
'label': i18nd("@option:pause_mode", "At least one window is shown")
},
{
'label': i18nd("@option:pause_mode", "Never")
}
]
textRole: "label"
onCurrentIndexChanged: root.cfg_pauseMode = currentIndex
currentIndex: root.cfg_pauseMode
}
CheckBox
{
visible: navBar.currentIndex === 1
id: activeScreenOnlyCheckbox
Kirigami.FormData.label: i18nd("@checkbox:screen_filter", "Filter:")
text: i18n("Only check for windows in active screen")
}
TextField
{
visible: navBar.currentIndex === 1
id: excludeWindows
property var windows: []
width: Kirigami.Units.gridUnit * 11
Kirigami.FormData.label: i18nd("com.github.digitalartifex.komplex", "Exclude windows:")
text: windows.join(",")
onEditingFinished: () =>
{
windows = excludeWindows.text.trim().replace(/\s+/g, "").split(",");
}
ToolTip.visible: hovered
ToolTip.text: qsTr("A comma-separated list of fully-qualified App-IDs to exclude their windows from triggering pause mode.")
}
CheckBox
{
visible: navBar.currentIndex === 1
Kirigami.FormData.label: i18nd("com.github.digitalartifex.komplex", cfg_isPaused ? "Playing" : "Paused")
id: runningCombo
checked: root.cfg_running
text: i18n("Play/Pause the shader")
onCheckedChanged: () =>
{
wallpaper.configuration.running = checked;
root.cfg_running = checked;
}
}
RowLayout
{
visible: navBar.currentIndex === 1
id: mouseLayout
Kirigami.FormData.label: i18nd("com.github.digitalartifex.komplex", "Mouse allowed:")
Button
{
id: mouseEnableButton
icon.name: checked ? "followmouse-symbolic" : "hidemouse-symbolic"
text: i18nd("@button:toggle_use_mouse", checked ? "Enabled" : "Disabled")
checkable: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Enabling this will allow the shader to interact with the cursor but will prevent interaction with desktop elements")
}
}
RowLayout
{
id: mouseBiasLayout
visible: root.cfg_mouseAllowed && navBar.currentIndex === 1
Kirigami.FormData.label: i18nd("com.github.digitalartifex.komplex", "Mouse bias:")
ColumnLayout
{
Slider
{
id: mouseBiasSlider
Layout.preferredWidth: Kirigami.Units.gridUnit * 16
from: -10.0
to: 10.0
stepSize: 0.01
value: root.cfg_mouseSpeedBias ? root.cfg_mouseSpeedBias : 1.0
onValueChanged: () =>
{
mouseBiasField.text = String(value.toFixed(2));
wallpaper.configuration.mouseBias = mouseBiasField.text;
root.cfg_mouseSpeedBias = mouseBiasField.text;
}
}
}
ColumnLayout
{
visible: navBar.currentIndex === 1
TextField
{
id: mouseBiasField
text: root.cfg_mouseSpeedBias ? String(root.cfg_mouseSpeedBias.toFixed(2)) : "1.00"
inputMethodHints: Qt.ImhFormattedNumbersOnly
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
onEditingFinished: () =>
{
let inputValue = parseFloat(text);
if (isNaN(inputValue) || inputValue < mouseBiasSlider.from)
inputValue = mouseBiasSlider.from;
else if (inputValue > mouseBiasSlider.to)
inputValue = mouseBiasSlider.to;
text = inputValue.toFixed(2);
mouseBiasSlider.value = inputValue;
wallpaper.configuration.mouseBias = inputValue;
root.cfg_mouseSpeedBias = inputValue;
}
Keys.onPressed: (event) =>
{
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter)
{
mouseBiasField.focus = false; // Unfocus the TextField
event.accepted = true; // Prevent further propagation of the key event
}
}
background: Rectangle
{
color: mouseBiasField.activeFocus ? palette.base : "transparent"
border.color: mouseBiasField.activeFocus ? palette.highlight : "transparent"
border.width: 1
radius: 4
anchors.fill: mouseBiasField
anchors.margins: -2
}
}
}
}
Button
{
visible: navBar.currentIndex === 1
id: kofiButton
Layout.preferredWidth: Kirigami.Units.gridUnit * 5
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
contentItem: RowLayout
{
AnimatedImage
{
source: "icons/kofi.gif"
sourceSize.width: 36
sourceSize.height: 36
fillMode: Image.Pad
horizontalAlignment: Image.AlignLeft
transform: Translate
{
x: 8
}
}
Text
{
text: i18nd("@button:kofi", "Kofi")
horizontalAlignment: Text.AlignHCenter
color: palette.text
transform: Translate
{
x: -8
}
}
}
onClicked: () =>
{
Qt.openUrlExternally("https://ko-fi.com/digitalartifex");
}
}
RowLayout
{
visible: root.cfg_komplex_mode === 1 && navBar.currentIndex === 0
spacing: Kirigami.Units.smallSpacing
Kirigami.FormData.label: i18nd("com.github.digitalartifex.komplex", "Shader Pack:")
ComboBox
{
property string shader
id: selectedShaderPack
Layout.preferredWidth: Kirigami.Units.gridUnit * 11
model: FolderListModel
{
id: packListModel
showDirs: true
showFiles: false
showHidden: false
nameFilters: ["*.frag.qsb"]
folder: "file://" + shaderPackModel.shadersPath + "/generative"
}
delegate: Component
{
id: packListDelegate
ItemDelegate
{
width: parent ? parent.width : 0
text: fileBaseName.replace("_", " ").charAt(0).toUpperCase() + fileBaseName.replace("_", " ").slice(1)
}
}
textRole: "fileBaseName"
currentIndex: root.cfg_shader_package
displayText: currentIndex === -1 ? "Custom File" : currentText.replace("_", " ").charAt(0).toUpperCase() + currentText.replace("_", " ").slice(1)
onCurrentTextChanged:
{
root.cfg_selectedShaderIndex = currentIndex;
if (root.cfg_selectedShaderIndex === -1)
return;
var source = model.get(currentIndex, "fileUrl");
shader = source;
}
}
Button
{
id: packFileButton
icon.name: "folder-symbolic"
text: i18nd("@button:toggle_select_shader", "Select File")
Layout.preferredWidth: Kirigami.Units.gridUnit * 9
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
onClicked:
{
packDialog.currentFolder = "file://" + shaderPackModel.shaderPackInstallPath;
packDialog.open();
}
}
FileDialog
{
id: packDialog
fileMode: FileDialog.OpenFile
title: i18nd("@dialog_title:choose_shader", "Choose a shader")
// will accept and auto convert .frag in the near future
nameFilters: ["Shader Package files (*.json)", "All files (*)"]
visible: false
currentFolder: "file://" + shaderPackModel.shaderPackInstallPath
onAccepted:
{
root.cfg_shader_package_index = -1;
root.cfg_shader_package = selectedFile;
}
}
}
}

View File

@@ -0,0 +1 @@
<svg fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"><path d="M 14 10 C 12.128906 10 8.585938 12.070313 2.3125 21.925781 C 1.996094 22.421875 1.929688 23.023438 2.097656 23.574219 C 2.035156 23.707031 2 23.851563 2 24 L 2 39 C 2 40.070313 2.863281 41 3.957031 41 L 19.992188 41 C 21.109375 41 22.125 40.332031 22.605469 39.324219 C 22.617188 39.300781 22.628906 39.277344 22.636719 39.25 L 24.523438 34.324219 C 24.648438 34.066406 24.8125 34 25 34 C 25.1875 34 25.351563 34.066406 25.476563 34.324219 L 27.363281 39.25 C 27.371094 39.277344 27.382813 39.300781 27.394531 39.324219 C 27.875 40.332031 28.890625 41 30.007813 41 L 46.042969 41 C 47.136719 41 48 40.070313 48 39 L 48 24 C 48 23.855469 47.96875 23.714844 47.90625 23.582031 C 48.0625 23.074219 48.015625 22.519531 47.753906 22.042969 C 44.757813 16.550781 40.460938 10 37 10 C 32 10 32 15.269531 32 17 C 32 17.027344 32 17.054688 32 17.082031 C 32.066406 18.65625 33.1875 21 36 21 C 36.496094 21 36.972656 20.851563 37.386719 20.578125 C 37.585938 21.382813 37.773438 22.203125 37.949219 23 L 28.828125 23 C 28.007813 23 27.441406 23.355469 26.941406 23.605469 C 26.445313 23.855469 26.007813 24 25.953125 24 L 24.042969 24 C 23.992188 24 23.554688 23.855469 23.058594 23.605469 C 22.558594 23.355469 21.992188 23 21.171875 23 L 12.121094 23 C 12.441406 22.0625 12.777344 21.097656 13.125 20.171875 C 13.5625 20.6875 14.164063 21 15 21 C 17.8125 21 18.933594 18.65625 19 17.082031 C 19 17.054688 19 17.027344 19 17 C 19 15.269531 19 10 14 10 Z M 14 12 C 17 12 17 15.082031 17 17 C 17 17 16.917969 19 15 19 C 14 19 14 15.582031 14 14 C 13.003906 14 10 23 10 23 L 4 23 C 4 23 11 12 14 12 Z M 37 12 C 40 12 46 23 46 23 L 40 23 C 40 23 38.082031 14 37 14 C 37 15.582031 37.082031 19 36 19 C 34.082031 19 34 17 34 17 C 34 15.082031 34 12 37 12 Z M 4 25 L 21.171875 25 C 21.226563 25 21.660156 25.144531 22.160156 25.394531 C 22.660156 25.644531 23.226563 26 24.042969 26 L 25.953125 26 C 26.773438 26 27.339844 25.644531 27.839844 25.394531 C 28.335938 25.144531 28.773438 25 28.828125 25 L 46 25 L 46 39 L 30.007813 39 C 29.679688 39 29.371094 38.808594 29.203125 38.464844 L 27.316406 33.535156 C 27.308594 33.511719 27.296875 33.484375 27.285156 33.460938 C 26.839844 32.53125 25.910156 32 25 32 C 24.089844 32 23.160156 32.53125 22.714844 33.460938 C 22.703125 33.484375 22.691406 33.511719 22.683594 33.535156 L 20.796875 38.464844 C 20.628906 38.808594 20.320313 39 19.992188 39 L 4 39 Z M 6.976563 27.03125 C 6.421875 27.03125 5.96875 27.464844 5.96875 28 L 5.96875 36.011719 C 5.96875 36.515625 6.480469 37.011719 7 37.011719 L 18 37.011719 C 18.507813 37.011719 18.839844 36.386719 19.035156 36.011719 C 19.964844 33.976563 20.230469 31.71875 21.21875 28.296875 C 21.410156 27.707031 21.261719 27.03125 19.988281 27.03125 Z M 29.972656 27.03125 C 28.699219 27.03125 28.550781 27.707031 28.746094 28.296875 C 29.734375 31.71875 29.996094 33.976563 30.925781 36.011719 C 31.121094 36.386719 31.457031 37.011719 31.964844 37.011719 L 42.964844 37.011719 C 43.484375 37.011719 43.996094 36.515625 43.996094 36.011719 L 43.996094 28 C 43.996094 27.464844 43.539063 27.03125 42.984375 27.03125 Z"/></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0"?><svg fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"> <path d="M15,29h13V16H15V29z M23.167,9c0.238,0,0.47,0.085,0.65,0.241L29.37,14H46L34.418,3.256C34.234,3.091,33.997,3,33.75,3 H4.357l5.5,6H23.167z M8,24.167V10c0-0.016,0.006-0.03,0.007-0.046c0-0.004-0.001-0.009-0.001-0.013L3,4.48v30.27 c0,0.23,0.08,0.454,0.226,0.632L13,47V30.369l-4.759-5.552C8.085,24.636,8,24.405,8,24.167z M30,16v5h9.586l-1.293-1.293 c-0.391-0.391-0.391-1.023,0-1.414s1.023-0.391,1.414,0l3,3c0.391,0.391,0.391,1.023,0,1.414l-3,3C39.512,25.902,39.256,26,39,26 s-0.512-0.098-0.707-0.293c-0.391-0.391-0.391-1.023,0-1.414L39.586,23H30v7c0,0.552-0.447,1-1,1h-7v9.586l1.293-1.293 c0.391-0.391,1.023-0.391,1.414,0s0.391,1.023,0,1.414l-3,3C21.512,43.902,21.256,44,21,44s-0.512-0.098-0.707-0.293l-3-3 c-0.391-0.391-0.391-1.023,0-1.414s1.023-0.391,1.414,0L20,40.586V31h-5v17h31c0.553,0,1-0.448,1-1V16H30z M10,23.796l3,3.5V15.414 l-3-3V23.796z M22.797,11H11.69l2.75,3h11.857L22.797,11z"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"><path d="M 12.78125 5.96875 C 11.75 6.082031 10.976563 6.964844 11 8 L 11 42 C 10.988281 42.722656 11.367188 43.390625 11.992188 43.753906 C 12.613281 44.121094 13.386719 44.121094 14.007813 43.753906 C 14.632813 43.390625 15.011719 42.722656 15 42 L 15 8 C 15.007813 7.457031 14.796875 6.9375 14.414063 6.554688 C 14.03125 6.171875 13.511719 5.960938 12.96875 5.96875 C 12.90625 5.964844 12.84375 5.964844 12.78125 5.96875 Z M 36.78125 5.96875 C 35.75 6.082031 34.976563 6.964844 35 8 L 35 42 C 34.988281 42.722656 35.367188 43.390625 35.992188 43.753906 C 36.613281 44.121094 37.386719 44.121094 38.007813 43.753906 C 38.632813 43.390625 39.011719 42.722656 39 42 L 39 8 C 39.007813 7.457031 38.796875 6.9375 38.414063 6.554688 C 38.03125 6.171875 37.511719 5.960938 36.96875 5.96875 C 36.90625 5.964844 36.84375 5.964844 36.78125 5.96875 Z M 6.78125 14.96875 C 5.75 15.082031 4.976563 15.964844 5 17 L 5 33 C 4.988281 33.722656 5.367188 34.390625 5.992188 34.753906 C 6.613281 35.121094 7.386719 35.121094 8.007813 34.753906 C 8.632813 34.390625 9.011719 33.722656 9 33 L 9 17 C 9.007813 16.457031 8.796875 15.9375 8.414063 15.554688 C 8.03125 15.171875 7.511719 14.960938 6.96875 14.96875 C 6.90625 14.964844 6.84375 14.964844 6.78125 14.96875 Z M 42.78125 14.96875 C 41.75 15.082031 40.976563 15.964844 41 17 L 41 33 C 40.988281 33.722656 41.367188 34.390625 41.992188 34.753906 C 42.613281 35.121094 43.386719 35.121094 44.007813 34.753906 C 44.632813 34.390625 45.011719 33.722656 45 33 L 45 17 C 45.007813 16.457031 44.796875 15.9375 44.414063 15.554688 C 44.03125 15.171875 43.511719 14.960938 42.96875 14.96875 C 42.90625 14.964844 42.84375 14.964844 42.78125 14.96875 Z M 18.78125 15.96875 C 17.75 16.082031 16.976563 16.964844 17 18 L 17 32 C 16.988281 32.722656 17.367188 33.390625 17.992188 33.753906 C 18.613281 34.121094 19.386719 34.121094 20.007813 33.753906 C 20.632813 33.390625 21.011719 32.722656 21 32 L 21 18 C 21.007813 17.457031 20.796875 16.9375 20.414063 16.554688 C 20.03125 16.171875 19.511719 15.960938 18.96875 15.96875 C 18.90625 15.964844 18.84375 15.964844 18.78125 15.96875 Z M 30.78125 15.96875 C 29.75 16.082031 28.976563 16.964844 29 18 L 29 32 C 28.988281 32.722656 29.367188 33.390625 29.992188 33.753906 C 30.613281 34.121094 31.386719 34.121094 32.007813 33.753906 C 32.632813 33.390625 33.011719 32.722656 33 32 L 33 18 C 33.007813 17.457031 32.796875 16.9375 32.414063 16.554688 C 32.03125 16.171875 31.511719 15.960938 30.96875 15.96875 C 30.90625 15.964844 30.84375 15.964844 30.78125 15.96875 Z M 0.90625 20.96875 C 0.863281 20.976563 0.820313 20.988281 0.78125 21 C 0.316406 21.105469 -0.0117188 21.523438 0 22 L 0 28 C -0.00390625 28.359375 0.183594 28.695313 0.496094 28.878906 C 0.808594 29.058594 1.191406 29.058594 1.503906 28.878906 C 1.816406 28.695313 2.003906 28.359375 2 28 L 2 22 C 2.011719 21.710938 1.894531 21.433594 1.6875 21.238281 C 1.476563 21.039063 1.191406 20.941406 0.90625 20.96875 Z M 24.78125 20.96875 C 23.75 21.082031 22.976563 21.964844 23 23 L 23 27 C 22.988281 27.722656 23.367188 28.390625 23.992188 28.753906 C 24.613281 29.121094 25.386719 29.121094 26.007813 28.753906 C 26.632813 28.390625 27.011719 27.722656 27 27 L 27 23 C 27.007813 22.457031 26.796875 21.9375 26.414063 21.554688 C 26.03125 21.171875 25.511719 20.960938 24.96875 20.96875 C 24.90625 20.964844 24.84375 20.964844 24.78125 20.96875 Z M 48.90625 20.96875 C 48.863281 20.976563 48.820313 20.988281 48.78125 21 C 48.316406 21.105469 47.988281 21.523438 48 22 L 48 28 C 47.996094 28.359375 48.183594 28.695313 48.496094 28.878906 C 48.808594 29.058594 49.191406 29.058594 49.503906 28.878906 C 49.816406 28.695313 50.003906 28.359375 50 28 L 50 22 C 50.011719 21.710938 49.894531 21.433594 49.6875 21.238281 C 49.476563 21.039063 49.191406 20.941406 48.90625 20.96875 Z"/></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1 @@
<svg fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"><path d="M 25 4 L 4 17.34375 L 4 32.652344 L 25 46 L 46 32.65625 L 46 17.34375 Z M 25 29.183594 L 19.066406 25.070313 L 25 21.023438 L 30.933594 25.070313 Z M 27 17.605469 L 27 9.949219 L 40.429688 18.484375 L 34.410156 22.65625 Z M 23 17.605469 L 15.589844 22.65625 L 9.570313 18.484375 L 23 9.949219 Z M 12.09375 25.042969 L 8 27.832031 L 8 22.203125 Z M 15.570313 27.453125 L 23 32.605469 L 23 40.050781 L 9.589844 31.527344 Z M 27 32.605469 L 34.429688 27.453125 L 40.410156 31.527344 L 27 40.050781 Z M 37.90625 25.042969 L 42 22.203125 L 42 27.832031 Z"/></svg>

After

Width:  |  Height:  |  Size: 670 B

View File

@@ -0,0 +1 @@
<svg fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"><path d="M25 23.844l18.096-10.447L25.504 3.136c-.311-.182-.697-.182-1.008 0L6.905 13.397 25 23.844zM24 25.577L6 15.185V35.5c0 .355.188.685.496.864L24 46.575V25.577zM26 25.577v20.998l17.504-10.211C43.812 36.185 44 35.855 44 35.5V15.186L26 25.577z"/></svg>

After

Width:  |  Height:  |  Size: 357 B

View File

@@ -0,0 +1 @@
<svg fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"><path d="M 0 5 L 0 29.585938 L 11.15625 18.429688 L 15.460938 24.890625 L 20.328125 23.914063 L 24.414063 28 L 30.320313 28 L 37.320313 33 L 50 33 L 50 5 Z M 37.5 14 C 39.433594 14 41 15.566406 41 17.5 C 41 19.433594 39.433594 21 37.5 21 C 35.566406 21 34 19.433594 34 17.5 C 34 15.566406 35.566406 14 37.5 14 Z M 10.84375 21.570313 L 0 32.414063 L 0 45 L 50 45 L 50 35 L 36.679688 35 L 29.679688 30 L 23.585938 30 L 19.671875 26.085938 L 14.539063 27.109375 Z"/></svg>

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -0,0 +1 @@
<svg fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"><path d="M 2 4 C 1.448 4 1 4.448 1 5 L 1 45 C 1 45.552 1.448 46 2 46 L 48 46 C 48.552 46 49 45.552 49 45 L 49 5 C 49 4.448 48.552 4 48 4 L 2 4 z M 3 6 L 6 6 L 6 9 L 3 9 L 3 6 z M 8 6 L 12 6 L 12 9 L 8 9 L 8 6 z M 14 6 L 18 6 L 18 9 L 14 9 L 14 6 z M 20 6 L 24 6 L 24 9 L 20 9 L 20 6 z M 26 6 L 30 6 L 30 9 L 26 9 L 26 6 z M 32 6 L 36 6 L 36 9 L 32 9 L 32 6 z M 38 6 L 42 6 L 42 9 L 38 9 L 38 6 z M 44 6 L 47 6 L 47 9 L 44 9 L 44 6 z M 21 19 L 31 25 L 21 31 L 21 19 z M 3 41 L 6 41 L 6 44 L 3 44 L 3 41 z M 8 41 L 12 41 L 12 44 L 8 44 L 8 41 z M 14 41 L 18 41 L 18 44 L 14 44 L 14 41 z M 20 41 L 24 41 L 24 44 L 20 44 L 20 41 z M 26 41 L 30 41 L 30 44 L 26 44 L 26 41 z M 32 41 L 36 41 L 36 44 L 32 44 L 32 41 z M 38 41 L 42 41 L 42 44 L 38 44 L 38 41 z M 44 41 L 47 41 L 47 44 L 44 44 L 44 41 z"/></svg>

After

Width:  |  Height:  |  Size: 906 B

View File

@@ -0,0 +1,63 @@
import QtCore
import QtQuick
import QtQml
import org.kde.plasma.core
import org.kde.plasma.components as PlasmaComponents
import org.kde.plasma.extras as PlasmaExtras
import org.kde.plasma.plasmoid
WallpaperItem
{
Item
{
anchors.fill: parent
Loader
{
id: pageLoader
anchors.fill: parent
active: true
sourceComponent: shaderToysContent
states: [
State
{
when: wallpaper.komplex_mode === 0
PropertyChanges
{
pageLoader.sourceComponent: shaderToysContent
}
},
State
{
when: wallpaper.komplex_mode === 1
PropertyChanges
{
pageLoader.sourceComponent: packContent
}
}
]
}
Component
{
id: shaderToysContent
ShaderToyModel
{
anchors.fill: parent
}
}
Component
{
id: packContent
KomplexModel
{
anchors.fill: parent
}
}
}
}