Inital commit of the Komplex Hub
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
### This file is automatically generated by Qt Design Studio.
|
||||
### Do not change
|
||||
|
||||
qt_add_library(KomplexHubModule_Controls STATIC)
|
||||
qt6_add_qml_module(KomplexHubModule_Controls
|
||||
URI "KomplexHub.Controls"
|
||||
VERSION 1.0
|
||||
RESOURCE_PREFIX "/qt/qml"
|
||||
QML_FILES
|
||||
"LineEdit.qml"
|
||||
"MenuButton.qml"
|
||||
"SearchBar.qml"
|
||||
"SquareButton.qml"
|
||||
"Throbber.qml"
|
||||
"SearchResultItem.qml"
|
||||
"SearchResultList.qml"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* QML LineEdit Control
|
||||
* Copyright (C) 2026 @DigitalArtifex | github.com/DigitalArtifex | digitalartifex.dev
|
||||
*
|
||||
* This control provides a wrapped TextInput that looks and acts more like the
|
||||
* LineEdit control from QtWidgets
|
||||
*
|
||||
* 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 QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
|
||||
/* Grouped Settings */
|
||||
component BorderSettings: QtObject {
|
||||
property color color: palette.alternateBase.lighter()
|
||||
property int width: 1
|
||||
property bool pixelAligned: true
|
||||
}
|
||||
|
||||
component Corners: QtObject {
|
||||
property int bottom: 0
|
||||
property int left: 0
|
||||
property int right: 0
|
||||
property int top: 0
|
||||
}
|
||||
|
||||
component Radius: QtObject {
|
||||
property int topLeft: 0
|
||||
property int topRight: 0
|
||||
property int bottomLeft: 0
|
||||
property int bottomRight: 0
|
||||
}
|
||||
/* End Grouped Settings */
|
||||
|
||||
property var background: palette.alternateBase.darker()
|
||||
property BorderSettings border: BorderSettings {}
|
||||
|
||||
property Corners margins: Corners {bottom: 3; top: 3; left: 3; right: 3}
|
||||
property Radius radius: Radius {}
|
||||
property font font
|
||||
property color color: palette.text
|
||||
property Corners padding: Corners {bottom: 6; top: 6; left: 6; right: 6}
|
||||
property alias text: lineEditInput.text
|
||||
|
||||
signal accepted
|
||||
signal editingFinished
|
||||
|
||||
id: lineEditRoot
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
id: lineEditBackground
|
||||
|
||||
border.color: lineEditRoot.border.color
|
||||
border.width: lineEditRoot.border.width
|
||||
border.pixelAligned: lineEditRoot.border.pixelAligned
|
||||
|
||||
color: lineEditRoot.background
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.topMargin: lineEditRoot.margins.top
|
||||
Layout.bottomMargin: lineEditRoot.margins.bottom
|
||||
Layout.leftMargin: lineEditRoot.margins.left
|
||||
Layout.rightMargin: lineEditRoot.margins.right
|
||||
|
||||
bottomLeftRadius: lineEditRoot.radius.bottomLeft
|
||||
bottomRightRadius: lineEditRoot.radius.bottomRight
|
||||
topLeftRadius: lineEditRoot.radius.topLeft
|
||||
topRightRadius: lineEditRoot.radius.topRight
|
||||
|
||||
TextInput {
|
||||
id: lineEditInput
|
||||
anchors.fill: parent
|
||||
color: lineEditRoot.color
|
||||
font: lineEditRoot.font
|
||||
|
||||
anchors.leftMargin: lineEditRoot.padding.left
|
||||
anchors.topMargin: lineEditRoot.padding.top
|
||||
anchors.bottomMargin: lineEditRoot.padding.bottom
|
||||
anchors.rightMargin: lineEditRoot.padding.right
|
||||
|
||||
onAccepted: () => lineEditRoot.accepted()
|
||||
onTextChanged: () => lineEditRoot.text = lineEditInput.text
|
||||
onEditingFinished: () => lineEditRoot.editingFinished()
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "focused"
|
||||
when: lineEditInput.focus
|
||||
PropertyChanges {
|
||||
target: lineEditBackground
|
||||
border.color: palette.accent
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import KomplexHub
|
||||
|
||||
Item {
|
||||
id: menuButtonRoot
|
||||
property string text: "Button Text"
|
||||
property IconSettings icon: IconSettings {}
|
||||
property var page: null
|
||||
property bool selected: false
|
||||
|
||||
signal triggered
|
||||
|
||||
component IconSettings: QtObject {
|
||||
property int width: 16
|
||||
property int height: 16
|
||||
property string url: ""
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: menuButtonBackground
|
||||
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
layoutDirection: Qt.LeftToRight
|
||||
|
||||
Image {
|
||||
id: menuButtonIcon
|
||||
source: icon.url
|
||||
|
||||
Layout.preferredWidth: icon.height
|
||||
Layout.preferredHeight: icon.width
|
||||
Layout.topMargin: Constants.mediumMargin
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
}
|
||||
|
||||
Text {
|
||||
color: palette.text
|
||||
text: menuButtonRoot.text
|
||||
|
||||
elide: Text.ElideMiddle
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
font.pixelSize: 12
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Constants.mediumMargin
|
||||
Layout.bottomMargin: Constants.mediumMargin
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Constants.fastAnimationDuration }
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
property string lastState
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
onEntered: () => {
|
||||
menuButtonRoot.state = "hovered"
|
||||
}
|
||||
|
||||
onExited: () => {
|
||||
lastState = ""
|
||||
|
||||
if(selected)
|
||||
menuButtonRoot.state = "selected"
|
||||
else
|
||||
menuButtonRoot.state = ""
|
||||
}
|
||||
|
||||
onPressed: () => {
|
||||
lastState = menuButtonRoot.state
|
||||
menuButtonRoot.state = "clicked"
|
||||
}
|
||||
|
||||
onReleased: () => {
|
||||
if(!mouseArea.containsMouse)
|
||||
return;
|
||||
|
||||
if(!selected)
|
||||
selected = true
|
||||
}
|
||||
}
|
||||
|
||||
onSelectedChanged: () => {
|
||||
if(selected) {
|
||||
triggered()
|
||||
state = "selected"
|
||||
}
|
||||
else {
|
||||
state = ""
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "selected"
|
||||
|
||||
PropertyChanges {
|
||||
target: menuButtonBackground
|
||||
color: palette.accent.darker(1.5)
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "hovered"
|
||||
//when: mouseArea.containsMouse
|
||||
|
||||
PropertyChanges {
|
||||
target: menuButtonBackground
|
||||
color: palette.button
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "clicked"
|
||||
//when: mouseArea.containsPress
|
||||
|
||||
PropertyChanges {
|
||||
target: menuButtonBackground
|
||||
color: palette.button.darker(1.5)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
This is a UI file (.ui.qml) that is intended to be edited in Qt Design Studio only.
|
||||
It is supposed to be strictly declarative and only uses a subset of QML. If you edit
|
||||
this file manually, you might introduce QML code that is not supported by Qt Design Studio.
|
||||
Check out https://doc.qt.io/qtcreator/creator-quick-ui-forms.html for details on .ui.qml files.
|
||||
*/
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import KomplexHub
|
||||
|
||||
Item {
|
||||
property string searchTerm: ""
|
||||
property string icon: ""
|
||||
property color color: "transparent"
|
||||
|
||||
id: searchBarRootItem
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
color: searchBarRootItem.color
|
||||
anchors.fill: parent
|
||||
|
||||
RowLayout {
|
||||
id: searchContainerLayout
|
||||
anchors.fill: parent
|
||||
|
||||
LineEdit {
|
||||
id: searchEdit
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Constants.mediumMargin
|
||||
Layout.rightMargin: Constants.mediumMargin
|
||||
Layout.topMargin: Constants.smallMargin
|
||||
Layout.bottomMargin: Constants.smallMargin
|
||||
padding.top: 12
|
||||
|
||||
font.pixelSize: 12
|
||||
|
||||
onAccepted: () => searchBarRootItem.searchTerm = text
|
||||
}
|
||||
|
||||
SquareButton {
|
||||
id: searchButton
|
||||
|
||||
Layout.fillHeight: searchEdit.height
|
||||
Layout.preferredWidth: searchEdit.height
|
||||
Layout.rightMargin: Constants.mediumMargin
|
||||
Layout.topMargin: Constants.mediumMargin
|
||||
Layout.bottomMargin: Constants.mediumMargin
|
||||
|
||||
icon.source: searchBarRootItem.icon
|
||||
icon.width: 16
|
||||
icon.height: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "hidden"
|
||||
PropertyChanges {
|
||||
target: searchBarRootItem
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
property string author: "Author"
|
||||
property string description: "This is a description of the search result item"
|
||||
property string title: "Wallpaper Title"
|
||||
property string thumbnail: ""
|
||||
property string url: ""
|
||||
property string uuid: ""
|
||||
property bool selected: false
|
||||
|
||||
signal triggered
|
||||
|
||||
id: searchResultItemRoot
|
||||
width: 256
|
||||
height: 256
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
id: searchResultItemContainer
|
||||
anchors.fill: parent
|
||||
|
||||
border.color: palette.alternateBase.lighter(1.75)
|
||||
|
||||
color: palette.alternateBase.lighter(1.25)
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 250
|
||||
Layout.preferredHeight: 141
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
Layout.topMargin: 3
|
||||
|
||||
border.color: palette.alternateBase.lighter(1.75)
|
||||
|
||||
color: palette.base.lighter(1.25)
|
||||
}
|
||||
|
||||
Text {
|
||||
color: palette.text
|
||||
text: searchResultItemRoot.title
|
||||
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
elide: Text.ElideRight
|
||||
|
||||
Layout.preferredWidth: 246
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
}
|
||||
|
||||
Text {
|
||||
color: palette.text
|
||||
font.pixelSize: 12
|
||||
text: "By: " + searchResultItemRoot.author
|
||||
leftPadding: 12
|
||||
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
|
||||
Layout.preferredWidth: 250
|
||||
}
|
||||
|
||||
Text {
|
||||
color: palette.text
|
||||
font.pixelSize: 12
|
||||
text: searchResultItemRoot.description
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||
Layout.preferredWidth: 236
|
||||
Layout.bottomMargin: 3
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: 200 }
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
property string lastState
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
onEntered: () => {
|
||||
if(!selected)
|
||||
searchResultItemRoot.state = "hovered"
|
||||
}
|
||||
|
||||
onExited: () => {
|
||||
lastState = ""
|
||||
|
||||
if(selected)
|
||||
searchResultItemRoot.state = "selected"
|
||||
else
|
||||
searchResultItemRoot.state = ""
|
||||
}
|
||||
|
||||
onPressed: () => {
|
||||
if(!selected) {
|
||||
lastState = searchResultItemRoot.state
|
||||
searchResultItemRoot.state = "clicked"
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: () => {
|
||||
if(!mouseArea.containsMouse)
|
||||
return;
|
||||
|
||||
selected = !selected
|
||||
}
|
||||
}
|
||||
|
||||
onSelectedChanged: () => {
|
||||
if(selected) {
|
||||
searchResultItemRoot.triggered()
|
||||
searchResultItemRoot.state = "selected"
|
||||
}
|
||||
else {
|
||||
searchResultItemRoot.state = ""
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "hovered"
|
||||
|
||||
PropertyChanges {
|
||||
target: searchResultItemContainer
|
||||
color: palette.alternateBase.lighter(1.5)
|
||||
border.color: Qt.hsva(palette.accent.hslHue,
|
||||
palette.accent.hslSaturation,
|
||||
0.5, // Set custom lightness
|
||||
1.0)
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "selected"
|
||||
|
||||
PropertyChanges {
|
||||
target: searchResultItemContainer
|
||||
color: palette.accent
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import KomplexHub
|
||||
|
||||
Item {
|
||||
readonly property bool searchable: true
|
||||
property int resultsPerPage: resultsPerRow * resultsPerColumn
|
||||
property int resultsPerRow: width / (256 + Constants.largeMargin)
|
||||
property int resultsPerColumn: height / (256 + Constants.largeMargin)
|
||||
property int totalResults: 7
|
||||
property int currentPage: 1
|
||||
property color color: "transparent"
|
||||
|
||||
id: searchResultsPageRoot
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: searchResultsPageRoot.color
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Constants.largeMargin
|
||||
|
||||
Component {
|
||||
id: highlight
|
||||
|
||||
Rectangle {
|
||||
width: view.cellWidth; height: view.cellHeight
|
||||
color: "lightsteelblue";
|
||||
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 } }
|
||||
}
|
||||
}
|
||||
|
||||
//So the idea here is that the real query would request X number of results
|
||||
// (say 50), but then the cache aggregator will paginate on the cache
|
||||
// in a way that it would also account for the total number of results for
|
||||
// the search term
|
||||
|
||||
//this is dummy data for the cache
|
||||
WallpaperModelData {
|
||||
property int totalResults: 17
|
||||
property string term: ""
|
||||
|
||||
onTermChanged: () => {
|
||||
//clear()
|
||||
|
||||
//this will be in the plugin
|
||||
//query(0, 100, term)
|
||||
}
|
||||
|
||||
id: wallpaperDataCache
|
||||
|
||||
//use the current search term to get the next chunk of data from the server
|
||||
function extendCache() {
|
||||
//this will be in the plugin
|
||||
//query(count, 100, term)
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
property int index: 0
|
||||
|
||||
id: wallpaperCacheAggregator
|
||||
|
||||
//get the previous page data
|
||||
function previous() {
|
||||
searchResultsPageRoot.state = "loading"
|
||||
index -= searchResultsPageRoot.resultsPerPage
|
||||
|
||||
if(index < 0)
|
||||
index = 0
|
||||
|
||||
searchResultsPageRoot.currentPage -= 1
|
||||
|
||||
if(searchResultsPageRoot.currentPage < 1)
|
||||
searchResultsPageRoot.currentPage = 1
|
||||
|
||||
searchResultsPageRoot.state = ""
|
||||
aggregateCache()
|
||||
}
|
||||
|
||||
//get the next page data
|
||||
function next() {
|
||||
searchResultsPageRoot.state = "loading"
|
||||
|
||||
index += searchResultsPageRoot.resultsPerPage
|
||||
|
||||
if(index >= searchResultsPageRoot.totalResults)
|
||||
index = searchResultsPageRoot.totalResults - searchResultsPageRoot.resultsPerPage
|
||||
|
||||
searchResultsPageRoot.currentPage += 1
|
||||
|
||||
if(searchResultsPageRoot.currentPage > Math.ceil(searchResultsPageRoot.totalResults / searchResultsPageRoot.resultsPerPage))
|
||||
searchResultsPageRoot.currentPage = Math.ceil(searchResultsPageRoot.totalResults / searchResultsPageRoot.resultsPerPage)
|
||||
|
||||
//download next chunk of data if we're at the end
|
||||
if(index >= wallpaperDataCache.count)
|
||||
wallpaperDataCache.extendCache()
|
||||
|
||||
searchResultsPageRoot.state = ""
|
||||
aggregateCache()
|
||||
}
|
||||
|
||||
//re-aggregate the current cache
|
||||
function aggregateCache() {
|
||||
clear()
|
||||
|
||||
for(var i = 0; i < searchResultsPageRoot.resultsPerPage && (index + i) < wallpaperDataCache.count; i++)
|
||||
insert(i, wallpaperDataCache.get(index + i))
|
||||
}
|
||||
}
|
||||
|
||||
GridView {
|
||||
id: resultsView
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
cellWidth: 256 + Constants.mediumMargin
|
||||
cellHeight: 256 + Constants.mediumMargin
|
||||
clip: true
|
||||
model: wallpaperCacheAggregator
|
||||
|
||||
delegate: Rectangle {
|
||||
required property string author
|
||||
required property string description
|
||||
required property string name
|
||||
required property string thumbnail
|
||||
required property string url
|
||||
required property string uuid
|
||||
|
||||
SearchResultItem {
|
||||
author: parent.author
|
||||
description: parent.description
|
||||
title: parent.name
|
||||
thumbnail: parent.thumbnail
|
||||
url: parent.url
|
||||
uuid: parent.uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: false
|
||||
Layout.preferredHeight: 36
|
||||
Layout.margins: Constants.largeMargin
|
||||
|
||||
SquareButton {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: 160
|
||||
|
||||
icon.source: "qrc:/images/icons/icons8-back.svg"
|
||||
icon.height: 16
|
||||
icon.width: 16
|
||||
text: qsTr("Previous")
|
||||
|
||||
enabled: searchResultsPageRoot.currentPage > 1
|
||||
|
||||
onTriggered: () => {
|
||||
wallpaperCacheAggregator.previous()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SquareButton {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: 160
|
||||
|
||||
icon.source: "qrc:/images/icons/icons8-next.svg"
|
||||
icon.height: 16
|
||||
icon.width: 16
|
||||
text: qsTr("Next")
|
||||
|
||||
enabled: searchResultsPageRoot.currentPage < Math.ceil(searchResultsPageRoot.totalResults / searchResultsPageRoot.resultsPerPage)
|
||||
|
||||
onTriggered: () => {
|
||||
wallpaperCacheAggregator.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "loading"
|
||||
},
|
||||
State {
|
||||
name: "empty"
|
||||
},
|
||||
State {
|
||||
name: "error"
|
||||
}
|
||||
]
|
||||
|
||||
onResultsPerPageChanged: () => wallpaperCacheAggregator.aggregateCache();
|
||||
Component.onCompleted: () => wallpaperCacheAggregator.aggregateCache()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
property string text: qsTr("")
|
||||
property IconSettings icon: IconSettings {}
|
||||
property alias spacing: squareButtonLayout.spacing
|
||||
property int margins: 4
|
||||
property bool checkable: false
|
||||
property bool checked: false
|
||||
property color color: palette.button
|
||||
property Gradient gradient: defaultGradient
|
||||
property BorderSettings border: BorderSettings {}
|
||||
|
||||
signal triggered
|
||||
|
||||
height: 36
|
||||
width: 180
|
||||
|
||||
id: squareButtonRoot
|
||||
|
||||
//icon properties. access them after Component.onLoad()
|
||||
component IconSettings: QtObject {
|
||||
property int width: 16
|
||||
property int height: 16
|
||||
property string source: "images/icons/icons8-next.svg"
|
||||
}
|
||||
|
||||
component BorderSettings: QtObject {
|
||||
}
|
||||
|
||||
Gradient {
|
||||
id: clickedGradient
|
||||
GradientStop { position: 0.0; color: palette.button.darker(1.25) }
|
||||
GradientStop { position: 1.0; color: palette.button }
|
||||
}
|
||||
|
||||
Gradient {
|
||||
id: defaultGradient
|
||||
GradientStop { position: 0.0; color: palette.button.lighter(1.25) }
|
||||
GradientStop { position: 1.0; color: palette.button }
|
||||
}
|
||||
|
||||
Gradient {
|
||||
id: hoveredGradient
|
||||
GradientStop { position: 0.0; color: palette.button.lighter(1.5) }
|
||||
GradientStop { position: 1.0; color: palette.button.lighter(1.25) }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: squareButtonBackground
|
||||
anchors.fill: parent
|
||||
gradient: squareButtonRoot.gradient
|
||||
border.color: palette.button.lighter()
|
||||
|
||||
RowLayout {
|
||||
id: squareButtonLayout
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredHeight: squareButtonRoot.height < squareButtonRoot.width ? squareButtonRoot.height : squareButtonRoot.width
|
||||
Layout.preferredWidth: squareButtonRoot.height < squareButtonRoot.width ? squareButtonRoot.height : squareButtonRoot.width
|
||||
|
||||
id: iconContainer
|
||||
|
||||
color: "transparent"
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Image {
|
||||
id: squareButtonIcon
|
||||
|
||||
Layout.preferredWidth: icon.width
|
||||
Layout.preferredHeight: icon.height
|
||||
Layout.maximumHeight: icon.height
|
||||
Layout.maximumWidth: icon.width
|
||||
|
||||
source: icon.source
|
||||
fillMode: Image.PreserveAspectFit
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: squareButtonText
|
||||
color: palette.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideMiddle
|
||||
text: squareButtonRoot.text
|
||||
|
||||
font.pixelSize: 14
|
||||
|
||||
visible: text.length > 0
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: iconContainer.width * -1
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: 250 }
|
||||
}
|
||||
|
||||
// Behavior on gradient {
|
||||
// PropertyAnimation { duration: 250 }
|
||||
// }
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
property string lastState
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
onEntered: () => {
|
||||
squareButtonRoot.state = "hovered"
|
||||
}
|
||||
|
||||
onExited: () => {
|
||||
lastState = ""
|
||||
|
||||
if(checked)
|
||||
squareButtonRoot.state = "checked"
|
||||
else
|
||||
squareButtonRoot.state = ""
|
||||
}
|
||||
|
||||
onPressed: () => {
|
||||
lastState = squareButtonRoot.state
|
||||
squareButtonRoot.state = "clicked"
|
||||
}
|
||||
|
||||
onReleased: () => {
|
||||
if(!mouseArea.containsMouse)
|
||||
return;
|
||||
|
||||
squareButtonRoot.state = lastState
|
||||
|
||||
if(checkable)
|
||||
checked = !checked
|
||||
|
||||
triggered()
|
||||
}
|
||||
}
|
||||
|
||||
onCheckedChanged: () => {
|
||||
if(checked) {
|
||||
squareButtonRoot.triggered()
|
||||
squareButtonRoot.state = "checked"
|
||||
}
|
||||
else {
|
||||
squareButtonRoot.state = ""
|
||||
}
|
||||
}
|
||||
|
||||
states:[
|
||||
State {
|
||||
name: "hovered"
|
||||
PropertyChanges {
|
||||
target: squareButtonBackground
|
||||
border.color: palette.accent
|
||||
gradient: hoveredGradient
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: squareButtonText
|
||||
color: palette.text
|
||||
}
|
||||
},
|
||||
|
||||
State {
|
||||
name: "clicked"
|
||||
PropertyChanges {
|
||||
target: squareButtonBackground
|
||||
border.color: palette.accent.darker(1.5)
|
||||
gradient: clickedGradient
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: squareButtonText
|
||||
color: palette.text
|
||||
}
|
||||
},
|
||||
|
||||
State {
|
||||
name: "checked"
|
||||
PropertyChanges {
|
||||
target: squareButtonBackground
|
||||
border.color: palette.accent.darker(1.5)
|
||||
gradient: clickedGradient
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: squareButtonText
|
||||
color: palette.text
|
||||
}
|
||||
},
|
||||
|
||||
State {
|
||||
name: "disabled"
|
||||
PropertyChanges {
|
||||
target: squareButtonBackground
|
||||
color: palette.button.darker()
|
||||
gradient: null
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: squareButtonText
|
||||
color: palette.text.darker()
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import QtQuick.Effects
|
||||
|
||||
Item {
|
||||
id: throbberRoot
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
|
||||
Item
|
||||
{
|
||||
id: outerRing
|
||||
anchors.fill: parent
|
||||
|
||||
Shape
|
||||
{
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
|
||||
ShapePath
|
||||
{
|
||||
property double progress: 0
|
||||
property int dashSpaceLength: 15
|
||||
property int dashLength: 1
|
||||
|
||||
id: outerRingPath
|
||||
strokeColor: palette.accent.darker(1 + (progress - 0.25))
|
||||
fillColor: "transparent"
|
||||
strokeWidth: 3
|
||||
strokeStyle: ShapePath.DashLine
|
||||
|
||||
startX: 0
|
||||
startY: outerRing.height * 0.5
|
||||
|
||||
dashPattern: [
|
||||
dashSpaceLength - (dashSpaceLength * progress),
|
||||
dashSpaceLength * progress
|
||||
]
|
||||
|
||||
PathArc
|
||||
{
|
||||
x: outerRing.width
|
||||
y: outerRing.height * 0.5
|
||||
|
||||
radiusX: outerRing.width / 2
|
||||
radiusY: outerRing.height / 2
|
||||
|
||||
useLargeArc: false
|
||||
}
|
||||
|
||||
PathArc
|
||||
{
|
||||
x: 0
|
||||
y: outerRing.height * 0.5
|
||||
|
||||
radiusX: outerRing.width / 2
|
||||
radiusY: outerRing.height / 2
|
||||
|
||||
useLargeArc: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transform: Rotation {
|
||||
id: outerRingRotation
|
||||
origin.x: outerRing.width / 2
|
||||
origin.y: outerRing.height / 2
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
property bool reverse: false
|
||||
|
||||
target: outerRingPath
|
||||
property: "progress"
|
||||
duration: 1600
|
||||
//easing.type: Easing.InOutQuad
|
||||
|
||||
from: 0
|
||||
to: 1
|
||||
loops: 1
|
||||
running: true
|
||||
|
||||
onFinished: () => {
|
||||
if(reverse)
|
||||
{
|
||||
from = 0
|
||||
to = 1
|
||||
start()
|
||||
}
|
||||
else
|
||||
{
|
||||
from = 1
|
||||
to = 0
|
||||
start()
|
||||
}
|
||||
|
||||
reverse = !reverse
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: outerRingRotation
|
||||
property: "angle"
|
||||
duration: 16000
|
||||
easing.type: Easing.InOutQuad
|
||||
|
||||
from: 0
|
||||
to: (360 * 4)
|
||||
loops: NumberAnimation.Infinite
|
||||
running: true
|
||||
}
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
source: outerRing
|
||||
anchors.fill: outerRing
|
||||
brightness: 0.4
|
||||
saturation: 0.2
|
||||
blurEnabled: true
|
||||
blurMax: 64
|
||||
blur: 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
module KomplexHub.Controls
|
||||
LineEdit 1.0 LineEdit.qml
|
||||
MenuButton 1.0 MenuButton.qml
|
||||
SquareButton 1.0 SquareButton.qml
|
||||
SearchResultItem 1.0 SearchResultItem.qml
|
||||
SearchResultList 1.0 SearchResultList.qml
|
||||
SearchBar 1.0 SearchBar.qml
|
||||
Throbber 1.0 Throbber.qml
|
||||
Reference in New Issue
Block a user