import QtQuick 2.15
import QtQuick.Controls as QQC2
import QtQuick.Layouts 1.15
import org.kde.plasma.components as PlasmaComponents3
import org.kde.kirigami as Kirigami
import org.kde.plasma.core as PlasmaCore
Item {
id: entityControl
property string entityId: ""
property string displayName: ""
property string controlType: "toggle"
property string iconName: "home-assistant"
property bool showLabel: true
property string buttonSize: "medium"
property var entityState: null
property bool isOn: entityState ? (entityState.state === "on" || entityState.state === "home" || entityState.state === "open") : false
property bool isLoadingState: false
property bool hasValidState: entityState !== null && entityState !== undefined
property bool showExpandedControls: false
// Light-specific properties
readonly property bool isLight: entityId.split('.')[0] === 'light'
readonly property bool supportsRgb: entityState && entityState.attributes && entityState.attributes.supported_color_modes &&
(entityState.attributes.supported_color_modes.indexOf('rgb') !== -1 ||
entityState.attributes.supported_color_modes.indexOf('rgbw') !== -1 ||
entityState.attributes.supported_color_modes.indexOf('rgbww') !== -1)
readonly property bool supportsCct: entityState && entityState.attributes && entityState.attributes.supported_color_modes &&
(entityState.attributes.supported_color_modes.indexOf('color_temp') !== -1)
readonly property bool supportsXy: entityState && entityState.attributes && entityState.attributes.supported_color_modes &&
(entityState.attributes.supported_color_modes.indexOf('xy') !== -1)
readonly property bool supportsBrightness: entityState && entityState.attributes && entityState.attributes.supported_color_modes &&
(entityState.attributes.supported_color_modes.indexOf('brightness') !== -1 ||
entityState.attributes.brightness !== undefined)
readonly property int currentBrightness: entityState && entityState.attributes && entityState.attributes.brightness ?
Math.round(entityState.attributes.brightness / 255 * 100) : 0
readonly property var currentRgbColor: entityState && entityState.attributes && entityState.attributes.rgb_color ?
entityState.attributes.rgb_color : [255, 255, 255]
readonly property int currentColorTemp: entityState && entityState.attributes && entityState.attributes.color_temp ?
entityState.attributes.color_temp : 370
readonly property var currentXyColor: entityState && entityState.attributes && entityState.attributes.xy_color ?
entityState.attributes.xy_color : [0.3127, 0.3290] // D65 white point
readonly property real currentHue: xyToHue(currentXyColor[0], currentXyColor[1])
readonly property real currentSaturation: xyToSaturation(currentXyColor[0], currentXyColor[1])
signal controlActivated(string entityId, string action, var data)
signal stateRequested(string entityId)
readonly property int buttonSizePixels: {
switch (buttonSize) {
case "small": return Kirigami.Units.iconSizes.medium
case "large": return Kirigami.Units.iconSizes.huge
default: return Kirigami.Units.iconSizes.large
}
}
readonly property bool hasAdvancedControls: isLight && controlType === "light" && (supportsRgb || supportsCct || supportsXy || supportsBrightness)
width: showExpandedControls ? parent.width : (buttonSizePixels + (showLabel ? Kirigami.Units.smallSpacing : 0))
height: showExpandedControls ? parent.height : (buttonSizePixels + (showLabel ? Kirigami.Units.gridUnit : 0))
// Expanded controls layout
ColumnLayout {
visible: showExpandedControls
anchors.fill: parent
anchors.margins: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing * 1.5
// Header with entity info
RowLayout {
Layout.fillWidth: true
Kirigami.Icon {
source: getIconName()
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
color: isOn ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.textColor
}
ColumnLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
PlasmaComponents3.Label {
text: displayName
font.bold: true
Layout.fillWidth: true
}
PlasmaComponents3.Label {
text: entityState ? ("State: " + entityState.state) : "State: Unavailable"
font.pointSize: Kirigami.Theme.smallFont.pointSize
color: Kirigami.Theme.disabledTextColor
Layout.fillWidth: true
}
}
// Toggle button
PlasmaComponents3.Button {
text: isOn ? "Turn Off" : "Turn On"
onClicked: handleControlClick()
}
}
// Advanced Light Controls (only for lights with advanced features)
ColumnLayout {
visible: hasAdvancedControls
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
// Brightness Control
RowLayout {
visible: supportsBrightness && isOn
Layout.fillWidth: true
Kirigami.Icon {
source: "brightness-low"
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Kirigami.Units.iconSizes.small
}
PlasmaComponents3.Label {
text: "Brightness"
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
}
QQC2.Slider {
id: expandedBrightnessSlider
Layout.fillWidth: true
from: 1
to: 100
value: currentBrightness
stepSize: 1
onMoved: {
var brightnessValue = Math.round(value * 255 / 100)
controlActivated(entityId, "turn_on", { brightness: brightnessValue })
}
}
PlasmaComponents3.Label {
text: currentBrightness + "%"
Layout.preferredWidth: Kirigami.Units.gridUnit * 1.5
}
}
// Color Temperature Control
RowLayout {
visible: supportsCct && isOn
Layout.fillWidth: true
Kirigami.Icon {
source: "weather-clear-night"
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Kirigami.Units.iconSizes.small
}
PlasmaComponents3.Label {
text: "Color Temp"
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
}
QQC2.Slider {
id: expandedColorTempSlider
Layout.fillWidth: true
from: 153 // ~6500K (cool)
to: 500 // ~2000K (warm)
value: currentColorTemp
stepSize: 1
onMoved: {
controlActivated(entityId, "turn_on", { color_temp: Math.round(value) })
}
background: Rectangle {
x: expandedColorTempSlider.leftPadding
y: expandedColorTempSlider.topPadding + expandedColorTempSlider.availableHeight / 2 - height / 2
implicitWidth: 200
implicitHeight: 8
width: expandedColorTempSlider.availableWidth
height: implicitHeight
radius: 4
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: "#87CEEB" } // Cool blue
GradientStop { position: 1.0; color: "#FFA500" } // Warm orange
}
border.color: Kirigami.Theme.separatorColor
border.width: 1
}
}
PlasmaComponents3.Label {
text: Math.round(1000000 / currentColorTemp) + "K"
Layout.preferredWidth: Kirigami.Units.gridUnit * 1.5
}
}
// RGB Color Control
ColumnLayout {
visible: supportsRgb && isOn
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
PlasmaComponents3.Label {
text: "RGB Colors"
font.bold: true
}
// Color preset buttons
GridLayout {
Layout.fillWidth: true
columns: 7
columnSpacing: Kirigami.Units.largeSpacing
rowSpacing: Kirigami.Units.largeSpacing
Repeater {
model: [
{ color: "#FF0000", name: "Red" },
{ color: "#00FF00", name: "Green" },
{ color: "#0000FF", name: "Blue" },
{ color: "#FFFF00", name: "Yellow" },
{ color: "#FF00FF", name: "Magenta" },
{ color: "#00FFFF", name: "Cyan" },
{ color: "#FFFFFF", name: "White" }
]
PlasmaComponents3.Button {
Layout.preferredWidth: Kirigami.Units.gridUnit * 1.5
Layout.preferredHeight: Kirigami.Units.gridUnit * 1.5
background: Rectangle {
radius: Kirigami.Units.cornerRadius
color: modelData.color
border.color: Qt.rgba(currentRgbColor[0]/255, currentRgbColor[1]/255, currentRgbColor[2]/255, 1.0) === color ?
Kirigami.Theme.highlightColor : Kirigami.Theme.separatorColor
border.width: Qt.rgba(currentRgbColor[0]/255, currentRgbColor[1]/255, currentRgbColor[2]/255, 1.0) === color ? 3 : 1
}
PlasmaComponents3.ToolTip {
text: modelData.name
}
onClicked: {
var rgb = hexToRgb(modelData.color)
controlActivated(entityId, "turn_on", { rgb_color: [rgb.r, rgb.g, rgb.b] })
}
}
}
}
// Current color indicator
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 1
radius: Kirigami.Units.cornerRadius
color: Qt.rgba(currentRgbColor[0]/255, currentRgbColor[1]/255, currentRgbColor[2]/255, 1.0)
border.color: Kirigami.Theme.separatorColor
border.width: 1
PlasmaComponents3.Label {
anchors.centerIn: parent
text: "Current: RGB(" + currentRgbColor[0] + ", " + currentRgbColor[1] + ", " + currentRgbColor[2] + ")"
font.pointSize: Kirigami.Theme.smallFont.pointSize
color: (currentRgbColor[0] + currentRgbColor[1] + currentRgbColor[2]) > 384 ? "black" : "white"
}
}
}
// XY Color Control (CIE 1931) with HUE Slider
ColumnLayout {
visible: supportsXy && isOn
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
PlasmaComponents3.Label {
text: "XY Color Space (CIE 1931)"
font.bold: true
}
// Hue Slider with color wheel visualization
RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
Kirigami.Icon {
source: "color-picker"
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Kirigami.Units.iconSizes.small
}
PlasmaComponents3.Label {
text: "Hue"
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
}
QQC2.Slider {
id: hueSlider
Layout.fillWidth: true
from: 0
to: 360
value: currentHue
stepSize: 1
onMoved: {
var xyColor = hueToXy(value, currentSaturation)
controlActivated(entityId, "turn_on", { xy_color: [xyColor.x, xyColor.y] })
}
background: Rectangle {
x: hueSlider.leftPadding
y: hueSlider.topPadding + hueSlider.availableHeight / 2 - height / 2
implicitWidth: 200
implicitHeight: 8
width: hueSlider.availableWidth
height: implicitHeight
radius: 4
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.000; color: "#FF0000" } // Red (0°)
GradientStop { position: 0.167; color: "#FFFF00" } // Yellow (60°)
GradientStop { position: 0.333; color: "#00FF00" } // Green (120°)
GradientStop { position: 0.500; color: "#00FFFF" } // Cyan (180°)
GradientStop { position: 0.667; color: "#0000FF" } // Blue (240°)
GradientStop { position: 0.833; color: "#FF00FF" } // Magenta (300°)
GradientStop { position: 1.000; color: "#FF0000" } // Red (360°)
}
border.color: Kirigami.Theme.separatorColor
border.width: 1
}
}
PlasmaComponents3.Label {
text: Math.round(currentHue) + "°"
Layout.preferredWidth: Kirigami.Units.gridUnit * 1.5
}
}
// Saturation Slider
RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
Kirigami.Icon {
source: "adjusthsl"
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Kirigami.Units.iconSizes.small
}
PlasmaComponents3.Label {
text: "Saturation"
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
}
QQC2.Slider {
id: saturationSlider
Layout.fillWidth: true
from: 0
to: 1
value: currentSaturation
stepSize: 0.01
onMoved: {
var xyColor = hueToXy(currentHue, value)
controlActivated(entityId, "turn_on", { xy_color: [xyColor.x, xyColor.y] })
}
background: Rectangle {
x: saturationSlider.leftPadding
y: saturationSlider.topPadding + saturationSlider.availableHeight / 2 - height / 2
implicitWidth: 200
implicitHeight: 8
width: saturationSlider.availableWidth
height: implicitHeight
radius: 4
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: "#FFFFFF" } // White (no saturation)
GradientStop { position: 1.0; color: hueToRgbColor(currentHue) } // Full saturation at current hue
}
border.color: Kirigami.Theme.separatorColor
border.width: 1
}
}
PlasmaComponents3.Label {
text: Math.round(currentSaturation * 100) + "%"
Layout.preferredWidth: Kirigami.Units.gridUnit * 1.5
}
}
// Color preset buttons for common colors in XY space
GridLayout {
Layout.fillWidth: true
columns: 6
columnSpacing: Kirigami.Units.largeSpacing
rowSpacing: Kirigami.Units.largeSpacing
Repeater {
model: [
{ xy: [0.7006, 0.2993], name: "Red", color: "#FF0000" },
{ xy: [0.1724, 0.7468], name: "Green", color: "#00FF00" },
{ xy: [0.1357, 0.0399], name: "Blue", color: "#0000FF" },
{ xy: [0.4316, 0.5016], name: "Yellow", color: "#FFFF00" },
{ xy: [0.3127, 0.3290], name: "White", color: "#FFFFFF" },
{ xy: [0.1670, 0.0090], name: "Deep Blue", color: "#000080" }
]
PlasmaComponents3.Button {
Layout.preferredWidth: Kirigami.Units.gridUnit * 1.5
Layout.preferredHeight: Kirigami.Units.gridUnit * 1.5
background: Rectangle {
radius: Kirigami.Units.cornerRadius
color: modelData.color
border.color: (Math.abs(currentXyColor[0] - modelData.xy[0]) < 0.01 &&
Math.abs(currentXyColor[1] - modelData.xy[1]) < 0.01) ?
Kirigami.Theme.highlightColor : Kirigami.Theme.separatorColor
border.width: (Math.abs(currentXyColor[0] - modelData.xy[0]) < 0.01 &&
Math.abs(currentXyColor[1] - modelData.xy[1]) < 0.01) ? 3 : 1
}
PlasmaComponents3.ToolTip {
text: modelData.name + "\nXY: (" + modelData.xy[0].toFixed(4) + ", " + modelData.xy[1].toFixed(4) + ")"
}
onClicked: {
controlActivated(entityId, "turn_on", { xy_color: modelData.xy })
}
}
}
}
// Current XY color indicator
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 1
radius: Kirigami.Units.cornerRadius
color: xyToRgbColor(currentXyColor[0], currentXyColor[1])
border.color: Kirigami.Theme.separatorColor
border.width: 1
PlasmaComponents3.Label {
anchors.centerIn: parent
text: "Current XY: (" + currentXyColor[0].toFixed(4) + ", " + currentXyColor[1].toFixed(4) + ")"
font.pointSize: Kirigami.Theme.smallFont.pointSize
color: {
// Calculate luminance to determine text color
var rgb = xyToRgb(currentXyColor[0], currentXyColor[1])
var luminance = (rgb.r + rgb.g + rgb.b) / 3
return luminance > 128 ? "black" : "white"
}
}
}
}
}
// Entity Information Section (previously shown in tooltips)
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: entityInfoColumn.implicitHeight + Kirigami.Units.largeSpacing * 2
color: Kirigami.Theme.alternateBackgroundColor
border.color: Kirigami.Theme.separatorColor
border.width: 1
radius: Kirigami.Units.cornerRadius
ColumnLayout {
id: entityInfoColumn
anchors.fill: parent
anchors.margins: Kirigami.Units.largeSpacing * 1.5
spacing: Kirigami.Units.largeSpacing
PlasmaComponents3.Label {
text: "Entity Information"
font.bold: true
Layout.fillWidth: true
}
// Entity ID
RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
PlasmaComponents3.Label {
text: "Entity ID:"
font.pointSize: Kirigami.Theme.smallFont.pointSize
color: Kirigami.Theme.disabledTextColor
Layout.preferredWidth: Kirigami.Units.gridUnit * 4
}
PlasmaComponents3.Label {
text: entityId
font.pointSize: Kirigami.Theme.smallFont.pointSize
Layout.fillWidth: true
wrapMode: Text.WrapAnywhere
}
}
// Friendly Name (if different from display name)
RowLayout {
visible: entityState && entityState.attributes && entityState.attributes.friendly_name &&
entityState.attributes.friendly_name !== displayName
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
PlasmaComponents3.Label {
text: "Friendly Name:"
font.pointSize: Kirigami.Theme.smallFont.pointSize
color: Kirigami.Theme.disabledTextColor
Layout.preferredWidth: Kirigami.Units.gridUnit * 4
}
PlasmaComponents3.Label {
text: entityState && entityState.attributes ? (entityState.attributes.friendly_name || "") : ""
font.pointSize: Kirigami.Theme.smallFont.pointSize
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
}
// Current State
RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
PlasmaComponents3.Label {
text: "State:"
font.pointSize: Kirigami.Theme.smallFont.pointSize
color: Kirigami.Theme.disabledTextColor
Layout.preferredWidth: Kirigami.Units.gridUnit * 4
}
PlasmaComponents3.Label {
text: entityState ? (entityState.state || "unknown") : "Unavailable"
font.pointSize: Kirigami.Theme.smallFont.pointSize
Layout.fillWidth: true
color: isOn ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.textColor
}
}
// Brightness (for lights)
RowLayout {
visible: entityState && entityState.attributes && entityState.attributes.brightness !== undefined
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
PlasmaComponents3.Label {
text: "Brightness:"
font.pointSize: Kirigami.Theme.smallFont.pointSize
color: Kirigami.Theme.disabledTextColor
Layout.preferredWidth: Kirigami.Units.gridUnit * 4
}
PlasmaComponents3.Label {
text: entityState && entityState.attributes && entityState.attributes.brightness ?
(Math.round(entityState.attributes.brightness / 255 * 100) + "%") : ""
font.pointSize: Kirigami.Theme.smallFont.pointSize
Layout.fillWidth: true
}
}
// RGB Color (for lights)
RowLayout {
visible: entityState && entityState.attributes && entityState.attributes.rgb_color
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
PlasmaComponents3.Label {
text: "RGB Color:"
font.pointSize: Kirigami.Theme.smallFont.pointSize
color: Kirigami.Theme.disabledTextColor
Layout.preferredWidth: Kirigami.Units.gridUnit * 4
}
PlasmaComponents3.Label {
text: entityState && entityState.attributes && entityState.attributes.rgb_color ?
entityState.attributes.rgb_color.join(", ") : ""
font.pointSize: Kirigami.Theme.smallFont.pointSize
Layout.fillWidth: true
}
}
// XY Color (for lights)
RowLayout {
visible: entityState && entityState.attributes && entityState.attributes.xy_color
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
PlasmaComponents3.Label {
text: "XY Color:"
font.pointSize: Kirigami.Theme.smallFont.pointSize
color: Kirigami.Theme.disabledTextColor
Layout.preferredWidth: Kirigami.Units.gridUnit * 4
}
PlasmaComponents3.Label {
text: entityState && entityState.attributes && entityState.attributes.xy_color ?
("(" + entityState.attributes.xy_color[0].toFixed(4) + ", " + entityState.attributes.xy_color[1].toFixed(4) + ")") : ""
font.pointSize: Kirigami.Theme.smallFont.pointSize
Layout.fillWidth: true
}
}
// Color Temperature (for lights)
RowLayout {
visible: entityState && entityState.attributes && entityState.attributes.color_temp
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
PlasmaComponents3.Label {
text: "Color Temp:"
font.pointSize: Kirigami.Theme.smallFont.pointSize
color: Kirigami.Theme.disabledTextColor
Layout.preferredWidth: Kirigami.Units.gridUnit * 4
}
PlasmaComponents3.Label {
text: entityState && entityState.attributes && entityState.attributes.color_temp ?
(Math.round(1000000 / entityState.attributes.color_temp) + "K") : ""
font.pointSize: Kirigami.Theme.smallFont.pointSize
Layout.fillWidth: true
}
}
// Supported Color Modes (for lights)
RowLayout {
visible: entityState && entityState.attributes && entityState.attributes.supported_color_modes && isLight
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
PlasmaComponents3.Label {
text: "Supported:"
font.pointSize: Kirigami.Theme.smallFont.pointSize
color: Kirigami.Theme.disabledTextColor
Layout.preferredWidth: Kirigami.Units.gridUnit * 4
}
PlasmaComponents3.Label {
text: entityState && entityState.attributes && entityState.attributes.supported_color_modes ?
entityState.attributes.supported_color_modes.join(", ") : ""
font.pointSize: Kirigami.Theme.smallFont.pointSize
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
}
// Temperature (for climate/weather entities)
RowLayout {
visible: entityState && entityState.attributes && entityState.attributes.temperature !== undefined
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
PlasmaComponents3.Label {
text: "Temperature:"
font.pointSize: Kirigami.Theme.smallFont.pointSize
color: Kirigami.Theme.disabledTextColor
Layout.preferredWidth: Kirigami.Units.gridUnit * 4
}
PlasmaComponents3.Label {
text: entityState && entityState.attributes && entityState.attributes.temperature !== undefined ?
(entityState.attributes.temperature + "°") : ""
font.pointSize: Kirigami.Theme.smallFont.pointSize
Layout.fillWidth: true
}
}
// Humidity (for climate/weather entities)
RowLayout {
visible: entityState && entityState.attributes && entityState.attributes.humidity !== undefined
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
PlasmaComponents3.Label {
text: "Humidity:"
font.pointSize: Kirigami.Theme.smallFont.pointSize
color: Kirigami.Theme.disabledTextColor
Layout.preferredWidth: Kirigami.Units.gridUnit * 4
}
PlasmaComponents3.Label {
text: entityState && entityState.attributes && entityState.attributes.humidity !== undefined ?
(entityState.attributes.humidity + "%") : ""
font.pointSize: Kirigami.Theme.smallFont.pointSize
Layout.fillWidth: true
}
}
// Battery Level (for battery-powered devices)
RowLayout {
visible: entityState && entityState.attributes && entityState.attributes.battery_level !== undefined
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
PlasmaComponents3.Label {
text: "Battery:"
font.pointSize: Kirigami.Theme.smallFont.pointSize
color: Kirigami.Theme.disabledTextColor
Layout.preferredWidth: Kirigami.Units.gridUnit * 4
}
PlasmaComponents3.Label {
text: entityState && entityState.attributes && entityState.attributes.battery_level !== undefined ?
(entityState.attributes.battery_level + "%") : ""
font.pointSize: Kirigami.Theme.smallFont.pointSize
Layout.fillWidth: true
color: {
if (entityState && entityState.attributes && entityState.attributes.battery_level !== undefined) {
var batteryLevel = entityState.attributes.battery_level
if (batteryLevel < 20) return Kirigami.Theme.negativeTextColor
if (batteryLevel < 50) return Kirigami.Theme.neutralTextColor
return Kirigami.Theme.positiveTextColor
}
return Kirigami.Theme.textColor
}
}
}
// Last Updated
RowLayout {
visible: entityState && entityState.last_updated
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
PlasmaComponents3.Label {
text: "Last Updated:"
font.pointSize: Kirigami.Theme.smallFont.pointSize
color: Kirigami.Theme.disabledTextColor
Layout.preferredWidth: Kirigami.Units.gridUnit * 4
}
PlasmaComponents3.Label {
text: {
if (entityState && entityState.last_updated) {
var lastUpdated = new Date(entityState.last_updated)
return lastUpdated.toLocaleTimeString()
}
return ""
}
font.pointSize: Kirigami.Theme.smallFont.pointSize
Layout.fillWidth: true
}
}
}
}
}
// Compact control layout
ColumnLayout {
visible: !showExpandedControls
anchors.fill: parent
spacing: Kirigami.Units.smallSpacing
// Main control button
PlasmaComponents3.Button {
id: controlButton
Layout.preferredWidth: buttonSizePixels
Layout.preferredHeight: buttonSizePixels
Layout.alignment: Qt.AlignHCenter
// Visual state based on entity state
property color normalColor: Kirigami.Theme.buttonBackgroundColor
property color activeColor: isOn ? (supportsRgb ? Qt.rgba(currentRgbColor[0]/255, currentRgbColor[1]/255, currentRgbColor[2]/255, 0.3) : Kirigami.Theme.positiveBackgroundColor) : normalColor
background: Rectangle {
radius: Kirigami.Units.cornerRadius
color: controlButton.pressed ? Qt.darker(controlButton.activeColor, 1.1) :
controlButton.hovered ? Qt.lighter(controlButton.activeColor, 1.1) :
controlButton.activeColor
border.color: isOn ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.buttonFocusColor
border.width: isOn ? 2 : 1
Behavior on color { ColorAnimation { duration: 150 } }
Behavior on border.color { ColorAnimation { duration: 150 } }
// Advanced controls indicator for lights
Rectangle {
visible: hasAdvancedControls
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: 2
width: 6
height: 6
radius: 3
color: Kirigami.Theme.disabledTextColor
}
}
contentItem: RowLayout {
spacing: 0
Kirigami.Icon {
source: getIconName()
color: isOn ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.textColor
Layout.fillWidth: true
Layout.fillHeight: true
Behavior on color { ColorAnimation { duration: 150 } }
}
// Loading indicator
PlasmaComponents3.BusyIndicator {
visible: isLoadingState
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Kirigami.Units.iconSizes.small
Layout.alignment: Qt.AlignCenter
}
}
// PlasmaComponents3.ToolTip {
// text: getTooltipText()
// }
onClicked: handleControlClick()
}
// Entity label
PlasmaComponents3.Label {
visible: showLabel
text: displayName
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
font.pointSize: Kirigami.Theme.smallFont.pointSize
color: isOn ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.textColor
Behavior on color { ColorAnimation { duration: 150 } }
}
}
function getIconName() {
if (iconName && iconName !== "home-assistant") {
return iconName
}
// Auto-detect icon based on entity type and state
var domain = entityId.split('.')[0]
switch (domain) {
case 'light':
return isOn ? "lightbulb-on" : "lightbulb"
case 'switch':
return isOn ? "toggle-switch" : "toggle-switch-off"
case 'fan':
return isOn ? "weather-windy" : "fan"
case 'automation':
return isOn ? "playlist-play" : "playlist-remove"
case 'climate':
return "thermometer"
case 'lock':
return entityState && entityState.state === "locked" ? "lock" : "lock-open"
case 'cover':
return entityState && entityState.state === "open" ? "window-open" : "window-close"
case 'input_boolean':
return isOn ? "checkbox-marked" : "checkbox-blank-outline"
default:
return "home-assistant"
}
}
function getTooltipText() {
return ""
var tooltip = displayName
if (isLoadingState) {
tooltip += "\nState: Loading..."
return tooltip
}
if (!hasValidState) {
tooltip += "\nState: Unavailable"
tooltip += "\nClick to refresh"
return tooltip
}
var stateText = entityState.state || "unknown"
// Use friendly name if available
if (entityState.attributes && entityState.attributes.friendly_name) {
tooltip = entityState.attributes.friendly_name
}
tooltip += "\nState: " + stateText
// Only show basic info in tooltip, since full details are in expanded view
if (entityState.attributes) {
// Show only the most relevant single piece of info
if (entityState.attributes.brightness && isOn) {
tooltip += "\nBrightness: " + Math.round(entityState.attributes.brightness / 255 * 100) + "%"
} else if (entityState.attributes.temperature !== undefined) {
tooltip += "\nTemperature: " + entityState.attributes.temperature + "°"
} else if (entityState.attributes.battery_level !== undefined) {
tooltip += "\nBattery: " + entityState.attributes.battery_level + "%"
}
}
// Add instruction for expanded view
if (showExpandedControls) {
tooltip += "\n\nDetailed information shown below"
} else {
if (isLight && controlType === "light" && (supportsRgb || supportsCct || supportsBrightness)) {
tooltip += "\n\nRight-click for advanced controls & details"
} else {
tooltip += "\n\nRight-click for detailed information"
}
}
return tooltip
}
function handleControlClick() {
switch (controlType) {
case "toggle":
controlActivated(entityId, "toggle", {})
break
case "switch":
controlActivated(entityId, isOn ? "turn_off" : "turn_on", {})
break
case "light":
// For lights, we could show a context menu or brightness slider
controlActivated(entityId, "toggle", {})
break
case "status":
// Status displays don't perform actions, just refresh state
stateRequested(entityId)
break
default:
controlActivated(entityId, "toggle", {})
break
}
// Always try to refresh state after any action (or for status entities)
if (controlType !== "status") {
Qt.callLater(function() {
stateRequested(entityId)
})
}
}
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
// XY to RGB conversion using simplified sRGB primaries
function xyToRgb(x, y) {
// Calculate z coordinate
var z = 1.0 - x - y;
// Convert XYZ to sRGB using sRGB transformation matrix
var Y = 1.0; // Assume maximum brightness for color calculation
var X = (Y / y) * x;
var Z = (Y / y) * z;
// sRGB transformation matrix
var r = X * 3.2406 + Y * -1.5372 + Z * -0.4986;
var g = X * -0.9689 + Y * 1.8758 + Z * 0.0415;
var b = X * 0.0557 + Y * -0.2040 + Z * 1.0570;
// Apply gamma correction
r = r > 0.0031308 ? 1.055 * Math.pow(r, 1.0 / 2.4) - 0.055 : 12.92 * r;
g = g > 0.0031308 ? 1.055 * Math.pow(g, 1.0 / 2.4) - 0.055 : 12.92 * g;
b = b > 0.0031308 ? 1.055 * Math.pow(b, 1.0 / 2.4) - 0.055 : 12.92 * b;
// Clamp values to [0, 1] and convert to [0, 255]
r = Math.max(0, Math.min(1, r)) * 255;
g = Math.max(0, Math.min(1, g)) * 255;
b = Math.max(0, Math.min(1, b)) * 255;
return {
r: Math.round(r),
g: Math.round(g),
b: Math.round(b)
};
}
// XY to RGB color string
function xyToRgbColor(x, y) {
var rgb = xyToRgb(x, y);
return Qt.rgba(rgb.r / 255, rgb.g / 255, rgb.b / 255, 1.0);
}
// Convert XY coordinates to HSV hue (0-360 degrees)
function xyToHue(x, y) {
var rgb = xyToRgb(x, y);
var r = rgb.r / 255;
var g = rgb.g / 255;
var b = rgb.b / 255;
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var delta = max - min;
if (delta === 0) return 0;
var hue;
if (max === r) {
hue = ((g - b) / delta) % 6;
} else if (max === g) {
hue = (b - r) / delta + 2;
} else {
hue = (r - g) / delta + 4;
}
hue = hue * 60;
if (hue < 0) hue += 360;
return hue;
}
// Convert XY coordinates to HSV saturation (0-1)
function xyToSaturation(x, y) {
var rgb = xyToRgb(x, y);
var r = rgb.r / 255;
var g = rgb.g / 255;
var b = rgb.b / 255;
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
if (max === 0) return 0;
return (max - min) / max;
}
// Convert HSV hue and saturation to XY coordinates
function hueToXy(hue, saturation) {
// Convert HSV to RGB first
var h = hue / 60;
var s = saturation;
var v = 1.0; // Maximum value for pure color
var c = v * s;
var x = c * (1 - Math.abs((h % 2) - 1));
var m = v - c;
var r, g, b;
if (h >= 0 && h < 1) {
r = c; g = x; b = 0;
} else if (h >= 1 && h < 2) {
r = x; g = c; b = 0;
} else if (h >= 2 && h < 3) {
r = 0; g = c; b = x;
} else if (h >= 3 && h < 4) {
r = 0; g = x; b = c;
} else if (h >= 4 && h < 5) {
r = x; g = 0; b = c;
} else {
r = c; g = 0; b = x;
}
r += m; g += m; b += m;
// Convert RGB to XYZ
// Apply inverse gamma correction
r = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
g = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
b = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
// sRGB to XYZ transformation matrix
var X = r * 0.4124 + g * 0.3576 + b * 0.1805;
var Y = r * 0.2126 + g * 0.7152 + b * 0.0722;
var Z = r * 0.0193 + g * 0.1192 + b * 0.9505;
// Convert XYZ to xy
var sum = X + Y + Z;
if (sum === 0) {
return { x: 0.3127, y: 0.3290 }; // D65 white point
}
var x_coord = X / sum;
var y_coord = Y / sum;
// Clamp to valid CIE 1931 color space bounds
x_coord = Math.max(0, Math.min(1, x_coord));
y_coord = Math.max(0, Math.min(1, y_coord));
return { x: x_coord, y: y_coord };
}
// Convert hue to RGB color string for gradient display
function hueToRgbColor(hue) {
var h = hue / 60;
var c = 1; // Full chroma
var x = c * (1 - Math.abs((h % 2) - 1));
var r, g, b;
if (h >= 0 && h < 1) {
r = c; g = x; b = 0;
} else if (h >= 1 && h < 2) {
r = x; g = c; b = 0;
} else if (h >= 2 && h < 3) {
r = 0; g = c; b = x;
} else if (h >= 3 && h < 4) {
r = 0; g = x; b = c;
} else if (h >= 4 && h < 5) {
r = x; g = 0; b = c;
} else {
r = c; g = 0; b = x;
}
return Qt.rgba(r, g, b, 1.0);
}
}
neiam
/home-assistant-plasmoid
Home Assistant Plasmoid
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
48.2 KiB · text
History
13e402a