diff --git a/package/contents/config/main.xml b/package/contents/config/main.xml
index 42b8cb3..74c9973 100644
--- a/package/contents/config/main.xml
+++ b/package/contents/config/main.xml
@@ -5,6 +5,10 @@
+
+
+ false
+
Shaders6/Ps3menu.frag.qsb
diff --git a/package/contents/ui/KomplexModel.qml b/package/contents/ui/KomplexModel.qml
index 7449fd8..8ac911a 100644
--- a/package/contents/ui/KomplexModel.qml
+++ b/package/contents/ui/KomplexModel.qml
@@ -58,14 +58,6 @@ Rectangle
property var pack: wallpaper.configuration.shader_package
- // onPackChanged: () =>
- // {
- // if(mainItem.ready)
- // {
- // shaderPackModel.loadJson(pack)
- // }
- // }
-
id: mainItem
color: "black"
@@ -108,11 +100,7 @@ Rectangle
property var bufferB
property var bufferC
property var bufferD
-
- // ShaderChannel { id: bufferA; visible: false; anchors.fill: parent }
- // ShaderChannel { id: bufferB; visible: false; anchors.fill: parent }
- // ShaderChannel { id: bufferC; visible: false; anchors.fill: parent }
- // ShaderChannel { id: bufferD; visible: false; anchors.fill: parent }
+
iTime: mainItem.iTime
iMouse: mainItem.iMouse
iResolution: mainItem.iResolution
@@ -230,35 +218,7 @@ Rectangle
// Recursive helper function to parse channels
function parseChannel(channel, json, typeDefault = 2, autodestroy = true)
{
- var source = getFilePath(json.source)
-
- channel.frameBufferChannel = json.frame_buffer_channel !== undefined ? json.frame_buffer_channel : -1
- channel.type = json.type !== undefined ? json.type : typeDefault
- channel.visible = false
- channel.iMouse = Qt.binding(() => { return mainItem.iMouse; })
- channel.iTime = Qt.binding(() => { return mainItem.iTime; })
- channel.iResolutionScale = json.resolution_scale ? json.resolution_scale : 1.0
- channel.iResolution = Qt.binding(() => { return json.resolution_x ? Qt.vector3d(json.resolution_x, json.resolution_y, 1.0) : Qt.vector3d(mainItem.iResolution.x,mainItem.iResolution.y,1.0); })
- channel.mouseBias = json.mouse_scale ? json.mouse_scale : 1.0
- channel.iTimeScale = json.time_scale ? json.time_scale : 1.0
- channel.iTimeDelta = Qt.binding(() => { return mainItem.iTimeDelta; })
- channel.width = Qt.binding(() => channel.iResolution.x)
- channel.height = Qt.binding(() => channel.iResolution.y)
-
- channel.iChannelTime = Qt.binding(() => {
- return [
- mainItem.iTime * channel.iTimeScale,
- mainItem.iTime * channel.iTimeScale,
- mainItem.iTime * channel.iTimeScale,
- mainItem.iTime * channel.iTimeScale
- ];
- });
-
- channel.iFrameRate = Qt.binding(() => { return mainItem.iFrameRate; })
- channel.iFrame = mainItem.iFrame
- channel.invert = json.invert ? json.invert : false
-
- channel.source = source
+ var component = Qt.createComponent("./ShaderChannel.qml")
if (json.channel0)
{
@@ -271,9 +231,8 @@ Rectangle
}
else if(typeof json.channel0 === "object")
{
- var component = Qt.createComponent("./ShaderChannel.qml")
-
- if (component.status === Component.Ready) {
+ if (component.status === Component.Ready)
+ {
channel.iChannel0 = component.createObject(mainItem, { })
parseChannel(channel.iChannel0, json.channel0)
}
@@ -293,9 +252,8 @@ Rectangle
}
else if(typeof json.channel1 === "object")
{
- var component = Qt.createComponent("./ShaderChannel.qml")
-
- if (component.status === Component.Ready) {
+ if (component.status === Component.Ready)
+ {
channel.iChannel1 = component.createObject(mainItem, { })
parseChannel(channel.iChannel1, json.channel1)
}
@@ -315,9 +273,8 @@ Rectangle
}
else if(typeof json.channel2 === "object")
{
- var component = Qt.createComponent("./ShaderChannel.qml")
-
- if (component.status === Component.Ready) {
+ if (component.status === Component.Ready)
+ {
channel.iChannel2 = component.createObject(mainItem, { })
parseChannel(channel.iChannel2, json.channel2)
}
@@ -337,9 +294,8 @@ Rectangle
}
else if(typeof json.channel3 === "object")
{
- var component = Qt.createComponent("./ShaderChannel.qml")
-
- if (component.status === Component.Ready) {
+ if (component.status === Component.Ready)
+ {
channel.iChannel3 = component.createObject(mainItem, { })
parseChannel(channel.iChannel3, json.channel3)
}
@@ -348,6 +304,37 @@ Rectangle
console.log('Uknown channel type 3 ' + typeof json.channel3)
}
+ channel.frameBufferChannel = json.frame_buffer_channel !== undefined ? json.frame_buffer_channel : -1
+ channel.type = json.type !== undefined ? json.type : typeDefault
+ channel.visible = false
+ channel.iMouse = Qt.binding(() => { return mainItem.iMouse; })
+ channel.iTime = Qt.binding(() => { return mainItem.iTime; })
+ channel.iResolutionScale = json.resolution_scale ? json.resolution_scale : 1.0
+ channel.iResolution = Qt.binding(() => { return json.resolution_x ? Qt.vector3d(json.resolution_x, json.resolution_y, 1.0) : Qt.vector3d(mainItem.iResolution.x,mainItem.iResolution.y,1.0); })
+ channel.mouseBias = json.mouse_scale ? json.mouse_scale : 1.0
+ channel.iTimeScale = json.time_scale ? json.time_scale : 1.0
+ channel.iTimeDelta = Qt.binding(() => { return mainItem.iTimeDelta; })
+ channel.width = Qt.binding(() => channel.iResolution.x)
+ channel.height = Qt.binding(() => channel.iResolution.y)
+ channel.materialTexture = json.materialTexture !== undefined ? getFilePath(json.materialTexture) : ""
+ channel.materialShader = json.materialShader !== undefined ? getFilePath(json.materialShader) : ""
+
+ channel.iChannelTime = Qt.binding(() => {
+ return [
+ mainItem.iTime * channel.iTimeScale,
+ mainItem.iTime * channel.iTimeScale,
+ mainItem.iTime * channel.iTimeScale,
+ mainItem.iTime * channel.iTimeScale
+ ];
+ });
+
+ channel.iFrameRate = Qt.binding(() => { return mainItem.iFrameRate; })
+ channel.iFrame = mainItem.iFrame
+ channel.invert = json.invert ? json.invert : false
+
+ var source = getFilePath(json.source)
+ channel.source = source
+
if(autodestroy)
data.channels.push(channel)
}
@@ -364,11 +351,13 @@ Rectangle
var component = Qt.createComponent("./ShaderChannel.qml")
- if (component.status === Component.Ready) {
+ if (component.status === Component.Ready)
+ {
channelOutput.bufferA = component.createObject(channelOutput, { visible: false })
channelOutput.bufferB = component.createObject(channelOutput, { visible: false })
channelOutput.bufferC = component.createObject(channelOutput, { visible: false })
channelOutput.bufferD = component.createObject(channelOutput, { visible: false })
+
data.buffers.set("{bufferA}", channelOutput.bufferA)
data.buffers.set("{bufferB}", channelOutput.bufferB)
data.buffers.set("{bufferC}", channelOutput.bufferC)
@@ -401,11 +390,11 @@ Rectangle
// Generate a new ShaderEffectSource for the requested buffer
function createBufferAssociation(buffer)
{
-
var component = Qt.createComponent('./ShaderBuffer.qml')
var result
- if (component.status === Component.Ready) {
+ if (component.status === Component.Ready)
+ {
result = component.createObject(mainItem, {
x:0,
y:0,
diff --git a/package/contents/ui/PexelsImageHub.qml b/package/contents/ui/PexelsImageHub.qml
new file mode 100644
index 0000000..f0af06e
--- /dev/null
+++ b/package/contents/ui/PexelsImageHub.qml
@@ -0,0 +1,434 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Dialogs
+import QtQuick.Layouts
+
+import Komplex.Pexels.Image as Pexels
+
+Item
+{
+ id: mainItem
+ anchors.fill: parent
+
+ Pexels.SearchModel
+ {
+ id: searchModel
+ }
+
+ ColumnLayout
+ {
+ anchors.fill: parent
+
+ RowLayout
+ {
+ Layout.fillHeight: false
+ Layout.fillWidth: true
+ Layout.margins: 6
+
+ TextField
+ {
+ Layout.preferredHeight: 32
+ Layout.fillWidth: true
+
+ id: searchField
+ placeholderText: "Search"
+ onEditingFinished: mainItem.updateSearch()
+ Keys.onPressed: (event) =>
+ {
+ if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter)
+ {
+ searchField.focus = false; // Unfocus the TextField
+ event.accepted = true; // Prevent further propagation of the key event
+ }
+ }
+ }
+
+ Button
+ {
+ Layout.preferredHeight: 32
+ Layout.preferredWidth: 32
+
+ icon.name: "search-symbolic"
+
+ onClicked: mainItem.updateSearch()
+ }
+ }
+
+ Component
+ {
+ id: highlight
+ Rectangle {
+ width: view.cellWidth; height: view.cellHeight
+ color: palette.highlight; radius: 5
+ x: view.currentItem.x
+ y: view.currentItem.y
+ Behavior on x { SpringAnimation { spring: 3; damping: 0.2 } }
+ Behavior on y { SpringAnimation { spring: 3; damping: 0.2 } }
+ }
+ }
+
+ Rectangle
+ {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ color: palette.base
+ clip: true
+
+ RowLayout
+ {
+ anchors.fill: parent
+
+ GridView
+ {
+ // The standard size
+ property int idealCellHeight: 300
+ property int idealCellWidth: 300
+ cellWidth: width / Math.floor(width / idealCellWidth)
+ cellHeight: idealCellHeight
+
+ id: view
+
+ model: searchModel
+ highlight: highlight
+ highlightFollowsCurrentItem: false
+
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ Layout.margins: 6
+
+ delegate: Column
+ {
+ property int itemIndex: index
+ property int originalHeight: imageHeight
+ property int originalWidth: imageWidth
+ property int imageId: id
+ property string author: photographer
+ property string authorUrl: photographerUrl
+ property string imageUrl: original
+ property string thumbnailUrl: thumbnail
+ property string altText: alt
+ property string largeThumbnail: large
+
+ id: entry
+
+ leftPadding: Math.floor((width - thumbnailImage.width) / 2)
+ topPadding: 10
+ rightPadding: Math.floor((width - thumbnailImage.width) / 2)
+ bottomPadding: 10
+ width: view.cellWidth
+
+ Image
+ {
+ width: 280
+ height: 200
+ id: thumbnailImage
+ source: thumbnail
+ anchors.horizontalCenter: parent.horizontalCenter
+ MouseArea
+ {
+ anchors.fill: parent
+ onClicked: (mouse) => {
+ view.currentIndex = parent.parent.itemIndex
+ }
+ }
+
+ Rectangle
+ {
+ color: palette.base
+ anchors.fill: parent
+ visible: thumbnailImage.status === Image.Loading
+
+ RowLayout
+ {
+ anchors.fill: parent
+
+ BusyIndicator
+ {
+ Layout.alignment: Qt.AlignCenter
+ Layout.preferredHeight: 64
+ Layout.preferredWidth: 64
+ visible: running
+ }
+ }
+ }
+ }
+ Text
+ {
+ property string externalLink: photographerUrl
+
+ elide: Text.ElideRight
+ topPadding: 4
+ bottomPadding: 2
+ text: "" + photographer + "
"
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: 280
+ color: palette.link
+ font.bold: true
+
+ MouseArea
+ {
+ anchors.fill: parent
+ onClicked: (mouse) => {
+ Qt.openUrlExternally(parent.externalLink)
+ }
+ }
+ }
+ Text
+ {
+ leftPadding: 8
+ rightPadding: 8
+ text: qsTr(alt)
+ anchors.horizontalCenter: parent.horizontalCenter
+ elide: Text.ElideRight
+ width: 280
+ color: palette.text
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+ maximumLineCount: 2
+ font.italic: true
+ }
+ RowLayout
+ {
+ visible: parent.itemIndex === view.currentIndex
+ width: 280
+ Button
+ {
+ Layout.topMargin: 4
+ Layout.alignment: Qt.AlignRight
+ Layout.preferredHeight: 32
+ Layout.preferredWidth: 32
+ icon.name: "emblem-downloads"
+ onClicked: {
+ downloadDialog.imageHeight = entry.originalHeight
+ downloadDialog.imageWidth = entry.originalWidth
+ downloadDialog.photographer = entry.author
+ downloadDialog.photographerUrl = entry.authorUrl
+ downloadDialog.alt = entry.altText
+ downloadDialog.thumbnail = entry.largeThumbnail
+ downloadDialog.imageUrl = entry.imageUrl
+ downloadDialog.id = entry.imageId
+ downloadDialog.open()
+ }
+ }
+ }
+
+ }
+ populate: Transition
+ {
+ NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 1000 }
+ }
+ }
+ }
+ }
+
+ RowLayout
+ {
+ Layout.margins: 6
+ Layout.fillWidth: true
+
+ Button
+ {
+ text: "Previous"
+ enabled: searchModel.previousPage !== ""
+ onClicked: searchModel.back()
+ }
+
+ RowLayout
+ {
+ Layout.margins: 6
+ Layout.fillWidth: true
+
+ Text
+ {
+ visible: searchModel.totalResults > 0
+ color: palette.text
+ text: ((searchModel.resultsPerPage * searchModel.currentPage) - searchModel.resultsPerPage + 1) + "-" + (searchModel.resultsPerPage * searchModel.currentPage) + " of " + searchModel.totalResults
+ }
+
+ Text
+ {
+ color: palette.text
+ Layout.fillWidth: true
+ text: "Page " + searchModel.currentPage + " of " + Math.ceil(searchModel.totalResults / searchModel.resultsPerPage)
+ }
+
+ Text
+ {
+ color: palette.link
+ text: "Photos provided by Pexels"
+ font.bold: true
+
+ onLinkActivated: (link) => Qt.openUrlExternally(link)
+ }
+ }
+ Button
+ {
+ text: "Next"
+ enabled: searchModel.nextPage !== ""
+ onClicked: searchModel.next()
+ }
+ }
+ }
+
+ Rectangle
+ {
+ color: palette.base
+ anchors.fill: parent
+ visible: searchModel.status === Pexels.SearchModel.Searching
+
+ RowLayout
+ {
+ anchors.fill: parent
+
+ BusyIndicator
+ {
+ Layout.alignment: Qt.AlignCenter
+ Layout.preferredHeight: 128
+ Layout.preferredWidth: 128
+ visible: running
+ }
+ }
+ }
+
+ Dialog
+ {
+ property string photographer
+ property string photographerUrl
+ property string imageUrl
+ property string thumbnail
+ property string alt
+ property int imageHeight
+ property int imageWidth
+ property int id
+
+ id: downloadDialog
+ modal: Qt.WindowModal
+ width: 600
+ height: 420
+
+ anchors.centerIn: parent
+ clip: true
+
+ ColumnLayout
+ {
+ anchors.fill: parent
+
+ Text
+ {
+ property string externalLink: downloadDialog.photographerUrl
+
+ elide: Text.ElideRight
+ topPadding: 4
+ bottomPadding: 2
+ text: "" + downloadDialog.photographer + "
"
+ width: 280
+ color: palette.link
+ font.bold: true
+
+ MouseArea
+ {
+ anchors.fill: parent
+ onClicked: (mouse) => {
+ Qt.openUrlExternally(parent.externalLink)
+ }
+ }
+ }
+
+ Image
+ {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+
+ fillMode: Image.PreserveAspectCrop
+ source: downloadDialog.thumbnail
+ }
+
+ Text
+ {
+ Layout.fillWidth: true
+ leftPadding: 8
+ rightPadding: 8
+ text: qsTr(downloadDialog.alt)
+ elide: Text.ElideRight
+ width: 280
+ color: palette.text
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+ maximumLineCount: 4
+ font.italic: true
+ }
+
+ Button
+ {
+ Layout.fillWidth: true
+ text: "Download (" + downloadDialog.imageWidth + "x" + downloadDialog.imageHeight + ")"
+ icon.name: "image-symbolic"
+
+ onClicked: () =>
+ {
+ downloadDialog.close();
+ progressDialog.thumbnail = downloadDialog.thumbnail
+ progressDialog.photographer = downloadDialog.photographer
+ progressDialog.open()
+ searchModel.download(downloadDialog.imageUrl, downloadDialog.id)
+ }
+ }
+ }
+ }
+
+ Dialog
+ {
+ property string photographer
+ property string thumbnail
+
+ modal: Qt.WindowModal
+ width: 600
+ height: 420
+
+ anchors.centerIn: parent
+ clip: true
+
+ id: progressDialog
+
+ ColumnLayout
+ {
+ anchors.fill: parent
+
+ Image
+ {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+
+ fillMode: Image.PreserveAspectCrop
+ source: downloadDialog.thumbnail
+ }
+
+ Text
+ {
+ text: "Downloading Photo..."
+ color: palette.text
+ }
+
+ ProgressBar
+ {
+ value: searchModel.downloadProgress
+ Layout.fillWidth: true
+ Layout.preferredHeight: 6
+ }
+ }
+
+ Connections
+ {
+ target: searchModel
+ function onDownloadFinished()
+ {
+ progressDialog.close()
+ }
+ }
+ }
+
+ function updateSearch()
+ {
+ console.log(searchField.text)
+ searchModel.query = searchField.text
+ }
+
+}
diff --git a/package/contents/ui/PexelsVideoHub.qml b/package/contents/ui/PexelsVideoHub.qml
new file mode 100644
index 0000000..0399037
--- /dev/null
+++ b/package/contents/ui/PexelsVideoHub.qml
@@ -0,0 +1,587 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Dialogs
+import QtQuick.Layouts
+import QtMultimedia
+
+import Komplex.Pexels.Video as Pexels
+
+Item
+{
+ property alias selectedFile: searchModel.lastSavedFile
+
+ id: mainItem
+ anchors.fill: parent
+
+ Pexels.SearchModel
+ {
+ id: searchModel
+ }
+
+ ColumnLayout
+ {
+ anchors.fill: parent
+
+ RowLayout
+ {
+ Layout.fillHeight: false
+ Layout.fillWidth: true
+ Layout.margins: 6
+
+ TextField
+ {
+ Layout.preferredHeight: 32
+ Layout.fillWidth: true
+
+ id: searchField
+ placeholderText: "Search"
+ onEditingFinished: mainItem.updateSearch()
+ Keys.onPressed: (event) =>
+ {
+ if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter)
+ {
+ searchField.focus = false; // Unfocus the TextField
+ event.accepted = true; // Prevent further propagation of the key event
+ }
+ }
+ }
+
+ Button
+ {
+ Layout.preferredHeight: 32
+ Layout.preferredWidth: 32
+
+ icon.name: "search-symbolic"
+
+ onClicked: mainItem.updateSearch()
+ }
+ }
+
+ Component
+ {
+ id: highlight
+ Rectangle
+ {
+ width: view.cellWidth; height: view.cellHeight
+ color: palette.highlight; radius: 5
+ x: view.currentItem.x
+ y: view.currentItem.y
+ Behavior on x { SpringAnimation { spring: 3; damping: 0.2 } }
+ Behavior on y { SpringAnimation { spring: 3; damping: 0.2 } }
+ }
+ }
+
+ Rectangle
+ {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ color: palette.base
+ clip: true
+
+ RowLayout
+ {
+ anchors.fill: parent
+
+ GridView
+ {
+ // The standard size
+ property int idealCellHeight: 220
+ property int idealCellWidth: 250
+ cellWidth: width / Math.floor(width / idealCellWidth)
+ cellHeight: idealCellHeight
+
+ id: view
+
+ model: searchModel
+ highlight: highlight
+ highlightFollowsCurrentItem: false
+
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ Layout.margins: 6
+
+ delegate: Column
+ {
+ property int itemIndex: index
+ property int originalHeight: videoHeight
+ property int originalWidth: videoWidth
+ property int videoId: id
+ property string author: user
+ property string authorUrl: userUrl
+ property string videoUrl: videoUrl
+ property string thumbnailUrl: thumbnail
+
+ id: entry
+
+ leftPadding: Math.floor((width - thumbnailImage.width) / 2)
+ topPadding: 10
+ rightPadding: Math.floor((width - thumbnailImage.width) / 2)
+ bottomPadding: 10
+ width: view.cellWidth
+
+ Image
+ {
+ width: 250
+ height: 140
+ id: thumbnailImage
+ source: thumbnail
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Rectangle
+ {
+ color: palette.base
+ anchors.fill: parent
+ visible: thumbnailImage.status === Image.Loading
+
+ RowLayout
+ {
+ anchors.fill: parent
+
+ BusyIndicator
+ {
+ Layout.alignment: Qt.AlignCenter
+ Layout.preferredHeight: 64
+ Layout.preferredWidth: 64
+ visible: running
+ }
+ }
+ }
+
+ MouseArea
+ {
+ anchors.fill: parent
+ onClicked: (mouse) =>
+ {
+ view.currentIndex = parent.parent.itemIndex
+ searchModel.currentIndex = view.currentIndex
+ }
+ }
+ }
+
+ Text
+ {
+ property string externalLink: authorUrl
+
+ elide: Text.ElideRight
+ topPadding: 4
+ bottomPadding: 2
+ text: "" + author + "
"
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: 280
+ color: palette.link
+
+ MouseArea
+ {
+ anchors.fill: parent
+ onClicked: (mouse) =>
+ {
+ Qt.openUrlExternally(parent.externalLink)
+ }
+ cursorShape: Qt.PointingHandCursor
+ }
+ }
+
+ RowLayout
+ {
+ visible: parent.itemIndex === view.currentIndex
+ width: thumbnailImage.width
+ Button
+ {
+ Layout.topMargin: 4
+ Layout.alignment: Qt.AlignRight
+ Layout.preferredHeight: 32
+ Layout.fillWidth: true
+ icon.name: "emblem-downloads"
+ text: qsTr("Preview & Download")
+
+ onClicked: () =>
+ {
+ downloadDialog.user = entry.author
+ downloadDialog.userUrl = entry.authorUrl
+ downloadDialog.thumbnail = entry.thumbnailUrl
+ downloadDialog.id = entry.videoId
+ downloadDialog.open()
+ searchModel.videoModel.update()
+ }
+ }
+ }
+ }
+ populate: Transition
+ {
+ NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 1000 }
+ }
+ }
+ }
+ }
+
+ RowLayout
+ {
+ Layout.margins: 6
+ Layout.fillWidth: true
+
+ Button
+ {
+ text: "Previous"
+ enabled: searchModel.previousPage !== ""
+ onClicked: searchModel.back()
+ }
+
+ RowLayout
+ {
+ Layout.margins: 6
+ Layout.fillWidth: true
+
+ Text
+ {
+ visible: searchModel.totalResults > 0
+ color: palette.text
+ text: ((searchModel.resultsPerPage * searchModel.currentPage) - searchModel.resultsPerPage + 1) + "-" + (searchModel.resultsPerPage * searchModel.currentPage) + " of " + searchModel.totalResults
+ }
+
+ Text
+ {
+ color: palette.text
+ Layout.fillWidth: true
+ text: "Page " + searchModel.currentPage + " of " + Math.ceil(searchModel.totalResults / searchModel.resultsPerPage)
+ }
+
+ Text
+ {
+ color: palette.text
+ text: "Videos provided by Pexels"
+ font.bold: true
+
+ onLinkActivated: (link) => Qt.openUrlExternally(link)
+
+ MouseArea
+ {
+ anchors.fill: parent
+ onClicked: (mouse) =>
+ {
+ Qt.openUrlExternally("https://www.pexels.com")
+ }
+ cursorShape: Qt.PointingHandCursor
+ }
+ }
+ }
+ Button
+ {
+ text: "Next"
+ enabled: searchModel.nextPage !== ""
+ onClicked: searchModel.next()
+ }
+ }
+ }
+
+ Rectangle
+ {
+ color: palette.base
+ anchors.fill: parent
+ visible: searchModel.status === Pexels.SearchModel.Searching
+
+ RowLayout
+ {
+ anchors.fill: parent
+
+ BusyIndicator
+ {
+ Layout.alignment: Qt.AlignCenter
+ Layout.preferredHeight: 128
+ Layout.preferredWidth: 128
+ visible: running
+ }
+ }
+ }
+
+ Dialog
+ {
+ property string user
+ property string userUrl
+ property string thumbnail
+ property string preview
+ property int id
+
+ id: downloadDialog
+ modal: Qt.WindowModal
+ width: 600
+ height: 440
+
+ anchors.centerIn: parent
+ clip: true
+
+ ColumnLayout
+ {
+ anchors.fill: parent
+
+ Text
+ {
+ property string externalLink: downloadDialog.userUrl
+
+ elide: Text.ElideRight
+ text: "" + downloadDialog.user + "
"
+ width: 280
+ color: palette.link
+
+ MouseArea
+ {
+ anchors.fill: parent
+ onClicked: (mouse) =>
+ {
+ Qt.openUrlExternally(parent.externalLink)
+ }
+ cursorShape: Qt.PointingHandCursor
+ }
+ }
+
+ Rectangle
+ {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 300
+ color: "black"
+ ColumnLayout
+ {
+ anchors.fill: parent
+ spacing: 0
+
+ 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
+ Layout.preferredWidth: 500
+ Layout.preferredHeight: 281
+ Layout.alignment: Qt.AlignHCenter
+ fillMode: VideoOutput.PreserveAspectCrop
+ smooth: true
+
+ onHeightChanged: this.sizeChanged()
+
+ MediaPlayer
+ {
+ id: mediaPlayer
+ videoOutput: videoOutput
+ source: Qt.resolvedUrl(downloadSelector.currentValue ? downloadSelector.currentValue : "")
+
+ 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); }
+
+ Image
+ {
+ visible: !mediaPlayer.playing
+ anchors.fill: parent
+
+ fillMode: Image.PreserveAspectFill
+ source: downloadDialog.thumbnail
+ }
+ }
+
+ Rectangle
+ {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 18
+ color: palette.alternateBase
+ RowLayout
+ {
+ anchors.fill: parent
+ spacing: 0
+
+ ProgressBar
+ {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ from: 0
+ to: mediaPlayer.duration
+ value: mediaPlayer.position
+ }
+ Button
+ {
+ Layout.preferredHeight: 18
+ Layout.preferredWidth: 18
+ icon.name: mediaPlayer.playing ? "stop-symbolic" : "play-symbolic"
+ icon.height: 16
+ icon.width: 16
+ onClicked: () =>
+ {
+ if(mediaPlayer.playing)
+ mediaPlayer.stop()
+ else
+ mediaPlayer.play()
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Text
+ {
+ text: qsTr("Download Options")
+ color: palette.text
+ font.bold: true
+ Layout.fillHeight: true
+ verticalAlignment: Text.AlignBottom
+ }
+
+ RowLayout
+ {
+ Layout.fillWidth: true
+ Layout.preferredHeight: downloadSelector.height
+ ComboBox
+ {
+ id: downloadSelector
+ Layout.fillWidth: true
+ model: searchModel.videoModel
+ textRole: "text"
+ valueRole: "url"
+ }
+
+ Button
+ {
+ enabled: downloadSelector.currentIndex >= 0
+ Layout.preferredHeight: downloadSelector.height
+ Layout.preferredWidth: downloadSelector.height
+ icon.name: "image-symbolic"
+
+ id: downloadButton
+
+ onClicked: () =>
+ {
+ downloadDialog.close();
+ progressDialog.thumbnail = downloadDialog.thumbnail
+ progressDialog.author = downloadDialog.user
+ progressDialog.open()
+ searchModel.videoModel.download(downloadSelector.currentIndex)
+ }
+ }
+ }
+ }
+
+ Connections
+ {
+ target: searchModel.videoModel
+ function onStatusChanged()
+ {
+ if(searchModel.videoModel.status === 0)
+ downloadSelector.currentIndex = 0
+ }
+ }
+
+ onClosed: () =>
+ {
+ mediaPlayer.stop()
+ }
+
+ Rectangle
+ {
+ color: palette.base
+ anchors.fill: parent
+ visible: searchModel.videoModel.status === 1
+
+ RowLayout
+ {
+ anchors.fill: parent
+
+ BusyIndicator
+ {
+ Layout.alignment: Qt.AlignCenter
+ Layout.preferredHeight: 128
+ Layout.preferredWidth: 128
+ visible: running
+ }
+ }
+ }
+ }
+
+ Dialog
+ {
+ property string author
+ property string thumbnail
+
+ modal: Qt.WindowModal
+ width: 600
+ height: 420
+
+ anchors.centerIn: parent
+ clip: true
+
+ id: progressDialog
+
+ ColumnLayout
+ {
+ anchors.fill: parent
+
+ Image
+ {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+
+ fillMode: Image.PreserveAspectCrop
+ source: downloadDialog.thumbnail
+ }
+
+ Text
+ {
+ text: "Downloading Video..."
+ color: palette.text
+ }
+
+ ProgressBar
+ {
+ value: searchModel.videoModel.downloadProgress
+ Layout.fillWidth: true
+ Layout.preferredHeight: 6
+ }
+ }
+
+ Connections
+ {
+ target: searchModel.videoModel
+ function onDownloadFinished()
+ {
+ progressDialog.close()
+ }
+ }
+ }
+
+ function updateSearch()
+ {
+ console.log(searchField.text)
+ searchModel.query = searchField.text
+ }
+}
diff --git a/package/contents/ui/ShaderChannel.qml b/package/contents/ui/ShaderChannel.qml
index 295d047..85780c9 100644
--- a/package/contents/ui/ShaderChannel.qml
+++ b/package/contents/ui/ShaderChannel.qml
@@ -43,7 +43,8 @@ Item
VideoChannel,
ShaderChannel,
CubeMapChannel,
- AudioChannel
+ AudioChannel,
+ SceneChannel
}
property int type: ShaderChannel.Type.ImageChannel
@@ -70,6 +71,9 @@ Item
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 int frameBufferChannel: -1
property bool blending: false
+ property string materialTexture:""
+ property string materialShader:""
+ property var windowModel
// bind to ShaderEffectSource.live to prevent data being updated causing a refresh between intended frames. I think this may be why
// the shaders are using so many resources. This is likely to cause issues of its own since we have no way of knowing the progress
@@ -80,8 +84,20 @@ Item
// but god damn does it feel hacky. Try to find a better way to actually limit framerate
onIFrameChanged: () =>
{
- active = true;
- active = false;
+ // this method of frame limiting breaks video playback
+ // even when the video is the source of an item being limited
+ // in this way
+
+ // if(type === ShaderChannel.ShaderChannel)
+ // {
+ // active = true;
+ // active = false;
+
+ // return;
+ // }
+
+ // if(active === false)
+ // active = true;
}
property bool invert
@@ -162,10 +178,14 @@ Item
PropertyChanges
{
loader.sourceComponent: channelAudio
-
- // loader.width: 512
- // loader.height: 2
- // loader.and
+ }
+ },
+ State
+ {
+ when: channel.type === ShaderChannel.Type.SceneChannel
+ PropertyChanges
+ {
+ loader.sourceComponent: Qt.createComponent(channel.source)
}
}
]
@@ -206,35 +226,42 @@ Item
Component
{
id: channelVideo
-
- VideoOutput
+ Rectangle
{
- 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: VideoOutput.PreserveAspectCrop
- smooth: true
+ color: "black"
- onHeightChanged: this.sizeChanged()
+ 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
+ property bool loaded: false
- MediaPlayer
+ signal sizeChanged
+ signal fatalError
+
+ id: videoComponent
+
+ visible: true
+ anchors.fill: parent
+ fillMode: VideoOutput.PreserveAspectCrop
+ smooth: true
+
+ onHeightChanged: this.sizeChanged()
+ }
+
+ MediaPlayer
{
id: mediaPlayer
- videoOutput: videoOutput
+ videoOutput: videoComponent
loops: MediaPlayer.Infinite
source: Qt.resolvedUrl(channel.source)
+ playbackRate: channel.iTimeScale >= 0.01 ? channel.iTimeScale : 0.01
audioOutput: AudioOutput
{
@@ -242,22 +269,96 @@ Item
volume: 0
}
- onErrorOccurred: function(error, errorString)
+ onErrorOccurred: (error, errorString) =>
{
if (MediaPlayer.NoError !== error)
{
console.log("[qmlvideo] VideoItem.onError error " + error + " errorString " + errorString)
- videoOutput.fatalError()
+ videoComponent.fatalError()
}
}
onSourceChanged:
{
- if(mediaPlayer.source != "")
- mediaPlayer.play()
- else
- mediaPlayer.stop()
+ if(!videoComponent.loaded)
+ return;
+ delayedStartTimer.start()
}
+
+ onMediaStatusChanged:
+ {
+ switch(mediaStatus)
+ {
+ case MediaPlayer.NoMedia:
+ console.log("No media loaded")
+ break;
+ case MediaPlayer.LoadingMedia:
+ console.log("Video loading")
+ break;
+ case MediaPlayer.LoadedMedia:
+ console.log("Video Loaded")
+ break;
+ case MediaPlayer.BufferingMedia:
+ console.log("Video buffering")
+ break;
+ case MediaPlayer.StalledMedia:
+ console.log("Video stalled")
+ break;
+ case MediaPlayer.BufferedMedia:
+ console.log("Video buffered")
+ break;
+ case MediaPlayer.EndOfMedia:
+ console.log("Video EOF")
+ break;
+ case MediaPlayer.InvalidMedia:
+ console.log("Video invalid")
+ break;
+ }
+ }
+
+ onPlaybackStateChanged:
+ {
+ switch(playbackState)
+ {
+ case MediaPlayer.PlayingState:
+ console.log("Video playback started")
+ break;
+ case MediaPlayer.PausedState:
+ console.log("Video playback paused")
+ break;
+ case MediaPlayer.StoppedState:
+ console.log("Video playback stopped")
+ break;
+ }
+ }
+
+ Component.onCompleted:
+ {
+ videoComponent.loaded = true
+ delayedStartTimer.start()
+ }
+
+ function autoStart()
+ {
+ if(mediaPlayer.source !== "" && mediaPlayer.source !== undefined)
+ {
+ console.log("Starting playback of " + mediaPlayer.source)
+ mediaPlayer.play()
+ }
+ else
+ {
+ console.log("Stopping playback")
+ mediaPlayer.stop()
+ }
+ }
+ }
+
+ Timer
+ {
+ id: delayedStartTimer
+ interval: 500
+ repeat: false
+ onTriggered: mediaPlayer.autoStart()
}
function start() { mediaPlayer.play() }
@@ -395,14 +496,6 @@ Item
blending: channel.blending
}
-
- // ShaderEffectSource
- // {
- // anchors.fill: parent
- // sourceItem: channelShaderOutput
- // hideSource: true
- // visible: true
- // }
}
}
@@ -530,4 +623,129 @@ Item
}
}
}
-}
\ No newline at end of file
+
+ // Scene is directly loaded, no comp needed
+
+ // 3D Model
+ Component
+ {
+ id: modelComponent
+
+ Item
+ {
+ anchors.fill: parent
+ id: channelModelContent
+
+ // recursive frame buffer
+ ShaderEffectSource
+ {
+ id: frameBufferSource
+ sourceItem: channel.frameBufferChannel === -1 ? null : channelModelContent
+ sourceRect: Qt.rect(0,0, channelModelContent.width, channelModelContent.height)
+ wrapMode: ShaderEffectSource.ClampToEdge
+ live: channel.active
+ mipmap: true
+ recursive: true
+ textureSize: Qt.size(channelModelContent.width, channelModelContent.height)
+ visible: false
+ textureMirroring: ShaderEffectSource.NoMirroring
+ width: channel.iResolution.x
+ height: channel.iResolution.y
+ }
+
+ 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 -= (data.iMouse.x - lastX) * 0.5
+ pitch -= (data.iMouse.y - lastY) * -0.5
+ pitch = Math.max(-89, Math.min(89, pitch))
+ lastX = data.iMouse.x
+ lastY = data.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()
+ }
+ }
+
+ Model
+ {
+ source: Qt.resolvedUrl(channel.source)
+ scale: Qt.vector3d(channel.scale)
+ geometry: Komplex.GeometryProvider
+ {
+ source: Qt.resolvedUrl(channel.source)
+ }
+ materials:
+ [
+ CustomMaterial
+ {
+ property var iChannel0: channel.frameBufferChannel === 0 ? frameBufferSource : channelSource0
+ property var iChannel1: channel.frameBufferChannel === 1 ? frameBufferSource : channelSource1
+ property var iChannel2: channel.frameBufferChannel === 2 ? frameBufferSource : channelSource2
+ property var iChannel3: channel.frameBufferChannel === 3 ? frameBufferSource : channelSource3
+
+ property var iResolution: channel.iResolution
+ property var iTime: data.iTime
+ 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: data.iMouse
+ property var iDate: channel.iDate
+
+ property var iChannelResolution: channel.iResolution
+
+ Texture
+ {
+ id: baseColorMap
+ source: Qt.resolvedUrl(channel.materialTexture)
+ }
+
+ cullMode: PrincipledMaterial.NoCulling
+ fragmentShader: Qt.resolvedUrl(channel.materialShader)
+ }
+ ]
+ }
+ }
+ }
+ }
+}
diff --git a/package/contents/ui/ShaderChannelConfiguration.qml b/package/contents/ui/ShaderChannelConfiguration.qml
index ab8a366..aa840cf 100644
--- a/package/contents/ui/ShaderChannelConfiguration.qml
+++ b/package/contents/ui/ShaderChannelConfiguration.qml
@@ -74,6 +74,7 @@ Item
property int resolution_y
property bool enabled
property bool invert
+ property bool changed
id: window
@@ -116,6 +117,16 @@ Item
type: ShaderChannel.Type.ImageChannel
}
+ ListElement
+ {
+ file: true
+ name: "Scene"
+ icon: "./icons/image.svg"
+ title: "Select a scene file"
+ filter: "Image Files (*.qml)"
+ type: ShaderChannel.Type.SceneChannel
+ }
+
ListElement
{
file: true
@@ -574,8 +585,8 @@ Item
function accept()
{
// copy over temp values
- source = tmp_source
type = tmp_type
+ source = tmp_source
timeScale = tmp_timeScale
resolution_scale = tmp_resolution_scale
resolution_x = tmp_resolution_x
@@ -629,6 +640,12 @@ Item
// Function to reset the selection to default values
function resetSelection()
{
+ if((tmp_source !== source) || (tmp_enabled !== enabled) ||
+ (tmp_invert !== invert) || (tmp_resolution_scale !== resolution_scale) ||
+ (tmp_resolution_x !== resolution_x) || (tmp_resolution_y !== resolution_y) ||
+ (tmp_timeScale !== timeScale) || (tmp_type !== type))
+ changed = true;
+
tmp_source = source
tmp_timeScale = timeScale
tmp_resolution_scale = resolution_scale
diff --git a/package/contents/ui/ShaderToyHub.qml b/package/contents/ui/ShaderToyHub.qml
new file mode 100644
index 0000000..cbc4700
--- /dev/null
+++ b/package/contents/ui/ShaderToyHub.qml
@@ -0,0 +1,577 @@
+import QtCore
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Dialogs
+import QtQuick.Layouts
+import QtMultimedia
+import QtWebView
+
+import Komplex.ShaderToy as ShaderToy
+
+Item
+{
+ id: mainItem
+ anchors.fill: parent
+
+ ShaderToy.SearchModel
+ {
+ id: searchModel
+ }
+
+ ColumnLayout
+ {
+ anchors.fill: parent
+
+ RowLayout
+ {
+ Layout.fillHeight: false
+ Layout.fillWidth: true
+ Layout.margins: 6
+
+ TextField
+ {
+ Layout.preferredHeight: 32
+ Layout.fillWidth: true
+
+ id: searchField
+ placeholderText: "Search"
+ onEditingFinished: mainItem.updateSearch()
+ Keys.onPressed: (event) =>
+ {
+ if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter)
+ {
+ searchField.focus = false; // Unfocus the TextField
+ event.accepted = true; // Prevent further propagation of the key event
+ }
+ }
+ }
+
+ Button
+ {
+ Layout.preferredHeight: 32
+ Layout.preferredWidth: 32
+
+ icon.name: "search-symbolic"
+
+ onClicked: mainItem.updateSearch()
+ }
+ }
+
+ Component
+ {
+ id: highlight
+ Rectangle
+ {
+ width: view.cellWidth; height: view.cellHeight
+ color: palette.highlight; radius: 5
+ x: view.currentItem.x
+ y: view.currentItem.y
+ Behavior on x { SpringAnimation { spring: 3; damping: 0.2 } }
+ Behavior on y { SpringAnimation { spring: 3; damping: 0.2 } }
+ }
+ }
+
+ Rectangle
+ {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ color: palette.base
+ clip: true
+
+ RowLayout
+ {
+ anchors.fill: parent
+
+ GridView
+ {
+ // The standard size
+ property int idealCellHeight: 200
+ property int idealCellWidth: 250
+ cellWidth: width / Math.floor(width / idealCellWidth)
+ cellHeight: idealCellHeight
+
+ id: view
+
+ model: searchModel
+ highlight: highlight
+ highlightFollowsCurrentItem: false
+
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ Layout.margins: 6
+
+ delegate: Column
+ {
+ property int itemIndex: index
+ property string shaderThumbnail: thumbnail
+ property string shaderEmbed: embedUrl
+ property string shaderId: id
+ property string author: username
+ property string shaderDescription: model.description
+
+ id: entry
+
+ leftPadding: Math.floor((width - thumbnailImage.width) / 2)
+ topPadding: 10
+ rightPadding: Math.floor((width - thumbnailImage.width) / 2)
+ bottomPadding: 10
+ width: view.cellWidth
+
+ Image
+ {
+ width: 250
+ height: 140
+ id: thumbnailImage
+ source: thumbnail
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Rectangle
+ {
+ color: palette.base
+ anchors.fill: parent
+ visible: thumbnailImage.status === Image.Loading
+
+ RowLayout
+ {
+ anchors.fill: parent
+
+ BusyIndicator
+ {
+ Layout.alignment: Qt.AlignCenter
+ Layout.preferredHeight: 64
+ Layout.preferredWidth: 64
+ visible: running
+ }
+ }
+ }
+
+ Rectangle
+ {
+ color: palette.dark
+ anchors.fill: parent
+ visible: thumbnailImage.status === Image.Error
+
+ Text
+ {
+ color: palette.text
+ anchors.centerIn: parent
+ text: qsTr("Error Loading Image")
+ }
+ }
+
+ MouseArea
+ {
+ anchors.fill: parent
+ onClicked:
+ (mouse) => {
+ view.currentIndex = parent.parent.itemIndex
+ //searchModel.currentIndex = view.currentIndex
+ }
+ }
+ }
+
+ RowLayout
+ {
+ visible: parent.itemIndex === view.currentIndex
+ width: thumbnailImage.width
+ Button
+ {
+ Layout.topMargin: 4
+ Layout.alignment: Qt.AlignRight
+ Layout.preferredHeight: 32
+ Layout.fillWidth: true
+ icon.name: "emblem-downloads"
+ text: qsTr("Preview & Download")
+ onClicked: () =>
+ {
+ downloadDialog.open()
+ }
+ }
+ }
+
+ Dialog
+ {
+ id: downloadDialog
+ modal: Qt.WindowModal
+ width: 600
+ height: 440
+
+ parent: mainItem
+ anchors.centerIn: mainItem
+ clip: true
+
+ ColumnLayout
+ {
+ anchors.fill: parent
+
+ WebView
+ {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ id: shaderPreview
+ Layout.preferredWidth: 500
+ Layout.preferredHeight: 281
+ Layout.alignment: Qt.AlignHCenter
+ url: ""
+ }
+
+ Text
+ {
+ Layout.preferredHeight: 64
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ id: shaderDescription
+ text: model.description
+ elide: Text.ElideRight
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+ maximumLineCount: 3
+ color: palette.text
+ }
+
+ Button
+ {
+ Layout.fillWidth: true
+ icon.name: "image-symbolic"
+
+ id: downloadButton
+ text: qsTr("Convert to Komplex Pack")
+
+ onClicked: () =>
+ {
+ workingThumbnail.source = model.thumbnail
+ searchModel.convert(model.index);
+ downloadDialog.close();
+ }
+ }
+ }
+
+ onClosed: () =>
+ {
+ shaderPreview.loadHtml(``)
+ }
+
+ onOpened: () =>
+ {
+ shaderDescription.text = model.description
+ shaderPreview.loadHtml(``)
+ }
+ }
+ }
+ populate: Transition
+ {
+ NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 1000 }
+ }
+ }
+ }
+ }
+
+ RowLayout
+ {
+ Layout.margins: 6
+ Layout.fillWidth: true
+
+ Button
+ {
+ text: "Previous"
+ enabled: searchModel.currentPage > 1
+ onClicked: searchModel.previous()
+ }
+
+ RowLayout
+ {
+ Layout.margins: 6
+ Layout.fillWidth: true
+
+ Text
+ {
+ visible: searchModel.totalResults > 0
+ color: palette.text
+ text: ((searchModel.resultsPerPage * searchModel.currentPage) - searchModel.resultsPerPage + 1) + "-" + (searchModel.resultsPerPage * searchModel.currentPage) + " of " + searchModel.totalResults
+ }
+
+ Text
+ {
+ color: palette.text
+ Layout.fillWidth: true
+ text: "Page " + searchModel.currentPage + " of " + Math.ceil(searchModel.totalResults / searchModel.resultsPerPage)
+ }
+
+ Text
+ {
+ color: palette.text
+ text: "Shaders provided by ShaderToy"
+ font.bold: true
+
+ onLinkActivated: (link) => Qt.openUrlExternally(link)
+
+ MouseArea
+ {
+ anchors.fill: parent
+ onClicked: (mouse) => Qt.openUrlExternally("https://www.shadertoy.com")
+ cursorShape: Qt.PointingHandCursor
+ }
+ }
+ }
+ Button
+ {
+ text: "Next"
+ enabled: searchModel.currentPage <= searchModel.totalPages
+ onClicked: searchModel.next()
+ }
+ }
+ }
+
+ Rectangle
+ {
+ color: palette.base
+ anchors.fill: parent
+ visible: searchModel.status === ShaderToy.SearchModel.Searching || searchModel.status === ShaderToy.SearchModel.Compiling
+
+ RowLayout
+ {
+ anchors.fill: parent
+
+ Image
+ {
+ visible: searchModel.status === ShaderToy.SearchModel.Compiling
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+
+ fillMode: Image.PreserveAspectCrop
+ id: workingThumbnail
+ }
+
+ Text
+ {
+ id: stateText
+ text: searchModel.statusMessage
+ color: palette.text
+ elide: Text.ElideRight
+ visible: searchModel.status === ShaderToy.SearchModel.Compiling
+ }
+
+ ProgressBar
+ {
+ id: totalProgress
+ Layout.fillWidth: true
+ Layout.preferredHeight: 6
+ visible: searchModel.status === ShaderToy.SearchModel.Compiling
+ }
+
+ Text
+ {
+ id: downloadText
+ text: qsTr(searchModel.downloadText)
+ color: palette.text
+ elide: Text.ElideRight
+ visible: searchModel.totalDownloads > 0 && searchModel.status === ShaderToy.SearchModel.Compiling
+ }
+
+ ProgressBar
+ {
+ id: downloadProgress
+ Layout.fillWidth: true
+ Layout.preferredHeight: 6
+ from: 0
+ to: searchModel.totalDownloads
+ value: searchModel.completedDownloads
+ visible: searchModel.totalDownloads > 0 && searchModel.status === ShaderToy.SearchModel.Compiling
+ }
+ }
+
+ BusyIndicator
+ {
+ Layout.alignment: Qt.AlignCenter
+ width: 128
+ height: 128
+ visible: running
+ anchors.centerIn: parent
+ }
+ }
+
+ Rectangle
+ {
+ property int totalVideos: searchModel.videoSelections.length
+ property int selectedVideos: 0
+
+ anchors.fill: parent
+ color: palette.base
+ visible: searchModel.status === ShaderToy.SearchModel.Compiled
+ enabled: visible
+
+ id: mediaSelectionItem
+
+ signal accepted
+
+ ColumnLayout
+ {
+ anchors.fill: parent
+ Text
+ {
+ Layout.margins: 6
+ Layout.alignment: Qt.AlignTop
+ Layout.fillWidth: true
+ Layout.preferredHeight: 50
+ color: palette.text
+ text: "Select Media
";
+ }
+
+ Text
+ {
+ Layout.margins: 6
+ Layout.alignment: Qt.AlignTop
+ Layout.fillWidth: true
+ color: palette.text
+ text: qsTr("The shader you selected contains one or more video references. However, the videos available on ShaderToy are not likely to be desired.\n\nPlease select a new video source from your local drive or from Pexels.")
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+ }
+
+ Repeater
+ {
+ Layout.fillWidth: true
+ Layout.margins: 6
+ model: searchModel.videoSelections
+
+ Item
+ {
+ width: parent.width
+ required property string modelData
+
+ RowLayout
+ {
+ anchors.fill: parent
+
+ TextField
+ {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 35
+ id: mediaSelectionField
+ placeholderText: "Select Media Source"
+ color: palette.text
+ onEditingFinished:
+ {
+ if(text.length > 0)
+ mediaSelectionItem.selectedVideos += 1
+ else if(mediaItem.selectedVideos > 0)
+ mediaSelectionItem.selectedVideos -= 1
+ }
+ }
+
+ Button
+ {
+ icon.name: "folder-symbolic"
+ onClicked: fileDialog.open()
+ }
+
+ Button
+ {
+ icon.name: "network-symbolic"
+ onClicked: pexelsDialog.open()
+ }
+ }
+
+ FileDialog
+ {
+ id: fileDialog
+ currentFolder: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
+ onAccepted:
+ {
+ mediaSelectionField.text = selectedFile
+ close();
+ }
+ }
+
+ Dialog
+ {
+ width: 640
+ height: 480
+ id: pexelsDialog
+
+ parent: mainItem
+ anchors.centerIn: parent
+
+ PexelsVideoHub
+ {
+ onSelectedFileChanged:
+ {
+ mediaSelectionField.text = selectedFile
+ mediaSelectionField.editingFinished()
+ pexelsDialog.close()
+ }
+ }
+ }
+
+ Connections
+ {
+ target: mediaSelectionItem
+ function onAccepted()
+ {
+ searchModel.replaceSource(view.currentIndex, modelData, mediaSelectionField.text)
+ }
+ }
+ }
+ }
+
+ Rectangle
+ {
+ color: "transparent"
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+
+ Button
+ {
+ Layout.preferredHeight: 35
+ Layout.alignment: Qt.AlignRight | Qt.AlignBottom
+ text: qsTr("Accept")
+ enabled: mediaSelectionItem.totalVideos === mediaSelectionItem.selectedVideos
+
+ onClicked: mediaSelectionItem.accepted()
+ }
+ }
+ }
+
+ MessageDialog
+ {
+ buttons: MessageDialog.Ok
+ id: warningDialog
+ text: searchModel.statusMessage
+
+ Connections
+ {
+ target: searchModel
+ function onStatusChanged()
+ {
+ console.log("Search Model Status " + searchModel.status)
+
+ if(searchModel.status === ShaderToy.SearchModel.Error)
+ {
+ warningDialog.open();
+ }
+ }
+ }
+ }
+
+ MessageDialog
+ {
+ buttons: MessageDialog.Ok
+ id: messageDialog
+ text: searchModel.statusMessage
+
+ Connections
+ {
+ target: searchModel
+ function onShaderInstalled()
+ {
+ messageDialog.open();
+ }
+ }
+ }
+
+ function updateSearch()
+ {
+ console.log(searchField.text)
+ searchModel.currentPage = 1
+ searchModel.query = searchField.text
+ }
+}
diff --git a/package/contents/ui/ShaderToyModel.qml b/package/contents/ui/ShaderToyModel.qml
index bc3e4bb..27f10d6 100644
--- a/package/contents/ui/ShaderToyModel.qml
+++ b/package/contents/ui/ShaderToyModel.qml
@@ -24,7 +24,6 @@
* 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
@@ -94,7 +93,7 @@ Item
source: wallpaper.configuration.iChannel0_flag ? Qt.resolvedUrl(wallpaper.configuration.iChannel0) : ""
// ShaderToy seems to start at bottom left for 0,0
- invert: wallpaper.configuration.iChannel0_inverted
+ invert: wallpaper.configuration.iChannel0_inverted !== undefined ? wallpaper.configuration.iChannel0_inverted : true
}
ShaderChannel
diff --git a/package/contents/ui/config.qml b/package/contents/ui/config.qml
index 8915be2..ee27b0c 100644
--- a/package/contents/ui/config.qml
+++ b/package/contents/ui/config.qml
@@ -94,6 +94,7 @@ Kirigami.FormLayout
property alias cfg_resolution_y: resolutionYField.value
property alias cfg_framerate_limit: frameRateField.value
+ property bool cfg_shader_updated
Palette
{
@@ -366,7 +367,13 @@ Kirigami.FormLayout
id: shaderChannelConfig0
palette: palette
height: 350
- onAccepted: shaderChannelOverlay0.close(); // Close the overlay after configuration
+ onAccepted: () =>
+ {
+ if(shaderChannelConfig0.changed)
+ root.cfg_shader_updated = true
+
+ shaderChannelOverlay0.close(); // Close the overlay after configuration
+ }
onRejected: shaderChannelOverlay0.close();
}
}
@@ -411,7 +418,13 @@ Kirigami.FormLayout
{
id: shaderChannelConfig1
height: 350
- onAccepted: shaderChannelOverlay1.close(); // Close the overlay after configuration
+ onAccepted: () =>
+ {
+ if(shaderChannelConfig1.changed)
+ root.cfg_shader_updated = true
+
+ shaderChannelOverlay1.close(); // Close the overlay after configuration
+ }
onRejected: shaderChannelOverlay1.close();
}
}
@@ -456,7 +469,13 @@ Kirigami.FormLayout
{
id: shaderChannelConfig2
height: 350
- onAccepted: shaderChannelOverlay2.close(); // Close the overlay after configuration
+ onAccepted: () =>
+ {
+ if(shaderChannelConfig2.changed)
+ root.cfg_shader_updated = true
+
+ shaderChannelOverlay2.close(); // Close the overlay after configuration
+ }
onRejected: shaderChannelOverlay2.close();
}
}
@@ -500,7 +519,13 @@ Kirigami.FormLayout
{
id: shaderChannelConfig3
height: 350
- onAccepted: shaderChannelOverlay3.close(); // Close the overlay after configuration
+ onAccepted: () =>
+ {
+ if(shaderChannelConfig3.changed)
+ root.cfg_shader_updated = true
+
+ shaderChannelOverlay3.close(); // Close the overlay after configuration
+ }
onRejected: shaderChannelOverlay3.close();
}
}
diff --git a/package/contents/ui/main.qml b/package/contents/ui/main.qml
index 8267086..b49ff4e 100644
--- a/package/contents/ui/main.qml
+++ b/package/contents/ui/main.qml
@@ -43,6 +43,13 @@ WallpaperItem
property string shaderPack: wallpaper.configuration.shader_package
property bool changing: false
+ property bool updated: wallpaper.configuration.shader_updated
+
+ property bool iChannel0_inverted: wallpaper.configuration.iChannel0_inverted
+ property bool iChannel1_inverted: wallpaper.configuration.iChannel1_inverted
+ property bool iChannel2_inverted: wallpaper.configuration.iChannel2_inverted
+ property bool iChannel3_inverted: wallpaper.configuration.iChannel3_inverted
+
anchors.fill: parent
Loader
@@ -78,6 +85,7 @@ WallpaperItem
ShaderToyModel
{
+ //wallpaper: wallpaper
screenGeometry: wallpaperItem.parent.screenGeometry
anchors.fill: parent
}
@@ -95,41 +103,18 @@ WallpaperItem
}
// band-aid section
- onResolution_xChanged: () =>
+ onResolution_xChanged: () => reload();
+ onResolution_yChanged: () => reload();
+ onShaderPackChanged: () => reload();
+ onIChannel0_invertedChanged: () => reload();
+
+ onUpdatedChanged: () =>
{
- if(changing)
- return;
-
- changing = true
-
- pageLoader.sourceComponent = null
-
- if(wallpaper.configuration.komplex_mode === 0)
- pageLoader.sourceComponent = shaderToyContent
- else
- pageLoader.sourceComponent = packContent
-
- changing = false
+ if(updated)
+ reload();
}
- onResolution_yChanged: () =>
- {
- if(changing)
- return;
-
- changing = true
-
- pageLoader.sourceComponent = null
-
- if(wallpaper.configuration.komplex_mode === 0)
- pageLoader.sourceComponent = shaderToyContent
- else
- pageLoader.sourceComponent = packContent
-
- changing = false
- }
-
- onShaderPackChanged: () =>
+ function reload()
{
if(changing)
return;