diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 520a053f..2c6da110 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -21,7 +21,7 @@ ui/connection/wifisetup/BoxInfoPage.qml ui/components/NymeaHeader.qml ui/components/HeaderButton.qml - ui/components/ColorPicker.qml + ui/components/ColorPickerPre510.qml ui/components/ColorIcon.qml ui/components/ThinDivider.qml ui/components/ThrottledSlider.qml @@ -232,5 +232,8 @@ ui/components/NymeaItemDelegate.qml ui/components/NymeaSwipeDelegate.qml ui/customviews/GarageController.qml + ui/components/ColorPicker.qml + ui/utils/ActionQueue.qml + ui/components/ColorTemperaturePicker.qml diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index f016ac9f..f367f90a 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -55,6 +55,9 @@ ApplicationWindow { property int margins: 16 property int bigMargins: 20 + + property int radius: 6 + property int extraSmallFont: 10 property int smallFont: 13 property int mediumFont: 16 diff --git a/nymea-app/ui/components/BigTile.qml b/nymea-app/ui/components/BigTile.qml index b66c13d2..4dd81bac 100644 --- a/nymea-app/ui/components/BigTile.qml +++ b/nymea-app/ui/components/BigTile.qml @@ -19,6 +19,37 @@ Item { property alias bottomPadding: content.bottomPadding signal clicked(); + signal pressAndHold(); + + function wobble() { + wobbleAnimation.start(); + } + + transform: Translate { id: wobbleTransform } + + SequentialAnimation { + id: wobbleAnimation + + PropertyAnimation { + target: wobbleTransform + property: "x" + from: 0 + to: 10 + duration: 50 + easing.type: Easing.OutCirc + } + PropertyAnimation { + target: wobbleTransform + property: "x" + from: 10 + to: 0 + duration: 400 + easing.type: Easing.OutElastic + easing.amplitude: 2 + easing.period: 0.4 + } + } + Rectangle { id: background @@ -64,6 +95,7 @@ Item { Layout.fillWidth: true height: contentItem.implicitHeight onClicked: root.clicked() + onPressAndHold: root.pressAndHold() } } } diff --git a/nymea-app/ui/components/BrightnessSlider.qml b/nymea-app/ui/components/BrightnessSlider.qml index c8587ab8..cfc4f009 100644 --- a/nymea-app/ui/components/BrightnessSlider.qml +++ b/nymea-app/ui/components/BrightnessSlider.qml @@ -28,89 +28,81 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -import QtQuick 2.3 +import QtQuick 2.9 +import QtGraphicalEffects 1.0 +import Nymea 1.0 +import "../utils" Item { id: root + implicitWidth: orientation == Qt.Horizontal ? 300 : app.hugeIconSize + implicitHeight: orientation == Qt.Horizontal ? app.hugeIconSize : 300 - property int brightness: min - property int min: 0 - property int max: 100 - property Component touchDelegate: Rectangle { height: root.height; width: 5; color: app.foregroundColor } + property Thing thing: null - property bool active: true - property bool pressed: mouseArea.pressed + readonly property StateType colorTemperatureStateType: root.thing.thingClass.stateTypes.findByName("brightness") - signal moved(real brightness); + property int value: thing.stateByName("brightness").value + + property int orientation: Qt.Horizontal + + ActionQueue { + id: actionQueue + thing: root.thing + stateType: root.colorTemperatureStateType + } Rectangle { - height: parent.width - width: parent.height - anchors.centerIn: parent - rotation: -90 - border.width: 1 - border.color: app.foregroundColor + id: clipRect + anchors.fill: parent + radius: app.radius + color: Qt.tint(app.backgroundColor, Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, 0.1)) + } + LinearGradient { + anchors.fill: parent + anchors.rightMargin: root.orientation == Qt.Horizontal ? + parent.width - (dragHandle.x + dragHandle.width / 2) + : 0 + anchors.topMargin: root.orientation == Qt.Vertical ? + dragHandle.y + dragHandle.height / 2 + : 0 + 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 gradient: Gradient { - GradientStop { position: 0.0; color: Qt.rgba(0, 0, 0, .5) } - GradientStop { position: 1.0; color: Qt.rgba(1, 1, 1, .5) } + GradientStop { position: 0.0; color: "transparent" } + GradientStop { position: 1.0; color: "#55ffffff" } } } - Loader { - id: touchDelegateLoader - // 0 : width = min : max - // x = (width * (brightness - min) / (max-min)) - property int position: (root.width * (root.brightness - root.min) / (root.max - root.min)); - x: item ? Math.max(0, Math.min(position - width * .5, parent.width - item.width)) : 0 - sourceComponent: root.touchDelegate - visible: !mouseArea.pressed && root.active - Behavior on x { - enabled: !mouseArea.pressed - NumberAnimation {} - } + Rectangle { + id: dragHandle + x: root.orientation === Qt.Horizontal ? + (actionQueue.pendingValue || root.value) * (root.width - dragHandle.width) / 100 + : 0 + 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 + color: app.foregroundColor } MouseArea { - id: mouseArea anchors.fill: parent - preventStealing: true - - drag.minimumX: 0 - drag.maximumX: width - dndItem.width - drag.minimumY: 0 - drag.maximumY: height - dndItem.height - - property var lastSentTime: new Date() - - onPressed: { - dndItem.x = Math.min(width - dndItem.width, Math.max(0, mouseX - dndItem.width / 2)) - dndItem.y = 0; - mouseArea.drag.target = dndItem; - } - onPositionChanged: { - root.brightness = Math.min(root.max, Math.max(root.min, (mouseX * (root.max - root.min) / width) + root.min)) - - var currentTime = new Date(); - if (pressed && currentTime - lastSentTime > 200) { - root.moved(root.brightness) - lastSentTime = currentTime + 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)) } + actionQueue.sendValue(ct); } - - onReleased: { - root.brightness = Math.min(root.max, Math.max(root.min, (mouseX * (root.max - root.min) / width) + root.min)) - root.moved(root.brightness) - mouseArea.drag.target = undefined; - } - } - - Loader { - id: dndItem - sourceComponent: root.touchDelegate - visible: mouseArea.pressed && root.active - } - } + diff --git a/nymea-app/ui/components/ColorPicker.qml b/nymea-app/ui/components/ColorPicker.qml index 0010dcb9..ebd4bc07 100644 --- a/nymea-app/ui/components/ColorPicker.qml +++ b/nymea-app/ui/components/ColorPicker.qml @@ -1,249 +1,126 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* 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 . -* -* 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 2.9 +import QtGraphicalEffects 1.0 +import Nymea 1.0 +import "../utils" Item { id: root - property color color: actionState - property bool pressed: mouseArea.pressed - property bool hovered: mouseArea.containsMouse + property Thing thing: null - property bool active: true - - property Component touchDelegate: Rectangle { - height: 15 - width: height - radius: height / 2 - color: app.accentColor - - - Rectangle { - color: root.hovered || root.pressed ? "#11000000" : "transparent" - anchors.centerIn: parent - height: 30 - width: height - radius: width / 2 - Behavior on color { ColorAnimation { duration: 200 } } - } + ActionQueue { + id: actionQueue + thing: root.thing + stateType: thing.thingClass.stateTypes.findByName("color") } - function calculateXy(color) { - if (!color.hasOwnProperty("r")) { - // Qt 4.8's color doesn't have r,g,b properties - var colorString = color.toString(); - color = new Object; - color.r = 1.0 * parseInt("0x" + colorString.substring(1,3), 16) / 256; - color.g = 1.0 * parseInt("0x" + colorString.substring(3,5), 16) / 256; - color.b = 1.0 * parseInt("0x" + colorString.substring(5,7), 16) / 256; - } - - var point = new Object; - var brightness = Math.min(color.r, color.g, color.b); - point.y = Math.round(root.height * brightness); - var newColor = new Object; - newColor.r = color.r - brightness; - newColor.g = color.g - brightness; - newColor.b = color.b - brightness; - - var sectionWidth = root.width / 6; - - var sections = [true, true, true]; - if (newColor.r > 0) { - sections[1] = false; - } - if (newColor.g > 0) { - sections[2] = false; - } - if (newColor.b > 0) { - sections[0] = false; - } - if (sections[0]) { - if (newColor.r > newColor.g) { - point.x = sectionWidth * newColor.g; - } else if (newColor.r == newColor.g) { - point.x = sectionWidth - } else { - point.x = sectionWidth * 2 - newColor.r * sectionWidth; - } - } else if (sections[1]) { - if (newColor.g > newColor.b) { - point.x = sectionWidth * 2 + sectionWidth * newColor.b; - } else if (newColor.g == newColor.b) { - point.x = sectionWidth * 3; - } else { - point.x = sectionWidth * 4 - newColor.g * sectionWidth - } - } else { - if (newColor.b > newColor.r) { - point.x = sectionWidth * 4 + sectionWidth * newColor.r - } else if (newColor.b == newColor.r) { - point.x = sectionWidth * 5; - } else { - point.x = sectionWidth * 6 - newColor.b * sectionWidth; - } - } - return point; - } - - function calculateColor(x, y) { - x = Math.min(Math.max(0, x), width - 1); - y = Math.min(Math.max(0, y), height - 1); - var color = root.color; - var sectionWidth = root.width / 6; - var section = Math.floor(x / sectionWidth) - var sectionX = Math.floor(x % (sectionWidth - 0.00001)) // the - 0.00001 is to prevent a mismatch when rounding - // sectionVal : 1.0 = sectionX : sectionWidth - var sectionVal = (1.0 * sectionX / sectionWidth) - - // brightnessVal : 1.0 = mouseY : height - var brightness = 1.0 * y / height - - switch (section) { - case 0: - // GradientStop { position: 0.0; color: "#ff0000" } - color = Qt.rgba(1, sectionVal + brightness, brightness, 1) - break; - case 1: - // GradientStop { position: 0.17; color: "#ffff00" } - color = Qt.rgba(1 - sectionVal + brightness, 1, brightness, 1) - break; - case 2: - // GradientStop { position: 0.33; color: "#00ff00" } - color = Qt.rgba(brightness, 1, sectionVal + brightness, 1) - break; - case 3: - // GradientStop { position: 0.5; color: "#00ffff" } - color = Qt.rgba(brightness, 1 - sectionVal + brightness, 1, 1) - break; - case 4: - // GradientStop { position: 0.66; color: "#0000ff" } - color = Qt.rgba(sectionVal + brightness, brightness, 1, 1) - break; - case 5: - // GradientStop { position: 0.83; color: "#ff00ff" } - color = Qt.rgba(1, brightness, 1 - sectionVal + brightness, 1) - break; - } - // print("got color", color.r, color.g, color.b) - return color - } - - Rectangle { - height: parent.width - width: parent.height + ConicalGradient { + id: gradient anchors.centerIn: parent - rotation: -90 - - gradient: Gradient { - GradientStop { position: 0.0; color: "#ff0000" } - GradientStop { position: 0.17; color: "#ffff00" } - GradientStop { position: 0.33; color: "#00ff00" } - GradientStop { position: 0.5; color: "#00ffff" } - GradientStop { position: 0.66; color: "#0000ff" } - GradientStop { position: 0.83; color: "#ff00ff" } - GradientStop { position: 1.0; color: "#ff0000" } + width: Math.min(parent.width, parent.height) + height: width + visible: false + gradient: Gradient{ + id: g + GradientStop { position: 0.000; color: Qt.rgba(1, 0, 0, 1) } + GradientStop { position: 0.167; color: Qt.rgba(1, 1, 0, 1) } + GradientStop { position: 0.333; color: Qt.rgba(0, 1, 0, 1) } + GradientStop { position: 0.500; color: Qt.rgba(0, 1, 1, 1) } + GradientStop { position: 0.667; color: Qt.rgba(0, 0, 1, 1) } + GradientStop { position: 0.833; color: Qt.rgba(1, 0, 1, 1) } + GradientStop { position: 1.000; color: Qt.rgba(1, 0, 0, 1) } } } Rectangle { - anchors.fill: parent - border.color: app.foregroundColor - border.width: 1 - gradient: Gradient { - GradientStop { position: 0.0; color: "transparent" } - GradientStop { position: 1.0; color: "white" } + id: mask + anchors.fill: gradient + radius: width / 2 + } + OpacityMask { + anchors.fill: gradient + source: gradient + 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" } } } MouseArea { id: mouseArea anchors.fill: parent - preventStealing: true - hoverEnabled: true - - drag.minimumX: 0 - drag.maximumX: width - dndItem.width - drag.minimumY: 0 - drag.maximumY: height - dndItem.height - - property variant draggedItem - property variant draggedLight - - onPressed: { - mouseArea.drag.target = dndItem; - dndItem.x = Math.min(width - dndItem.width, Math.max(0, mouseX - dndItem.width / 2)) - dndItem.y = Math.min(height - dndItem.height, Math.max(0, mouseY - dndItem.height / 2)) - mouseArea.draggedItem = touchDelegateLoader; - } onPositionChanged: { - if (mouseArea.draggedItem) { - root.color = root.calculateColor(mouseX, mouseY); - if (mouseArea.draggedLight) { - mouseArea.draggedLight.color = root.calculateColor(mouseX, mouseY); + + var angle = calculateAngle(mouseX, mouseY) + var position = angle / 360; + + var stopBefore = null; + var stopAfter = null; + for (var i = 0; i < g.stops.length; i++) { + var stop = g.stops[i]; + if (stop.position < position) { + stopBefore = stop; + continue } + stopAfter = stop; + break; } + + var colorBefore = stopBefore.color + var colorAfter = stopAfter.color + // p : 1 = pis : (a - b) + var positionInStop = (position - stopBefore.position) / (stopAfter.position - stopBefore.position); + + var dr = stopAfter.color.r - stopBefore.color.r; + var dg = stopAfter.color.g - stopBefore.color.g; + var db = stopAfter.color.b - stopBefore.color.b; + + var distanceFromCenter = calculateDistance(mouseX, mouseY) + + // Reduce width a bit to keep outside circle for full color intensity + var positionFromCenter = Math.min(distanceFromCenter / (width * 0.9 / 2), 1) + // Invert it, The further we're away, the less impact this should have + positionFromCenter = 1 - positionFromCenter; + print("pos", positionFromCenter) + + var color = Qt.rgba((stopBefore.color.r + dr * positionInStop) + positionFromCenter, + (stopBefore.color.g + dg * positionInStop) + positionFromCenter, + (stopBefore.color.b + db * positionInStop) + positionFromCenter, + 1) + + actionQueue.sendValue(color); } - onReleased: { - if (mouseArea.draggedItem) { - root.color = root.calculateColor(mouseX, mouseY); - if (mouseArea.draggedLight) { - mouseArea.draggedLight.color = root.calculateColor(mouseX, mouseY); - } - } - mouseArea.draggedItem = undefined; - mouseArea.draggedLight = undefined; - mouseArea.drag.target = undefined; + function calculateAngle(mouseX, mouseY) { + // transform coords to center of dial + mouseX -= mouseArea.width / 2 + mouseY -= mouseArea.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; } - } + function calculateDistance(mouseX, mouseY) { + mouseX -= mouseArea.width / 2 + mouseY -= mouseArea.height / 2 - Loader { - id: touchDelegateLoader - property variant point: calculateXy(root.color); - x: item ? Math.max(0, Math.min(point.x - width * .5, parent.width - item.width)) : 0 - y: item ? Math.max(0, Math.min(point.y - height * .5, parent.height - item.height)) : 0 - sourceComponent: root.touchDelegate - visible: mouseArea.draggedItem !== touchDelegateLoader && root.active - // Behavior on x { - // enabled: !mouseArea.pressed - // NumberAnimation {} - // } - } - - Loader { - id: dndItem - sourceComponent: root.touchDelegate - visible: mouseArea.draggedItem !== undefined && root.active + return Math.abs(Math.sqrt(Math.pow(mouseX, 2) + Math.pow(mouseY, 2))) + } } } diff --git a/nymea-app/ui/components/ColorPickerCt.qml b/nymea-app/ui/components/ColorPickerCt.qml index bdd59609..1f3ea0d9 100644 --- a/nymea-app/ui/components/ColorPickerCt.qml +++ b/nymea-app/ui/components/ColorPickerCt.qml @@ -102,5 +102,4 @@ Item { sourceComponent: root.touchDelegate visible: mouseArea.pressed && root.active } - } diff --git a/nymea-app/ui/components/ColorPickerPre510.qml b/nymea-app/ui/components/ColorPickerPre510.qml new file mode 100644 index 00000000..0010dcb9 --- /dev/null +++ b/nymea-app/ui/components/ColorPickerPre510.qml @@ -0,0 +1,249 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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 + +Item { + id: root + + property color color: actionState + property bool pressed: mouseArea.pressed + property bool hovered: mouseArea.containsMouse + + property bool active: true + + property Component touchDelegate: Rectangle { + height: 15 + width: height + radius: height / 2 + color: app.accentColor + + + Rectangle { + color: root.hovered || root.pressed ? "#11000000" : "transparent" + anchors.centerIn: parent + height: 30 + width: height + radius: width / 2 + Behavior on color { ColorAnimation { duration: 200 } } + } + } + + function calculateXy(color) { + if (!color.hasOwnProperty("r")) { + // Qt 4.8's color doesn't have r,g,b properties + var colorString = color.toString(); + color = new Object; + color.r = 1.0 * parseInt("0x" + colorString.substring(1,3), 16) / 256; + color.g = 1.0 * parseInt("0x" + colorString.substring(3,5), 16) / 256; + color.b = 1.0 * parseInt("0x" + colorString.substring(5,7), 16) / 256; + } + + var point = new Object; + var brightness = Math.min(color.r, color.g, color.b); + point.y = Math.round(root.height * brightness); + var newColor = new Object; + newColor.r = color.r - brightness; + newColor.g = color.g - brightness; + newColor.b = color.b - brightness; + + var sectionWidth = root.width / 6; + + var sections = [true, true, true]; + if (newColor.r > 0) { + sections[1] = false; + } + if (newColor.g > 0) { + sections[2] = false; + } + if (newColor.b > 0) { + sections[0] = false; + } + if (sections[0]) { + if (newColor.r > newColor.g) { + point.x = sectionWidth * newColor.g; + } else if (newColor.r == newColor.g) { + point.x = sectionWidth + } else { + point.x = sectionWidth * 2 - newColor.r * sectionWidth; + } + } else if (sections[1]) { + if (newColor.g > newColor.b) { + point.x = sectionWidth * 2 + sectionWidth * newColor.b; + } else if (newColor.g == newColor.b) { + point.x = sectionWidth * 3; + } else { + point.x = sectionWidth * 4 - newColor.g * sectionWidth + } + } else { + if (newColor.b > newColor.r) { + point.x = sectionWidth * 4 + sectionWidth * newColor.r + } else if (newColor.b == newColor.r) { + point.x = sectionWidth * 5; + } else { + point.x = sectionWidth * 6 - newColor.b * sectionWidth; + } + } + return point; + } + + function calculateColor(x, y) { + x = Math.min(Math.max(0, x), width - 1); + y = Math.min(Math.max(0, y), height - 1); + var color = root.color; + var sectionWidth = root.width / 6; + var section = Math.floor(x / sectionWidth) + var sectionX = Math.floor(x % (sectionWidth - 0.00001)) // the - 0.00001 is to prevent a mismatch when rounding + // sectionVal : 1.0 = sectionX : sectionWidth + var sectionVal = (1.0 * sectionX / sectionWidth) + + // brightnessVal : 1.0 = mouseY : height + var brightness = 1.0 * y / height + + switch (section) { + case 0: + // GradientStop { position: 0.0; color: "#ff0000" } + color = Qt.rgba(1, sectionVal + brightness, brightness, 1) + break; + case 1: + // GradientStop { position: 0.17; color: "#ffff00" } + color = Qt.rgba(1 - sectionVal + brightness, 1, brightness, 1) + break; + case 2: + // GradientStop { position: 0.33; color: "#00ff00" } + color = Qt.rgba(brightness, 1, sectionVal + brightness, 1) + break; + case 3: + // GradientStop { position: 0.5; color: "#00ffff" } + color = Qt.rgba(brightness, 1 - sectionVal + brightness, 1, 1) + break; + case 4: + // GradientStop { position: 0.66; color: "#0000ff" } + color = Qt.rgba(sectionVal + brightness, brightness, 1, 1) + break; + case 5: + // GradientStop { position: 0.83; color: "#ff00ff" } + color = Qt.rgba(1, brightness, 1 - sectionVal + brightness, 1) + break; + } + // print("got color", color.r, color.g, color.b) + return color + } + + Rectangle { + height: parent.width + width: parent.height + anchors.centerIn: parent + rotation: -90 + + gradient: Gradient { + GradientStop { position: 0.0; color: "#ff0000" } + GradientStop { position: 0.17; color: "#ffff00" } + GradientStop { position: 0.33; color: "#00ff00" } + GradientStop { position: 0.5; color: "#00ffff" } + GradientStop { position: 0.66; color: "#0000ff" } + GradientStop { position: 0.83; color: "#ff00ff" } + GradientStop { position: 1.0; color: "#ff0000" } + } + } + + Rectangle { + anchors.fill: parent + border.color: app.foregroundColor + border.width: 1 + gradient: Gradient { + GradientStop { position: 0.0; color: "transparent" } + GradientStop { position: 1.0; color: "white" } + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + preventStealing: true + hoverEnabled: true + + drag.minimumX: 0 + drag.maximumX: width - dndItem.width + drag.minimumY: 0 + drag.maximumY: height - dndItem.height + + property variant draggedItem + property variant draggedLight + + onPressed: { + mouseArea.drag.target = dndItem; + dndItem.x = Math.min(width - dndItem.width, Math.max(0, mouseX - dndItem.width / 2)) + dndItem.y = Math.min(height - dndItem.height, Math.max(0, mouseY - dndItem.height / 2)) + mouseArea.draggedItem = touchDelegateLoader; + } + onPositionChanged: { + if (mouseArea.draggedItem) { + root.color = root.calculateColor(mouseX, mouseY); + if (mouseArea.draggedLight) { + mouseArea.draggedLight.color = root.calculateColor(mouseX, mouseY); + } + } + } + + onReleased: { + if (mouseArea.draggedItem) { + root.color = root.calculateColor(mouseX, mouseY); + if (mouseArea.draggedLight) { + mouseArea.draggedLight.color = root.calculateColor(mouseX, mouseY); + } + } + mouseArea.draggedItem = undefined; + mouseArea.draggedLight = undefined; + mouseArea.drag.target = undefined; + + } + + } + + Loader { + id: touchDelegateLoader + property variant point: calculateXy(root.color); + x: item ? Math.max(0, Math.min(point.x - width * .5, parent.width - item.width)) : 0 + y: item ? Math.max(0, Math.min(point.y - height * .5, parent.height - item.height)) : 0 + sourceComponent: root.touchDelegate + visible: mouseArea.draggedItem !== touchDelegateLoader && root.active + // Behavior on x { + // enabled: !mouseArea.pressed + // NumberAnimation {} + // } + } + + Loader { + id: dndItem + sourceComponent: root.touchDelegate + visible: mouseArea.draggedItem !== undefined && root.active + } +} diff --git a/nymea-app/ui/components/ColorTemperaturePicker.qml b/nymea-app/ui/components/ColorTemperaturePicker.qml new file mode 100644 index 00000000..ec133b34 --- /dev/null +++ b/nymea-app/ui/components/ColorTemperaturePicker.qml @@ -0,0 +1,69 @@ +import QtQuick 2.9 +import QtGraphicalEffects 1.0 +import Nymea 1.0 +import "../utils" + +Item { + id: root + implicitWidth: orientation == Qt.Horizontal ? 300 : app.hugeIconSize + implicitHeight: orientation == Qt.Horizontal ? app.hugeIconSize : 300 + + property Thing thing: null + + property int orientation: Qt.Horizontal + + readonly property StateType colorTemperatureStateType: root.thing.thingClass.stateTypes.findByName("colorTemperature") + + property int value: thing.stateByName("colorTemperature").value + + ActionQueue { + id: actionQueue + thing: root.thing + stateType: root.colorTemperatureStateType + } + + Rectangle { + id: clipRect + anchors.fill: parent + radius: app.radius + } + + 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 + 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) + 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(app.backgroundColor, Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, 0.5)) + } + + MouseArea { + anchors.fill: parent + 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)) + } + actionQueue.sendValue(ct); + } + } +} + diff --git a/nymea-app/ui/delegates/ParamDelegate.qml b/nymea-app/ui/delegates/ParamDelegate.qml index 5fe0cf74..846a01f9 100644 --- a/nymea-app/ui/delegates/ParamDelegate.qml +++ b/nymea-app/ui/delegates/ParamDelegate.qml @@ -280,7 +280,7 @@ ItemDelegate { Component { id: colorPickerComponent - ColorPicker { + ColorPickerPre510 { id: colorPicker implicitHeight: 200 // color: root.param.value diff --git a/nymea-app/ui/delegates/statedelegates/ColorDelegate.qml b/nymea-app/ui/delegates/statedelegates/ColorDelegate.qml index a68e7bf4..010166d2 100644 --- a/nymea-app/ui/delegates/statedelegates/ColorDelegate.qml +++ b/nymea-app/ui/delegates/statedelegates/ColorDelegate.qml @@ -81,7 +81,7 @@ Item { padding: app.margins property var colorValue property int preferredY: 0 - contentItem: ColorPicker { + contentItem: ColorPickerPre510 { color: colorPickerDialog.colorValue property var lastSentTime: new Date() onColorChanged: { diff --git a/nymea-app/ui/devicelistpages/LightsDeviceListPage.qml b/nymea-app/ui/devicelistpages/LightsDeviceListPage.qml index 22ca1f05..397371c6 100644 --- a/nymea-app/ui/devicelistpages/LightsDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/LightsDeviceListPage.qml @@ -29,12 +29,13 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ import QtQuick 2.5 -import QtQuick.Controls 2.1 -import QtQuick.Controls.Material 2.1 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 import QtQuick.Layouts 1.1 import QtGraphicalEffects 1.0 import Nymea 1.0 import "../components" +import "../utils" DeviceListPageBase { id: root @@ -95,50 +96,37 @@ DeviceListPageBase { bottomPadding: 0 leftPadding: 0 rightPadding: 0 - enabled: connectedState == null || connectedState.value === true property State connectedState: thing.stateByName("connected") property State powerState: thing.stateByName("power") property State brightnessState: thing.stateByName("brightness") property State colorState: thing.stateByName("color") - onClicked: root.enterPage(index) + property bool tileColored: enabled && colorState && powerState.value === true + property bool colorInverted: tileColored && NymeaUtils.isDark(app.foregroundColor) === NymeaUtils.isDark(colorState.value) + property bool isConnected: connectedState && connectedState.value === true - property int pendingCommand: -1 - property var pendingValue: null - function adjustBrightness(value) { - if (pendingCommand != -1) { - // busy, cache value - pendingValue = value; - return; + onClicked: { + if (isConnected) { + root.enterPage(index) + } else { + itemDelegate.wobble() } - pendingCommand = engine.thingManager.executeAction(itemDelegate.thing.id, - itemDelegate.brightnessState.stateTypeId, - [{ - paramTypeId: itemDelegate.brightnessState.stateTypeId, - value: value - }]) - pendingValue = null; } - Connections { - target: engine.thingManager - onExecuteActionReply: { - if (itemDelegate.pendingCommand == commandId) { - itemDelegate.pendingCommand = -1; - if (itemDelegate.pendingValue != null) { - itemDelegate.adjustBrightness(pendingValue) - } - } - } + ActionQueue { + id: actionQueue + thing: itemDelegate.thing + stateType: thing.thingClass.stateTypes.findByName("brightness") } contentItem: Rectangle { - color: itemDelegate.powerState.value === true && itemDelegate.colorState ? itemDelegate.colorState.value : "#00000000" + color: enabled && itemDelegate.powerState.value === true && itemDelegate.colorState ? itemDelegate.colorState.value : "#00000000" implicitHeight: contentColumn.implicitHeight Behavior on implicitHeight { NumberAnimation { duration: 100 } } radius: 6 + enabled: itemDelegate.connectedState == null || connectedState.value === true ColumnLayout { id: contentColumn @@ -150,10 +138,17 @@ DeviceListPageBase { spacing: app.margins ColorIcon { + id: lightIcon Layout.preferredHeight: app.iconSize Layout.preferredWidth: app.iconSize name: itemDelegate.powerState.value === true ? "../images/light-on.svg" : "../images/light-off.svg" color: itemDelegate.powerState.value === true ? app.accentColor : keyColor + Binding { + target: lightIcon + property: "color" + value: itemDelegate.colorInverted ? app.backgroundColor : app.foregroundColor + when: itemDelegate.tileColored + } } Label { @@ -165,9 +160,8 @@ DeviceListPageBase { Binding { target: nameLabel property: "color" - value: itemDelegate.colorState && NymeaUtils.isDark(app.foregroundColor) === NymeaUtils.isDark(itemDelegate.colorState.value) ? - app.backgroundColor : app.foregroundColor - when: nameLabel.enabled && itemDelegate.colorState !== null && itemDelegate.powerState.value === true + value: itemDelegate.colorInverted ? app.backgroundColor : app.foregroundColor + when: itemDelegate.tileColored } } @@ -176,6 +170,7 @@ DeviceListPageBase { } Switch { + id: lightSwitch checked: itemDelegate.powerState.value === true onClicked: { var params = []; @@ -186,6 +181,76 @@ DeviceListPageBase { print("executing for thing:", itemDelegate.thing.id) engine.deviceManager.executeAction(itemDelegate.thing.id, itemDelegate.powerState.stateTypeId, params) } + + indicator: Item { + id: indicator + implicitWidth: 38 + implicitHeight: 32 + x: lightSwitch.leftPadding + (lightSwitch.availableWidth - width) / 2 + y: lightSwitch.topPadding + (lightSwitch.availableHeight - height) / 2 + + property Item control + property alias handle: handle + + Material.elevation: 1 + + Rectangle { + id: indicatorBackground + width: parent.width + height: 14 + radius: height / 2 + y: parent.height / 2 - height / 2 + color: lightSwitch.enabled ? + (lightSwitch.checked ? lightSwitch.Material.switchCheckedTrackColor : lightSwitch.Material.switchUncheckedTrackColor) + : lightSwitch.Material.switchDisabledTrackColor + + Binding { + target: indicatorBackground + property: "color" + value: "#808080" + when: itemDelegate.tileColored + } + } + + Rectangle { + id: handle + x: Math.max(0, Math.min(parent.width - width, lightSwitch.visualPosition * parent.width - (width / 2))) + y: (parent.height - height) / 2 + width: 20 + height: 20 + radius: width / 2 + color: lightSwitch.enabled ? (lightSwitch.checked ? lightSwitch.Material.switchCheckedHandleColor : lightSwitch.Material.switchUncheckedHandleColor) + : lightSwitch.Material.switchDisabledHandleColor + + + Binding { + target: handle + property: "color" + value: "#f0f0f0" + when: itemDelegate.tileColored + } + + Behavior on x { + enabled: !lightSwitch.pressed + SmoothedAnimation { + duration: 300 + } + } +// layer.enabled: indicator.Material.elevation > 0 +// layer.effect: ElevationEffect { +// elevation: indicator.Material.elevation +// } + } + DropShadow { + anchors.fill: handle + horizontalOffset: 1 + verticalOffset: 1 + radius: 4.0 + samples: 17 + color: "#80000000" + source: handle + } + } } } @@ -196,21 +261,45 @@ DeviceListPageBase { radius: 6 color: Qt.tint(app.backgroundColor, Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .1)) + Rectangle { + height: knob.x + knob.width / 2 + width: parent.height + anchors.centerIn: parent + anchors.horizontalCenterOffset: -(parent.width - height) / 2 + rotation: -90 + gradient: Gradient { + GradientStop { position: 0; color: "transparent" } + GradientStop { position: 1; color: "#55ffffff" } + } + } + Rectangle { id: knob - height: 12 - width: 12 + height: 14 + width: 14 radius: 8 - color: app.accentColor + color: "#f0f0f0" anchors.verticalCenter: parent.verticalCenter - x: itemDelegate.brightnessState ? itemDelegate.brightnessState.value * (parent.width - width) / 100 : 0 + x: itemDelegate.brightnessState ? + (actionQueue.queuedValue || actionQueue.pendingValue || itemDelegate.brightnessState.value) * (parent.width - width) / 100 + : 0 + } + DropShadow { + anchors.fill: knob + horizontalOffset: 1 + verticalOffset: 1 + radius: 4.0 + samples: 17 + color: "#80000000" + source: knob } MouseArea { + id: brightnessMouseArea anchors.fill: parent preventStealing: true onMouseXChanged: { - itemDelegate.adjustBrightness(Math.max(1, Math.min(100, mouseX / width * 100))) + actionQueue.sendValue(Math.max(1, Math.min(100, mouseX / width * 100))) } } } diff --git a/nymea-app/ui/devicepages/GenericDevicePage.qml b/nymea-app/ui/devicepages/GenericDevicePage.qml index 4b9a7e0a..90f1a31a 100644 --- a/nymea-app/ui/devicepages/GenericDevicePage.qml +++ b/nymea-app/ui/devicepages/GenericDevicePage.qml @@ -239,7 +239,7 @@ DevicePageBase { } property int pendingActionId: -1 - property real valueCache: 0 + property var valueCache: 0 property bool valueCacheDirty: false function enqueueSetValue(value) { diff --git a/nymea-app/ui/devicepages/LightDevicePage.qml b/nymea-app/ui/devicepages/LightDevicePage.qml index 5cea7c52..eeb54373 100644 --- a/nymea-app/ui/devicepages/LightDevicePage.qml +++ b/nymea-app/ui/devicepages/LightDevicePage.qml @@ -28,11 +28,12 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -import QtQuick 2.5 +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" DevicePageBase { @@ -54,279 +55,139 @@ DevicePageBase { readonly property var ctState: ctStateType ? device.states.getState(ctStateType.id) : null readonly property var ctActionType: deviceClass.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 ? 2 : 1 + columns: app.landscape ? root.statesCount : 1 rowSpacing: app.margins columnSpacing: app.margins Layout.alignment: Qt.AlignCenter - Dial { - Layout.minimumWidth: app.landscape ? parent.width / 3 :app.iconSize * 4 - Layout.preferredHeight: width - Layout.fillWidth: true - Layout.topMargin: app.margins - Layout.bottomMargin: app.landscape ? app.margins : 0 - Layout.alignment: Qt.AlignCenter - Layout.rowSpan: app.landscape ? 3 : 1 + GridLayout { Layout.fillHeight: true - device: root.device - stateType: root.brightnessStateType - showValueLabel: false - } - -// Item { -// Layout.preferredWidth: Math.max(app.iconSize * 4, 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: "white" -// border.color: root.powerState.value === true ? app.accentColor : bulbIcon.keyColor -// border.width: 4 -// radius: width / 2 -// } - -// ColorIcon { -// id: bulbIcon -// anchors.fill: parent -// anchors.margins: app.margins * 1.5 -// name: root.powerState.value === true ? "../images/light-on.svg" : "../images/light-off.svg" -// color: root.powerState.value === true ? app.accentColor : keyColor -// } -// onClicked: { -// var params = [] -// var param = {} -// param["paramTypeId"] = root.powerActionType.paramTypes.get(0).id; -// param["value"] = !root.powerState.value; -// params.push(param) -// engine.deviceManager.executeAction(root.device.id, root.powerStateType.id, params); -// } -// } -// } - - - RowLayout { - Layout.fillHeight: true - spacing: app.margins + 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 } - ListElement { name: "concentrate"; ct: "23"; bri: 100 } - ListElement { name: "reading"; ct: "57"; bri: 100 } - ListElement { name: "relax"; ct: "95" ; bri: 55} + 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: Pane { + delegate: Item { + Layout.preferredWidth: app.hugeIconSize + Layout.preferredHeight: app.hugeIconSize Layout.fillWidth: true - Layout.preferredHeight: Math.max(20, width) - Material.elevation: 1 - // Layout.maximumWidth: app.iconSize * 2 - padding: 0 - Image { - source: "../images/lighting/" + model.name + ".svg" - anchors.fill: parent - ItemDelegate { - anchors.fill: parent - 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["paramTypeId"] = root.ctActionType.paramTypes.get(0).id; - param1["value"] = absoluteCtValue; - params.push(param1) - engine.deviceManager.executeAction(root.device.id, root.ctActionType.id, params) - params = []; - param1 = {}; - param1["paramTypeId"] = root.brightnessActionType.paramTypes.get(0).id; - param1["value"] = model.bri; - params.push(param1) - engine.deviceManager.executeAction(root.device.id, root.brightnessActionType.id, params) - } - } - } - } - } - } - -// Rectangle { -// color: "blue" -// Layout.fillWidth: true -// Layout.fillHeight: true -// Layout.minimumHeight: 20 -// Layout.preferredHeight: 20 -// visible: root.brightnessStateType - -// Pane { -// anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter } -// height: parent.height -// Material.elevation: 1 -// padding: 0 - -// BrightnessSlider { -// anchors.fill: parent -// brightness: root.brightnessState ? root.brightnessState.value : 0 -// onMoved: { -// var params = [] -// var param = {} -// param["paramTypeId"] = root.brightnessActionType.paramTypes.get(0).id; -// param["value"] = brightness; -// params.push(param) -// engine.deviceManager.executeAction(root.device.id, root.brightnessActionType.id, params); -// } -// } -// } -// } - - - Rectangle { - color: "red" - Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumHeight: 20 - Layout.preferredHeight: 20 - visible: root.ctStateType - - Pane { - anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter } - height: parent.height - Material.elevation: 1 - padding: 0 - - ColorPickerCt { - id: pickerCt - anchors.fill: parent - ct: root.ctState ? root.ctState.value : 0 - minCt: root.ctActionType ? root.ctStateType.minValue : 0 - maxCt: root.ctActionType ? root.ctStateType.maxValue : 0 - - - touchDelegate: Rectangle { - height: pickerCt.height - width: 5 - color: app.accentColor - } - - property var lastSentTime: new Date() - onCtChanged: { - var currentTime = new Date(); - if (pressed && currentTime - lastSentTime > 200) { - setColorTemp(ct) - lastSentTime = currentTime - } - } - - function setColorTemp(ct) { - var params = [] - var param = {} - param["paramTypeId"] = root.ctActionType.paramTypes.get(0).id; - param["value"] = ct; - params.push(param) - engine.deviceManager.executeAction(root.device.id, root.ctActionType.id, params); - } - } - } - } - - Rectangle { - color: "green" - Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumHeight: 80 - Layout.preferredHeight: 80 - visible: root.colorStateType - - Pane { - anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter } - height: parent.height - Material.elevation: 1 - padding: 0 - - ColorPicker { - id: colorPicker - anchors.fill: parent - - property int pendingCommand: -1 - - property var queuedColor: null - - function sendColor(color) { - var params = []; - var param1 = {}; - param1["paramTypeId"] = root.colorActionType.paramTypes.get(0).id; - param1["value"] = color; - params.push(param1) - colorPicker.pendingCommand = engine.deviceManager.executeAction(root.device.id, root.colorActionType.id, params) - print("sent command", colorPicker.pendingCommand, color) - } - - color: root.colorState ? root.colorState.value : "white" - touchDelegate: Rectangle { - height: 15 + Layout.fillHeight: app.landscape + ItemDelegate { + height: app.hugeIconSize width: height - radius: height / 2 - color: app.foregroundColor + anchors.centerIn: parent - Rectangle { - color: colorPicker.hovered || colorPicker.pressed ? "#11000000" : "transparent" - anchors.centerIn: parent - height: 30 - width: height - radius: width / 2 - Behavior on color { - ColorAnimation { - duration: 200 - } + leftPadding: 0 + rightPadding: 0 + topPadding: 0 + bottomPadding: 0 + + contentItem: Rectangle { + color: model.color + radius: 6 + + ColorIcon { + anchors.fill: parent + name: "../images/lighting/" + model.name + ".svg" + color: "white" } } - } - Connections { - target: engine.deviceManager - onExecuteActionReply: { - print("action finished", JSON.stringify(params)) - if (commandId === colorPicker.pendingCommand) { - colorPicker.pendingCommand = -1; - if (colorPicker.queuedColor) { - colorPicker.sendColor(colorPicker.queuedColor); - colorPicker.queuedColor = null - } - } + 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["paramTypeId"] = root.ctActionType.paramTypes.get(0).id; + param1["value"] = absoluteCtValue; + params.push(param1) + engine.deviceManager.executeAction(root.device.id, root.ctActionType.id, params) + params = []; + param1 = {}; + param1["paramTypeId"] = root.brightnessActionType.paramTypes.get(0).id; + param1["value"] = model.bri; + params.push(param1) + engine.deviceManager.executeAction(root.device.id, root.brightnessActionType.id, params) } } - - onColorChanged: { - if (!pressed) { - return; - } - - if (pendingCommand != -1) { - queuedColor = color; - return; - } - - sendColor(color); - } } } } + 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: app.hugeIconSize + Layout.preferredHeight: width + radius: app.radius + color: root.colorState ? root.colorState.value : "red" +// color: Qt.tint(app.backgroundColor, Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, 0.1)) + ColorIcon { + anchors.centerIn: parent + height: app.largeIconSize + 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: { + engine.thingManager.executeAction(root.thing.id, root.powerStateType.id, [{paramTypeId: root.powerStateType.id, 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 + } + } } } diff --git a/nymea-app/ui/devicepages/ShutterDevicePage.qml b/nymea-app/ui/devicepages/ShutterDevicePage.qml index ba3224ea..e49cbf17 100644 --- a/nymea-app/ui/devicepages/ShutterDevicePage.qml +++ b/nymea-app/ui/devicepages/ShutterDevicePage.qml @@ -196,7 +196,7 @@ DevicePageBase { Rectangle { id: mask anchors.fill: parent - radius: app.margins + radius: app.radius color: "blue" visible: false } diff --git a/nymea-app/ui/images/lighting/activate.svg b/nymea-app/ui/images/lighting/activate.svg index 027b729d..b5f7c428 100644 --- a/nymea-app/ui/images/lighting/activate.svg +++ b/nymea-app/ui/images/lighting/activate.svg @@ -1,6 +1,4 @@ - - @@ -25,18 +23,19 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="2.8" - inkscape:cx="66.409554" - inkscape:cy="199.91527" + inkscape:zoom="2.1753186" + inkscape:cx="123.21035" + inkscape:cy="147.11516" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" units="px" - inkscape:window-width="2880" - inkscape:window-height="1752" - inkscape:window-x="0" - inkscape:window-y="48" - inkscape:window-maximized="1"> + inkscape:window-width="1380" + inkscape:window-height="873" + inkscape:window-x="60" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:document-rotation="0"> @@ -49,7 +48,7 @@ image/svg+xml - + @@ -58,24 +57,10 @@ inkscape:groupmode="layer" id="layer1" transform="translate(0,-796.36216)"> - - + id="path4140" + style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 127.86133 36 A 92.000023 92.000023 0 0 0 36 128 A 92.000023 92.000023 0 0 0 128 220 A 92.000023 92.000023 0 0 0 220 128 A 92.000023 92.000023 0 0 0 128 36 A 92.000023 92.000023 0 0 0 127.86133 36 z M 199.95898 88.650391 C 206.36028 100.33144 210 113.74028 210 128 C 210 173.28736 173.28736 210 128 210 C 88.819508 210 56.056981 182.52033 47.933594 145.7832 C 54.999891 140.99933 130.28516 95.857422 130.28516 95.857422 L 130.14258 136 C 130.14258 136 195.00011 91.000721 199.95898 88.650391 z " + transform="translate(0,796.36216)" /> diff --git a/nymea-app/ui/images/lighting/concentrate.svg b/nymea-app/ui/images/lighting/concentrate.svg index 9e2d636b..6765a9f8 100644 --- a/nymea-app/ui/images/lighting/concentrate.svg +++ b/nymea-app/ui/images/lighting/concentrate.svg @@ -1,6 +1,4 @@ - - @@ -25,18 +23,19 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="11.2" - inkscape:cx="128.78304" - inkscape:cy="130.74689" + inkscape:zoom="2.0704471" + inkscape:cx="115.75638" + inkscape:cy="121.69523" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" units="px" - inkscape:window-width="2880" - inkscape:window-height="1752" - inkscape:window-x="0" - inkscape:window-y="48" - inkscape:window-maximized="1"> + inkscape:window-width="1380" + inkscape:window-height="873" + inkscape:window-x="60" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:document-rotation="0"> @@ -49,7 +48,7 @@ image/svg+xml - + @@ -58,46 +57,10 @@ inkscape:groupmode="layer" id="layer1" transform="translate(0,-796.36216)"> - - - - - - + id="path4140" + style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 127.86133 36 A 92.000023 92.000023 0 0 0 36 128 A 92.000023 92.000023 0 0 0 128 220 A 92.000023 92.000023 0 0 0 220 128 A 92.000023 92.000023 0 0 0 128 36 A 92.000023 92.000023 0 0 0 127.86133 36 z M 127.88281 46 A 82.000023 82.000023 0 0 1 128 46 A 82.000023 82.000023 0 0 1 210 128 A 82.000023 82.000023 0 0 1 128 210 A 82.000023 82.000023 0 0 1 46 128 A 82.000023 82.000023 0 0 1 127.88281 46 z M 127.79688 58 A 70 70 0 0 0 58 128 A 70 70 0 0 0 128 198 A 70 70 0 0 0 198 128 A 70 70 0 0 0 128 58 A 70 70 0 0 0 127.79688 58 z M 128 73 A 55.000023 55.000023 0 0 1 183 128 A 55.000023 55.000023 0 0 1 128 183 A 55.000023 55.000023 0 0 1 73 128 A 55.000023 55.000023 0 0 1 128 73 z M 127.74414 93 A 35.000023 35.000023 0 0 0 93 128 A 35.000023 35.000023 0 0 0 128 163 A 35.000023 35.000023 0 0 0 163 128 A 35.000023 35.000023 0 0 0 128 93 A 35.000023 35.000023 0 0 0 127.74414 93 z M 128 108 A 20.000023 20.000023 0 0 1 148 128 A 20.000023 20.000023 0 0 1 128 148 A 20.000023 20.000023 0 0 1 108 128 A 20.000023 20.000023 0 0 1 128 108 z " + transform="translate(0,796.36216)" /> diff --git a/nymea-app/ui/images/lighting/reading.svg b/nymea-app/ui/images/lighting/reading.svg index 8b993f16..69d45cf6 100644 --- a/nymea-app/ui/images/lighting/reading.svg +++ b/nymea-app/ui/images/lighting/reading.svg @@ -1,6 +1,4 @@ - - @@ -26,17 +24,18 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="2.8" - inkscape:cx="66.409554" - inkscape:cy="199.91527" + inkscape:cx="122.92774" + inkscape:cy="133.69504" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" units="px" - inkscape:window-width="2880" - inkscape:window-height="1752" - inkscape:window-x="0" - inkscape:window-y="48" - inkscape:window-maximized="1"> + inkscape:window-width="1380" + inkscape:window-height="873" + inkscape:window-x="60" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:document-rotation="0"> @@ -49,7 +48,7 @@ image/svg+xml - + @@ -58,65 +57,10 @@ inkscape:groupmode="layer" id="layer1" transform="translate(0,-796.36216)"> - - - - - - - - - - + style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 127.86133 36 A 92.000023 92.000023 0 0 0 36 128 A 92.000023 92.000023 0 0 0 128 220 A 92.000023 92.000023 0 0 0 220 128 A 92.000023 92.000023 0 0 0 128 36 A 92.000023 92.000023 0 0 0 127.86133 36 z M 78 73 L 178 73 L 178 83 L 78 83 L 78 73 z M 78 93 L 178 93 L 178 103 L 78 103 L 78 93 z M 78 113 L 178 113 L 178 123 L 78 123 L 78 113 z M 78 133 L 178 133 L 178 143 L 78 143 L 78 133 z M 78 153 L 178 153 L 178 163 L 78 163 L 78 153 z M 78 173 L 178 173 L 178 183 L 78 183 L 78 173 z " + transform="translate(0,796.36216)" /> diff --git a/nymea-app/ui/images/lighting/relax.svg b/nymea-app/ui/images/lighting/relax.svg index 13b680ca..6cc9d468 100644 --- a/nymea-app/ui/images/lighting/relax.svg +++ b/nymea-app/ui/images/lighting/relax.svg @@ -1,6 +1,4 @@ - - @@ -25,18 +23,19 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="2.8" - inkscape:cx="66.409554" - inkscape:cy="199.91527" + inkscape:zoom="1.1480033" + inkscape:cx="56.435095" + inkscape:cy="139.24953" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" units="px" - inkscape:window-width="2880" - inkscape:window-height="1752" - inkscape:window-x="0" - inkscape:window-y="48" - inkscape:window-maximized="1"> + inkscape:window-width="1380" + inkscape:window-height="873" + inkscape:window-x="60" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:document-rotation="0"> @@ -49,7 +48,7 @@ image/svg+xml - + @@ -58,24 +57,10 @@ inkscape:groupmode="layer" id="layer1" transform="translate(0,-796.36216)"> - - + id="path4140" + style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 127.86133 36 A 92.000023 92.000023 0 0 0 36 128 A 92.000023 92.000023 0 0 0 128 220 A 92.000023 92.000023 0 0 0 220 128 A 92.000023 92.000023 0 0 0 128 36 A 92.000023 92.000023 0 0 0 127.86133 36 z M 87 86.572266 C 109.88037 86.572266 128.42857 105.11963 128 128 C 127.57143 150.88037 146.11963 169.42773 169 169.42773 C 191.12817 169.42773 209.2011 152.08002 210.17188 130.24219 C 210.1953 129.49734 210.2113 128.74967 210.21484 128 C 210.22189 128.7522 210.20487 129.49986 210.17188 130.24219 C 208.78032 174.49311 172.53769 210 128 210 C 82.712639 210 45.999432 173.28736 45.785156 128 C 45.570883 105.11963 64.119633 86.572266 87 86.572266 z " + transform="translate(0,796.36216)" /> diff --git a/nymea-app/ui/utils/ActionQueue.qml b/nymea-app/ui/utils/ActionQueue.qml new file mode 100644 index 00000000..c1369520 --- /dev/null +++ b/nymea-app/ui/utils/ActionQueue.qml @@ -0,0 +1,48 @@ +import QtQuick 2.9 +import Nymea 1.0 + +Item { + id: root + + property Thing thing: null + property StateType stateType: null + + readonly property var pendingValue: d.queuedValue || d.pendingValue + + function sendValue(value) { + if (d.pendingCommand != -1) { + // busy, cache value + d.queuedValue = value; + return; + } + d.pendingValue = value; + d.pendingCommand = engine.thingManager.executeAction(root.thing.id, + root.stateType.id, + [{ + paramTypeId: root.stateType.id, + value: value + }]) + d.queuedValue = null + } + + QtObject { + id: d + property int pendingCommand: -1 + property var pendingValue: null + property var queuedValue: null + } + + Connections { + target: engine.thingManager + onExecuteActionReply: { + if (d.pendingCommand == commandId) { + d.pendingCommand = -1; + if (d.queuedValue != null) { + root.sendValue(d.queuedValue) + } else { + d.pendingValue = null + } + } + } + } +}