Initial Commit
This commit is contained in:
623
package/contents/ui/ShaderChannelConfiguration.qml
Normal file
623
package/contents/ui/ShaderChannelConfiguration.qml
Normal 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user