Merge PR #666: Modernize and align controllable things pages
This commit is contained in:
commit
0eed335226
@ -144,9 +144,13 @@ Interfaces::Interfaces(QObject *parent) : QAbstractListModel(parent)
|
||||
|
||||
addInterface("heating", tr("Heating"));
|
||||
addStateType("heating", "power", QVariant::Bool, true, tr("Heating enabled"), tr("Heating enabled changed"), tr("Enable heating"));
|
||||
addStateType("heating", "percentage", QVariant::Int, true, tr("Percentage"), tr("Percentage changed"), tr("Set percentage"), 0, 100);
|
||||
|
||||
|
||||
addInterface("cooling", tr("Cooling"));
|
||||
addStateType("cooling", "power", QVariant::Bool, true, tr("Cooling enabled"), tr("Cooling enabled changed"), tr("Enable cooling"));
|
||||
addStateType("cooling", "percentage", QVariant::Int, true, tr("Percentage"), tr("Percentage changed"), tr("Set percentage"), 0, 100);
|
||||
|
||||
addInterface("extendedheating", tr("Heatings"), {"heating"});
|
||||
addStateType("extendedheating", "percentage", QVariant::Int, true, tr("Percentage"), tr("Percentage changed"), tr("Set percentage"), 0, 100);
|
||||
|
||||
addInterface("media", tr("Media"));
|
||||
|
||||
|
||||
@ -32,7 +32,8 @@
|
||||
#include "thingclass.h"
|
||||
#include "thingmanager.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QLoggingCategory>
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcThingManager)
|
||||
|
||||
Thing::Thing(ThingManager *thingManager, ThingClass *thingClass, const QUuid &parentId, QObject *parent) :
|
||||
QObject(parent),
|
||||
@ -220,6 +221,10 @@ void Thing::setStateValue(const QUuid &stateTypeId, const QVariant &value)
|
||||
int Thing::executeAction(const QString &actionName, const QVariantList ¶ms)
|
||||
{
|
||||
ActionType *actionType = m_thingClass->actionTypes()->findByName(actionName);
|
||||
if (!actionType) {
|
||||
qCWarning(dcThingManager) << "No such action name" << actionName << "in thing class" << m_thingClass->name();
|
||||
return -1;
|
||||
}
|
||||
|
||||
QVariantList finalParams;
|
||||
foreach (const QVariant ¶mVariant, params) {
|
||||
|
||||
@ -171,6 +171,7 @@ void ConfiguredHostsModel::saveToDisk()
|
||||
QSettings settings;
|
||||
settings.beginGroup("ConfiguredHosts");
|
||||
settings.remove("");
|
||||
settings.setValue("currentIndex", m_currentIndex);
|
||||
for (int i = 0; i < m_list.count(); i++) {
|
||||
settings.beginGroup(QString::number(i));
|
||||
settings.setValue("uuid", m_list.at(i)->uuid());
|
||||
|
||||
@ -58,7 +58,7 @@
|
||||
<file>ui/devicepages/GarageThingPage.qml</file>
|
||||
<file>ui/devicepages/AwningThingPage.qml</file>
|
||||
<file>ui/devicepages/NotificationsDevicePage.qml</file>
|
||||
<file>ui/devicepages/LightDevicePage.qml</file>
|
||||
<file>ui/devicepages/LightThingPage.qml</file>
|
||||
<file>ui/devicepages/FingerprintReaderDevicePage.qml</file>
|
||||
<file>ui/devicepages/DeviceLogPage.qml</file>
|
||||
<file>ui/devicelistpages/GenericThingsListPage.qml</file>
|
||||
@ -254,5 +254,8 @@
|
||||
<file>ui/system/ZigbeeNetworkPage.qml</file>
|
||||
<file>ui/components/ConnectionInfoDialog.qml</file>
|
||||
<file>ui/components/ButtonControls.qml</file>
|
||||
<file>ui/components/CircleBackground.qml</file>
|
||||
<file>ui/devicepages/CoolingThingPage.qml</file>
|
||||
<file>ui/devicepages/EvChargerThingPage.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@ -149,6 +149,7 @@ ApplicationWindow {
|
||||
"powersocket",
|
||||
"thermostat",
|
||||
"heating",
|
||||
"cooling",
|
||||
"smartlock",
|
||||
"doorbell",
|
||||
"irrigation",
|
||||
|
||||
@ -80,7 +80,8 @@ Item {
|
||||
"smartmeterconsumer": "orange",
|
||||
"smartmeterproducer": "lightgreen",
|
||||
"energymeter": "deepskyblue",
|
||||
"heating" : "gainsboro",
|
||||
"heating" : "crimson",
|
||||
"cooling": "dodgerBlue",
|
||||
"thermostat": "dodgerblue",
|
||||
"irrigation": "lightblue",
|
||||
"windspeedsensor": "blue",
|
||||
@ -88,7 +89,9 @@ Item {
|
||||
"watersensor": "aqua",
|
||||
"phsensor": "green",
|
||||
"o2sensor": "lightblue",
|
||||
"orpsensor": "yellow"
|
||||
"orpsensor": "yellow",
|
||||
"powersocket": "aquamarine",
|
||||
"evcharger": "limegreen"
|
||||
}
|
||||
|
||||
property var stateColors: {
|
||||
@ -97,7 +100,10 @@ Item {
|
||||
"currentPower": "deepskyblue",
|
||||
}
|
||||
|
||||
property color red: "#952727"
|
||||
property color red: "indianred"
|
||||
property color green: "mediumseagreen"
|
||||
property color yellow: "gold"
|
||||
|
||||
property color white: "white"
|
||||
property color gray: "gray"
|
||||
property color darkGray: "darkGray"
|
||||
|
||||
@ -35,12 +35,12 @@ import "../utils"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
implicitWidth: orientation == Qt.Horizontal ? 300 : Style.hugeIconSize
|
||||
implicitHeight: orientation == Qt.Horizontal ? Style.hugeIconSize : 300
|
||||
implicitWidth: orientation == Qt.Horizontal ? 300 : 12
|
||||
implicitHeight: orientation == Qt.Horizontal ? 12 : 300
|
||||
|
||||
property Thing thing: null
|
||||
|
||||
readonly property StateType colorTemperatureStateType: root.thing.thingClass.stateTypes.findByName("brightness")
|
||||
readonly property StateType brightnessStateType: root.thing.thingClass.stateTypes.findByName("brightness")
|
||||
|
||||
property int value: thing.stateByName("brightness").value
|
||||
|
||||
@ -49,7 +49,7 @@ Item {
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: root.thing
|
||||
stateType: root.colorTemperatureStateType
|
||||
stateName: "brightness"
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@ -84,17 +84,18 @@ Item {
|
||||
y: root.orientation === Qt.Vertical ?
|
||||
root.height - dragHandle.height - ((actionQueue.pendingValue || root.value) * (root.height - dragHandle.height) / 100)
|
||||
: 0
|
||||
height: root.orientation === Qt.Horizontal ? parent.height : 8
|
||||
width: root.orientation === Qt.Horizontal ? 8 : parent.width
|
||||
radius: 4
|
||||
height: 14
|
||||
width: 14
|
||||
radius: 7
|
||||
color: Style.foregroundColor
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -Style.smallMargins
|
||||
onPositionChanged: {
|
||||
var minCt = root.colorTemperatureStateType.minValue;
|
||||
var maxCt = root.colorTemperatureStateType.maxValue
|
||||
var minCt = root.brightnessStateType.minValue;
|
||||
var maxCt = root.brightnessStateType.maxValue
|
||||
var ct;
|
||||
if (root.orientation == Qt.Horizontal) {
|
||||
ct = Math.min(maxCt, Math.max(minCt, (mouseX * (maxCt - minCt) / (width - dragHandle.width)) + minCt))
|
||||
|
||||
98
nymea-app/ui/components/CircleBackground.qml
Normal file
98
nymea-app/ui/components/CircleBackground.qml
Normal file
@ -0,0 +1,98 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtGraphicalEffects 1.0
|
||||
import Nymea 1.0
|
||||
import "../utils"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
implicitHeight: 400
|
||||
implicitWidth: 400
|
||||
|
||||
property alias iconSource: icon.name
|
||||
property color onColor: Style.accentColor
|
||||
property bool on: false
|
||||
|
||||
readonly property Item contentItem: background
|
||||
|
||||
signal clicked()
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.centerIn: parent
|
||||
height: Math.min(400, Math.min(parent.height, parent.width))
|
||||
width: height
|
||||
radius: width / 2
|
||||
color: Style.tileBackgroundColor
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: background
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
id: icon
|
||||
anchors.centerIn: background
|
||||
size: Style.hugeIconSize
|
||||
color: root.on ? root.onColor : Style.iconColor
|
||||
Behavior on color { ColorAnimation { duration: Style.animationDuration } }
|
||||
}
|
||||
|
||||
RadialGradient {
|
||||
id: gradient
|
||||
anchors.fill: background
|
||||
visible: false
|
||||
gradient: Gradient{
|
||||
GradientStop { position: .45; color: "transparent" }
|
||||
GradientStop { position: .5; color: root.onColor }
|
||||
}
|
||||
}
|
||||
|
||||
OpacityMask {
|
||||
id: opacityMask
|
||||
opacity: root.on ? 1 : 0
|
||||
anchors.fill: gradient
|
||||
source: gradient
|
||||
maskSource: background
|
||||
Behavior on opacity { NumberAnimation { duration: Style.animationDuration } }
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentContainer
|
||||
anchors.fill: background
|
||||
}
|
||||
|
||||
}
|
||||
@ -8,16 +8,31 @@ Item {
|
||||
|
||||
property Thing thing: null
|
||||
|
||||
readonly property State colorState: thing ? thing.stateByName("color") : null
|
||||
readonly property State powerState: thing ? thing.stateByName("power") : null
|
||||
|
||||
Connections {
|
||||
target: colorState
|
||||
onValueChanged: {
|
||||
if (actionQueue.pendingValue === null) {
|
||||
actionQueue.useStoredPoint = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: root.thing
|
||||
stateType: thing.thingClass.stateTypes.findByName("color")
|
||||
|
||||
property bool useStoredPoint: false
|
||||
property point storedPoint: Qt.point(0, 0)
|
||||
}
|
||||
|
||||
ConicalGradient {
|
||||
id: gradient
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(parent.width, parent.height)
|
||||
width: Math.min(400, Math.min(parent.width, parent.height))
|
||||
height: width
|
||||
visible: false
|
||||
gradient: Gradient{
|
||||
@ -30,6 +45,29 @@ Item {
|
||||
GradientStop { position: 0.833; color: Qt.rgba(1, 0, 1, 1) }
|
||||
GradientStop { position: 1.000; color: Qt.rgba(1, 0, 0, 1) }
|
||||
}
|
||||
onWidthChanged: dragHandle.updatePoint()
|
||||
onHeightChanged: dragHandle.updatePoint()
|
||||
|
||||
RadialGradient {
|
||||
anchors.fill: gradient
|
||||
gradient: Gradient{
|
||||
GradientStop { position: 0.05; color: Qt.rgba(1, 1, 1, 1) }
|
||||
GradientStop { position: 0.10; color: Qt.rgba(1, 1, 1, .9) }
|
||||
GradientStop { position: 0.20; color: Qt.rgba(1, 1, 1, .7) }
|
||||
GradientStop { position: 0.30; color: Qt.rgba(1, 1, 1, .5) }
|
||||
GradientStop { position: 0.40; color: Qt.rgba(1, 1, 1, .3) }
|
||||
GradientStop { position: 0.50; color: "transparent" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Desaturate {
|
||||
id: colorizer
|
||||
anchors.fill: gradient
|
||||
source: gradient
|
||||
desaturation: root.powerState.value === true ? 0 : 1
|
||||
Behavior on desaturation { NumberAnimation { duration: Style.animationDuration } }
|
||||
visible: false
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@ -39,25 +77,106 @@ Item {
|
||||
}
|
||||
OpacityMask {
|
||||
anchors.fill: gradient
|
||||
source: gradient
|
||||
source: colorizer
|
||||
maskSource: mask
|
||||
}
|
||||
|
||||
RadialGradient {
|
||||
anchors.fill: gradient
|
||||
gradient: Gradient{
|
||||
GradientStop { position: 0.05; color: Qt.rgba(1, 1, 1, 1) }
|
||||
GradientStop { position: 0.10; color: Qt.rgba(1, 1, 1, .9) }
|
||||
GradientStop { position: 0.20; color: Qt.rgba(1, 1, 1, .7) }
|
||||
GradientStop { position: 0.30; color: Qt.rgba(1, 1, 1, .5) }
|
||||
GradientStop { position: 0.40; color: Qt.rgba(1, 1, 1, .3) }
|
||||
GradientStop { position: 0.50; color: "transparent" }
|
||||
|
||||
Rectangle {
|
||||
id: dragHandle
|
||||
width: 20
|
||||
height: 20
|
||||
radius: height / 2
|
||||
color: Style.backgroundColor
|
||||
border.color: Style.foregroundColor
|
||||
border.width: 2
|
||||
|
||||
x: point.x + gradient.width / 2 + gradient.x - width / 2
|
||||
y: point.y + gradient.height / 2 + gradient.y - height / 2
|
||||
|
||||
property color shownColor: root.colorState ? actionQueue.pendingValue || root.colorState.value : "white`"
|
||||
onShownColorChanged: updatePoint()
|
||||
// Component.onCompleted: updatePoint()
|
||||
|
||||
property point point: Qt.point(0,0);
|
||||
function updatePoint() {
|
||||
|
||||
if (actionQueue.useStoredPoint) {
|
||||
point = actionQueue.storedPoint
|
||||
return
|
||||
}
|
||||
|
||||
print("current color:", shownColor.r, shownColor.g, shownColor.b)
|
||||
|
||||
var whitePart = Math.min(Math.min(shownColor.r, shownColor.g), shownColor.b)
|
||||
|
||||
var stopIndex = 0
|
||||
var progressInStop = 0
|
||||
if (shownColor.r === 1) {
|
||||
if (shownColor.g > shownColor.b) {
|
||||
stopIndex = 0
|
||||
progressInStop = shownColor.g - whitePart
|
||||
} else {
|
||||
stopIndex = 5
|
||||
progressInStop = 1 - shownColor.b + whitePart
|
||||
}
|
||||
}
|
||||
if (shownColor.g === 1) {
|
||||
if (shownColor.r > shownColor.b) {
|
||||
stopIndex = 1
|
||||
progressInStop = 1 - shownColor.r + whitePart
|
||||
} else {
|
||||
stopIndex = 2
|
||||
progressInStop = shownColor.b - whitePart
|
||||
}
|
||||
}
|
||||
if (shownColor.b === 1) {
|
||||
if (shownColor.r > shownColor.g) {
|
||||
stopIndex = 4
|
||||
progressInStop = shownColor.r - whitePart
|
||||
} else {
|
||||
stopIndex = 3
|
||||
progressInStop = 1-shownColor.g + whitePart
|
||||
}
|
||||
}
|
||||
|
||||
var stopBefore = g.stops[stopIndex]
|
||||
var stopAfter = g.stops[stopIndex+1]
|
||||
|
||||
print("stopIndex", stopIndex)
|
||||
print("stopBefore:", stopBefore.color.r, stopBefore.color.g, stopBefore.color.b)
|
||||
print("stopAfter:", stopAfter.color.r, stopAfter.color.g, stopAfter.color.b)
|
||||
print("progressInStop", progressInStop)
|
||||
|
||||
|
||||
print("beforePosition", stopBefore.position)
|
||||
|
||||
var positionInGradient = stopBefore.position + (stopAfter.position - stopBefore.position) * progressInStop
|
||||
|
||||
print("positionInGradient", positionInGradient)
|
||||
|
||||
var degrees = 360 * positionInGradient;
|
||||
degrees -= 90;
|
||||
|
||||
var radian = degrees * 0.0174532925
|
||||
|
||||
var radius = gradient.height * 0.9 / 2 * (1-whitePart)
|
||||
|
||||
|
||||
var x = radius * Math.cos(radian)
|
||||
var y = radius * Math.sin(radian)
|
||||
|
||||
print("degrees", degrees)
|
||||
print("radius", radius)
|
||||
|
||||
print("Setting point to", x, y)
|
||||
point = Qt.point(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
anchors.fill: gradient
|
||||
onPositionChanged: {
|
||||
|
||||
var angle = calculateAngle(mouseX, mouseY)
|
||||
@ -98,6 +217,13 @@ Item {
|
||||
1)
|
||||
|
||||
actionQueue.sendValue(color);
|
||||
|
||||
// Store the coordinates (limited to the circle) as the above calculation is lossy so we can't precicely
|
||||
// calcuate the position from the color but we don't want the drag handle jumping while dragging.
|
||||
var rad = (angle - 90) / 180 * Math.PI
|
||||
var radius = Math.min(distanceFromCenter, width * 0.9 / 2)
|
||||
actionQueue.storedPoint = Qt.point(radius * Math.cos(rad), radius * Math.sin(rad))
|
||||
actionQueue.useStoredPoint = true
|
||||
}
|
||||
|
||||
function calculateAngle(mouseX, mouseY) {
|
||||
|
||||
@ -10,9 +10,10 @@ Item {
|
||||
|
||||
property Thing thing: null
|
||||
|
||||
property int orientation: Qt.Horizontal
|
||||
property int orientation: Qt.Vertical
|
||||
|
||||
readonly property StateType colorTemperatureStateType: root.thing.thingClass.stateTypes.findByName("colorTemperature")
|
||||
readonly property State powerState: root.thing.stateByName("power")
|
||||
|
||||
property int value: thing.stateByName("colorTemperature").value
|
||||
|
||||
@ -23,45 +24,55 @@ Item {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: clipRect
|
||||
anchors.fill: parent
|
||||
radius: Style.cornerRadius
|
||||
}
|
||||
|
||||
LinearGradient {
|
||||
anchors.fill: parent
|
||||
start: root.orientation == Qt.Horizontal ? Qt.point(0, 0) : Qt.point(0, height)
|
||||
end: root.orientation == Qt.Horizontal ? Qt.point(width, 0) : Qt.point(0, 0)
|
||||
source: clipRect
|
||||
id: background
|
||||
width: Math.min(400, Math.min(parent.width, parent.height))
|
||||
anchors.centerIn: parent
|
||||
height: width
|
||||
radius: width / 2
|
||||
visible: false
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: "#dfffff" }
|
||||
GradientStop { position: 0.5; color: "#ffffea" }
|
||||
GradientStop { position: 1.0; color: "#ffd649" }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: dragHandle
|
||||
property double valuePercentage: ((actionQueue.pendingValue || root.value) - root.colorTemperatureStateType.minValue) / (root.colorTemperatureStateType.maxValue - root.colorTemperatureStateType.minValue)
|
||||
width: 20
|
||||
height: 20
|
||||
radius: height / 2
|
||||
color: Style.backgroundColor
|
||||
border.color: Style.foregroundColor
|
||||
border.width: 2
|
||||
x: (background.width - width) / 2
|
||||
y: (background.height - height) * valuePercentage
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: dragHandle
|
||||
property double valuePercentage: ((actionQueue.pendingValue || root.value) - root.colorTemperatureStateType.minValue) / (root.colorTemperatureStateType.maxValue - root.colorTemperatureStateType.minValue)
|
||||
x: orientation == Qt.Horizontal ? valuePercentage * (root.width - dragHandle.width) : 0
|
||||
y: root.orientation === Qt.Vertical ? root.height - dragHandle.height - (valuePercentage * (root.height - dragHandle.height)) : 0
|
||||
height: root.orientation == Qt.Horizontal ? parent.height : 8
|
||||
width: root.orientation == Qt.Horizontal ? 8 : parent.width
|
||||
radius: 4
|
||||
color: Qt.tint(Style.backgroundColor, Qt.rgba(Style.foregroundColor.r, Style.foregroundColor.g, Style.foregroundColor.b, 0.5))
|
||||
Desaturate {
|
||||
anchors.fill: background
|
||||
source: background
|
||||
desaturation: root.powerState.value === true ? 0 : 1
|
||||
Behavior on desaturation { NumberAnimation { duration: Style.animationDuration } }
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.fill: background
|
||||
onPositionChanged: {
|
||||
var minCt = root.colorTemperatureStateType.minValue;
|
||||
var maxCt = root.colorTemperatureStateType.maxValue
|
||||
var ct;
|
||||
if (root.orientation == Qt.Horizontal) {
|
||||
ct = Math.min(maxCt, Math.max(minCt, (mouseX * (maxCt - minCt) / (width - dragHandle.width)) + minCt))
|
||||
} else {
|
||||
ct = Math.min(maxCt, Math.max(minCt, ((height - mouseY) * (maxCt - minCt) / (height - dragHandle.height)) + minCt))
|
||||
}
|
||||
// if (root.orientation == Qt.Horizontal) {
|
||||
// ct = Math.min(maxCt, Math.max(minCt, (mouseX * (maxCt - minCt) / (width - dragHandle.width)) + minCt))
|
||||
// } else {
|
||||
// ct : y = max : height
|
||||
ct = mouseY * (maxCt - minCt) / (height) + minCt
|
||||
ct = Math.min(maxCt, ct)
|
||||
ct = Math.max(minCt, ct)
|
||||
// ct = Math.min(maxCt, Math.max(minCt, ((height - mouseY) * (maxCt - minCt) / (height - dragHandle.height)) + minCt))
|
||||
// }
|
||||
actionQueue.sendValue(ct);
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,321 +32,174 @@ import QtQuick 2.5
|
||||
import QtQuick.Controls 2.2
|
||||
import Nymea 1.0
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Controls.Material 2.2
|
||||
import "../utils"
|
||||
|
||||
ColumnLayout {
|
||||
id: dial
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property Thing thing: null
|
||||
property alias device: dial.thing
|
||||
property StateType stateType: null
|
||||
property string stateName: ""
|
||||
property StateType stateType: thing ? thing.thingClass.stateTypes.findByName(stateName) : null
|
||||
|
||||
property bool showValueLabel: true
|
||||
property int steps: 10
|
||||
property color color: Style.accentColor
|
||||
property int maxAngle: 235
|
||||
property int precision: 1
|
||||
|
||||
// value : max = angle : maxAngle
|
||||
function valueToAngle(value) {
|
||||
return (value - from) * maxAngle / (to - from)
|
||||
}
|
||||
function angleToValue(angle) {
|
||||
return (to - from) * angle / maxAngle + from
|
||||
readonly property State progressState: thing ? thing.states.getState(stateType.id) : null
|
||||
readonly property State powerState: thing ? thing.stateByName("power") : null
|
||||
|
||||
property int startAngle: 135
|
||||
property int maxAngle: 270
|
||||
readonly property int steps: canvas.roundToPrecision(root.stateType.maxValue - root.stateType.minValue) / root.precision + 1
|
||||
readonly property double stepSize: (root.stateType.maxValue - root.stateType.minValue) / steps
|
||||
readonly property double anglePerStep: maxAngle / steps
|
||||
|
||||
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: root.thing
|
||||
stateType: root.stateType
|
||||
onPendingValueChanged: canvas.requestPaint()
|
||||
}
|
||||
|
||||
readonly property State deviceState: thing && stateType ? thing.states.getState(stateType.id) : null
|
||||
readonly property double from: dial.stateType ? dial.stateType.minValue : 0
|
||||
readonly property double to: dial.stateType ? dial.stateType.maxValue : 100
|
||||
readonly property double anglePerStep: maxAngle / dial.steps
|
||||
readonly property double startAngle: -(dial.steps * dial.anglePerStep) / 2
|
||||
|
||||
readonly property StateType powerStateType: dial.thing.thingClass.stateTypes.findByName("power")
|
||||
readonly property State powerState: powerStateType ? dial.thing.states.getState(powerStateType.id) : null
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
property int pendingActionId: -1
|
||||
property real valueCache: 0
|
||||
property bool valueCacheDirty: false
|
||||
|
||||
property bool busy: rotateMouseArea.pressed || pendingActionId != -1 || valueCacheDirty
|
||||
|
||||
property color onColor: dial.color
|
||||
property color offColor: "#808080"
|
||||
property color poweredColor: dial.powerStateType
|
||||
? (dial.powerState.value === true ? onColor : offColor)
|
||||
: onColor
|
||||
|
||||
|
||||
function enqueueSetValue(value) {
|
||||
if (d.pendingActionId == -1) {
|
||||
executeAction(value);
|
||||
return;
|
||||
} else {
|
||||
valueCache = value
|
||||
valueCacheDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
function executeAction(value) {
|
||||
var params = []
|
||||
var param = {}
|
||||
param["paramName"] = dial.stateType.name
|
||||
param["value"] = value
|
||||
params.push(param)
|
||||
d.pendingActionId = dial.thing.executeAction(dial.stateType.name, params)
|
||||
}
|
||||
ActionQueue {
|
||||
id: powerActionQueue
|
||||
thing: root.thing
|
||||
stateName: "power"
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: engine.thingManager
|
||||
onExecuteActionReply: {
|
||||
if (d.pendingActionId == commandId) {
|
||||
d.pendingActionId = -1
|
||||
if (d.valueCacheDirty) {
|
||||
d.executeAction(d.valueCache)
|
||||
d.valueCacheDirty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: dial.thing
|
||||
onActionExecutionFinished: {
|
||||
if (id == d.pendingActionId) {
|
||||
d.pendingActionId = -1;
|
||||
if (d.valueCacheDirty) {
|
||||
d.executeAction(d.valueCache)
|
||||
d.valueCacheDirty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: rotationButton.rotation = dial.valueToAngle(dial.thingState.value)
|
||||
Connections {
|
||||
target: dial.thingState
|
||||
target: root.progressState
|
||||
onValueChanged: {
|
||||
if (!d.busy) {
|
||||
rotationButton.rotation = dial.valueToAngle(dial.thingState.value)
|
||||
canvas.requestPaint()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
anchors.centerIn: root
|
||||
width: Math.min(root.width, root.height)
|
||||
height: width
|
||||
|
||||
function roundToPrecision(value) {
|
||||
var tmp = Math.round(value / root.precision) * root.precision;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.reset();
|
||||
|
||||
var center = { x: canvas.width / 2, y: canvas.height / 2 };
|
||||
|
||||
// Step lines
|
||||
var currentValue = actionQueue.pendingValue || root.progressState.value
|
||||
var currentStep;
|
||||
if (root.progressState) {
|
||||
currentStep = roundToPrecision(currentValue - root.stateType.minValue) / root.precision
|
||||
}
|
||||
|
||||
print("* current step", currentStep, root.steps, currentValue)
|
||||
|
||||
for(var step = 0; step < steps; step += root.precision) {
|
||||
var angle = step * anglePerStep + startAngle;
|
||||
var innerRadius = canvas.width * 0.4
|
||||
var outerRadius = canvas.width * 0.5
|
||||
|
||||
if (step === currentStep) {
|
||||
ctx.strokeStyle = root.color
|
||||
innerRadius = canvas.width * 0.38
|
||||
ctx.lineWidth = 4;
|
||||
} else {
|
||||
ctx.strokeStyle = Style.tileOverlayColor;
|
||||
ctx.lineWidth = 1;
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
// rotate
|
||||
//convert to radians
|
||||
var rad = angle * Math.PI/180;
|
||||
var c = Math.cos(rad);
|
||||
var s = Math.sin(rad);
|
||||
var innerPointX = center.x + (innerRadius * c);
|
||||
var innerPointY = center.y + (innerRadius * s);
|
||||
var outerPointX = center.x + (outerRadius * c);
|
||||
var outerPointY = center.x + (outerRadius * s);
|
||||
|
||||
ctx.moveTo(innerPointX, innerPointY);
|
||||
ctx.lineTo(outerPointX, outerPointY);
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
id: topLabel
|
||||
Layout.fillWidth: true
|
||||
property var unit: dial.stateType ? dial.stateType.unit : Types.UnitNone
|
||||
text: Types.toUiValue(rotateMouseArea.currentValue, unit) + Types.toUiUnit(unit)
|
||||
font.pixelSize: app.largeFont * 1.5
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
visible: dial.showValueLabel && dial.stateType !== null
|
||||
}
|
||||
|
||||
Item {
|
||||
id: buttonContainer
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
MouseArea {
|
||||
anchors.fill: canvas
|
||||
|
||||
Item {
|
||||
id: innerDial
|
||||
property bool dragging: false
|
||||
property double lastAngle
|
||||
property double angleDiff: 0
|
||||
|
||||
height: Math.min(parent.height, parent.width) * .9
|
||||
width: height
|
||||
anchors.centerIn: parent
|
||||
rotation: dial.startAngle
|
||||
onPressed: {
|
||||
angleDiff = 0
|
||||
lastAngle = calculateAngle(mouseX, mouseY)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: rotationButton
|
||||
radius: height / 2
|
||||
border.color: Style.foregroundColor
|
||||
border.width: 2
|
||||
color: "transparent"
|
||||
opacity: rotateMouseArea.pressed && !rotateMouseArea.grabbed ? .7 : 1
|
||||
onReleased: {
|
||||
if (!dragging && root.powerState) {
|
||||
PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackSelection)
|
||||
powerActionQueue.sendValue(!root.powerState.value)
|
||||
}
|
||||
dragging = false
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
var angle = calculateAngle(mouseX, mouseY)
|
||||
var tmpDiff = angle - lastAngle
|
||||
if (tmpDiff > 300) {
|
||||
tmpDiff -= 360
|
||||
}
|
||||
if (tmpDiff < -300) {
|
||||
tmpDiff += 360
|
||||
}
|
||||
|
||||
Item {
|
||||
id: rotationButton
|
||||
height: parent.height * .75
|
||||
width: height
|
||||
anchors.centerIn: parent
|
||||
visible: dial.stateType !== null
|
||||
Behavior on rotation {
|
||||
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
|
||||
enabled: !rotateMouseArea.pressed && !d.busy
|
||||
}
|
||||
lastAngle = angle;
|
||||
|
||||
Item {
|
||||
id: handle
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
height: parent.height * .35
|
||||
width: height
|
||||
|
||||
// Rectangle { anchors.fill: parent; color: "red"; opacity: .3}
|
||||
|
||||
Rectangle {
|
||||
height: parent.height * .5
|
||||
width: innerDial.width * 0.02
|
||||
radius: width / 2
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: height * .25
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: d.poweredColor
|
||||
Behavior on color { ColorAnimation { duration: 200 } }
|
||||
}
|
||||
}
|
||||
angleDiff += tmpDiff
|
||||
if (Math.abs(angleDiff) > 1) {
|
||||
dragging = true
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: indexLEDs
|
||||
model: dial.steps + 1
|
||||
|
||||
Item {
|
||||
height: parent.height
|
||||
width: parent.width * .04
|
||||
anchors.centerIn: parent
|
||||
rotation: dial.anglePerStep * index
|
||||
visible: dial.stateType !== null
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: dial.deviceState && dial.angleToValue(parent.rotation) <= dial.deviceState.value ? d.poweredColor : d.offColor
|
||||
Behavior on color { ColorAnimation { duration: 200 } }
|
||||
}
|
||||
var valueDiff = angleDiff / root.anglePerStep * root.stepSize
|
||||
valueDiff = canvas.roundToPrecision(valueDiff)
|
||||
if (Math.abs(valueDiff) > 0) {
|
||||
var currentValue = actionQueue.pendingValue || root.progressState.value
|
||||
var newValue = currentValue + valueDiff
|
||||
newValue = Math.min(root.stateType.maxValue, Math.max(root.stateType.minValue, newValue))
|
||||
if (currentValue !== newValue) {
|
||||
actionQueue.sendValue(newValue)
|
||||
}
|
||||
var steps = Math.round(valueDiff / root.stepSize)
|
||||
angleDiff -= steps * root.anglePerStep
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: buttonBorder
|
||||
height: innerDial.height * .8
|
||||
width: height
|
||||
anchors.centerIn: parent
|
||||
radius: height / 2
|
||||
border.color: Style.foregroundColor
|
||||
opacity: .3
|
||||
border.width: width * .025
|
||||
color: "transparent"
|
||||
}
|
||||
function calculateAngle(mouseX, mouseY) {
|
||||
// transform coords to center of dial
|
||||
mouseX -= canvas.width / 2
|
||||
mouseY -= canvas.height / 2
|
||||
|
||||
Label {
|
||||
anchors { left: innerDial.left; bottom: innerDial.bottom; bottomMargin: innerDial.height * .1 }
|
||||
text: "MIN"
|
||||
font.pixelSize: innerDial.height * .06
|
||||
visible: dial.stateType !== null
|
||||
}
|
||||
var rad = Math.atan(mouseY / mouseX);
|
||||
var angle = rad * 180 / Math.PI
|
||||
|
||||
Label {
|
||||
anchors { right: innerDial.right; bottom: innerDial.bottom; bottomMargin: innerDial.height * .1 }
|
||||
text: "MAX"
|
||||
font.pixelSize: innerDial.height * .06
|
||||
visible: dial.stateType !== null
|
||||
}
|
||||
angle += 90;
|
||||
|
||||
ColorIcon {
|
||||
anchors.centerIn: innerDial
|
||||
height: innerDial.height * .2
|
||||
width: height
|
||||
name: "../images/system-shutdown.svg"
|
||||
visible: dial.powerStateType !== null
|
||||
color: d.poweredColor
|
||||
Behavior on color { ColorAnimation { duration: 200 } }
|
||||
}
|
||||
if (mouseX < 0 && mouseY >= 0) angle = 180 + angle;
|
||||
if (mouseX < 0 && mouseY < 0) angle = 180 + angle;
|
||||
|
||||
MouseArea {
|
||||
id: rotateMouseArea
|
||||
anchors.fill: buttonBorder
|
||||
onPressedChanged: PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackImpact)
|
||||
|
||||
// Rectangle { anchors.fill: parent; color: "blue"; opacity: .3}
|
||||
|
||||
property bool grabbed: false
|
||||
onPressed: {
|
||||
startX = mouseX
|
||||
startY = mouseY
|
||||
var mappedToHandle = mapToItem(handle, mouseX, mouseY);
|
||||
if (mappedToHandle.x >= 0
|
||||
&& mappedToHandle.x < handle.width
|
||||
&& mappedToHandle.y >= 0
|
||||
&& mappedToHandle.y < handle.height
|
||||
) {
|
||||
grabbed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
onCanceled: grabbed = false;
|
||||
|
||||
property bool dragging: false
|
||||
onReleased: {
|
||||
grabbed = false;
|
||||
if (dial.powerStateType && !dragging) {
|
||||
var params = []
|
||||
var param = {}
|
||||
param["paramName"] = "power"
|
||||
param["value"] = !dial.powerState.value
|
||||
params.push(param)
|
||||
dial.thing.executeAction("power", params)
|
||||
}
|
||||
dragging = false;
|
||||
}
|
||||
|
||||
readonly property int decimals: dial.stateType && dial.stateType.type.toLowerCase() === "int" ? 0 : 1
|
||||
property var currentValue: dial.deviceState ? dial.deviceState.value.toFixed(decimals) : 0
|
||||
property date lastVibration: new Date()
|
||||
property int startX
|
||||
property int startY
|
||||
onPositionChanged: {
|
||||
if (Math.abs(mouseX - startX) > 10 || Math.abs(mouseY - startY) > 10) {
|
||||
dragging = true;
|
||||
}
|
||||
|
||||
if (!grabbed) {
|
||||
return;
|
||||
}
|
||||
var angle = calculateAngle(mouseX, mouseY)
|
||||
angle = (360 + angle - dial.startAngle) % 360;
|
||||
|
||||
if (angle > 360 - ((360 - dial.maxAngle) / 2)) {
|
||||
angle = 0;
|
||||
} else if (angle > dial.maxAngle) {
|
||||
angle = dial.maxAngle
|
||||
}
|
||||
|
||||
var newValue = Math.round(dial.angleToValue(angle) * 2) / 2;
|
||||
rotationButton.rotation = angle;
|
||||
newValue = newValue.toFixed(decimals)
|
||||
|
||||
if (newValue != currentValue) {
|
||||
currentValue = newValue;
|
||||
if (newValue <= dial.stateType.minValue || newValue >= dial.stateType.maxValue) {
|
||||
PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackImpact)
|
||||
} else {
|
||||
if (lastVibration.getTime() + 35 < new Date()) {
|
||||
PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackSelection)
|
||||
}
|
||||
lastVibration = new Date()
|
||||
}
|
||||
|
||||
d.enqueueSetValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
function calculateAngle(mouseX, mouseY) {
|
||||
// transform coords to center of dial
|
||||
mouseX -= innerDial.width / 2
|
||||
mouseY -= innerDial.height / 2
|
||||
|
||||
var rad = Math.atan(mouseY / mouseX);
|
||||
var angle = rad * 180 / Math.PI
|
||||
|
||||
angle += 90;
|
||||
|
||||
if (mouseX < 0 && mouseY >= 0) angle = 180 + angle;
|
||||
if (mouseX < 0 && mouseY < 0) angle = 180 + angle;
|
||||
|
||||
return angle;
|
||||
}
|
||||
return angle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,7 +46,8 @@ Item {
|
||||
property alias color: icon.color
|
||||
property alias backgroundColor: background.color
|
||||
|
||||
property bool longpressEnabled: true
|
||||
property bool longpressEnabled: false
|
||||
property bool busy: false
|
||||
|
||||
property int size: Style.iconSize
|
||||
|
||||
@ -100,6 +101,16 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: busyCanvas
|
||||
property: "rotation"
|
||||
from: 360
|
||||
to: 0
|
||||
duration: 2000
|
||||
loops: Animation.Infinite
|
||||
running: root.busy
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
@ -136,9 +147,8 @@ Item {
|
||||
property bool inverted: false
|
||||
|
||||
readonly property int penWidth: 2
|
||||
onProgressChanged: {
|
||||
requestPaint()
|
||||
}
|
||||
|
||||
onProgressChanged: requestPaint()
|
||||
Connections {
|
||||
target: buttonDelegate
|
||||
onPressedChanged: {
|
||||
@ -152,21 +162,49 @@ Item {
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = Qt.rgba(1, 0, 0, 1);
|
||||
ctx.lineWidth = canvas.penWidth
|
||||
ctx.strokeStyle = Style.accentColor
|
||||
|
||||
var start = -Math.PI / 2;
|
||||
var stop = -Math.PI / 2;
|
||||
if (inverted) {
|
||||
start += canvas.progress * 2 * Math.PI
|
||||
} else {
|
||||
stop += canvas.progress * 2 * Math.PI
|
||||
// Draw longpress progress
|
||||
if (canvas.progress > 0) {
|
||||
ctx.save();
|
||||
ctx.fillStyle = Qt.rgba(1, 0, 0, 1);
|
||||
ctx.lineWidth = canvas.penWidth
|
||||
ctx.strokeStyle = Style.accentColor
|
||||
|
||||
var start = -Math.PI / 2;
|
||||
var stop = -Math.PI / 2;
|
||||
if (inverted) {
|
||||
start += canvas.progress * 2 * Math.PI
|
||||
} else {
|
||||
stop += canvas.progress * 2 * Math.PI
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(canvas.width / 2, canvas.height / 2, ((canvas.width - canvas.penWidth) / 2), start, stop);
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: busyCanvas
|
||||
visible: root.busy
|
||||
anchors.fill: parent
|
||||
anchors.margins: -4
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.save();
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeStyle = root.backgroundColor;
|
||||
var radius = (width - ctx.lineWidth) / 2
|
||||
var circumference = 2 * Math.PI * radius
|
||||
var dashRatio = circumference / 6 / ctx.lineWidth
|
||||
ctx.setLineDash([dashRatio, dashRatio])
|
||||
ctx.beginPath();
|
||||
ctx.arc(canvas.width / 2, canvas.height / 2, ((canvas.width - canvas.penWidth) / 2), start, stop);
|
||||
ctx.arc(width / 2, height / 2, (width - 2) / 2, 0, 2 * Math.PI);
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -30,13 +30,13 @@
|
||||
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Controls.Material 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import Nymea 1.0
|
||||
|
||||
RowLayout {
|
||||
Item {
|
||||
id: root
|
||||
spacing: 0
|
||||
implicitHeight: size * 4
|
||||
implicitWidth: size * 7
|
||||
|
||||
property Thing thing: null
|
||||
readonly property State openState: thing.stateByName("state")
|
||||
@ -48,47 +48,65 @@ RowLayout {
|
||||
|
||||
signal activated(string button);
|
||||
|
||||
Item { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
RowLayout {
|
||||
anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter }
|
||||
spacing: 0
|
||||
|
||||
ProgressButton {
|
||||
longpressEnabled: false
|
||||
imageSource: root.invert ? "../images/down.svg" : "../images/up.svg"
|
||||
mode: root.backgroundEnabled ? "normal" : "transparent"
|
||||
size: root.size
|
||||
color: root.openState && root.openState.value === "opening" ? Material.accent : Style.iconColor
|
||||
onClicked: {
|
||||
engine.thingManager.executeAction(root.thing.id, root.thing.thingClass.actionTypes.findByName("open").id)
|
||||
root.activated("open")
|
||||
Item { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
|
||||
ProgressButton {
|
||||
imageSource: root.invert ? "../images/down.svg" : "../images/up.svg"
|
||||
backgroundColor: root.backgroundEnabled ? Style.green : "transparent"
|
||||
size: root.size
|
||||
busy: root.openState ? root.openState.value === "opening" : openBusyTimer.running
|
||||
onClicked: {
|
||||
engine.thingManager.executeAction(root.thing.id, root.thing.thingClass.actionTypes.findByName("open").id)
|
||||
root.activated("open")
|
||||
openBusyTimer.start()
|
||||
closeBusyTimer.stop()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: openBusyTimer
|
||||
interval: 5000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
Item { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
|
||||
ProgressButton {
|
||||
visible: root.canStop
|
||||
longpressEnabled: false
|
||||
mode: root.backgroundEnabled ? "normal" : "transparent"
|
||||
size: root.size
|
||||
imageSource: "../images/media-playback-stop.svg"
|
||||
onClicked: {
|
||||
engine.thingManager.executeAction(root.thing.id, root.thing.thingClass.actionTypes.findByName("stop").id)
|
||||
root.activated("stop")
|
||||
ProgressButton {
|
||||
visible: root.canStop
|
||||
backgroundColor: root.backgroundEnabled ? Style.yellow : "transparent"
|
||||
size: root.size
|
||||
imageSource: "../images/media-playback-stop.svg"
|
||||
onClicked: {
|
||||
engine.thingManager.executeAction(root.thing.id, root.thing.thingClass.actionTypes.findByName("stop").id)
|
||||
root.activated("stop")
|
||||
openBusyTimer.stop()
|
||||
closeBusyTimer.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
Item { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
|
||||
ProgressButton {
|
||||
imageSource: root.invert ? "../images/up.svg" : "../images/down.svg"
|
||||
longpressEnabled: false
|
||||
mode: root.backgroundEnabled ? "normal" : "transparent"
|
||||
size: root.size
|
||||
color: root.openState && root.openState.value === "closing" ? Material.accent : Style.iconColor
|
||||
onClicked: {
|
||||
engine.thingManager.executeAction(root.thing.id, root.thing.thingClass.actionTypes.findByName("close").id)
|
||||
root.activated("close")
|
||||
ProgressButton {
|
||||
imageSource: root.invert ? "../images/up.svg" : "../images/down.svg"
|
||||
backgroundColor: root.backgroundEnabled ? Style.red : "transparent"
|
||||
size: root.size
|
||||
busy: root.openState ? root.openState.value === "closing" : closeBusyTimer.running
|
||||
onClicked: {
|
||||
engine.thingManager.executeAction(root.thing.id, root.thing.thingClass.actionTypes.findByName("close").id)
|
||||
root.activated("close")
|
||||
openBusyTimer.stop();
|
||||
closeBusyTimer.start()
|
||||
}
|
||||
Timer {
|
||||
id: closeBusyTimer
|
||||
interval: 5000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
Item { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls 2.1
|
||||
import QtGraphicalEffects 1.0
|
||||
import "../components"
|
||||
import Nymea 1.0
|
||||
|
||||
@ -42,86 +43,77 @@ Item {
|
||||
: Math.min(Math.min(parent.width, 500), parent.height - shutterControlsContainer.minimumHeight)
|
||||
Layout.preferredHeight: width
|
||||
|
||||
ColorIcon {
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.centerIn: parent
|
||||
size: Math.min(parent.height, parent.width) - Style.hugeMargins * 2
|
||||
property string currentImage: {
|
||||
if (root.isExtended) {
|
||||
return NymeaUtils.pad(Math.round(root.percentageState.value / 10), 2) + "0"
|
||||
}
|
||||
if (root.intermediatePositionStateType) {
|
||||
return root.stateState.value === "closed" ? "100"
|
||||
: root.intermediatePositionState.value === false ? "000" : "050"
|
||||
}
|
||||
return "100"
|
||||
}
|
||||
name: "../images/garage/garage-" + currentImage + ".svg"
|
||||
width: Math.min(500, Math.min(parent.width, parent.height) - Style.hugeMargins * 2)
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: Style.tileBackgroundColor
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
id: arrows
|
||||
Item {
|
||||
id: door
|
||||
anchors.fill: background
|
||||
Canvas {
|
||||
id: canvas
|
||||
anchors.centerIn: parent
|
||||
width: Style.iconSize * 2
|
||||
height: parent.height * .6
|
||||
clip: true
|
||||
visible: root.stateStateType && (root.stateState.value === "opening" || root.stateState.value === "closing")
|
||||
property bool up: root.stateState && root.stateState.value === "opening"
|
||||
width: parent.width
|
||||
height: parent.height + Style.margins
|
||||
anchors.verticalCenterOffset: root.percentageState
|
||||
? -height * (1 - (root.percentageState.value / 100))
|
||||
: -height / 2
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.reset();
|
||||
|
||||
// NumberAnimation doesn't reload to/from while it's running. If we switch from closing to opening or vice versa
|
||||
// we need to somehow stop and start the animation
|
||||
property bool animationHack: true
|
||||
onAnimationHackChanged: {
|
||||
if (!animationHack) hackTimer.start();
|
||||
}
|
||||
Timer { id: hackTimer; interval: 1; onTriggered: arrows.animationHack = true }
|
||||
Connections { target: root.stateState; onValueChanged: arrows.animationHack = false }
|
||||
|
||||
NumberAnimation {
|
||||
target: arrowColumn
|
||||
property: "y"
|
||||
duration: 500
|
||||
easing.type: Easing.Linear
|
||||
from: arrows.up ? Style.iconSize : -Style.iconSize
|
||||
to: arrows.up ? -Style.iconSize : Style.iconSize
|
||||
loops: Animation.Infinite
|
||||
running: arrows.animationHack && root.stateState && (root.stateState.value === "opening" || root.stateState.value === "closing")
|
||||
}
|
||||
|
||||
Column {
|
||||
id: arrowColumn
|
||||
width: parent.width
|
||||
|
||||
Repeater {
|
||||
model: arrows.height / Style.iconSize + 1
|
||||
ColorIcon {
|
||||
name: arrows.up ? "../images/up.svg" : "../images/down.svg"
|
||||
width: parent.width
|
||||
height: width
|
||||
color: Style.accentColor
|
||||
}
|
||||
ctx.fillStyle = Style.tileForegroundColor
|
||||
var segments = 10;
|
||||
var segmentHeight = height / segments
|
||||
var barHeight = segmentHeight - Style.smallMargins
|
||||
for (var i = 0; i < segments; i++) {
|
||||
ctx.fillRect(0, i * segmentHeight, width, barHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
OpacityMask {
|
||||
anchors.fill: background
|
||||
source: ShaderEffectSource {
|
||||
sourceItem: door
|
||||
hideSource: true
|
||||
}
|
||||
maskSource: background
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: shutterControlsContainer
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: minimumWidth
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredHeight: Style.bigIconSize * 4
|
||||
property int minimumWidth: Style.iconSize * 10
|
||||
property int minimumHeight: Style.iconSize * 2.5
|
||||
|
||||
ProgressButton {
|
||||
anchors.centerIn: parent
|
||||
mode: "highlight"
|
||||
visible: root.isImpulseBased
|
||||
longpressEnabled: false
|
||||
size: Style.bigIconSize
|
||||
imageSource: "../images/closable-move.svg"
|
||||
busy: busyTimer.running
|
||||
onClicked: {
|
||||
var actionTypeId = root.thing.thingClass.actionTypes.findByName("triggerImpulse").id
|
||||
print("Triggering impulse", actionTypeId)
|
||||
engine.thingManager.executeAction(root.thing.id, actionTypeId)
|
||||
busyTimer.start();
|
||||
}
|
||||
Timer {
|
||||
id: busyTimer
|
||||
interval: 5000
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,7 +124,6 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
backgroundEnabled: true
|
||||
size: Style.bigIconSize
|
||||
spacing: (parent.width - Style.iconSize*2*children.length) / (children.length - 1)
|
||||
visible: !root.isImpulseBased
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,9 +90,9 @@ Item {
|
||||
|
||||
if (targetTempStep === step) {
|
||||
if (currentTempStep && currentTempStep < targetTempStep) {
|
||||
ctx.strokeStyle = "red";
|
||||
ctx.strokeStyle = app.interfaceToColor("heating");
|
||||
} else if (currentTempStep && currentTempStep > targetTempStep) {
|
||||
ctx.strokeStyle = "dodgerblue";
|
||||
ctx.strokeStyle = app.interfaceToColor("cooling");
|
||||
} else {
|
||||
ctx.strokeStyle = Style.accentColor;
|
||||
}
|
||||
@ -100,16 +100,16 @@ Item {
|
||||
ctx.lineWidth = 4;
|
||||
} else if (currentTempStep && currentTempStep === step) {
|
||||
if (currentTempStep < targetTempStep) {
|
||||
ctx.strokeStyle = "red";
|
||||
ctx.strokeStyle = app.interfaceToColor("heating");
|
||||
} else {
|
||||
ctx.strokeStyle = "dodgerblue";
|
||||
ctx.strokeStyle = app.interfaceToColor("cooling");
|
||||
}
|
||||
ctx.lineWidth = 3;
|
||||
} else if (currentTempStep && currentTempStep < step && step < targetTempStep) {
|
||||
ctx.strokeStyle = "red";
|
||||
ctx.strokeStyle = app.interfaceToColor("heating");
|
||||
ctx.lineWidth = 2;
|
||||
} else if (currentTempStep && currentTempStep > step && step > targetTempStep) {
|
||||
ctx.strokeStyle = "dodgerblue";
|
||||
ctx.strokeStyle = app.interfaceToColor("cooling");
|
||||
ctx.lineWidth = 2;
|
||||
} else {
|
||||
ctx.strokeStyle = Style.tileOverlayColor;
|
||||
@ -127,8 +127,8 @@ Item {
|
||||
var outerPointX = center.x + (outerRadius * c);
|
||||
var outerPointY = center.x + (outerRadius * s);
|
||||
|
||||
context.moveTo(innerPointX, innerPointY);
|
||||
context.lineTo(outerPointX, outerPointY);
|
||||
ctx.moveTo(innerPointX, innerPointY);
|
||||
ctx.lineTo(outerPointX, outerPointY);
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
}
|
||||
@ -166,16 +166,16 @@ Item {
|
||||
ColorIcon {
|
||||
width: Style.largeIconSize
|
||||
height: width
|
||||
anchors { bottom: canvas.bottom; horizontalCenter: canvas.horizontalCenter }
|
||||
anchors { bottom: canvas.bottom; horizontalCenter: canvas.horizontalCenter; margins: Style.margins }
|
||||
name: root.heatingOnState && root.heatingOnState.value === true
|
||||
? "../images/thermostat/heating.svg"
|
||||
: root.coolingOnState && root.coolingOnState.value === true
|
||||
? "../images/thermostat/cooling.svg"
|
||||
: ""
|
||||
color: root.heatingOnState && root.heatingOnState.value === true
|
||||
? "red"
|
||||
? app.interfaceToColor("heating")
|
||||
: root.coolingOnState && root.coolingOnState.value === true
|
||||
? "dodgerblue"
|
||||
? app.interfaceToColor("cooling")
|
||||
: Style.iconColor
|
||||
}
|
||||
|
||||
|
||||
@ -74,6 +74,7 @@ MainPageTile {
|
||||
// Open interface specific things list
|
||||
switch (iface.name) {
|
||||
case "heating":
|
||||
case "cooling":
|
||||
case "sensor":
|
||||
page = "SensorsDeviceListPage.qml"
|
||||
break;
|
||||
@ -172,6 +173,7 @@ MainPageTile {
|
||||
case "extendedsmartmeterconsumer":
|
||||
case "extendedsmartmeterproducer":
|
||||
case "heating":
|
||||
case "cooling":
|
||||
case "thermostat":
|
||||
return sensorComponent;
|
||||
// return labelComponent;
|
||||
@ -209,7 +211,7 @@ MainPageTile {
|
||||
case "light":
|
||||
var group = engine.thingManager.createGroup(Interfaces.findByName("colorlight"), thingsProxy);
|
||||
print("opening lights page for group", group)
|
||||
pageStack.push("../devicepages/LightDevicePage.qml", {thing: group})
|
||||
pageStack.push("../devicepages/LightThingPage.qml", {thing: group})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,6 +143,8 @@ ThingsListPageBase {
|
||||
ShutterControls {
|
||||
id: shutterControls
|
||||
Layout.fillWidth: false
|
||||
Layout.preferredWidth: Style.iconSize * 5
|
||||
Layout.preferredHeight: Style.iconSize
|
||||
height: parent.height
|
||||
thing: itemDelegate.thing
|
||||
invert: root.invertControls
|
||||
|
||||
@ -108,6 +108,26 @@ ThingsListPageBase {
|
||||
ThingStatusIcons {
|
||||
thing: itemDelegate.thing
|
||||
}
|
||||
ProgressButton {
|
||||
visible: itemDelegate.thing.thingClass.interfaces.indexOf("impulsegaragedoor") >= 0
|
||||
imageSource: "../images/closable-move.svg"
|
||||
onClicked: {
|
||||
var actionTypeId = itemDelegate.thing.thingClass.actionTypes.findByName("triggerImpulse").id
|
||||
engine.thingManager.executeAction(itemDelegate.thing.id, actionTypeId)
|
||||
}
|
||||
}
|
||||
|
||||
ShutterControls {
|
||||
visible: itemDelegate.thing.thingClass.interfaces.indexOf("simpleclosable") >= 0
|
||||
id: shutterControls
|
||||
Layout.fillWidth: false
|
||||
Layout.preferredWidth: Style.iconSize * 5
|
||||
Layout.preferredHeight: Style.iconSize
|
||||
height: parent.height
|
||||
thing: itemDelegate.thing
|
||||
invert: root.invertControls
|
||||
enabled: itemDelegate.isEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,7 +107,7 @@ ThingsListPageBase {
|
||||
property bool colorInverted: tileColored && NymeaUtils.isDark(Style.foregroundColor) === NymeaUtils.isDark(colorState.value)
|
||||
|
||||
onClicked: {
|
||||
if (isEnabled && (colorState || colorTemperatureState)) {
|
||||
if (isEnabled /*&& (colorState || colorTemperatureState)*/) {
|
||||
root.enterPage(index)
|
||||
} else {
|
||||
itemDelegate.wobble()
|
||||
|
||||
@ -30,11 +30,12 @@
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Controls.Material 2.2
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "../customviews"
|
||||
import "../utils"
|
||||
|
||||
ThingPageBase {
|
||||
id: root
|
||||
@ -48,75 +49,71 @@ ThingPageBase {
|
||||
anchors.fill: parent
|
||||
columns: root.landscape ? 2 : 1
|
||||
|
||||
Item {
|
||||
id: shutterImage
|
||||
Layout.preferredWidth: root.landscape ?
|
||||
Math.min(parent.width - shutterControlsContainer.minimumWidth, parent.height)
|
||||
: Math.min(Math.min(500, parent.width), parent.height - shutterControlsContainer.minimumHeight)
|
||||
Layout.preferredHeight: width
|
||||
CircleBackground {
|
||||
id: background
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Style.hugeMargins
|
||||
onColor: Style.yellow
|
||||
on: true
|
||||
iconSource: "weathericons/weather-clear-day"
|
||||
|
||||
ColorIcon {
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: root.thing
|
||||
stateName: "percentage"
|
||||
}
|
||||
|
||||
Item {
|
||||
id: awning
|
||||
anchors.fill: parent
|
||||
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: background.contentItem.width
|
||||
height: background.contentItem.height
|
||||
property real progress: root.percentageState ?
|
||||
dragArea.pressed ? dragArea.draggedProgress : root.percentageState.value / 100
|
||||
: .5
|
||||
anchors.verticalCenterOffset: -height * (1 - progress)
|
||||
color: Style.tileOverlayColor
|
||||
}
|
||||
}
|
||||
|
||||
OpacityMask {
|
||||
anchors.fill: background
|
||||
source: ShaderEffectSource {
|
||||
sourceItem: awning
|
||||
hideSource: true
|
||||
}
|
||||
maskSource: background
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dragArea
|
||||
anchors.centerIn: parent
|
||||
size: Math.min(parent.height, parent.width) - Style.hugeMargins * 2
|
||||
name: root.isExtended ?
|
||||
"../images/awning/awning-" + app.pad(Math.round(root.percentageState.value / 10) * 10, 3) + ".svg"
|
||||
: "../images/awning/awning-100.svg"
|
||||
width: background.contentItem.width
|
||||
height: background.contentItem.height
|
||||
property real draggedProgress: mouseY / height
|
||||
onMouseYChanged: print("mouseY", mouseY, draggedProgress)
|
||||
onReleased: {
|
||||
actionQueue.sendValue(draggedProgress * 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: shutterControlsContainer
|
||||
|
||||
ShutterControls {
|
||||
id: shutterControls
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.margins: app.margins * 2
|
||||
property int minimumWidth: Style.iconSize * 2.7 * 3
|
||||
property int minimumHeight: Style.iconSize * 4.5
|
||||
Layout.minimumWidth: implicitWidth
|
||||
Layout.preferredHeight: implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
spacing: app.margins
|
||||
|
||||
Slider {
|
||||
id: percentageSlider
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 100
|
||||
stepSize: 1
|
||||
visible: isExtended
|
||||
|
||||
Binding {
|
||||
target: percentageSlider
|
||||
property: "value"
|
||||
value: root.percentageState.value
|
||||
when: !percentageSlider.pressed
|
||||
}
|
||||
|
||||
onPressedChanged: {
|
||||
if (pressed) {
|
||||
return
|
||||
}
|
||||
print("should move", value)
|
||||
|
||||
var actionType = root.thing.thingClass.actionTypes.findByName("percentage");
|
||||
var params = [];
|
||||
var percentageParam = {}
|
||||
percentageParam["paramTypeId"] = actionType.paramTypes.findByName("percentage").id;
|
||||
percentageParam["value"] = value
|
||||
params.push(percentageParam);
|
||||
engine.thingManager.executeAction(root.thing.id, actionType.id, params);
|
||||
}
|
||||
}
|
||||
|
||||
ShutterControls {
|
||||
id: shutterControls
|
||||
thing: root.thing
|
||||
size: Style.bigIconSize
|
||||
backgroundEnabled: true
|
||||
invert: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
thing: root.thing
|
||||
size: Style.bigIconSize
|
||||
backgroundEnabled: true
|
||||
invert: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
nymea-app/ui/devicepages/CoolingThingPage.qml
Normal file
59
nymea-app/ui/devicepages/CoolingThingPage.qml
Normal file
@ -0,0 +1,59 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "../utils"
|
||||
|
||||
ThingPageBase {
|
||||
id: root
|
||||
|
||||
readonly property State powerState: thing.stateByName("power")
|
||||
|
||||
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: root.thing
|
||||
stateName: "power"
|
||||
}
|
||||
|
||||
CircleBackground {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.hugeMargins
|
||||
iconSource: "thermostat/cooling"
|
||||
onColor: app.interfaceToColor("cooling")
|
||||
on: actionQueue.pendingValue || root.powerState.value
|
||||
onClicked: actionQueue.sendValue(!root.powerState.value)
|
||||
}
|
||||
}
|
||||
74
nymea-app/ui/devicepages/EvChargerThingPage.qml
Normal file
74
nymea-app/ui/devicepages/EvChargerThingPage.qml
Normal file
@ -0,0 +1,74 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "../utils"
|
||||
|
||||
ThingPageBase {
|
||||
id: root
|
||||
|
||||
readonly property State powerState: thing.stateByName("power")
|
||||
readonly property State maxChargingCurrentState: thing.stateByName("maxChargingCurrent")
|
||||
readonly property StateType maxChargingCurrentStateType: thing.thingClass.stateTypes.findByName("maxChargingCurrent")
|
||||
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: root.thing
|
||||
stateName: "power"
|
||||
}
|
||||
|
||||
CircleBackground {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.hugeMargins
|
||||
iconSource: "ev-charger"
|
||||
onColor: app.interfaceToColor("evcharger")
|
||||
on: (actionQueue.pendingValue || powerState.value) === true
|
||||
onClicked: {
|
||||
PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackSelection)
|
||||
actionQueue.sendValue(!root.powerState.value)
|
||||
}
|
||||
}
|
||||
|
||||
Dial {
|
||||
anchors.centerIn: parent
|
||||
height: background.contentItem.height
|
||||
width: background.contentItem.width
|
||||
visible: root.maxChargingCurrentState
|
||||
|
||||
thing: root.thing
|
||||
stateName: "maxChargingCurrent"
|
||||
color: app.interfaceToColor("evcharger")
|
||||
}
|
||||
}
|
||||
@ -34,58 +34,26 @@ import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "../utils"
|
||||
|
||||
ThingPageBase {
|
||||
id: root
|
||||
|
||||
readonly property State powerState: thing.stateByName("power")
|
||||
readonly property ActionType powerActionType: thing.thingClass.actionTypes.findByName("power");
|
||||
|
||||
GridLayout {
|
||||
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: root.thing
|
||||
stateName: "power"
|
||||
}
|
||||
|
||||
CircleBackground {
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins
|
||||
columns: app.landscape ? 2 : 1
|
||||
rowSpacing: app.margins
|
||||
columnSpacing: app.margins
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: Math.max(Style.iconSize * 6, parent.width / 5)
|
||||
Layout.preferredHeight: width
|
||||
Layout.topMargin: app.margins
|
||||
Layout.bottomMargin: app.landscape ? app.margins : 0
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.rowSpan: app.landscape ? 4 : 1
|
||||
Layout.fillHeight: true
|
||||
|
||||
AbstractButton {
|
||||
height: Math.min(parent.height, parent.width)
|
||||
width: height
|
||||
anchors.centerIn: parent
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.color: root.powerState.value === true ? Style.accentColor : Style.iconColor
|
||||
border.width: 4
|
||||
radius: width / 2
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
id: bulbIcon
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins * 1.5
|
||||
name: "../images/thermostat/heating.svg"
|
||||
color: root.powerState.value === true ? Style.accentColor : Style.iconColor
|
||||
}
|
||||
onClicked: {
|
||||
var params = []
|
||||
var param = {}
|
||||
param["paramTypeId"] = root.powerActionType.paramTypes.get(0).id;
|
||||
param["value"] = !root.powerState.value;
|
||||
params.push(param)
|
||||
engine.thingManager.executeAction(root.thing.id, root.powerActionType.id, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
anchors.margins: Style.hugeMargins
|
||||
iconSource: "thermostat/heating"
|
||||
onColor: app.interfaceToColor("heating")
|
||||
on: actionQueue.pendingValue || root.powerState.value
|
||||
onClicked: actionQueue.sendValue(!root.powerState.value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "../utils"
|
||||
|
||||
ThingPageBase {
|
||||
id: root
|
||||
@ -149,54 +150,36 @@ ThingPageBase {
|
||||
filterValue: root.thing.id
|
||||
}
|
||||
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: root.thing
|
||||
stateName: "power"
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: mainGrid
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins
|
||||
columns: app.landscape ? 2 : 1
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: app.landscape ? parent.width * .4 : parent.width
|
||||
Layout.preferredHeight: app.landscape ? parent.height : parent.height *.4
|
||||
|
||||
AbstractButton {
|
||||
height: Math.min(Math.min(parent.height, parent.width), Style.iconSize * 5)
|
||||
width: height
|
||||
anchors.centerIn: parent
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.color: root.powerState.value === true ? Style.accentColor : Style.iconColoor
|
||||
border.width: 4
|
||||
radius: width / 2
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
id: irrigationIcon
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins * 1.5
|
||||
name: "../images/irrigation.svg"
|
||||
color: root.powerState.value === true ? Style.accentColor : Style.iconColor
|
||||
}
|
||||
onClicked: {
|
||||
var params = []
|
||||
var param = {}
|
||||
param["paramTypeId"] = root.powerActionType.paramTypes.get(0).id;
|
||||
param["value"] = !root.powerState.value;
|
||||
params.push(param)
|
||||
engine.thingManager.executeAction(root.thing.id, root.powerStateType.id, params);
|
||||
PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackSelection)
|
||||
}
|
||||
}
|
||||
CircleBackground {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: Style.hugeMargins; Layout.rightMargin: Style.hugeMargins; Layout.topMargin: Style.hugeMargins
|
||||
iconSource: "irrigation"
|
||||
onColor: app.interfaceToColor("irrigation")
|
||||
on: actionQueue.pendingValue || root.powerState.value
|
||||
onClicked: actionQueue.sendValue(!root.powerState.value)
|
||||
}
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
Layout.preferredWidth: app.landscape ? parent.width * .6 : parent.width
|
||||
Layout.preferredHeight: app.landscape ? parent.height : parent.height * .6
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.margins: Style.margins
|
||||
Item { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
|
||||
Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: root.isOn ? qsTr("Watering since")
|
||||
: history.lastWatering ? qsTr("Last watering")
|
||||
@ -205,7 +188,7 @@ ThingPageBase {
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
|
||||
Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: history.lastWatering ? Qt.formatDateTime(history.lastWatering) : ""
|
||||
font.pixelSize: app.largeFont
|
||||
@ -213,7 +196,7 @@ ThingPageBase {
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.bottomMargin: app.margins
|
||||
Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins; Layout.bottomMargin: Style.margins
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: app.smallFont
|
||||
text: {
|
||||
@ -252,7 +235,7 @@ ThingPageBase {
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
|
||||
Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins; Layout.topMargin: Style.margins
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: tagsProxy.count > 0 ?
|
||||
//: Irrigation will be turned of at, e.g. 09:00
|
||||
@ -299,7 +282,7 @@ ThingPageBase {
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
|
||||
Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
visible: tagsProxy.count > 0
|
||||
font.pixelSize: app.largeFont
|
||||
@ -308,7 +291,7 @@ ThingPageBase {
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.bottomMargin: app.margins
|
||||
Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins; Layout.bottomMargin: Style.margins
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
visible: tagsProxy.count > 0
|
||||
font.pixelSize: app.smallFont
|
||||
|
||||
@ -1,188 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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.9
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import Nymea 1.0
|
||||
import QtGraphicalEffects 1.0
|
||||
import "../components"
|
||||
|
||||
ThingPageBase {
|
||||
id: root
|
||||
|
||||
readonly property State powerState: thing.stateByName("power")
|
||||
|
||||
readonly property State brightnessState: thing.stateByName("brightness")
|
||||
readonly property ActionType brightnessActionType: thingClass.actionTypes.findByName("brightness");
|
||||
|
||||
readonly property State colorState: thing.stateByName("color")
|
||||
|
||||
readonly property StateType ctStateType: thingClass.stateTypes.findByName("colorTemperature")
|
||||
readonly property State ctState: thing.stateByName("colorTemperature")
|
||||
readonly property ActionType ctActionType: thingClass.actionTypes.findByName("colorTemperature")
|
||||
|
||||
readonly property int statesCount: (powerState !== null ? 1 : 0) +
|
||||
(brightnessState !== null ? 1 : 0) +
|
||||
(ctState !== null ? 1 : 0) +
|
||||
(colorState !== null ? 1 : 0)
|
||||
|
||||
GridLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins
|
||||
columns: app.landscape ? root.statesCount : 1
|
||||
rowSpacing: app.margins
|
||||
columnSpacing: app.margins
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
GridLayout {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: !app.landscape
|
||||
columnSpacing: app.margins
|
||||
rowSpacing: app.margins
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: root.ctStateType !== null
|
||||
columns: app.landscape ? 1 : 4
|
||||
|
||||
Repeater {
|
||||
model: ListModel {
|
||||
ListElement { name: "activate"; ct: "0"; bri: 100; color: "#00c5ff" }
|
||||
ListElement { name: "concentrate"; ct: "23"; bri: 100; color: "#3dddff" }
|
||||
ListElement { name: "reading"; ct: "57"; bri: 100; color: "#f4de00" }
|
||||
ListElement { name: "relax"; ct: "95" ; bri: 55; color: "#ffaf2a"}
|
||||
}
|
||||
delegate: Item {
|
||||
Layout.preferredWidth: Style.hugeIconSize
|
||||
Layout.preferredHeight: Style.hugeIconSize
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: app.landscape
|
||||
ItemDelegate {
|
||||
height: Style.hugeIconSize
|
||||
width: height
|
||||
anchors.centerIn: parent
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
contentItem: Rectangle {
|
||||
color: model.color
|
||||
radius: Style.cornerRadius
|
||||
|
||||
ColorIcon {
|
||||
anchors.fill: parent
|
||||
name: "../images/lighting/" + model.name + ".svg"
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onClicked: {
|
||||
// Translate from % to absolute value in min/max
|
||||
// % : 100 = abs : (max - min)
|
||||
print("min,max", root.ctStateType, root.ctStateType.minValue, root.ctStateType.maxValue)
|
||||
var absoluteCtValue = (model.ct * (root.ctStateType.maxValue - root.ctStateType.minValue) / 100) + root.ctStateType.minValue
|
||||
var params = [];
|
||||
var param1 = {};
|
||||
param1["paramName"] = root.ctActionType.paramTypes.get(0).name;
|
||||
param1["value"] = absoluteCtValue;
|
||||
params.push(param1)
|
||||
root.thing.executeAction(root.ctActionType.name, params)
|
||||
params = [];
|
||||
param1 = {};
|
||||
param1["paramName"] = root.brightnessActionType.paramTypes.get(0).name;
|
||||
param1["value"] = model.bri;
|
||||
params.push(param1)
|
||||
root.thing.executeAction(root.brightnessActionType.name, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColorPicker {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.maximumHeight: width
|
||||
thing: root.thing
|
||||
visible: root.thing.thingClass.stateTypes.findByName("color") !== null
|
||||
}
|
||||
|
||||
ColorTemperaturePicker {
|
||||
Layout.fillWidth: !app.landscape
|
||||
Layout.fillHeight: app.landscape
|
||||
thing: root.thing
|
||||
orientation: app.landscape ? Qt.Vertical : Qt.Horizontal
|
||||
visible: root.thing.thingClass.stateTypes.findByName("colorTemperature") !== null
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: basicItems
|
||||
Layout.fillWidth: !app.landscape
|
||||
Layout.fillHeight: app.landscape
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
columnSpacing: app.margins
|
||||
rowSpacing: app.margins
|
||||
columns: (app.landscape && (root.colorState !== null && root.ctState !== null))
|
||||
|| (!app.landscape && (root.colorState === null && root.ctState === null)) ? 1 : 2
|
||||
Rectangle {
|
||||
Layout.preferredWidth: Style.hugeIconSize
|
||||
Layout.preferredHeight: width
|
||||
radius: Style.cornerRadius
|
||||
color: root.colorState ? root.colorState.value : "red"
|
||||
// color: Qt.tint(Style.backgroundColor, Qt.rgba(Style.foregroundColor.r, Style.foregroundColor.g, Style.foregroundColor.b, 0.1))
|
||||
ColorIcon {
|
||||
anchors.centerIn: parent
|
||||
height: Style.bigIconSize
|
||||
width: height
|
||||
name: root.powerState.value === true ? "../images/light-on.svg" : "../images/light-off.svg"
|
||||
color: root.colorState ?
|
||||
NymeaUtils.isDark(root.colorState.value) ? "white" : "black" : "white"
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
root.thing.executeAction("power", [{paramName: "power", value: !root.powerState.value}])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BrightnessSlider {
|
||||
Layout.fillWidth: orientation == Qt.Horizontal
|
||||
Layout.fillHeight: orientation == Qt.Vertical
|
||||
thing: root.thing
|
||||
orientation: basicItems.columns === 1 ? Qt.Vertical : Qt.Horizontal
|
||||
visible: root.thing.stateByName("brightness") !== null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
308
nymea-app/ui/devicepages/LightThingPage.qml
Normal file
308
nymea-app/ui/devicepages/LightThingPage.qml
Normal file
@ -0,0 +1,308 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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.9
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import Nymea 1.0
|
||||
import QtGraphicalEffects 1.0
|
||||
import "../components"
|
||||
import "../utils"
|
||||
|
||||
ThingPageBase {
|
||||
id: root
|
||||
|
||||
readonly property State powerState: thing.stateByName("power")
|
||||
|
||||
readonly property State brightnessState: thing.stateByName("brightness")
|
||||
readonly property ActionType brightnessActionType: thingClass.actionTypes.findByName("brightness");
|
||||
|
||||
readonly property State colorState: thing.stateByName("color")
|
||||
|
||||
readonly property StateType ctStateType: thingClass.stateTypes.findByName("colorTemperature")
|
||||
readonly property State ctState: thing.stateByName("colorTemperature")
|
||||
readonly property ActionType ctActionType: thingClass.actionTypes.findByName("colorTemperature")
|
||||
|
||||
readonly property int statesCount: (powerState !== null ? 1 : 0) +
|
||||
(brightnessState !== null ? 1 : 0) +
|
||||
(ctState !== null ? 1 : 0) +
|
||||
(colorState !== null ? 1 : 0)
|
||||
|
||||
GridLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.bigMargins
|
||||
columns: app.landscape ? root.statesCount : 1
|
||||
rowSpacing: Style.bigMargins
|
||||
columnSpacing: Style.bigMargins
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
GridLayout {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: !app.landscape
|
||||
columnSpacing: app.margins
|
||||
rowSpacing: app.margins
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: root.ctStateType !== null
|
||||
columns: app.landscape ? 1 : 4
|
||||
|
||||
Repeater {
|
||||
model: ListModel {
|
||||
ListElement { name: "activate"; ct: "0"; bri: 100; color: "#00c5ff" }
|
||||
ListElement { name: "concentrate"; ct: "23"; bri: 100; color: "#3dddff" }
|
||||
ListElement { name: "reading"; ct: "57"; bri: 100; color: "#f4de00" }
|
||||
ListElement { name: "relax"; ct: "95" ; bri: 55; color: "#ffaf2a"}
|
||||
}
|
||||
delegate: ProgressButton {
|
||||
Layout.preferredHeight: Style.hugeIconSize
|
||||
Layout.preferredWidth: Style.hugeIconSize
|
||||
imageSource: "../images/lighting/" + model.name + ".svg"
|
||||
longpressEnabled: false
|
||||
// mode: "normal"
|
||||
// backgroundColor: model.color
|
||||
|
||||
|
||||
onClicked: {
|
||||
// Translate from % to absolute value in min/max
|
||||
// % : 100 = abs : (max - min)
|
||||
print("min,max", root.ctStateType, root.ctStateType.minValue, root.ctStateType.maxValue)
|
||||
var absoluteCtValue = (model.ct * (root.ctStateType.maxValue - root.ctStateType.minValue) / 100) + root.ctStateType.minValue
|
||||
var params = [];
|
||||
var param1 = {};
|
||||
param1["paramName"] = root.ctActionType.paramTypes.get(0).name;
|
||||
param1["value"] = absoluteCtValue;
|
||||
params.push(param1)
|
||||
root.thing.executeAction(root.ctActionType.name, params)
|
||||
params = [];
|
||||
param1 = {};
|
||||
param1["paramName"] = root.brightnessActionType.paramTypes.get(0).name;
|
||||
param1["value"] = model.bri;
|
||||
params.push(param1)
|
||||
root.thing.executeAction(root.brightnessActionType.name, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.hugeMargins
|
||||
|
||||
StackLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.maximumHeight: width
|
||||
currentIndex: selectionTabs.currentIndex
|
||||
|
||||
Repeater {
|
||||
model: modeModel
|
||||
delegate: Loader {
|
||||
sourceComponent: model.comp
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: colorPickerComponent
|
||||
ColorPicker {
|
||||
anchors.fill: parent
|
||||
thing: root.thing
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: colorTemperatureComponent
|
||||
ColorTemperaturePicker {
|
||||
anchors.fill: parent
|
||||
thing: root.thing
|
||||
orientation: app.landscape ? Qt.Vertical : Qt.Horizontal
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: brightnessComponent
|
||||
Item {
|
||||
id: brightnessController
|
||||
property Thing thing: root.thing
|
||||
readonly property State brightnessState: thing ? thing.stateByName("brightness") : null
|
||||
readonly property State colorState: thing ? thing.stateByName("color") : null
|
||||
readonly property State powerState: thing ? thing.stateByName("power") : null
|
||||
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: brightnessController.thing
|
||||
stateType: thing.thingClass.stateTypes.findByName("brightness")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: brightnessCircle
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(400, Math.min(parent.width, parent.height))
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: Style.tileBackgroundColor
|
||||
|
||||
}
|
||||
|
||||
ShaderEffect {
|
||||
anchors.fill: brightnessCircle
|
||||
property Item source: ShaderEffectSource {
|
||||
id: shaderSource
|
||||
anchors.fill: parent
|
||||
sourceItem: brightnessCircle
|
||||
hideSource: true
|
||||
}
|
||||
property color inColor: Style.tileBackgroundColor
|
||||
property color outColor: powerState.value === true
|
||||
? colorState ? colorState.value : "#ffd649"
|
||||
: Style.tileOverlayColor
|
||||
Behavior on outColor { ColorAnimation { duration: Style.animationDuration } }
|
||||
|
||||
property real threshold: 0.1
|
||||
property real brightness: 1 - (actionQueue.pendingValue || brightnessState.value) / 100
|
||||
|
||||
fragmentShader: "
|
||||
varying highp vec2 qt_TexCoord0;
|
||||
uniform sampler2D source;
|
||||
uniform highp vec4 outColor;
|
||||
uniform highp vec4 inColor;
|
||||
uniform lowp float threshold;
|
||||
uniform lowp float qt_Opacity;
|
||||
uniform lowp float brightness;
|
||||
void main() {
|
||||
bool isOn = qt_TexCoord0.y > brightness;
|
||||
lowp vec4 sourceColor = texture2D(source, qt_TexCoord0);
|
||||
if (isOn) {
|
||||
gl_FragColor = mix(vec4(outColor.rgb, 1.0) * sourceColor.a, sourceColor, step(threshold, distance(sourceColor.rgb / sourceColor.a, inColor.rgb))) * qt_Opacity;
|
||||
} else {
|
||||
gl_FragColor = sourceColor;
|
||||
}
|
||||
}"
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: brightnessCircle
|
||||
onMouseYChanged: {
|
||||
var progress = 1 - mouseY / height
|
||||
actionQueue.sendValue(progress * 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: powerComponent
|
||||
Item {
|
||||
id: powerController
|
||||
property Thing thing: root.thing
|
||||
readonly property State powerState: thing ? thing.stateByName("power") : null
|
||||
|
||||
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: powerController.thing
|
||||
stateType: thing.thingClass.stateTypes.findByName("power")
|
||||
}
|
||||
|
||||
CircleBackground {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.bigMargins
|
||||
onColor: "#ffd649"
|
||||
iconSource: (actionQueue.pendingValue || powerState.value) === true ? "light-on" : "light-off"
|
||||
on: (actionQueue.pendingValue || powerState.value) === true ? 1 : 0
|
||||
onClicked: {
|
||||
actionQueue.sendValue(!powerState.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: modeModel
|
||||
Component.onCompleted: {
|
||||
if (root.colorState) {
|
||||
append({modelData: qsTr("Color"), comp: colorPickerComponent})
|
||||
}
|
||||
if (root.ctState) {
|
||||
append({modelData: qsTr("Temperature"), comp: colorTemperatureComponent})
|
||||
}
|
||||
if (root.brightnessState && !root.ctState && !root.colorState) {
|
||||
append({modelData: qsTr("Brightness"), comp: brightnessComponent})
|
||||
}
|
||||
if (!root.colorState && !root.ctState && !root.brightnessState) {
|
||||
append({modelData: qsTr("Power"), comp: powerComponent})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SelectionTabs {
|
||||
id: selectionTabs
|
||||
Layout.fillWidth: true
|
||||
model: modeModel
|
||||
visible: modeModel.count > 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
GridLayout {
|
||||
id: basicItems
|
||||
Layout.fillWidth: !app.landscape
|
||||
Layout.fillHeight: app.landscape
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
columnSpacing: app.margins
|
||||
rowSpacing: app.margins
|
||||
columns: app.landscape ? 1 : 2
|
||||
visible: powerButton.visible || brightnessSlider.visible
|
||||
|
||||
ProgressButton {
|
||||
id: powerButton
|
||||
imageSource: root.powerState.value === true ? "../images/light-on.svg" : "../images/light-off.svg"
|
||||
mode: "normal"
|
||||
size: Style.bigIconSize
|
||||
longpressEnabled: false
|
||||
visible: root.brightnessState || root.ctState || root.colorState
|
||||
onClicked: {
|
||||
root.thing.executeAction("power", [{paramName: "power", value: !root.powerState.value}])
|
||||
}
|
||||
}
|
||||
|
||||
BrightnessSlider {
|
||||
id: brightnessSlider
|
||||
Layout.fillWidth: orientation == Qt.Horizontal
|
||||
Layout.fillHeight: orientation == Qt.Vertical
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
thing: root.thing
|
||||
orientation: basicItems.columns === 1 ? Qt.Vertical : Qt.Horizontal
|
||||
visible: root.brightnessState && (root.ctState || root.colorState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -31,61 +31,31 @@
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtGraphicalEffects 1.0
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "../utils"
|
||||
|
||||
ThingPageBase {
|
||||
id: root
|
||||
|
||||
readonly property State powerState: thing.stateByName("power")
|
||||
readonly property var powerActionType: thing.thingClass.actionTypes.findByName("power");
|
||||
|
||||
GridLayout {
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: root.thing
|
||||
stateName: "power"
|
||||
}
|
||||
|
||||
CircleBackground {
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins
|
||||
columns: app.landscape ? 2 : 1
|
||||
rowSpacing: app.margins
|
||||
columnSpacing: app.margins
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: Math.max(Style.iconSize * 6, parent.width / 5)
|
||||
Layout.preferredHeight: width
|
||||
Layout.topMargin: app.margins
|
||||
Layout.bottomMargin: app.landscape ? app.margins : 0
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.rowSpan: app.landscape ? 4 : 1
|
||||
Layout.fillHeight: true
|
||||
|
||||
AbstractButton {
|
||||
height: Math.min(parent.height, parent.width)
|
||||
width: height
|
||||
anchors.centerIn: parent
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.color: root.powerState.value === true ? Style.accentColor : Style.iconColor
|
||||
border.width: 4
|
||||
radius: width / 2
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
id: bulbIcon
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins * 1.5
|
||||
name: "../images/powersocket.svg"
|
||||
color: root.powerState.value === true ? Style.accentColor : Style.iconColor
|
||||
}
|
||||
onClicked: {
|
||||
var params = []
|
||||
var param = {}
|
||||
param["paramTypeId"] = root.powerActionType.paramTypes.get(0).id;
|
||||
param["value"] = !root.powerState.value;
|
||||
params.push(param)
|
||||
engine.thingManager.executeAction(root.thing.id, root.powerActionType.id, params);
|
||||
}
|
||||
}
|
||||
anchors.margins: Style.hugeMargins
|
||||
iconSource: "../images/powersocket.svg"
|
||||
onColor: app.interfaceToColor("powersocket")
|
||||
on: (actionQueue.pendingValue || powerState.value) === true
|
||||
onClicked: {
|
||||
PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackSelection)
|
||||
actionQueue.sendValue(!root.powerState.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ import QtGraphicalEffects 1.0
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "../customviews"
|
||||
import "../utils"
|
||||
|
||||
ThingPageBase {
|
||||
id: root
|
||||
@ -52,259 +53,391 @@ ThingPageBase {
|
||||
|
||||
|
||||
readonly property bool moving: movingState ? movingState.value === true : false
|
||||
readonly property int percentage: percentageState ? percentageState.value : 50
|
||||
readonly property int angle: angleState ? angleState.value : 0
|
||||
|
||||
onMovingChanged: if (!moving) angleMovable.visible = false
|
||||
// onMovingChanged: if (!moving) angleMovable.visible = false
|
||||
|
||||
GridLayout {
|
||||
anchors.fill: parent
|
||||
columns: root.isVenetian ?
|
||||
root.landscape ? 3 : 2
|
||||
: root.landscape ? 2 : 1
|
||||
// columns: root.isVenetian ?
|
||||
// root.landscape ? 3 : 2
|
||||
// : root.landscape ? 2 : 1
|
||||
columns: root.landscape ? 2 : 1
|
||||
|
||||
Item {
|
||||
id: window
|
||||
|
||||
Layout.preferredWidth: root.landscape ?
|
||||
Math.min(parent.width *.4, parent.height)
|
||||
: Math.min(Math.min(parent.width, 500), (parent.height - shutterControlsContainer.minimumHeight)) / (root.isVenetian ? 2 : 1)
|
||||
// Layout.preferredWidth: root.landscape ?
|
||||
// Math.min(parent.width - shutterControlsContainer.minimumWidth, parent.height) - app.margins
|
||||
// : Math.min(Math.min(parent.width, parent.height - shutterControlsContainer.minimumHeight), 500)
|
||||
Layout.preferredHeight: root.landscape ?
|
||||
width
|
||||
: width * (root.isVenetian ? 2 : 1)
|
||||
Layout.alignment: root.landscape ? Qt.AlignVCenter : Qt.AlignHCenter
|
||||
clip: true
|
||||
|
||||
ClosablesControlLarge {
|
||||
anchors { left: parent.left; top: parent.top; bottom: parent.bottom; }
|
||||
width: height
|
||||
thing: root.thing
|
||||
|
||||
ClosableArrowAnimation {
|
||||
id: arrowAnimation
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: isVenetian ? -width: 0
|
||||
|
||||
onStateChanged: {
|
||||
if (state != "") {
|
||||
animationTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: animationTimer
|
||||
running: false
|
||||
interval: 5000
|
||||
repeat: false
|
||||
onTriggered: parent.state = ""
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
id: angleControls
|
||||
Layout.preferredWidth: root.landscape ? window.width / 2 : window.width
|
||||
Layout.preferredHeight: window.height
|
||||
visible: root.isVenetian
|
||||
CircleBackground {
|
||||
id: background
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Style.hugeMargins
|
||||
|
||||
Item {
|
||||
id: blind
|
||||
anchors.fill: parent
|
||||
|
||||
Item {
|
||||
anchors { fill: parent; topMargin: parent.height * .09; bottomMargin: parent.height * 0.09; leftMargin: app.margins * 2; rightMargin: app.margins * 2 }
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
height: parent.height
|
||||
width: 2
|
||||
color: Style.accentColor
|
||||
visible: root.angleState
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: 10
|
||||
Item {
|
||||
width: parent.height * .1
|
||||
height: width
|
||||
y: parent.height / 10 * index
|
||||
Canvas {
|
||||
id: canvas
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: root.angleState ? -parent.width / 4 - Style.smallMargins: 0
|
||||
width: background.contentItem.width / (root.angleState ? 2 : 1)
|
||||
height: background.contentItem.height
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: width / 4
|
||||
rotation: root.angle
|
||||
color: "#808080"
|
||||
}
|
||||
}
|
||||
}
|
||||
property real progress: root.percentageState ?
|
||||
percentageDragArea.pressed ? percentageDragArea.draggedProgress : root.percentageState.value / 100
|
||||
: .5
|
||||
|
||||
Item {
|
||||
id: angleMovable
|
||||
anchors.fill: parent
|
||||
property int angle: 0
|
||||
visible: false
|
||||
anchors.verticalCenterOffset: -height * (1 - progress)
|
||||
|
||||
Repeater {
|
||||
model: 10
|
||||
Item {
|
||||
width: parent.height * .1
|
||||
height: width
|
||||
y: parent.height / 10 * index
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.reset();
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: width / 4
|
||||
rotation: angleMovable.angle
|
||||
color: Style.foregroundColor
|
||||
opacity: 0.1
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors { top: parent.top; bottom: parent.bottom; right: parent.right; rightMargin: app.margins / 2 }
|
||||
width: parent.width * .5
|
||||
|
||||
Rectangle {
|
||||
id: angleSlider
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(Style.foregroundColor.r, Style.foregroundColor.g, Style.foregroundColor.b, 0.1)
|
||||
visible: false
|
||||
ColorIcon {
|
||||
anchors { horizontalCenter: parent.horizontalCenter; top: parent.top; topMargin: app.margins }
|
||||
height: Style.iconSize
|
||||
width: Style.iconSize
|
||||
name: "../images/up.svg"
|
||||
}
|
||||
ColorIcon {
|
||||
anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom; bottomMargin: app.margins }
|
||||
height: Style.iconSize
|
||||
width: Style.iconSize
|
||||
name: "../images/down.svg"
|
||||
}
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 2
|
||||
color: angleMouseArea.containsMouse ? Style.accentColor : "transparent"
|
||||
y: angleMouseArea.mouseY
|
||||
onYChanged: sliderMask.update()
|
||||
}
|
||||
|
||||
}
|
||||
Rectangle {
|
||||
id: mask
|
||||
anchors.fill: parent
|
||||
radius: Style.cornerRadius
|
||||
color: "blue"
|
||||
visible: false
|
||||
}
|
||||
OpacityMask {
|
||||
id: sliderMask
|
||||
anchors.fill: parent
|
||||
source: angleSlider
|
||||
maskSource: mask
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: angleMouseArea
|
||||
anchors.fill: parent
|
||||
// angle : totalAngle = mouseY : height
|
||||
property int totalAngle: root.angleState ? root.angleStateType.maxValue - root.angleStateType.minValue : 0
|
||||
property int angle: root.angleState ? totalAngle * mouseY / height + root.angleStateType.minValue : 0
|
||||
hoverEnabled: true
|
||||
|
||||
property int startY: 0
|
||||
|
||||
onPressed: {
|
||||
startY = mouseY
|
||||
angleMovable.visible = true
|
||||
}
|
||||
onMouseYChanged: if (pressed) angleMovable.angle = angle
|
||||
|
||||
onReleased: {
|
||||
print("released at", angle)
|
||||
var targetAngle = 0
|
||||
if (Math.abs(mouseY - startY) < 5) {
|
||||
print("clicked")
|
||||
// clicked without drag
|
||||
if (mouseY < width) {
|
||||
print("top area")
|
||||
// clicked in top area
|
||||
if (root.angle > 5) {
|
||||
targetAngle = 0;
|
||||
} else {
|
||||
targetAngle = root.angleStateType.minValue
|
||||
}
|
||||
} else if (mouseY > height - width){
|
||||
print("bottom area")
|
||||
//clicked in bottom area
|
||||
if (root.angle < -5) {
|
||||
targetAngle = 0;
|
||||
} else {
|
||||
targetAngle = root.angleStateType.maxValue
|
||||
}
|
||||
} else {
|
||||
targetAngle = angle
|
||||
}
|
||||
|
||||
} else {
|
||||
targetAngle = angle
|
||||
}
|
||||
|
||||
angleMovable.angle = targetAngle
|
||||
|
||||
|
||||
var actionType = root.thing.thingClass.actionTypes.findByName("angle");
|
||||
var params = [];
|
||||
var percentageParam = {}
|
||||
percentageParam["paramTypeId"] = actionType.paramTypes.findByName("angle").id;
|
||||
percentageParam["value"] = targetAngle
|
||||
params.push(percentageParam);
|
||||
engine.thingManager.executeAction(root.thing.id, actionType.id, params);
|
||||
|
||||
}
|
||||
ctx.fillStyle = Style.tileForegroundColor
|
||||
var segments = 10;
|
||||
var segmentHeight = height / segments
|
||||
var barHeight = segmentHeight - Style.smallMargins
|
||||
for (var i = 0; i < segments; i++) {
|
||||
ctx.fillRect(0, i * segmentHeight + (segmentHeight - barHeight) / 2, width, barHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ActionQueue {
|
||||
id: percentageActionQueue
|
||||
thing: root.thing
|
||||
stateName: "percentage"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: percentageDragArea
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: root.angleState ? -parent.width / 4 - Style.smallMargins: 0
|
||||
width: background.contentItem.width / (root.angleState ? 2 : 1)
|
||||
height: background.contentItem.height
|
||||
property real draggedProgress: Math.max(0, Math.min(1, mouseY / height))
|
||||
onReleased: percentageActionQueue.sendValue(mouseY / height * 100)
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: angleCanvas
|
||||
anchors.centerIn: parent
|
||||
visible: root.angleState
|
||||
anchors.horizontalCenterOffset: parent.width / 4
|
||||
width: background.contentItem.width / (root.angleState ? 2 : 1)
|
||||
height: background.contentItem.height
|
||||
|
||||
property real angle: root.angleState ?
|
||||
angleDragArea.pressed ? angleDragArea.draggedAngle : root.angleState.value
|
||||
: 0
|
||||
onAngleChanged: requestPaint()
|
||||
|
||||
property real pendingAngle: angle
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.reset();
|
||||
|
||||
ctx.fillStyle = Style.tileForegroundColor
|
||||
|
||||
var segments = 10;
|
||||
var segmentHeight = height / segments
|
||||
var barHeight = Style.smallMargins
|
||||
var barWidth = width / 4
|
||||
ctx.beginPath();
|
||||
for (var i = 0; i < segments; i++) {
|
||||
ctx.save()
|
||||
ctx.translate(barWidth / 2 + Style.smallMargins, i * segmentHeight + (segmentHeight - barHeight) / 2)
|
||||
ctx.rotate(angleCanvas.angle * Math.PI / 180)
|
||||
ctx.fillRect(-barWidth / 2, -barHeight / 2, width / 4, barHeight)
|
||||
ctx.restore()
|
||||
}
|
||||
ctx.closePath()
|
||||
|
||||
|
||||
ctx.strokeStyle = Style.accentColor
|
||||
ctx.lineWidth = 2
|
||||
|
||||
ctx.save()
|
||||
ctx.beginPath();
|
||||
ctx.translate(barWidth / 2 + Style.smallMargins, (height - barHeight) / 2)
|
||||
ctx.rotate(angleCanvas.pendingAngle * Math.PI / 180)
|
||||
ctx.moveTo(-barWidth / 2, 0)
|
||||
ctx.lineTo(width, 0)
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
ctx.restore()
|
||||
|
||||
ctx.strokeStyle = Style.tileForegroundColor
|
||||
|
||||
ctx.save()
|
||||
ctx.beginPath();
|
||||
ctx.translate(barWidth / 2 + Style.smallMargins, (height - barHeight) / 2)
|
||||
ctx.rotate(angleCanvas.angle * Math.PI / 180)
|
||||
ctx.moveTo(-barWidth / 2, 0)
|
||||
ctx.lineTo(width, 0)
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ActionQueue {
|
||||
id: angleActionQueue
|
||||
thing: root.thing
|
||||
stateName: "angle"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: angleDragArea
|
||||
visible: root.angleState
|
||||
anchors.fill: angleCanvas
|
||||
property real draggedAngle: root.angleState ? Math.min(root.angleStateType.maxValue,
|
||||
Math.max(root.angleStateType.minValue,
|
||||
mouseY / height * (root.angleStateType.maxValue - root.angleStateType.minValue) + root.angleStateType.minValue))
|
||||
: 0
|
||||
onReleased: {
|
||||
print("sending angle", draggedAngle)
|
||||
angleCanvas.pendingAngle = draggedAngle
|
||||
angleActionQueue.sendValue(draggedAngle)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
OpacityMask {
|
||||
anchors.fill: parent
|
||||
source: ShaderEffectSource {
|
||||
sourceItem: blind
|
||||
sourceRect: background.contentItem.childrenRect
|
||||
hideSource: true
|
||||
}
|
||||
maskSource: background
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Item {
|
||||
// id: window
|
||||
|
||||
Item {
|
||||
id: shutterControlsContainer
|
||||
Layout.columnSpan: root.isVenetian && !root.landscape ? 2 : 1
|
||||
// Layout.preferredWidth: root.landscape ?
|
||||
// Math.min(parent.width *.4, parent.height)
|
||||
// : Math.min(Math.min(parent.width, 500), (parent.height - shutterControlsContainer.minimumHeight)) / (root.isVenetian ? 2 : 1)
|
||||
//// Layout.preferredWidth: root.landscape ?
|
||||
//// Math.min(parent.width - shutterControlsContainer.minimumWidth, parent.height) - app.margins
|
||||
//// : Math.min(Math.min(parent.width, parent.height - shutterControlsContainer.minimumHeight), 500)
|
||||
// Layout.preferredHeight: root.landscape ?
|
||||
// width
|
||||
// : width * (root.isVenetian ? 2 : 1)
|
||||
// Layout.alignment: root.landscape ? Qt.AlignVCenter : Qt.AlignHCenter
|
||||
// clip: true
|
||||
|
||||
// ClosablesControlLarge {
|
||||
// anchors { left: parent.left; top: parent.top; bottom: parent.bottom; }
|
||||
// width: height
|
||||
// thing: root.thing
|
||||
|
||||
// ClosableArrowAnimation {
|
||||
// id: arrowAnimation
|
||||
// anchors.centerIn: parent
|
||||
// anchors.horizontalCenterOffset: isVenetian ? -width: 0
|
||||
|
||||
// onStateChanged: {
|
||||
// if (state != "") {
|
||||
// animationTimer.start();
|
||||
// }
|
||||
// }
|
||||
|
||||
// Timer {
|
||||
// id: animationTimer
|
||||
// running: false
|
||||
// interval: 5000
|
||||
// repeat: false
|
||||
// onTriggered: parent.state = ""
|
||||
// }
|
||||
// }
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// Item {
|
||||
// id: angleControls
|
||||
// Layout.preferredWidth: root.landscape ? window.width / 2 : window.width
|
||||
// Layout.preferredHeight: window.height
|
||||
// visible: root.isVenetian
|
||||
|
||||
// Item {
|
||||
// anchors.fill: parent
|
||||
|
||||
// Item {
|
||||
// anchors { fill: parent; topMargin: parent.height * .09; bottomMargin: parent.height * 0.09; leftMargin: app.margins * 2; rightMargin: app.margins * 2 }
|
||||
|
||||
// Repeater {
|
||||
// model: 10
|
||||
// Item {
|
||||
// width: parent.height * .1
|
||||
// height: width
|
||||
// y: parent.height / 10 * index
|
||||
|
||||
// Rectangle {
|
||||
// anchors.centerIn: parent
|
||||
// width: parent.width
|
||||
// height: width / 4
|
||||
// rotation: root.angle
|
||||
// color: "#808080"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Item {
|
||||
// id: angleMovable
|
||||
// anchors.fill: parent
|
||||
// property int angle: 0
|
||||
// visible: false
|
||||
|
||||
// Repeater {
|
||||
// model: 10
|
||||
// Item {
|
||||
// width: parent.height * .1
|
||||
// height: width
|
||||
// y: parent.height / 10 * index
|
||||
|
||||
// Rectangle {
|
||||
// anchors.centerIn: parent
|
||||
// width: parent.width
|
||||
// height: width / 4
|
||||
// rotation: angleMovable.angle
|
||||
// color: Style.foregroundColor
|
||||
// opacity: 0.1
|
||||
// }
|
||||
// }
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
// Item {
|
||||
// anchors { top: parent.top; bottom: parent.bottom; right: parent.right; rightMargin: app.margins / 2 }
|
||||
// width: parent.width * .5
|
||||
|
||||
// Rectangle {
|
||||
// id: angleSlider
|
||||
// anchors.fill: parent
|
||||
// color: Qt.rgba(Style.foregroundColor.r, Style.foregroundColor.g, Style.foregroundColor.b, 0.1)
|
||||
// visible: false
|
||||
// ColorIcon {
|
||||
// anchors { horizontalCenter: parent.horizontalCenter; top: parent.top; topMargin: app.margins }
|
||||
// height: Style.iconSize
|
||||
// width: Style.iconSize
|
||||
// name: "../images/up.svg"
|
||||
// }
|
||||
// ColorIcon {
|
||||
// anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom; bottomMargin: app.margins }
|
||||
// height: Style.iconSize
|
||||
// width: Style.iconSize
|
||||
// name: "../images/down.svg"
|
||||
// }
|
||||
// Rectangle {
|
||||
// width: parent.width
|
||||
// height: 2
|
||||
// color: angleMouseArea.containsMouse ? Style.accentColor : "transparent"
|
||||
// y: angleMouseArea.mouseY
|
||||
// onYChanged: sliderMask.update()
|
||||
// }
|
||||
|
||||
// }
|
||||
// Rectangle {
|
||||
// id: mask
|
||||
// anchors.fill: parent
|
||||
// radius: Style.cornerRadius
|
||||
// color: "blue"
|
||||
// visible: false
|
||||
// }
|
||||
// OpacityMask {
|
||||
// id: sliderMask
|
||||
// anchors.fill: parent
|
||||
// source: angleSlider
|
||||
// maskSource: mask
|
||||
// }
|
||||
|
||||
// MouseArea {
|
||||
// id: angleMouseArea
|
||||
// anchors.fill: parent
|
||||
// // angle : totalAngle = mouseY : height
|
||||
// property int totalAngle: root.angleState ? root.angleStateType.maxValue - root.angleStateType.minValue : 0
|
||||
// property int angle: root.angleState ? totalAngle * mouseY / height + root.angleStateType.minValue : 0
|
||||
// hoverEnabled: true
|
||||
|
||||
// property int startY: 0
|
||||
|
||||
// onPressed: {
|
||||
// startY = mouseY
|
||||
// angleMovable.visible = true
|
||||
// }
|
||||
// onMouseYChanged: if (pressed) angleMovable.angle = angle
|
||||
|
||||
// onReleased: {
|
||||
// print("released at", angle)
|
||||
// var targetAngle = 0
|
||||
// if (Math.abs(mouseY - startY) < 5) {
|
||||
// print("clicked")
|
||||
// // clicked without drag
|
||||
// if (mouseY < width) {
|
||||
// print("top area")
|
||||
// // clicked in top area
|
||||
// if (root.angle > 5) {
|
||||
// targetAngle = 0;
|
||||
// } else {
|
||||
// targetAngle = root.angleStateType.minValue
|
||||
// }
|
||||
// } else if (mouseY > height - width){
|
||||
// print("bottom area")
|
||||
// //clicked in bottom area
|
||||
// if (root.angle < -5) {
|
||||
// targetAngle = 0;
|
||||
// } else {
|
||||
// targetAngle = root.angleStateType.maxValue
|
||||
// }
|
||||
// } else {
|
||||
// targetAngle = angle
|
||||
// }
|
||||
|
||||
// } else {
|
||||
// targetAngle = angle
|
||||
// }
|
||||
|
||||
// angleMovable.angle = targetAngle
|
||||
|
||||
|
||||
// var actionType = root.thing.thingClass.actionTypes.findByName("angle");
|
||||
// var params = [];
|
||||
// var percentageParam = {}
|
||||
// percentageParam["paramTypeId"] = actionType.paramTypes.findByName("angle").id;
|
||||
// percentageParam["value"] = targetAngle
|
||||
// params.push(percentageParam);
|
||||
// engine.thingManager.executeAction(root.thing.id, actionType.id, params);
|
||||
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
ShutterControls {
|
||||
id: shutterControls
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: 500
|
||||
// Layout.preferredWidth: root.landscape ? Math.max(parent.width / 2, shutterControls.implicitWidth) : parent.width
|
||||
Layout.margins: app.margins * 2
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillHeight: true
|
||||
property int minimumHeight: Style.iconSize * 2.5
|
||||
property int minimumWidth: Style.iconSize * 2.5 * 3
|
||||
|
||||
ShutterControls {
|
||||
id: shutterControls
|
||||
thing: root.thing
|
||||
width: parent.width
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
size: Style.bigIconSize
|
||||
backgroundEnabled: true
|
||||
spacing: (width - Style.iconSize*2*children.length) / (children.length - 1)
|
||||
|
||||
property int count: children.length
|
||||
|
||||
onActivated: {
|
||||
if (button == "open") {
|
||||
arrowAnimation.state = "opening"
|
||||
} else if (button == "close") {
|
||||
arrowAnimation.state = "closing"
|
||||
} else {
|
||||
arrowAnimation.state = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
size: Style.bigIconSize
|
||||
backgroundEnabled: true
|
||||
thing: root.thing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@ import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "../customviews"
|
||||
import "../utils"
|
||||
|
||||
ThingPageBase {
|
||||
id: root
|
||||
@ -58,48 +59,32 @@ ThingPageBase {
|
||||
anchors.margins: app.margins
|
||||
columns: app.landscape ? 2 : 1
|
||||
|
||||
ThermostatController {
|
||||
CircleBackground {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
thing: root.thing
|
||||
Layout.margins: Style.bigMargins
|
||||
ThermostatController {
|
||||
anchors.centerIn: parent
|
||||
height: Math.min(400, Math.min(parent.height, parent.width))
|
||||
width: height
|
||||
thing: root.thing
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: app.landscape ? parent.width / 2 : parent.width
|
||||
Layout.preferredHeight: 50
|
||||
visible: root.boostStateType
|
||||
border.color: boostMouseArea.pressed || root.boostStateType && root.boostState.value === true ? Style.accentColor : Style.foregroundColor
|
||||
border.width: 1
|
||||
radius: height / 2
|
||||
color: root.boostStateType && root.boostState.value === true ? Style.accentColor : "transparent"
|
||||
ProgressButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.margins: Style.bigMargins
|
||||
size: Style.largeIconSize
|
||||
imageSource: "thermostat/heating"
|
||||
backgroundColor: app.interfaceToColor("heating")
|
||||
visible: root.boostState
|
||||
busy: actionQueue.pendingValue ? actionQueue.pendingValue : (root.boostState && root.boostState.value === true)
|
||||
onClicked: actionQueue.sendValue(!root.boostState.value)
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: app.margins / 2
|
||||
ColorIcon {
|
||||
height: Style.iconSize
|
||||
width: Style.iconSize
|
||||
name: "../images/sensors/temperature.svg"
|
||||
color: root.boostStateType && root.boostState.value === true ? "red" : Style.iconColor
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Boost")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
id: boostMouseArea
|
||||
anchors.fill: parent
|
||||
onPressedChanged: PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackImpact)
|
||||
onClicked: {
|
||||
var params = []
|
||||
var param = {}
|
||||
param["paramTypeId"] = root.boostStateType.id
|
||||
param["value"] = !root.boostState.value
|
||||
params.push(param)
|
||||
engine.thingManager.executeAction(root.thing.id, root.boostStateType.id, params);
|
||||
}
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: root.thing
|
||||
stateName: "boost"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,73 +31,55 @@
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "../utils"
|
||||
|
||||
ThingPageBase {
|
||||
id: root
|
||||
|
||||
readonly property State powerState: thing.stateByName("power")
|
||||
readonly property ActionType powerActionType: thing.thingClass.actionTypes.findByName("power");
|
||||
readonly property State flowRateState: thing.stateByName("flowRate")
|
||||
readonly property StateType flowRateStateType: thing.thingClass.stateTypes.findByName("flowRate")
|
||||
|
||||
GridLayout {
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: root.thing
|
||||
stateName: "power"
|
||||
}
|
||||
|
||||
CircleBackground {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins
|
||||
columns: app.landscape ? 2 : 1
|
||||
rowSpacing: app.margins
|
||||
columnSpacing: app.margins
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: Math.max(Style.iconSize * 6, parent.width / 5)
|
||||
Layout.preferredHeight: width
|
||||
Layout.topMargin: app.margins
|
||||
Layout.bottomMargin: app.landscape ? app.margins : 0
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.rowSpan: app.landscape ? 4 : 1
|
||||
Layout.fillHeight: true
|
||||
|
||||
AbstractButton {
|
||||
height: Math.min(parent.height, parent.width)
|
||||
width: height
|
||||
anchors.centerIn: parent
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.color: root.powerState.value === true ? Style.accentColor : Style.iconColor
|
||||
border.width: 4
|
||||
radius: width / 2
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
id: bulbIcon
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins * 1.5
|
||||
name: "../images/ventilation.svg"
|
||||
color: root.powerState.value === true ? Style.accentColor : Style.iconColor
|
||||
|
||||
PropertyAnimation on rotation {
|
||||
running: root.powerState.value === true
|
||||
duration: 2000
|
||||
from: 0
|
||||
to: 360
|
||||
loops: Animation.Infinite
|
||||
onDurationChanged: {
|
||||
running = false;
|
||||
running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
var params = []
|
||||
var param = {}
|
||||
param["paramTypeId"] = root.powerActionType.paramTypes.get(0).id;
|
||||
param["value"] = !root.powerState.value;
|
||||
params.push(param)
|
||||
engine.thingManager.executeAction(root.thing.id, root.powerActionType.id, params);
|
||||
}
|
||||
anchors.margins: Style.hugeMargins
|
||||
iconSource: "ventilation"
|
||||
onColor: app.interfaceToColor("ventilation")
|
||||
on: (actionQueue.pendingValue || powerState.value) === true
|
||||
onClicked: {
|
||||
PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackSelection)
|
||||
actionQueue.sendValue(!root.powerState.value)
|
||||
}
|
||||
PropertyAnimation on rotation {
|
||||
running: root.powerState.value === true
|
||||
duration: 2000
|
||||
from: 0
|
||||
to: 360
|
||||
loops: Animation.Infinite
|
||||
onDurationChanged: {
|
||||
running = false;
|
||||
running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dial {
|
||||
anchors.centerIn: parent
|
||||
height: background.contentItem.height
|
||||
width: background.contentItem.width
|
||||
visible: root.flowRateState
|
||||
|
||||
thing: root.thing
|
||||
stateName: "flowRate"
|
||||
color: app.interfaceToColor("ventilation")
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,8 @@ Item {
|
||||
page = "WeatherDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("heating") >= 0) {
|
||||
page = "HeatingDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("cooling") >= 0) {
|
||||
page = "CoolingThingPage.qml";
|
||||
} else if (interfaceList.indexOf("thermostat") >= 0) {
|
||||
page = "ThermostatDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("sensor") >= 0) {
|
||||
@ -38,7 +40,7 @@ Item {
|
||||
} else if (interfaceList.indexOf("garagedoor") >= 0 ) {
|
||||
page = "GarageThingPage.qml";
|
||||
} else if (interfaceList.indexOf("light") >= 0) {
|
||||
page = "LightDevicePage.qml";
|
||||
page = "LightThingPage.qml";
|
||||
} else if (interfaceList.indexOf("shutter") >= 0 || interfaceList.indexOf("blind") >= 0) {
|
||||
page = "ShutterDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("awning") >= 0) {
|
||||
@ -47,6 +49,8 @@ Item {
|
||||
page = "NotificationsDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("fingerprintreader") >= 0) {
|
||||
page = "FingerprintReaderDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("evcharger") >= 0) {
|
||||
page = "EvChargerThingPage.qml"
|
||||
} else if (interfaceList.indexOf("smartmeter") >= 0) {
|
||||
page = "SmartMeterDevicePage.qml"
|
||||
} else if (interfaceList.indexOf("powersocket") >= 0) {
|
||||
@ -120,7 +124,7 @@ Item {
|
||||
"water": "/ui/images/sensors/water.svg",
|
||||
"wind": "/ui/images/sensors/windspeed.svg",
|
||||
"cloud": "/ui/images/weathericons/weather-clouds.svg",
|
||||
"send": "/ui/images/send.svg"
|
||||
"send": "/ui/images/send.svg",
|
||||
}
|
||||
function namedIcon(name) {
|
||||
if (!namedIcons.hasOwnProperty(name)) {
|
||||
|
||||
Reference in New Issue
Block a user