From e9ec00b56f358054907ab766b12914d58a752b24 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Fri, 28 Sep 2018 20:08:58 +0200 Subject: [PATCH] rework lights pages --- nymea-app/nymea-app.pro | 5 +- nymea-app/resources.qrc | 3 +- nymea-app/ui/components/BrightnessSlider.qml | 86 +++++++++ nymea-app/ui/components/ColorPicker.qml | 2 + nymea-app/ui/components/ColorPickerCt.qml | 2 + .../devicelistpages/LightsDeviceListPage.qml | 164 +++++++++++------ .../ui/devicepages/ColorLightDevicePage.qml | 173 ------------------ nymea-app/ui/devicepages/LightDevicePage.qml | 155 ++++++++++++++++ 8 files changed, 356 insertions(+), 234 deletions(-) create mode 100644 nymea-app/ui/components/BrightnessSlider.qml delete mode 100644 nymea-app/ui/devicepages/ColorLightDevicePage.qml create mode 100644 nymea-app/ui/devicepages/LightDevicePage.qml diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index 2e157f87..18d6e32d 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -28,8 +28,6 @@ SOURCES += main.cpp \ platformhelper.cpp \ platformintegration/generic/platformhelpergeneric.cpp \ -OTHER_FILES += $$files(*.qml, true) - RESOURCES += resources.qrc \ ruletemplates.qrc equals(STYLES_PATH, "") { @@ -125,4 +123,5 @@ DISTFILES += \ ui/components/BusyOverlay.qml \ ui/devicepages/NotificationsDevicePage.qml \ ruletemplates/buttontemplates.json \ - ruletemplates/notificationtemplates.json + ruletemplates/notificationtemplates.json \ + ui/devicepages/LightDevicePage.qml diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 9cd6c08a..df6573e2 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -62,7 +62,6 @@ ui/images/weathericons/humidity.svg ui/images/weathericons/wind.svg ui/devicepages/WeatherDevicePage.qml - ui/devicepages/ColorLightDevicePage.qml ui/components/ThrottledSlider.qml ui/components/ColorPickerCt.qml ui/images/navigation-menu.svg @@ -249,5 +248,7 @@ ui/appsettings/DeveloperOptionsPage.qml ui/appsettings/CloudLoginPage.qml ui/devicepages/NotificationsDevicePage.qml + ui/components/BrightnessSlider.qml + ui/devicepages/LightDevicePage.qml diff --git a/nymea-app/ui/components/BrightnessSlider.qml b/nymea-app/ui/components/BrightnessSlider.qml new file mode 100644 index 00000000..22aade07 --- /dev/null +++ b/nymea-app/ui/components/BrightnessSlider.qml @@ -0,0 +1,86 @@ +import QtQuick 2.3 + +Item { + id: root + + 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 bool active: true + property bool pressed: mouseArea.pressed + + signal moved(real brightness); + + Rectangle { + height: parent.width + width: parent.height + anchors.centerIn: parent + rotation: -90 + border.width: 1 + border.color: app.foregroundColor + + gradient: Gradient { + GradientStop { position: 0.0; color: Qt.rgba(0, 0, 0, .5) } + GradientStop { position: 1.0; color: Qt.rgba(1, 1, 1, .5) } + } + } + + 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 {} + } + } + + 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 + } + } + + 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 982e16df..0f87e147 100644 --- a/nymea-app/ui/components/ColorPicker.qml +++ b/nymea-app/ui/components/ColorPicker.qml @@ -131,6 +131,8 @@ Item { 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" } diff --git a/nymea-app/ui/components/ColorPickerCt.qml b/nymea-app/ui/components/ColorPickerCt.qml index c48faceb..1a9630f9 100644 --- a/nymea-app/ui/components/ColorPickerCt.qml +++ b/nymea-app/ui/components/ColorPickerCt.qml @@ -15,6 +15,8 @@ Item { width: parent.height anchors.centerIn: parent rotation: -90 + border.width: 1 + border.color: app.foregroundColor gradient: Gradient { GradientStop { position: 0.0; color: "#efffff" } diff --git a/nymea-app/ui/devicelistpages/LightsDeviceListPage.qml b/nymea-app/ui/devicelistpages/LightsDeviceListPage.qml index c2e41da3..fe25cfde 100644 --- a/nymea-app/ui/devicelistpages/LightsDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/LightsDeviceListPage.qml @@ -9,56 +9,89 @@ Page { header: GuhHeader { text: qsTr("Lights") onBackPressed: pageStack.pop() - } - ColumnLayout { - anchors.fill: parent - RowLayout { - Layout.fillWidth: true - Layout.margins: 10 - Label { - text: qsTr("All") - Layout.fillWidth: true - } - Button { - text: qsTr("off") - onClicked: { - for (var i = 0; i < devicesProxy.count; i++) { - var device = devicesProxy.get(i); - var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); - var actionType = deviceClass.actionTypes.findByName("power"); - var params = []; - var param1 = {}; - param1["paramTypeId"] = actionType.paramTypes.get(0).id; - param1["value"] = false; - params.push(param1) - Engine.deviceManager.executeAction(device.id, actionType.id, params) - } + HeaderButton { + imageSource: "../images/system-shutdown.svg" + onClicked: { + for (var i = 0; i < devicesProxy.count; i++) { + var device = devicesProxy.get(i); + var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + var actionType = deviceClass.actionTypes.findByName("power"); + + var params = []; + var param1 = {}; + param1["paramTypeId"] = actionType.paramTypes.get(0).id; + param1["value"] = false; + params.push(param1) + Engine.deviceManager.executeAction(device.id, actionType.id, params) } } } + } - ListView { - Layout.fillHeight: true - Layout.fillWidth: true - clip: true - model: DevicesProxy { - id: devicesProxy - devices: Engine.deviceManager.devices - } + ListView { + anchors.fill: parent + model: DevicesProxy { + id: devicesProxy + devices: Engine.deviceManager.devices + } - delegate: ItemDelegate { - id: itemDelegate - width: parent.width - height: childrenRect.height - property var device: devicesProxy.get(index); - property var deviceClass: Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + delegate: ItemDelegate { + id: itemDelegate + width: parent.width + + property bool inline: width > 500 + + property var device: devicesProxy.get(index); + property var deviceClass: Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + + property var connectedStateType: deviceClass.stateTypes.findByName("connected"); + property var connectedState: device.states.getState(connectedStateType.id) + + property var powerStateType: deviceClass.stateTypes.findByName("power"); + property var powerActionType: deviceClass.actionTypes.findByName("power"); + property var powerState: device.states.getState(powerStateType.id) + + property var brightnessStateType: deviceClass.stateTypes.findByName("brightness"); + property var brightnessActionType: deviceClass.actionTypes.findByName("brightness"); + property var brightnessState: brightnessStateType ? device.states.getState(brightnessStateType.id) : null + + property var colorStateType: deviceClass.stateTypes.findByName("color"); + property var colorState: colorStateType ? device.states.getState(colorStateType.id) : null + + topPadding: 0 + bottomPadding: 0 + leftPadding: 0 + rightPadding: 0 + contentItem: Rectangle { + id: contentItem + implicitHeight: itemDelegate.brightnessStateType && !itemDelegate.inline && enabled ? nameRow.implicitHeight + sliderRow.implicitHeight : nameRow.implicitHeight + gradient: Gradient { + GradientStop { position: 0.0; color: "transparent" } + GradientStop { position: 1.0; color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, 0.05) } + } + + enabled: itemDelegate.connectedState === null || itemDelegate.connectedState.value === true ColumnLayout { - anchors { left: parent.left; right: parent.right; top: parent.top } + anchors { left: parent.left; right: parent.right; margins: app.margins } + spacing: 0 RowLayout { - Layout.fillWidth: true - Layout.margins: 10 + id: nameRow + z: 2 // make sure the switch in here is on top of the slider, given we cheated a bit and made them overlap + spacing: app.margins + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: height + Layout.alignment: Qt.AlignVCenter + color: itemDelegate.connectedState !== null && itemDelegate.connectedState.value === false ? + "red" + : itemDelegate.colorStateType ? itemDelegate.colorState.value : "#00000000" + name: itemDelegate.connectedState !== null && itemDelegate.connectedState.value === false ? + "../images/dialog-warning-symbolic.svg" + : itemDelegate.powerState.value === true ? "../images/light-on.svg" : "../images/light-off.svg" + } + Label { Layout.fillWidth: true text: model.name @@ -67,42 +100,59 @@ Page { } ThrottledSlider { id: inlineSlider - visible: model.interfaces.indexOf("dimmablelight") >= 0 && parent.width > 350 - property var stateType: itemDelegate.deviceClass.stateTypes.findByName("brightness"); - property var actionType: itemDelegate.deviceClass.actionTypes.findByName("brightness"); - property var actionState: itemDelegate.device.states.getState(stateType.id) + visible: contentItem.enabled && itemDelegate.brightnessStateType && itemDelegate.inline from: 0; to: 100 - value: actionState.value + value: itemDelegate.brightnessState ? itemDelegate.brightnessState.value : 0 onMoved: { var params = []; var param1 = {}; - param1["paramTypeId"] = actionType.paramTypes.get(0).id; + param1["paramTypeId"] = itemDelegate.brightnessActionType.paramTypes.get(0).id; param1["value"] = value; params.push(param1) - Engine.deviceManager.executeAction(device.id, actionType.id, params) + Engine.deviceManager.executeAction(itemDelegate.device.id, itemDelegate.brightnessActionType.id, params) } } Switch { - property var stateType: itemDelegate.deviceClass.stateTypes.findByName("power"); - property var actionType: itemDelegate.deviceClass.actionTypes.findByName("power"); - property var actionState: itemDelegate.device.states.getState(stateType.id) - checked: actionState.value === true + checked: itemDelegate.powerState.value === true onClicked: { var params = []; var param1 = {}; - param1["paramTypeId"] = actionType.paramTypes.get(0).id; + param1["paramTypeId"] = itemDelegate.powerActionType.paramTypes.get(0).id; param1["value"] = checked; params.push(param1) - Engine.deviceManager.executeAction(device.id, actionType.id, params) + Engine.deviceManager.executeAction(device.id, itemDelegate.powerActionType.id, params) } + } + } + Item { + id: sliderRow + Layout.fillWidth: true + implicitHeight: outlineSlider.implicitHeight * .6 + Layout.preferredHeight: implicitHeight + ThrottledSlider { + id: outlineSlider + anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter } + visible: contentItem.enabled && itemDelegate.brightnessStateType && !inlineSlider.visible + from: 0; to: 100 + value: itemDelegate.brightnessState ? itemDelegate.brightnessState.value : 0 + onMoved: { + var params = []; + var param1 = {}; + param1["paramTypeId"] = itemDelegate.brightnessActionType.paramTypes.get(0).id; + param1["value"] = value; + params.push(param1) + Engine.deviceManager.executeAction(itemDelegate.device.id, itemDelegate.brightnessActionType.id, params) + } } } } + } - onClicked: { - pageStack.push(Qt.resolvedUrl("../devicepages/ColorLightDevicePage.qml"), {device: devicesProxy.get(index)}) - } + onClicked: { + var device = devicesProxy.get(index); + var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) + pageStack.push(Qt.resolvedUrl("../devicepages/LightDevicePage.qml"), {device: devicesProxy.get(index)}) } } } diff --git a/nymea-app/ui/devicepages/ColorLightDevicePage.qml b/nymea-app/ui/devicepages/ColorLightDevicePage.qml deleted file mode 100644 index b3645e18..00000000 --- a/nymea-app/ui/devicepages/ColorLightDevicePage.qml +++ /dev/null @@ -1,173 +0,0 @@ -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" - -DevicePageBase { - id: root - - ColumnLayout { - anchors.fill: parent - - RowLayout { - id: powerRow - Layout.fillWidth: true - Layout.margins: app.margins - spacing: app.margins - - property var powerStateType: root.deviceClass.stateTypes.findByName("power") - property var powerState: root.device.states.getState(powerStateType.id) - - property var brightnessStateType: root.deviceClass.stateTypes.findByName("brightness") - property var brightnessState: root.device.states.getState(brightnessStateType.id) - - AbstractButton { - width: app.iconSize * 2 - height: width - ColorIcon { - anchors.fill: parent - name: "../images/light-off.svg" - color: powerRow.powerState.value === true ? keyColor : app.accentColor - } - onClicked: { - var actionType = root.deviceClass.actionTypes.findByName("power"); - var params = [] - var param = {} - param["paramTypeId"] = actionType.paramTypes.get(0).id; - param["value"] = false; - params.push(param) - Engine.deviceManager.executeAction(root.device.id, powerRow.powerStateType.id, params); - } - } - - ThrottledSlider { - id: brightnessSlider - Layout.fillWidth: true - value: powerRow.brightnessState.value - onMoved: { - var actionType = root.deviceClass.actionTypes.findByName("brightness"); - var params = [] - var param = {} - param["paramTypeId"] = actionType.paramTypes.get(0).id; - param["value"] = value; - params.push(param) - Engine.deviceManager.executeAction(root.device.id, powerRow.brightnessStateType.id, params); - } - } - - AbstractButton { - width: app.iconSize * 2 - height: width - ColorIcon { - anchors.fill: parent - name: "../images/light-on.svg" - color: powerRow.powerState.value === true ? app.accentColor : keyColor - } - onClicked: { - var actionType = root.deviceClass.actionTypes.findByName("power"); - var params = [] - var param = {} - param["paramTypeId"] = actionType.paramTypes.get(0).id; - param["value"] = true; - params.push(param) - Engine.deviceManager.executeAction(root.device.id, powerRow.powerStateType.id, params); - } - } - } - - - ColorPickerCt { - id: pickerCt - Layout.fillWidth: true - Layout.margins: app.margins - property var actionType: root.deviceClass.actionTypes.findByName("colorTemperature") - property var stateType: root.deviceClass.stateTypes.findByName("colorTemperature") - property var ctState: actionType ? root.device.states.getState(actionType.id) : null - ct: ctState ? ctState.value : 0 - visible: root.deviceClass.interfaces.indexOf("colorlight") >= 0 - minCt: actionType.paramTypes.findByName("colorTemperature").minValue - maxCt: actionType.paramTypes.findByName("colorTemperature").maxValue - - height: 80 - - touchDelegate: Rectangle { - height: pickerCt.height - width: 5 - color: "black" - } - - property var lastSentTime: new Date() - onCtChanged: { - var currentTime = new Date(); - if (pressed && currentTime - lastSentTime > 200) { - setColorTemp(ct) - lastSentTime = currentTime - } - } - - function setColorTemp(ct) { - var actionType = root.deviceClass.actionTypes.findByName("colorTemperature"); - var params = [] - var param = {} - param["paramTypeId"] = actionType.paramTypes.get(0).id; - param["value"] = ct; - params.push(param) - Engine.deviceManager.executeAction(root.device.id, actionType.id, params); - } - - } - - ColorPicker { - id: colorPicker - Layout.fillWidth: true - Layout.fillHeight: true - Layout.margins: app.margins - property var actionType: root.deviceClass.actionTypes.findByName("color") - property var actionState: actionType ? root.device.states.getState(actionType.id).value : null - visible: root.deviceClass.interfaces.indexOf("colorlight") >= 0 - - color: actionState ? actionState : "white" - touchDelegate: Rectangle { - height: 15 - width: height - radius: height / 2 - color: Material.accent - - Rectangle { - color: colorPicker.hovered || colorPicker.pressed ? "#11000000" : "transparent" - anchors.centerIn: parent - height: 30 - width: height - radius: width / 2 - Behavior on color { - ColorAnimation { - duration: 200 - } - } - } - } - - property var lastSentTime: new Date() - onColorChanged: { - var currentTime = new Date(); - if (pressed && currentTime - lastSentTime > 200) { - var params = []; - var param1 = new Object(); - param1["paramTypeId"] = actionType.paramTypes.get(0).id; - param1["value"] = color; - params.push(param1) - Engine.deviceManager.executeAction(root.device.id, actionType.id, params) - lastSentTime = currentTime - } - } - } - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.preferredHeight: 0 - } - } -} diff --git a/nymea-app/ui/devicepages/LightDevicePage.qml b/nymea-app/ui/devicepages/LightDevicePage.qml new file mode 100644 index 00000000..60415056 --- /dev/null +++ b/nymea-app/ui/devicepages/LightDevicePage.qml @@ -0,0 +1,155 @@ +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" + +DevicePageBase { + id: root + + readonly property var powerStateType: deviceClass.stateTypes.findByName("power") + readonly property var powerState: device.states.getState(powerStateType.id) + readonly property var powerActionType: deviceClass.actionTypes.findByName("power"); + + readonly property var brightnessStateType: deviceClass.stateTypes.findByName("brightness") + readonly property var brightnessState: brightnessStateType ? device.states.getState(brightnessStateType.id) : null + readonly property var brightnessActionType: deviceClass.actionTypes.findByName("brightness"); + + readonly property var colorStateType: deviceClass.stateTypes.findByName("color") + readonly property var colorState: colorStateType ? device.states.getState(colorStateType.id) : null + readonly property var colorActionType: deviceClass.actionTypes.findByName("color") + + readonly property var ctStateType: deviceClass.stateTypes.findByName("colorTemperature") + readonly property var ctState: ctStateType ? device.states.getState(ctStateType.id) : null + readonly property var ctActionType: deviceClass.actionTypes.findByName("colorTemperature") + + + GridLayout { + anchors.fill: parent + columns: app.landscape ? 2 : 1 + + AbstractButton { + Layout.preferredWidth: app.iconSize * 4 + Layout.preferredHeight: width + Layout.leftMargin: app.margins + Layout.rightMargin: app.landscape ? 0 : app.margins + Layout.topMargin: app.margins + Layout.bottomMargin: app.landscape ? app.margins : 0 + Layout.alignment: Qt.AlignCenter + + ColorIcon { + anchors.fill: parent + 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); + } + } + + ColumnLayout { + Layout.fillWidth: true + visible: root.brightnessStateType + + BrightnessSlider { + Layout.fillWidth: true + Layout.margins: app.margins + Layout.preferredHeight: 40 + brightness: root.brightnessState ? root.brightnessState.value : 0 + visible: root.brightnessStateType + 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); + } + } + + ColorPickerCt { + id: pickerCt + Layout.fillWidth: true + Layout.preferredHeight: 40 + Layout.margins: app.margins + ct: root.ctState ? root.ctState.value : 0 + visible: root.ctStateType + minCt: root.ctActionType ? root.ctActionType.paramTypes.findByName("colorTemperature").minValue : 0 + maxCt: root.ctActionType ? root.ctActionType.paramTypes.findByName("colorTemperature").maxValue : 0 + + + touchDelegate: Rectangle { + height: pickerCt.height + width: 5 + color: app.foregroundColor + } + + 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); + } + } + ColorPicker { + id: colorPicker + Layout.fillWidth: true + Layout.preferredHeight: 80 + Layout.margins: app.margins + visible: root.colorStateType + + color: root.colorState ? root.colorState.value : "white" + touchDelegate: Rectangle { + height: 15 + width: height + radius: height / 2 + color: app.foregroundColor + + Rectangle { + color: colorPicker.hovered || colorPicker.pressed ? "#11000000" : "transparent" + anchors.centerIn: parent + height: 30 + width: height + radius: width / 2 + Behavior on color { + ColorAnimation { + duration: 200 + } + } + } + } + + property var lastSentTime: new Date() + onColorChanged: { + var currentTime = new Date(); + if (pressed && currentTime - lastSentTime > 200) { + var params = []; + var param1 = {}; + param1["paramTypeId"] = root.colorActionType.paramTypes.get(0).id; + param1["value"] = color; + params.push(param1) + Engine.deviceManager.executeAction(root.device.id, root.colorActionType.id, params) + lastSentTime = currentTime + } + } + } + } + } +}