import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
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 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
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 || supportsBrightness)
width: buttonSizePixels + (showLabel ? Kirigami.Units.smallSpacing : 0)
height: buttonSizePixels + (showLabel ? Kirigami.Units.gridUnit : 0)
ColumnLayout {
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()
// Right-click to show advanced controls popup for lights
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
if (hasAdvancedControls) {
advancedControlsPopup.open()
}
}
}
}
// 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 } }
}
}
// Advanced Controls Popup
QQC2.Popup {
id: advancedControlsPopup
parent: entityControl
modal: true
focus: true
closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutside
width: Kirigami.Units.gridUnit * 16
height: Kirigami.Units.gridUnit * 12
x: (parent.width - width) / 2
y: parent.height + Kirigami.Units.smallSpacing
background: Rectangle {
color: Kirigami.Theme.backgroundColor
border.color: Kirigami.Theme.separatorColor
border.width: 1
radius: Kirigami.Units.cornerRadius
// Add a subtle shadow effect
Rectangle {
anchors.fill: parent
anchors.margins: -2
z: -1
color: "transparent"
border.color: Qt.rgba(0, 0, 0, 0.1)
border.width: 1
radius: Kirigami.Units.cornerRadius + 1
}
}
contentItem: ColumnLayout {
spacing: Kirigami.Units.largeSpacing
// Popup Header
RowLayout {
Layout.fillWidth: true
Kirigami.Icon {
source: getIconName()
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Kirigami.Units.iconSizes.small
color: isOn ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.textColor
}
PlasmaComponents3.Label {
text: displayName
font.bold: true
Layout.fillWidth: true
}
PlasmaComponents3.Button {
icon.name: "window-close"
flat: true
onClicked: advancedControlsPopup.close()
}
}
// Brightness Control
RowLayout {
visible: supportsBrightness && isOn
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 1.5
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: popupBrightnessSlider
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 1.5
from: 1
to: 100
value: currentBrightness
stepSize: 1
onMoved: {
var brightnessValue = Math.round(value * 255 / 100)
controlActivated(entityId, "turn_on", { brightness: brightnessValue })
}
background: Rectangle {
x: popupBrightnessSlider.leftPadding
y: popupBrightnessSlider.topPadding + popupBrightnessSlider.availableHeight / 2 - height / 2
implicitWidth: 200
implicitHeight: 8
width: popupBrightnessSlider.availableWidth
height: implicitHeight
radius: 4
color: Kirigami.Theme.backgroundColor
border.color: Kirigami.Theme.separatorColor
Rectangle {
width: popupBrightnessSlider.visualPosition * parent.width
height: parent.height
color: Kirigami.Theme.highlightColor
radius: 4
}
}
handle: Rectangle {
x: popupBrightnessSlider.leftPadding + popupBrightnessSlider.visualPosition * (popupBrightnessSlider.availableWidth - width)
y: popupBrightnessSlider.topPadding + popupBrightnessSlider.availableHeight / 2 - height / 2
implicitWidth: 20
implicitHeight: 20
radius: 10
color: popupBrightnessSlider.pressed ? Qt.darker(Kirigami.Theme.highlightColor, 1.1) : Kirigami.Theme.highlightColor
border.color: Kirigami.Theme.backgroundColor
border.width: 2
}
}
PlasmaComponents3.Label {
text: currentBrightness + "%"
Layout.preferredWidth: Kirigami.Units.gridUnit * 1.5
}
}
// Color Temperature Control
RowLayout {
visible: supportsCct && isOn
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 1.5
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: popupColorTempSlider
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 1.5
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: popupColorTempSlider.leftPadding
y: popupColorTempSlider.topPadding + popupColorTempSlider.availableHeight / 2 - height / 2
implicitWidth: 200
implicitHeight: 8
width: popupColorTempSlider.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
}
handle: Rectangle {
x: popupColorTempSlider.leftPadding + popupColorTempSlider.visualPosition * (popupColorTempSlider.availableWidth - width)
y: popupColorTempSlider.topPadding + popupColorTempSlider.availableHeight / 2 - height / 2
implicitWidth: 20
implicitHeight: 20
radius: 10
color: popupColorTempSlider.pressed ? Qt.darker(Kirigami.Theme.highlightColor, 1.1) : Kirigami.Theme.highlightColor
border.color: Kirigami.Theme.backgroundColor
border.width: 2
}
}
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.smallSpacing
PlasmaComponents3.Label {
text: "RGB Colors"
font.bold: true
}
// Color preset buttons
GridLayout {
Layout.fillWidth: true
columns: 7
columnSpacing: Kirigami.Units.smallSpacing
rowSpacing: Kirigami.Units.smallSpacing
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"
}
}
}
}
}
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() {
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
// Add additional info for specific entity types
if (entityState.attributes) {
if (entityState.attributes.brightness) {
tooltip += "\nBrightness: " + Math.round(entityState.attributes.brightness / 255 * 100) + "%"
}
if (entityState.attributes.rgb_color) {
tooltip += "\nRGB: " + entityState.attributes.rgb_color.join(", ")
}
if (entityState.attributes.color_temp) {
tooltip += "\nColor Temp: " + Math.round(1000000 / entityState.attributes.color_temp) + "K"
}
if (entityState.attributes.supported_color_modes && isLight) {
tooltip += "\nSupported: " + entityState.attributes.supported_color_modes.join(", ")
}
if (entityState.attributes.temperature) {
tooltip += "\nTemperature: " + entityState.attributes.temperature + "°"
}
if (entityState.attributes.humidity) {
tooltip += "\nHumidity: " + entityState.attributes.humidity + "%"
}
if (entityState.attributes.battery_level) {
tooltip += "\nBattery: " + entityState.attributes.battery_level + "%"
}
// Add last updated time
if (entityState.last_updated) {
var lastUpdated = new Date(entityState.last_updated)
tooltip += "\nLast updated: " + lastUpdated.toLocaleTimeString()
}
}
// Add instruction for light controls
if (isLight && controlType === "light" && (supportsRgb || supportsCct || supportsBrightness)) {
tooltip += "\n\nRight-click for advanced controls"
}
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;
}
}
neiam
/home-assistant-plasmoid
Home Assistant Plasmoid
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
23.3 KiB · text
History
ac76a3b