C

Qt Quick Ultralite Thermostat Demo

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
import QtQuick 2.15
import QtQuickUltralite.Extras 2.0
import Thermo 1.0

ThermoView {
    id: root

    property int maxTemp: Units.fahrenheitToTemperatureUnit(90)
    property int minTemp: Units.fahrenheitToTemperatureUnit(50)
    property int prevTemp: GlobalState.selectedRoom.temperature
    property int smallestAngle: 25
    property alias thermoOn: powerBtn.checked

    function _setTemperatureImpl(t: real, stopAnimation: bool) {
        GlobalState.selectedRoom.temperature = Math.min(Math.max(Math.round(t), minTemp), maxTemp);
        tens.stopAnimation = stopAnimation;
        ones.stopAnimation = stopAnimation;
        tens.reversed = GlobalState.selectedRoom.temperature < prevTemp;
        ones.reversed = GlobalState.selectedRoom.temperature < prevTemp;
        prevTemp = GlobalState.selectedRoom.temperature;
    }
    function setTemperature(t: real) {
        _setTemperatureImpl(t, false);
    }
    function setTemperatureWithoutAnimation(t: real) {
        _setTemperatureImpl(t, true);
    }

    Item {
        anchors.horizontalCenter: parent.horizontalCenter
        height: width
        width: wheelImg.width
        y: Theme.thermoY

        Image {
            id: wheelImg
            source: root.thermoOn ? "jog.png" : "jog-off.png"
        }
        Row {
            id: thermoText
            anchors.centerIn: parent
            clip: true
            height: Theme.roomViewTempHeight
            width: Theme.roomViewTempWidth

            AnimatedDigit {
                id: tens
                stopAnimation: true
                value: GlobalState.selectedRoom.temperature / 10
                visible: root.thermoOn
            }
            AnimatedDigit {
                id: ones
                stopAnimation: true
                value: GlobalState.selectedRoom.temperature % 10
                visible: root.thermoOn
            }
        }
        Image {
            id: digitMaskTop
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.top: thermoText.top
            source: "digitMaskTop.png"
        }
        Image {
            id: digitMaskBottom
            anchors.bottom: thermoText.bottom
            anchors.horizontalCenter: parent.horizontalCenter
            source: "digitMaskBottom.png"
        }
        Text {
            anchors.left: thermoText.right
            anchors.top: thermoText.top
            anchors.topMargin: Theme.isBig ? 13 : 11
            color: "#3d464d"
            font.pixelSize: Theme.wheelTempUnitSize
            text: Units.temperatureSymbol
            visible: root.thermoOn
        }
        MouseArea {
            height: parent.height / 2
            visible: root.thermoOn
            width: parent.width

            onClicked: {
                root.setTemperature(GlobalState.selectedRoom.temperature + 1);
            }

            Image {
                anchors.centerIn: parent
                source: parent.pressed ? "pressed-bg-up.png" : ""
            }
            ColorizedImage {
                anchors.centerIn: parent
                anchors.verticalCenterOffset: Theme.tempControlOffset
                color: parent.pressed ? ColorStyle.blue : ColorStyle.greyMedium3
                source: "temp-up-pressed.png"
            }
        }
        MouseArea {
            anchors.bottom: parent.bottom
            height: parent.height / 2
            visible: root.thermoOn
            width: parent.width

            onClicked: {
                root.setTemperature(GlobalState.selectedRoom.temperature - 1);
            }

            Image {
                anchors.centerIn: parent
                source: parent.pressed ? "pressed-bg-down.png" : ""
            }
            ColorizedImage {
                anchors.centerIn: parent
                anchors.verticalCenterOffset: -Theme.tempControlOffset
                color: parent.pressed ? ColorStyle.blue : ColorStyle.greyMedium3
                source: "temp-down-pressed.png"
            }
        }
        Image {
            id: thermoHandle

            property real angle: (90 + root.smallestAngle + (GlobalState.selectedRoom.temperature - root.minTemp) / (root.maxTemp - root.minTemp) * (360 - 2 * root.smallestAngle)) * 2 * Math.PI / 360

            source: "images/inner-circle.png"
            visible: root.thermoOn
            x: parent.width / 2 - width / 2 + Theme.wheelSize * Math.cos(angle)
            y: parent.height / 2 - height / 2 + Theme.wheelSize * Math.sin(angle)
            z: 10

            MouseArea {
                property real pressedX: 0
                property real pressedY: 0

                anchors.centerIn: parent
                height: Theme.thermoHandleSize
                width: Theme.thermoHandleSize

                onPositionChanged: {
                    var px = mouse.x - pressedX + x + width / 2 + parent.x - parent.parent.width / 2;
                    var py = mouse.y - pressedY + y + height / 2 + parent.y - parent.parent.height / 2;
                    var d = Math.sqrt(px * px + py * py);
                    if (d < Theme.wheelSize * 0.6)
                        return; // too far from the position
                    var angle = Math.atan2(px / d, py / d) * 360 / (Math.PI * 2);
                    angle = (360 - angle);
                    while (angle > 360)
                        angle -= 360;
                    var temperature = root.minTemp + (angle - root.smallestAngle) * (root.maxTemp - root.minTemp) / (360 - 2 * root.smallestAngle);
                    root.setTemperatureWithoutAnimation(temperature);
                }
                onPressed: {
                    pressedX = mouse.x;
                    pressedY = mouse.y;
                }

                Image {
                    anchors.centerIn: parent
                    source: "thermo-handle.png"
                    visible: parent.pressed
                }
            }
        }
    }
    Text {
        anchors.bottom: parent.bottom
        anchors.bottomMargin: Theme.isBig ? 36 : 12
        anchors.horizontalCenter: parent.horizontalCenter
        color: GlobalState.selectedRoom.status == Room.Off ? ColorStyle.greyDark1 : ColorStyle.greyDark4
        font.pixelSize: Theme.wheelStatusTextSize
        text: {
            switch (GlobalState.selectedRoom.status) {
            //% "Heating"
            case Room.Heating:
                return qsTrId("id-heating");
            //% "Cooling"
            case Room.Cooling:
                return qsTrId("id-cooling");
            //% "Off"
            default:
                return qsTrId("id-off");
            }
        }
        visible: root.thermoOn || Theme.isBig
    }
    ColorizedImage {
        anchors.bottom: parent.bottom
        anchors.bottomMargin: Theme.isBig ? 15 : 0
        anchors.horizontalCenter: parent.horizontalCenter
        color: GlobalState.selectedRoom.status == Room.Heating ? ColorStyle.pinkyRed : ColorStyle.blue
        source: "status-small.png"
        visible: root.thermoOn
    }
    Column {
        anchors.left: parent.left
        anchors.leftMargin: Theme.roomButtonMargin
        spacing: Theme.roomButtonSpacing
        y: Theme.roomButtonsY

        RoomControlButton {
            id: autoBtn
            checked: GlobalState.selectedRoom.auto_
            enabled: root.thermoOn
            icon.source: "auto-on.png"

            onCheckedChanged: {
                if (checked) {
                    dryerBtn.checked = fanBtn.checked = ecoBtn.checked = streamerBtn.checked = false;
                }
                GlobalState.selectedRoom.auto_ = checked;
            }
        }
        RoomControlButton {
            id: dryerBtn
            checked: GlobalState.selectedRoom.dryer
            enabled: root.thermoOn
            icon.source: "dryer-on.png"

            onCheckedChanged: {
                if (checked) {
                    autoBtn.checked = false;
                }
                GlobalState.selectedRoom.dryer = checked;
            }
        }
        FanControlButton {
            id: fanBtn
            currentRoom: GlobalState.selectedRoom
            enabled: root.thermoOn
        }
    }
    Column {
        anchors.right: parent.right
        anchors.rightMargin: Theme.roomButtonMargin
        spacing: Theme.roomButtonSpacing
        y: Theme.roomButtonsY

        RoomControlButton {
            id: powerBtn
            checked: GlobalState.selectedRoom.power
            icon.source: "power-on.png"
            pulsing: !checked

            onCheckedChanged: {
                GlobalState.selectedRoom.power = checked;
            }
        }
        RoomControlButton {
            id: ecoBtn
            checked: GlobalState.selectedRoom.eco
            enabled: root.thermoOn
            icon.source: "eco-on.png"

            onCheckedChanged: {
                if (checked) {
                    autoBtn.checked = false;
                }
                GlobalState.selectedRoom.eco = checked;
            }
        }
        RoomControlButton {
            id: streamerBtn
            checked: GlobalState.selectedRoom.streamer
            enabled: root.thermoOn
            icon.source: "streamer-on.png"

            onCheckedChanged: {
                if (checked) {
                    autoBtn.checked = false;
                }
                GlobalState.selectedRoom.streamer = checked;
            }
        }
    }
}