This repository has been archived on 2026-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
2021-08-29 15:30:02 +02:00

487 lines
18 KiB
QML

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, GNU version 3. This project 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 project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
import QtQuick 2.8
import QtQuick.Controls 2.1
import QtQuick.Controls.Material 2.1
import QtQuick.Layouts 1.2
import QtCharts 2.2
import Nymea 1.0
import "../../components"
import "../../delegates"
Item {
id: root
property var model: null
property bool editMode: false
property bool dashboardVisible: false
property int contentY: flickable.contentY - flickable.originY
function addItem(index) {
if (index === undefined) {
index = root.model.count
}
var addComponent = Qt.createComponent(Qt.resolvedUrl("DashboardAddWizard.qml"))
if (addComponent.status === Component.Error) {
console.warn(addComponent.errorString())
}
var popup = addComponent.createObject(root, {dashboardModel: root.model, index: index})
popup.open()
}
readonly property var componentMap: {
"thing": "DashboardThingDelegate.qml",
"folder": "DashboardFolderDelegate.qml",
"graph": "DashboardGraphDelegate.qml",
"scene": "DashboardSceneDelegate.qml",
"webview": "DashboardWebViewDelegate.qml"
}
onEditModeChanged: {
if (!editMode) {
root.model.save()
}
}
Flickable {
id: flickable
anchors.fill: parent
anchors.margins: app.margins / 2
contentHeight: Math.max(layout.implicitHeight, height)
contentWidth: width
MouseArea {
width: flickable.contentWidth
height: flickable.contentHeight
onPressAndHold: root.editMode = true
}
GridLayout {
id: layout
width: Math.min(root.model.count * cellWidth, flickable.width)
readonly property int minTileWidth: 172
readonly property int tilesPerRow: root.width / minTileWidth
columns: tilesPerRow
columnSpacing: 0
rowSpacing: 0
property int cellWidth: flickable.width / tilesPerRow
property int cellHeight: cellWidth
Repeater {
id: repeater
model: root.model
Loader {
id: loader
Layout.preferredWidth: layout.cellWidth * columnSpan
Layout.preferredHeight: layout.cellHeight * rowSpan
property int columnSpan: model.columnSpan
property int rowSpan: model.rowSpan
Layout.columnSpan: columnSpan
Layout.rowSpan: rowSpan
property string type: model.type
property DashboardItem dashboardItem: root.model.get(index)
opacity: dragArea.fromIndex == index ? .3 : 1
Component.onCompleted: {
setSource(Qt.resolvedUrl(componentMap[model.type]), {item: loader.dashboardItem})
}
Binding {
target: loader.item
property: "enabled"
value: !root.editMode// dragArea.editIndex === index
}
Binding {
target: loader.item
property: "editMode"
value: root.editMode
}
Binding {
target: loader.item
property: "dashboardVisible"
value: root.dashboardVisible
}
Binding {
target: loader.item
property: "bottomClip"
value: loader.bottomClip
}
Binding {
target: loader.item
property: "topClip"
value: loader.topClip
}
Connections {
target: loader.item
onOpenDialog: {
dialogComponent.createObject(root).open()
}
}
property int topClip: Math.min(height, Math.max(0, -y + (flickable.contentY - flickable.originY) - Style.margins))
property int bottomClip: Math.max(0, y + height - flickable.height - Style.margins - (flickable.contentY - flickable.originY))
Rectangle {
anchors.fill: parent
anchors.margins: Style.smallMargins
color: "transparent"
border.color: Style.accentColor
border.width: 4
radius: Style.cornerRadius
visible: dragArea.fromIndex == index
}
Rectangle {
z: 2
anchors.fill: parent
anchors.margins: Style.smallMargins
visible: opacity > 0
radius: Style.cornerRadius
color: Qt.rgba(Style.backgroundColor.r, Style.backgroundColor.g, Style.backgroundColor.b, .5)
opacity: root.editMode ? 1 : 0
Behavior on opacity { NumberAnimation { duration: 200 } }
MouseArea {
anchors.fill: parent
onClicked: root.editMode = false
}
Rectangle {
anchors {
left: parent.left; top: parent.top;
margins: Style.smallMargins
}
height: Style.largeIconSize
width: Style.largeIconSize
color: Style.red
radius: Style.cornerRadius
opacity: dragArea.fromIndex == -1
Rectangle {
anchors.fill: parent
radius: Style.cornerRadius
color: Style.foregroundColor
opacity: deleteMouseArea.pressed || deleteMouseArea.containsMouse ? .08 : 0
Behavior on opacity {
NumberAnimation { duration: 200 }
}
}
ColorIcon {
name: "/ui/images/delete.svg"
size: Style.iconSize
anchors.centerIn: parent
color: Style.white
}
MouseArea {
id: deleteMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
print("delete clicked")
root.model.removeItem(index)
}
}
}
Rectangle {
anchors {
right: parent.right
top: parent.top
margins: Style.smallMargins
}
height: Style.largeIconSize
width: Style.largeIconSize
color: Style.tileOverlayColor
radius: Style.cornerRadius
visible: opacity > 0
opacity: dragArea.fromIndex == -1 && loader.item.configurable
Behavior on opacity { NumberAnimation { duration: 200 } }
Rectangle {
anchors.fill: parent
radius: Style.cornerRadius
color: Style.foregroundColor
opacity: configureMouseArea.pressed || configureMouseArea.containsMouse ? .08 : 0
Behavior on opacity {
NumberAnimation { duration: 200 }
}
}
ColorIcon {
name: "/ui/images/configure.svg"
size: Style.iconSize
anchors.centerIn: parent
// color: Style.white
}
MouseArea {
id: configureMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
loader.item.configure()
}
}
}
}
}
}
MouseArea {
id: addTile
Layout.preferredWidth: layout.cellWidth
Layout.preferredHeight: layout.cellHeight
hoverEnabled: true
opacity: root.editMode ? 1 : 0
visible: opacity > 0
Behavior on opacity { NumberAnimation { duration: 200 } }
onClicked: {
print("add clicked")
root.addItem()
}
Rectangle {
anchors.fill: parent
anchors.margins: Style.smallMargins
border.width: 4
border.color: Style.tileOverlayColor
color: Qt.rgba( Style.tileBackgroundColor.r, Style.tileBackgroundColor.g, Style.tileBackgroundColor.b, addTile.containsMouse ? 1 : 0)
Behavior on color { ColorAnimation { duration: 200 } }
radius: Style.cornerRadius
ColorIcon {
name: "/ui/images/add.svg"
size: Style.bigIconSize
anchors.centerIn: parent
color: Style.tileOverlayColor
}
}
}
}
MouseArea {
id: dragArea
anchors.fill: parent
enabled: root.editMode
propagateComposedEvents: true
preventStealing: fakeDragItem != null
property var fakeDragItem: null
property int fromIndex: -1
property int editIndex: -1
property int fakeDragOffsetX: 0
property int fakeDragOffsetY: 0
Timer {
id: scrollTimer
interval: 10
repeat: true
running: dragArea.fakeDragItem !== null
property int scrollOffset: 0
onTriggered: {
var mappedPos = dragArea.mapToItem(flickable, dragArea.mouseX, dragArea.mouseY)
var scrollPixels = 0
if (mappedPos.y + scrollOffset < 60) {
scrollPixels = Math.max(-2, -flickable.contentY)
} else if (mappedPos.y + scrollOffset > flickable.height - 60) {
scrollPixels = Math.min(2, flickable.contentHeight - flickable.height - flickable.contentY)
}
flickable.contentY += scrollPixels
scrollOffset += scrollPixels
dragArea.fakeDragItem.y += scrollPixels
}
}
onClicked: {
var ret = itemUnderMouse()
if (ret.item) {
dragArea.editIndex = ret.index
// Let the click pass through
mouse.accepted = false
} else if (itemContainsMouse(addTile)) {
mouse.accepted = false
} else {
root.editMode = false
}
}
onPressAndHold: {
print("position", mouseX, mouseY)
var draggedItem = null
for (var i = 0; i < repeater.count; i++) {
var item = repeater.itemAt(i);
print("item coords:", item.x, item.y, item.width, item.height)
if (itemContainsMouse(item)) {
print("Yes!, Item at:", i)
fromIndex = i;
draggedItem = item;
break;
}
}
if (!draggedItem) {
return
}
var mappedCursor = dragArea.mapToItem(item, mouseX, mouseY)
fakeDragOffsetX = mappedCursor.x
fakeDragOffsetY = mappedCursor.y
dragArea.fakeDragItem = dragItemComponent.createObject(dragArea,
{
x: mouseX - fakeDragOffsetX,
y: mouseY - fakeDragOffsetY,
draggedItem: draggedItem
})
}
function itemUnderMouse() {
var ret = {}
for (var i = 0; i < repeater.count; i++) {
var item = repeater.itemAt(i);
if (itemContainsMouse(item)) {
ret.item = item;
ret.index = i
break;
}
}
return ret
}
function itemContainsMouse(item) {
var mapped = dragArea.mapToItem(item, mouseX, mouseY)
return mapped.x > 0 && mapped.x < item.width && mapped.y > 0 && mapped.y < item.height
}
onPositionChanged: {
if (!fakeDragItem) {
return
}
scrollTimer.scrollOffset = 0;
fakeDragItem.x = mouseX - fakeDragOffsetX
fakeDragItem.y = mouseY - fakeDragOffsetY
var itemUnderCursor = null
var itemIndex = -1
for (var i = 0; i < repeater.count; i++) {
var item = repeater.itemAt(i);
if (itemContainsMouse(item)) {
print("Yes!, Item at:", i)
itemUnderCursor = item;
itemIndex = i
break;
}
}
if (!itemUnderCursor) {
return
}
if (fromIndex === itemIndex) {
return
}
print("over item:", itemIndex)
root.model.move(fromIndex, itemIndex)
fromIndex = itemIndex
}
onReleased: {
if (dragArea.fakeDragItem) {
dragArea.fakeDragItem.destroy();
dragArea.fakeDragItem = null
dragArea.fromIndex = -1
}
}
}
}
EmptyViewPlaceholder {
anchors { left: parent.left; right: parent.right; margins: Style.margins }
anchors.verticalCenter: parent.verticalCenter
visible: !engine.thingManager.fetchingData && root.model.count === 0 && !root.editMode
title: qsTr("Dashboard is empty")
text: qsTr("Start with adding a new item to this dashboard.")
buttonText: qsTr("Add item")
imageSource: "/ui/images/dashboard.svg"
onButtonClicked: {
root.addItem()
}
}
Component {
id: dragItemComponent
Item {
property Item draggedItem: null
layer.enabled: true
layer.effect: ShaderEffectSource {
sourceItem: draggedItem
live: true
}
height: draggedItem.height
width: draggedItem.width
}
}
Component {
id: editDialogComponent
MeaDialog {
id: editDialog
standardButtons: Dialog.NoButton
property DashboardItem dashboardItem: null
property int index: -1
ColumnLayout {
Button {
text: qsTr("Remove")
Layout.fillWidth: true
onClicked: {
root.model.removeItem(editDialog.index)
editDialog.close()
}
}
}
}
}
}