More work on all sorts of things pages
parent
554575698b
commit
1661dd4eba
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ Item {
|
|||
|
||||
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 {
|
||||
|
|
@ -94,8 +94,8 @@ Item {
|
|||
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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ Item {
|
|||
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{
|
||||
|
|
@ -176,7 +176,7 @@ Item {
|
|||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
anchors.fill: gradient
|
||||
onPositionChanged: {
|
||||
|
||||
var angle = calculateAngle(mouseX, mouseY)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ Item {
|
|||
|
||||
Rectangle {
|
||||
id: background
|
||||
width: Math.min(parent.width, parent.height)
|
||||
width: Math.min(400, Math.min(parent.width, parent.height))
|
||||
anchors.centerIn: parent
|
||||
height: width
|
||||
radius: width / 2
|
||||
|
|
@ -35,14 +35,6 @@ Item {
|
|||
GradientStop { position: 0.5; color: "#ffffea" }
|
||||
GradientStop { position: 1.0; color: "#ffd649" }
|
||||
}
|
||||
}
|
||||
|
||||
Desaturate {
|
||||
anchors.fill: background
|
||||
source: background
|
||||
desaturation: root.powerState.value === true ? 0 : 1
|
||||
Behavior on desaturation { NumberAnimation { duration: Style.animationDuration } }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: dragHandle
|
||||
|
|
@ -53,13 +45,21 @@ Item {
|
|||
color: Style.backgroundColor
|
||||
border.color: Style.foregroundColor
|
||||
border.width: 2
|
||||
x: (parent.width - width) / 2
|
||||
y: parent.height * valuePercentage - (height / 2)
|
||||
|
||||
x: (background.width - width) / 2
|
||||
y: (background.height - height) * valuePercentage
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -68,7 +68,7 @@ Item {
|
|||
// 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 = 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))
|
||||
|
|
|
|||
|
|
@ -32,310 +32,164 @@ 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;
|
||||
}
|
||||
ActionQueue {
|
||||
id: powerActionQueue
|
||||
thing: root.thing
|
||||
stateName: "power"
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Item {
|
||||
id: innerDial
|
||||
|
||||
height: Math.min(parent.height, parent.width) * .9
|
||||
width: height
|
||||
anchors.centerIn: parent
|
||||
rotation: dial.startAngle
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: rotationButton
|
||||
radius: height / 2
|
||||
border.color: Style.foregroundColor
|
||||
border.width: 2
|
||||
color: "transparent"
|
||||
opacity: rotateMouseArea.pressed && !rotateMouseArea.grabbed ? .7 : 1
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
Canvas {
|
||||
id: canvas
|
||||
anchors.centerIn: root
|
||||
width: Math.min(root.width, root.height)
|
||||
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 } }
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
Label {
|
||||
anchors { left: innerDial.left; bottom: innerDial.bottom; bottomMargin: innerDial.height * .1 }
|
||||
text: "MIN"
|
||||
font.pixelSize: innerDial.height * .06
|
||||
visible: dial.stateType !== null
|
||||
}
|
||||
|
||||
Label {
|
||||
anchors { right: innerDial.right; bottom: innerDial.bottom; bottomMargin: innerDial.height * .1 }
|
||||
text: "MAX"
|
||||
font.pixelSize: innerDial.height * .06
|
||||
visible: dial.stateType !== null
|
||||
}
|
||||
|
||||
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 } }
|
||||
}
|
||||
|
||||
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;
|
||||
anchors.fill: canvas
|
||||
|
||||
property bool dragging: false
|
||||
property double lastAngle
|
||||
property double angleDiff: 0
|
||||
|
||||
onPressed: {
|
||||
angleDiff = 0
|
||||
lastAngle = calculateAngle(mouseX, mouseY)
|
||||
}
|
||||
|
||||
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()) {
|
||||
if (!dragging && root.powerState) {
|
||||
PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackSelection)
|
||||
powerActionQueue.sendValue(!root.powerState.value)
|
||||
}
|
||||
lastVibration = new Date()
|
||||
dragging = false
|
||||
}
|
||||
|
||||
d.enqueueSetValue(newValue);
|
||||
onPositionChanged: {
|
||||
var angle = calculateAngle(mouseX, mouseY)
|
||||
var tmpDiff = angle - lastAngle
|
||||
if (tmpDiff > 300) {
|
||||
tmpDiff -= 360
|
||||
}
|
||||
if (tmpDiff < -300) {
|
||||
tmpDiff += 360
|
||||
}
|
||||
|
||||
lastAngle = angle;
|
||||
|
||||
angleDiff += tmpDiff
|
||||
if (Math.abs(angleDiff) > 1) {
|
||||
dragging = true
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
function calculateAngle(mouseX, mouseY) {
|
||||
// transform coords to center of dial
|
||||
mouseX -= innerDial.width / 2
|
||||
mouseY -= innerDial.height / 2
|
||||
mouseX -= canvas.width / 2
|
||||
mouseY -= canvas.height / 2
|
||||
|
||||
var rad = Math.atan(mouseY / mouseX);
|
||||
var angle = rad * 180 / Math.PI
|
||||
|
|
@ -349,4 +203,3 @@ ColumnLayout {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,6 +162,10 @@ Item {
|
|||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// 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
|
||||
|
|
@ -167,6 +181,30 @@ Item {
|
|||
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(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,17 +48,27 @@ RowLayout {
|
|||
|
||||
signal activated(string button);
|
||||
|
||||
RowLayout {
|
||||
anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter }
|
||||
spacing: 0
|
||||
|
||||
Item { Layout.fillWidth: true; Layout.fillHeight: true }
|
||||
|
||||
ProgressButton {
|
||||
longpressEnabled: false
|
||||
imageSource: root.invert ? "../images/down.svg" : "../images/up.svg"
|
||||
mode: root.backgroundEnabled ? "normal" : "transparent"
|
||||
backgroundColor: root.backgroundEnabled ? Style.green : "transparent"
|
||||
size: root.size
|
||||
color: root.openState && root.openState.value === "opening" ? Material.accent : Style.iconColor
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,13 +76,14 @@ RowLayout {
|
|||
|
||||
ProgressButton {
|
||||
visible: root.canStop
|
||||
longpressEnabled: false
|
||||
mode: root.backgroundEnabled ? "normal" : "transparent"
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,15 +91,22 @@ RowLayout {
|
|||
|
||||
ProgressButton {
|
||||
imageSource: root.invert ? "../images/up.svg" : "../images/down.svg"
|
||||
longpressEnabled: false
|
||||
mode: root.backgroundEnabled ? "normal" : "transparent"
|
||||
backgroundColor: root.backgroundEnabled ? Style.red : "transparent"
|
||||
size: root.size
|
||||
color: root.openState && root.openState.value === "closing" ? Material.accent : Style.iconColor
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,66 +43,49 @@ 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"
|
||||
width: Math.min(500, Math.min(parent.width, parent.height) - Style.hugeMargins * 2)
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: Style.tileBackgroundColor
|
||||
}
|
||||
if (root.intermediatePositionStateType) {
|
||||
return root.stateState.value === "closed" ? "100"
|
||||
: root.intermediatePositionState.value === false ? "000" : "050"
|
||||
}
|
||||
return "100"
|
||||
}
|
||||
name: "../images/garage/garage-" + currentImage + ".svg"
|
||||
|
||||
|
||||
Item {
|
||||
id: arrows
|
||||
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"
|
||||
|
||||
// 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
|
||||
height: parent.height + Style.margins
|
||||
anchors.verticalCenterOffset: root.percentageState
|
||||
? -height * (1 - (root.percentageState.value / 100))
|
||||
: -height / 2
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.reset();
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -109,19 +93,27 @@ 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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
ColorIcon {
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: shutterControlsContainer
|
||||
Layout.fillWidth: true
|
||||
CircleBackground {
|
||||
id: background
|
||||
Layout.fillHeight: true
|
||||
Layout.margins: app.margins * 2
|
||||
property int minimumWidth: Style.iconSize * 2.7 * 3
|
||||
property int minimumHeight: Style.iconSize * 4.5
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
spacing: app.margins
|
||||
|
||||
Slider {
|
||||
id: percentageSlider
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 100
|
||||
stepSize: 1
|
||||
visible: isExtended
|
||||
Layout.margins: Style.hugeMargins
|
||||
onColor: Style.yellow
|
||||
on: true
|
||||
iconSource: "weathericons/weather-clear-day"
|
||||
|
||||
Binding {
|
||||
target: percentageSlider
|
||||
property: "value"
|
||||
value: root.percentageState.value
|
||||
when: !percentageSlider.pressed
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: root.thing
|
||||
stateName: "percentage"
|
||||
}
|
||||
|
||||
onPressedChanged: {
|
||||
if (pressed) {
|
||||
return
|
||||
}
|
||||
print("should move", value)
|
||||
Item {
|
||||
id: awning
|
||||
anchors.fill: parent
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
width: background.contentItem.width
|
||||
height: background.contentItem.height
|
||||
property real draggedProgress: mouseY / height
|
||||
onMouseYChanged: print("mouseY", mouseY, draggedProgress)
|
||||
onReleased: {
|
||||
actionQueue.sendValue(draggedProgress * 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ShutterControls {
|
||||
id: shutterControls
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: implicitWidth
|
||||
Layout.preferredHeight: implicitHeight
|
||||
|
||||
thing: root.thing
|
||||
size: Style.bigIconSize
|
||||
backgroundEnabled: true
|
||||
invert: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ ThingPageBase {
|
|||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.margins
|
||||
spacing: Style.hugeMargins
|
||||
|
||||
StackLayout {
|
||||
Layout.fillWidth: true
|
||||
|
|
@ -162,7 +162,7 @@ ThingPageBase {
|
|||
Rectangle {
|
||||
id: brightnessCircle
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(parent.width, parent.height)
|
||||
width: Math.min(400, Math.min(parent.width, parent.height))
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: Style.tileBackgroundColor
|
||||
|
|
@ -223,54 +223,24 @@ ThingPageBase {
|
|||
property Thing thing: root.thing
|
||||
readonly property State powerState: thing ? thing.stateByName("power") : null
|
||||
|
||||
property color borderColor: "#ffd649"
|
||||
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: powerController.thing
|
||||
stateType: thing.thingClass.stateTypes.findByName("power")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(parent.width, parent.height)
|
||||
height: width
|
||||
color: Style.tileBackgroundColor
|
||||
radius: width / 2
|
||||
|
||||
ColorIcon {
|
||||
anchors.centerIn: parent
|
||||
size: Style.hugeIconSize
|
||||
name: (actionQueue.pendingValue || powerState.value) === true ? "light-on" : "light-off"
|
||||
// color: (actionQueue.pendingValue || powerState.value) === true ? Style.accentColor : Style.iconColor
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RadialGradient {
|
||||
id: gradient
|
||||
anchors.fill: background
|
||||
visible: false
|
||||
gradient: Gradient{
|
||||
GradientStop { position: .45; color: "transparent" }
|
||||
GradientStop { position: .5; color: Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 1) }
|
||||
}
|
||||
}
|
||||
OpacityMask {
|
||||
opacity: (actionQueue.pendingValue || powerState.value) === true ? 1 : 0
|
||||
anchors.fill: gradient
|
||||
source: gradient
|
||||
maskSource: background
|
||||
Behavior on opacity { NumberAnimation { duration: Style.animationDuration } }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
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
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: root.thing
|
||||
stateName: "power"
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
id: bulbIcon
|
||||
CircleBackground {
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins * 1.5
|
||||
name: "../images/powersocket.svg"
|
||||
color: root.powerState.value === true ? Style.accentColor : Style.iconColor
|
||||
}
|
||||
anchors.margins: Style.hugeMargins
|
||||
iconSource: "../images/powersocket.svg"
|
||||
onColor: app.interfaceToColor("powersocket")
|
||||
on: (actionQueue.pendingValue || powerState.value) === 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);
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
CircleBackground {
|
||||
id: background
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Style.hugeMargins
|
||||
|
||||
Item {
|
||||
id: window
|
||||
id: blind
|
||||
anchors.fill: parent
|
||||
|
||||
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
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
height: parent.height
|
||||
width: 2
|
||||
color: Style.accentColor
|
||||
visible: root.angleState
|
||||
}
|
||||
|
||||
ClosablesControlLarge {
|
||||
anchors { left: parent.left; top: parent.top; bottom: parent.bottom; }
|
||||
width: height
|
||||
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
|
||||
|
||||
property real progress: root.percentageState ?
|
||||
percentageDragArea.pressed ? percentageDragArea.draggedProgress : root.percentageState.value / 100
|
||||
: .5
|
||||
|
||||
anchors.verticalCenterOffset: -height * (1 - progress)
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.reset();
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
stateName: "percentage"
|
||||
}
|
||||
|
||||
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
|
||||
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)
|
||||
}
|
||||
onMouseYChanged: if (pressed) angleMovable.angle = angle
|
||||
|
||||
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("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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
// 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);
|
||||
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
Item {
|
||||
id: shutterControlsContainer
|
||||
Layout.columnSpan: root.isVenetian && !root.landscape ? 2 : 1
|
||||
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
|
||||
Layout.fillWidth: true
|
||||
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 = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
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"
|
||||
|
||||
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);
|
||||
}
|
||||
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)
|
||||
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: root.thing
|
||||
stateName: "boost"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,52 +31,34 @@
|
|||
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 {
|
||||
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
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: root.thing
|
||||
stateName: "power"
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
id: bulbIcon
|
||||
CircleBackground {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins * 1.5
|
||||
name: "../images/ventilation.svg"
|
||||
color: root.powerState.value === true ? Style.accentColor : Style.iconColor
|
||||
|
||||
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
|
||||
|
|
@ -89,15 +71,15 @@ ThingPageBase {
|
|||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
@ -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)) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue