chkpt

608d4a9 · gmorell · 2025-06-24 21:17

5 files +617 -416

Files changed

modified contents/ui/EntityControl.qml
+211 −282
@@ -51,10 +51,216 @@ Item {
51 51
52 52 readonly property bool hasAdvancedControls: isLight && controlType === "light" && (supportsRgb || supportsCct || supportsBrightness)
53 53
54 − width: buttonSizePixels + (showLabel ? Kirigami.Units.smallSpacing : 0)
55 − height: buttonSizePixels + (showLabel ? Kirigami.Units.gridUnit : 0)
54 + width: showExpandedControls ? parent.width : (buttonSizePixels + (showLabel ? Kirigami.Units.smallSpacing : 0))
55 + height: showExpandedControls ? parent.height : (buttonSizePixels + (showLabel ? Kirigami.Units.gridUnit : 0))
56 56
57 + // Expanded controls layout
57 58 ColumnLayout {
59 + visible: showExpandedControls && hasAdvancedControls
60 + anchors.fill: parent
61 + spacing: Kirigami.Units.largeSpacing
62 +
63 + // Header with entity info
64 + RowLayout {
65 + Layout.fillWidth: true
66 +
67 + Kirigami.Icon {
68 + source: getIconName()
69 + Layout.preferredWidth: Kirigami.Units.iconSizes.medium
70 + Layout.preferredHeight: Kirigami.Units.iconSizes.medium
71 + color: isOn ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.textColor
72 + }
73 +
74 + ColumnLayout {
75 + Layout.fillWidth: true
76 + spacing: Kirigami.Units.smallSpacing
77 +
78 + PlasmaComponents3.Label {
79 + text: displayName
80 + font.bold: true
81 + Layout.fillWidth: true
82 + }
83 +
84 + PlasmaComponents3.Label {
85 + text: entityState ? ("State: " + entityState.state) : "State: Unavailable"
86 + font.pointSize: Kirigami.Theme.smallFont.pointSize
87 + color: Kirigami.Theme.disabledTextColor
88 + Layout.fillWidth: true
89 + }
90 + }
91 +
92 + // Toggle button
93 + PlasmaComponents3.Button {
94 + text: isOn ? "Turn Off" : "Turn On"
95 + onClicked: handleControlClick()
96 + }
97 + }
98 +
99 + // Brightness Control
100 + RowLayout {
101 + visible: supportsBrightness && isOn
102 + Layout.fillWidth: true
103 +
104 + Kirigami.Icon {
105 + source: "brightness-low"
106 + Layout.preferredWidth: Kirigami.Units.iconSizes.small
107 + Layout.preferredHeight: Kirigami.Units.iconSizes.small
108 + }
109 +
110 + PlasmaComponents3.Label {
111 + text: "Brightness"
112 + Layout.preferredWidth: Kirigami.Units.gridUnit * 3
113 + }
114 +
115 + QQC2.Slider {
116 + id: expandedBrightnessSlider
117 + Layout.fillWidth: true
118 + from: 1
119 + to: 100
120 + value: currentBrightness
121 + stepSize: 1
122 +
123 + onMoved: {
124 + var brightnessValue = Math.round(value * 255 / 100)
125 + controlActivated(entityId, "turn_on", { brightness: brightnessValue })
126 + }
127 + }
128 +
129 + PlasmaComponents3.Label {
130 + text: currentBrightness + "%"
131 + Layout.preferredWidth: Kirigami.Units.gridUnit * 1.5
132 + }
133 + }
134 +
135 + // Color Temperature Control
136 + RowLayout {
137 + visible: supportsCct && isOn
138 + Layout.fillWidth: true
139 +
140 + Kirigami.Icon {
141 + source: "weather-clear-night"
142 + Layout.preferredWidth: Kirigami.Units.iconSizes.small
143 + Layout.preferredHeight: Kirigami.Units.iconSizes.small
144 + }
145 +
146 + PlasmaComponents3.Label {
147 + text: "Color Temp"
148 + Layout.preferredWidth: Kirigami.Units.gridUnit * 3
149 + }
150 +
151 + QQC2.Slider {
152 + id: expandedColorTempSlider
153 + Layout.fillWidth: true
154 + from: 153 // ~6500K (cool)
155 + to: 500 // ~2000K (warm)
156 + value: currentColorTemp
157 + stepSize: 1
158 +
159 + onMoved: {
160 + controlActivated(entityId, "turn_on", { color_temp: Math.round(value) })
161 + }
162 +
163 + background: Rectangle {
164 + x: expandedColorTempSlider.leftPadding
165 + y: expandedColorTempSlider.topPadding + expandedColorTempSlider.availableHeight / 2 - height / 2
166 + implicitWidth: 200
167 + implicitHeight: 8
168 + width: expandedColorTempSlider.availableWidth
169 + height: implicitHeight
170 + radius: 4
171 +
172 + gradient: Gradient {
173 + orientation: Gradient.Horizontal
174 + GradientStop { position: 0.0; color: "#87CEEB" } // Cool blue
175 + GradientStop { position: 1.0; color: "#FFA500" } // Warm orange
176 + }
177 +
178 + border.color: Kirigami.Theme.separatorColor
179 + border.width: 1
180 + }
181 + }
182 +
183 + PlasmaComponents3.Label {
184 + text: Math.round(1000000 / currentColorTemp) + "K"
185 + Layout.preferredWidth: Kirigami.Units.gridUnit * 1.5
186 + }
187 + }
188 +
189 + // RGB Color Control
190 + ColumnLayout {
191 + visible: supportsRgb && isOn
192 + Layout.fillWidth: true
193 + spacing: Kirigami.Units.smallSpacing
194 +
195 + PlasmaComponents3.Label {
196 + text: "RGB Colors"
197 + font.bold: true
198 + }
199 +
200 + // Color preset buttons
201 + GridLayout {
202 + Layout.fillWidth: true
203 + columns: 7
204 + columnSpacing: Kirigami.Units.smallSpacing
205 + rowSpacing: Kirigami.Units.smallSpacing
206 +
207 + Repeater {
208 + model: [
209 + { color: "#FF0000", name: "Red" },
210 + { color: "#00FF00", name: "Green" },
211 + { color: "#0000FF", name: "Blue" },
212 + { color: "#FFFF00", name: "Yellow" },
213 + { color: "#FF00FF", name: "Magenta" },
214 + { color: "#00FFFF", name: "Cyan" },
215 + { color: "#FFFFFF", name: "White" }
216 + ]
217 +
218 + PlasmaComponents3.Button {
219 + Layout.preferredWidth: Kirigami.Units.gridUnit * 1.5
220 + Layout.preferredHeight: Kirigami.Units.gridUnit * 1.5
221 +
222 + background: Rectangle {
223 + radius: Kirigami.Units.cornerRadius
224 + color: modelData.color
225 + border.color: Qt.rgba(currentRgbColor[0]/255, currentRgbColor[1]/255, currentRgbColor[2]/255, 1.0) === color ?
226 + Kirigami.Theme.highlightColor : Kirigami.Theme.separatorColor
227 + border.width: Qt.rgba(currentRgbColor[0]/255, currentRgbColor[1]/255, currentRgbColor[2]/255, 1.0) === color ? 3 : 1
228 + }
229 +
230 + PlasmaComponents3.ToolTip {
231 + text: modelData.name
232 + }
233 +
234 + onClicked: {
235 + var rgb = hexToRgb(modelData.color)
236 + controlActivated(entityId, "turn_on", { rgb_color: [rgb.r, rgb.g, rgb.b] })
237 + }
238 + }
239 + }
240 + }
241 +
242 + // Current color indicator
243 + Rectangle {
244 + Layout.fillWidth: true
245 + Layout.preferredHeight: Kirigami.Units.gridUnit * 1
246 + radius: Kirigami.Units.cornerRadius
247 + color: Qt.rgba(currentRgbColor[0]/255, currentRgbColor[1]/255, currentRgbColor[2]/255, 1.0)
248 + border.color: Kirigami.Theme.separatorColor
249 + border.width: 1
250 +
251 + PlasmaComponents3.Label {
252 + anchors.centerIn: parent
253 + text: "Current: RGB(" + currentRgbColor[0] + ", " + currentRgbColor[1] + ", " + currentRgbColor[2] + ")"
254 + font.pointSize: Kirigami.Theme.smallFont.pointSize
255 + color: (currentRgbColor[0] + currentRgbColor[1] + currentRgbColor[2]) > 384 ? "black" : "white"
256 + }
257 + }
258 + }
259 + }
260 +
261 + // Compact control layout
262 + ColumnLayout {
263 + visible: !showExpandedControls
58 264 anchors.fill: parent
59 265 spacing: Kirigami.Units.smallSpacing
60 266
@@ -120,17 +326,6 @@ Item {
120 326 }
121 327
122 328 onClicked: handleControlClick()
123 −
124 − // Right-click to show advanced controls popup for lights
125 − MouseArea {
126 − anchors.fill: parent
127 − acceptedButtons: Qt.RightButton
128 − onClicked: {
129 − if (hasAdvancedControls) {
130 − advancedControlsPopup.open()
131 − }
132 − }
133 − }
134 329 }
135 330
136 331 // Entity label
@@ -148,272 +343,6 @@ Item {
148 343 }
149 344 }
150 345
151 − // Advanced Controls Popup
152 − QQC2.Popup {
153 − id: advancedControlsPopup
154 − parent: entityControl
155 − modal: true
156 − focus: true
157 − closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutside
158 −
159 − width: Kirigami.Units.gridUnit * 16
160 − height: Kirigami.Units.gridUnit * 12
161 −
162 − x: (parent.width - width) / 2
163 − y: parent.height + Kirigami.Units.smallSpacing
164 −
165 − background: Rectangle {
166 − color: Kirigami.Theme.backgroundColor
167 − border.color: Kirigami.Theme.separatorColor
168 − border.width: 1
169 − radius: Kirigami.Units.cornerRadius
170 −
171 − // Add a subtle shadow effect
172 − Rectangle {
173 − anchors.fill: parent
174 − anchors.margins: -2
175 − z: -1
176 − color: "transparent"
177 − border.color: Qt.rgba(0, 0, 0, 0.1)
178 − border.width: 1
179 − radius: Kirigami.Units.cornerRadius + 1
180 − }
181 − }
182 − contentItem: ColumnLayout {
183 − spacing: Kirigami.Units.largeSpacing
184 −
185 − // Popup Header
186 − RowLayout {
187 − Layout.fillWidth: true
188 −
189 − Kirigami.Icon {
190 − source: getIconName()
191 − Layout.preferredWidth: Kirigami.Units.iconSizes.small
192 − Layout.preferredHeight: Kirigami.Units.iconSizes.small
193 − color: isOn ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.textColor
194 − }
195 −
196 − PlasmaComponents3.Label {
197 − text: displayName
198 − font.bold: true
199 − Layout.fillWidth: true
200 − }
201 −
202 − PlasmaComponents3.Button {
203 − icon.name: "window-close"
204 − flat: true
205 − onClicked: advancedControlsPopup.close()
206 − }
207 − }
208 −
209 − // Brightness Control
210 − RowLayout {
211 − visible: supportsBrightness && isOn
212 − Layout.fillWidth: true
213 − Layout.preferredHeight: Kirigami.Units.gridUnit * 1.5
214 −
215 − Kirigami.Icon {
216 − source: "brightness-low"
217 − Layout.preferredWidth: Kirigami.Units.iconSizes.small
218 − Layout.preferredHeight: Kirigami.Units.iconSizes.small
219 − }
220 −
221 − PlasmaComponents3.Label {
222 − text: "Brightness"
223 − Layout.preferredWidth: Kirigami.Units.gridUnit * 3
224 − }
225 −
226 − QQC2.Slider {
227 − id: popupBrightnessSlider
228 − Layout.fillWidth: true
229 − Layout.preferredHeight: Kirigami.Units.gridUnit * 1.5
230 − from: 1
231 − to: 100
232 − value: currentBrightness
233 − stepSize: 1
234 −
235 − onMoved: {
236 − var brightnessValue = Math.round(value * 255 / 100)
237 − controlActivated(entityId, "turn_on", { brightness: brightnessValue })
238 − }
239 −
240 − background: Rectangle {
241 − x: popupBrightnessSlider.leftPadding
242 − y: popupBrightnessSlider.topPadding + popupBrightnessSlider.availableHeight / 2 - height / 2
243 − implicitWidth: 200
244 − implicitHeight: 8
245 − width: popupBrightnessSlider.availableWidth
246 − height: implicitHeight
247 − radius: 4
248 − color: Kirigami.Theme.backgroundColor
249 − border.color: Kirigami.Theme.separatorColor
250 −
251 − Rectangle {
252 − width: popupBrightnessSlider.visualPosition * parent.width
253 − height: parent.height
254 − color: Kirigami.Theme.highlightColor
255 − radius: 4
256 − }
257 − }
258 −
259 − handle: Rectangle {
260 − x: popupBrightnessSlider.leftPadding + popupBrightnessSlider.visualPosition * (popupBrightnessSlider.availableWidth - width)
261 − y: popupBrightnessSlider.topPadding + popupBrightnessSlider.availableHeight / 2 - height / 2
262 − implicitWidth: 20
263 − implicitHeight: 20
264 − radius: 10
265 − color: popupBrightnessSlider.pressed ? Qt.darker(Kirigami.Theme.highlightColor, 1.1) : Kirigami.Theme.highlightColor
266 − border.color: Kirigami.Theme.backgroundColor
267 − border.width: 2
268 − }
269 − }
270 −
271 − PlasmaComponents3.Label {
272 − text: currentBrightness + "%"
273 − Layout.preferredWidth: Kirigami.Units.gridUnit * 1.5
274 − }
275 − }
276 −
277 − // Color Temperature Control
278 − RowLayout {
279 − visible: supportsCct && isOn
280 − Layout.fillWidth: true
281 − Layout.preferredHeight: Kirigami.Units.gridUnit * 1.5
282 −
283 − Kirigami.Icon {
284 − source: "weather-clear-night"
285 − Layout.preferredWidth: Kirigami.Units.iconSizes.small
286 − Layout.preferredHeight: Kirigami.Units.iconSizes.small
287 − }
288 −
289 − PlasmaComponents3.Label {
290 − text: "Color Temp"
291 − Layout.preferredWidth: Kirigami.Units.gridUnit * 3
292 − }
293 −
294 − QQC2.Slider {
295 − id: popupColorTempSlider
296 − Layout.fillWidth: true
297 − Layout.preferredHeight: Kirigami.Units.gridUnit * 1.5
298 − from: 153 // ~6500K (cool)
299 − to: 500 // ~2000K (warm)
300 − value: currentColorTemp
301 − stepSize: 1
302 −
303 − onMoved: {
304 − controlActivated(entityId, "turn_on", { color_temp: Math.round(value) })
305 − }
306 −
307 − background: Rectangle {
308 − x: popupColorTempSlider.leftPadding
309 − y: popupColorTempSlider.topPadding + popupColorTempSlider.availableHeight / 2 - height / 2
310 − implicitWidth: 200
311 − implicitHeight: 8
312 − width: popupColorTempSlider.availableWidth
313 − height: implicitHeight
314 − radius: 4
315 −
316 − gradient: Gradient {
317 − orientation: Gradient.Horizontal
318 − GradientStop { position: 0.0; color: "#87CEEB" } // Cool blue
319 − GradientStop { position: 1.0; color: "#FFA500" } // Warm orange
320 − }
321 −
322 − border.color: Kirigami.Theme.separatorColor
323 − border.width: 1
324 − }
325 −
326 − handle: Rectangle {
327 − x: popupColorTempSlider.leftPadding + popupColorTempSlider.visualPosition * (popupColorTempSlider.availableWidth - width)
328 − y: popupColorTempSlider.topPadding + popupColorTempSlider.availableHeight / 2 - height / 2
329 − implicitWidth: 20
330 − implicitHeight: 20
331 − radius: 10
332 − color: popupColorTempSlider.pressed ? Qt.darker(Kirigami.Theme.highlightColor, 1.1) : Kirigami.Theme.highlightColor
333 − border.color: Kirigami.Theme.backgroundColor
334 − border.width: 2
335 − }
336 − }
337 −
338 − PlasmaComponents3.Label {
339 − text: Math.round(1000000 / currentColorTemp) + "K"
340 − Layout.preferredWidth: Kirigami.Units.gridUnit * 1.5
341 − }
342 − }
343 −
344 − // RGB Color Control
345 − ColumnLayout {
346 − visible: supportsRgb && isOn
347 − Layout.fillWidth: true
348 − spacing: Kirigami.Units.smallSpacing
349 −
350 − PlasmaComponents3.Label {
351 − text: "RGB Colors"
352 − font.bold: true
353 − }
354 −
355 − // Color preset buttons
356 − GridLayout {
357 − Layout.fillWidth: true
358 − columns: 7
359 − columnSpacing: Kirigami.Units.smallSpacing
360 − rowSpacing: Kirigami.Units.smallSpacing
361 −
362 − Repeater {
363 − model: [
364 − { color: "#FF0000", name: "Red" },
365 − { color: "#00FF00", name: "Green" },
366 − { color: "#0000FF", name: "Blue" },
367 − { color: "#FFFF00", name: "Yellow" },
368 − { color: "#FF00FF", name: "Magenta" },
369 − { color: "#00FFFF", name: "Cyan" },
370 − { color: "#FFFFFF", name: "White" }
371 − ]
372 −
373 − PlasmaComponents3.Button {
374 − Layout.preferredWidth: Kirigami.Units.gridUnit * 1.5
375 − Layout.preferredHeight: Kirigami.Units.gridUnit * 1.5
376 −
377 − background: Rectangle {
378 − radius: Kirigami.Units.cornerRadius
379 − color: modelData.color
380 − border.color: Qt.rgba(currentRgbColor[0]/255, currentRgbColor[1]/255, currentRgbColor[2]/255, 1.0) === color ?
381 − Kirigami.Theme.highlightColor : Kirigami.Theme.separatorColor
382 − border.width: Qt.rgba(currentRgbColor[0]/255, currentRgbColor[1]/255, currentRgbColor[2]/255, 1.0) === color ? 3 : 1
383 − }
384 −
385 − PlasmaComponents3.ToolTip {
386 − text: modelData.name
387 − }
388 −
389 − onClicked: {
390 − var rgb = hexToRgb(modelData.color)
391 − controlActivated(entityId, "turn_on", { rgb_color: [rgb.r, rgb.g, rgb.b] })
392 − }
393 − }
394 − }
395 − }
396 −
397 − // Current color indicator
398 − Rectangle {
399 − Layout.fillWidth: true
400 − Layout.preferredHeight: Kirigami.Units.gridUnit * 1
401 − radius: Kirigami.Units.cornerRadius
402 − color: Qt.rgba(currentRgbColor[0]/255, currentRgbColor[1]/255, currentRgbColor[2]/255, 1.0)
403 − border.color: Kirigami.Theme.separatorColor
404 − border.width: 1
405 −
406 − PlasmaComponents3.Label {
407 − anchors.centerIn: parent
408 − text: "Current: RGB(" + currentRgbColor[0] + ", " + currentRgbColor[1] + ", " + currentRgbColor[2] + ")"
409 − font.pointSize: Kirigami.Theme.smallFont.pointSize
410 − color: (currentRgbColor[0] + currentRgbColor[1] + currentRgbColor[2]) > 384 ? "black" : "white"
411 − }
412 − }
413 − }
414 − }
415 − }
416 −
417 346 function getIconName() {
418 347 if (iconName && iconName !== "home-assistant") {
419 348 return iconName
@@ -496,9 +425,9 @@ Item {
496 425 }
497 426 }
498 427
499 − // Add instruction for light controls
500 − if (isLight && controlType === "light" && (supportsRgb || supportsCct || supportsBrightness)) {
501 − tooltip += "\n\nRight-click for advanced controls"
428 + // Add instruction for light controls - only show if not in expanded mode
429 + if (isLight && controlType === "light" && (supportsRgb || supportsCct || supportsBrightness) && !showExpandedControls) {
430 + tooltip += "\n\nAdvanced controls available in expanded view"
502 431 }
503 432
504 433 return tooltip
modified contents/ui/main.qml
+404 −133
@@ -9,13 +9,18 @@ import org.kde.kirigami as Kirigami
9 9 PlasmoidItem {
10 10 id: root
11 11
12 − Layout.minimumWidth: Kirigami.Units.gridUnit * 8
13 − Layout.minimumHeight: Kirigami.Units.gridUnit * 4
14 − Layout.preferredWidth: Kirigami.Units.gridUnit * 12
15 − Layout.preferredHeight: Kirigami.Units.gridUnit * 6
12 + // Panel widget sizing - compact for panel mode
13 + Layout.minimumWidth: hasValidConfig && hasEntities ?
14 + (configuredEntities.length * (Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing)) + Kirigami.Units.smallSpacing :
15 + Kirigami.Units.gridUnit * 3
16 + Layout.minimumHeight: Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing * 2
17 + Layout.preferredWidth: hasValidConfig && hasEntities ?
18 + (configuredEntities.length * (Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing)) + Kirigami.Units.smallSpacing :
19 + Kirigami.Units.gridUnit * 3
20 + Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing * 2
16 21
17 − switchWidth: Kirigami.Units.gridUnit * 5
18 − switchHeight: Kirigami.Units.gridUnit * 5
22 + switchWidth: Kirigami.Units.gridUnit * 10
23 + switchHeight: Kirigami.Units.gridUnit * 8
19 24
20 25 Plasmoid.backgroundHints: PlasmaCore.Types.DefaultBackground | PlasmaCore.Types.ConfigurableBackground
21 26
@@ -32,6 +37,10 @@ PlasmoidItem {
32 37 property bool hasValidConfig: homeAssistantUrl && accessToken
33 38 property bool hasEntities: configuredEntities.length > 0
34 39
40 + // For expanded popup with advanced controls
41 + property string expandedEntityId: ""
42 + property var expandedEntityData: null
43 +
35 44 // Parse configured entities from JSON
36 45 function parseConfiguredEntities() {
37 46 try {
@@ -90,123 +99,182 @@ PlasmoidItem {
90 99 anchors.fill: parent
91 100 anchors.margins: Kirigami.Units.smallSpacing
92 101
93 − // Title
94 − PlasmaComponents3.Label {
95 − text: i18n("Home Assistant Control")
96 − font.bold: true
97 − Layout.alignment: Qt.AlignHCenter
98 − visible: !hasValidConfig || !hasEntities
99 − }
100 −
101 − // Configuration prompt
102 − ColumnLayout {
103 − visible: !hasValidConfig
102 + // Advanced lighting controls popup for the selected entity
103 + Rectangle {
104 + visible: expandedEntityId !== ""
104 105 Layout.fillWidth: true
105 106 Layout.fillHeight: true
106 − spacing: Kirigami.Units.largeSpacing
107 + color: Kirigami.Theme.backgroundColor
108 + border.color: Kirigami.Theme.separatorColor
109 + border.width: 1
110 + radius: Kirigami.Units.cornerRadius
107 111
108 − PlasmaComponents3.Label {
109 − text: i18n("Configure the widget to connect to your Home Assistant instance")
110 − wrapMode: Text.WordWrap
111 − Layout.fillWidth: true
112 − Layout.alignment: Qt.AlignHCenter
113 − horizontalAlignment: Text.AlignHCenter
112 + EntityControl {
113 + anchors.fill: parent
114 + anchors.margins: Kirigami.Units.largeSpacing * 2
115 +
116 + entityId: root.expandedEntityId
117 + displayName: root.expandedEntityData ? (root.expandedEntityData.displayName || root.expandedEntityData.entityId) : ""
118 + controlType: root.expandedEntityData ? (root.expandedEntityData.controlType || "toggle") : "toggle"
119 + iconName: root.expandedEntityData ? (root.expandedEntityData.icon || "home-assistant") : "home-assistant"
120 + showLabel: true
121 + buttonSize: "large"
122 + entityState: root.entityStates[root.expandedEntityId] || null
123 + showExpandedControls: true
124 +
125 + onControlActivated: function(entityId, action, data) {
126 + switch (action) {
127 + case "toggle":
128 + homeAssistantAPI.toggleEntity(entityId)
129 + break
130 + case "turn_on":
131 + homeAssistantAPI.turnOn(entityId, data)
132 + break
133 + case "turn_off":
134 + homeAssistantAPI.turnOff(entityId)
135 + break
136 + }
137 +
138 + // Update state after a short delay
139 + Qt.callLater(function() {
140 + homeAssistantAPI.getState(entityId)
141 + })
142 + }
143 +
144 + onStateRequested: function(entityId) {
145 + if (root.hasValidConfig) {
146 + homeAssistantAPI.getState(entityId)
147 + }
148 + }
114 149 }
115 150
151 + // Close button
116 152 PlasmaComponents3.Button {
117 − text: i18n("Configure Connection")
118 − Layout.alignment: Qt.AlignHCenter
119 − onClicked: plasmoid.internalAction("configure").trigger()
153 + anchors.top: parent.top
154 + anchors.right: parent.right
155 + anchors.margins: Kirigami.Units.smallSpacing
156 + icon.name: "window-close"
157 + flat: true
158 + onClicked: {
159 + root.expandedEntityId = ""
160 + root.expandedEntityData = null
161 + }
120 162 }
121 163 }
122 164
123 − // Entity setup prompt
124 − ColumnLayout {
125 − visible: hasValidConfig && !hasEntities
165 + // Regular grid view when no specific entity is expanded
166 + Item {
167 + visible: expandedEntityId === ""
126 168 Layout.fillWidth: true
127 169 Layout.fillHeight: true
128 − spacing: Kirigami.Units.largeSpacing
129 170
171 + // Title
130 172 PlasmaComponents3.Label {
131 − text: i18n("Configure entities to control them from the widget")
132 − wrapMode: Text.WordWrap
133 − Layout.fillWidth: true
134 − Layout.alignment: Qt.AlignHCenter
135 − horizontalAlignment: Text.AlignHCenter
173 + id: titleLabel
174 + text: i18n("Home Assistant Control")
175 + font.bold: true
176 + anchors.top: parent.top
177 + anchors.horizontalCenter: parent.horizontalCenter
178 + visible: !hasValidConfig || !hasEntities
136 179 }
137 180
138 − PlasmaComponents3.Button {
139 − text: i18n("Configure Entities")
140 − Layout.alignment: Qt.AlignHCenter
141 − onClicked: plasmoid.internalAction("configure").trigger()
181 + // Configuration prompt
182 + ColumnLayout {
183 + visible: !hasValidConfig
184 + anchors.fill: parent
185 + anchors.topMargin: titleLabel.height + Kirigami.Units.largeSpacing
186 + spacing: Kirigami.Units.largeSpacing
187 +
188 + PlasmaComponents3.Label {
189 + text: i18n("Configure the widget to connect to your Home Assistant instance")
190 + wrapMode: Text.WordWrap
191 + Layout.fillWidth: true
192 + Layout.alignment: Qt.AlignHCenter
193 + horizontalAlignment: Text.AlignHCenter
194 + }
195 +
196 + PlasmaComponents3.Button {
197 + text: i18n("Configure Connection")
198 + Layout.alignment: Qt.AlignHCenter
199 + onClicked: plasmoid.internalAction("configure").trigger()
200 + }
142 201 }
143 − }
144 −
145 − // Entity controls grid
146 − QQC2.ScrollView {
147 − id: scrollView
148 − visible: hasValidConfig && hasEntities
149 − Layout.fillWidth: true
150 − Layout.fillHeight: true
151 202
152 − GridLayout {
153 − id: entityGrid
154 − columns: root.maxEntitiesPerRow
155 − columnSpacing: Kirigami.Units.smallSpacing
156 − rowSpacing: Kirigami.Units.smallSpacing
203 + // Entity setup prompt
204 + ColumnLayout {
205 + visible: hasValidConfig && !hasEntities
206 + anchors.fill: parent
207 + anchors.topMargin: titleLabel.height + Kirigami.Units.largeSpacing
208 + spacing: Kirigami.Units.largeSpacing
157 209
158 − Repeater {
159 − model: root.configuredEntities
210 + PlasmaComponents3.Label {
211 + text: i18n("Configure entities to control them from the widget")
212 + wrapMode: Text.WordWrap
213 + Layout.fillWidth: true
214 + Layout.alignment: Qt.AlignHCenter
215 + horizontalAlignment: Text.AlignHCenter
216 + }
217 +
218 + PlasmaComponents3.Button {
219 + text: i18n("Configure Entities")
220 + Layout.alignment: Qt.AlignHCenter
221 + onClicked: plasmoid.internalAction("configure").trigger()
222 + }
223 + }
224 +
225 + // Entity controls grid
226 + QQC2.ScrollView {
227 + id: scrollView
228 + visible: hasValidConfig && hasEntities
229 + anchors.fill: parent
230 +
231 + GridLayout {
232 + id: entityGrid
233 + columns: root.maxEntitiesPerRow
234 + columnSpacing: Kirigami.Units.smallSpacing
235 + rowSpacing: Kirigami.Units.smallSpacing
160 236
161 − EntityControl {
162 − entityId: modelData.entityId || ""
163 − displayName: modelData.displayName || modelData.entityId || ""
164 − controlType: modelData.controlType || "toggle"
165 − iconName: modelData.icon || "home-assistant"
166 − showLabel: root.showEntityLabels
167 − buttonSize: root.buttonSize
168 − entityState: root.entityStates[entityId] || null
237 + Repeater {
238 + model: root.configuredEntities
169 239
170 − onControlActivated: function(entityId, action, data) {
171 − switch (action) {
172 − case "toggle":
173 − homeAssistantAPI.toggleEntity(entityId)
174 − break
175 − case "turn_on":
176 − homeAssistantAPI.turnOn(entityId, data)
177 − break
178 − case "turn_off":
179 − homeAssistantAPI.turnOff(entityId)
180 − break
181 − }
240 + EntityControl {
241 + entityId: modelData.entityId || ""
242 + displayName: modelData.displayName || modelData.entityId || ""
243 + controlType: modelData.controlType || "toggle"
244 + iconName: modelData.icon || "home-assistant"
245 + showLabel: root.showEntityLabels
246 + buttonSize: root.buttonSize
247 + entityState: root.entityStates[entityId] || null
182 248
183 − // Update state after a short delay
184 − Qt.callLater(function() {
185 − homeAssistantAPI.getState(entityId)
186 − })
187 − }
188 −
189 − onStateRequested: function(entityId) {
190 − if (root.hasValidConfig) {
191 − isLoadingState = true
192 − homeAssistantAPI.getState(entityId, function(success, state) {
193 − isLoadingState = false
194 − if (!success) {
195 − console.log("Failed to refresh state for:", entityId)
196 − }
249 + onControlActivated: function(entityId, action, data) {
250 + switch (action) {
251 + case "toggle":
252 + homeAssistantAPI.toggleEntity(entityId)
253 + break
254 + case "turn_on":
255 + homeAssistantAPI.turnOn(entityId, data)
256 + break
257 + case "turn_off":
258 + homeAssistantAPI.turnOff(entityId)
259 + break
260 + }
261 +
262 + // Update state after a short delay
263 + Qt.callLater(function() {
264 + homeAssistantAPI.getState(entityId)
197 265 })
198 266 }
199 − }
200 −
201 − Component.onCompleted: {
202 − if (root.hasValidConfig) {
203 − isLoadingState = true
204 − homeAssistantAPI.getState(entityId, function(success, state) {
205 − isLoadingState = false
206 − if (!success) {
207 − console.log("Failed to get initial state for:", entityId)
208 − }
209 − })
267 +
268 + onStateRequested: function(entityId) {
269 + if (root.hasValidConfig) {
270 + homeAssistantAPI.getState(entityId)
271 + }
272 + }
273 +
274 + Component.onCompleted: {
275 + if (root.hasValidConfig) {
276 + homeAssistantAPI.getState(entityId)
277 + }
210 278 }
211 279 }
212 280 }
@@ -215,49 +283,252 @@ PlasmoidItem {
215 283 }
216 284 }
217 285
218 − compactRepresentation: Kirigami.Icon {
219 − source: plasmoid.icon
286 + // Helper functions for panel buttons
287 + function getEntityIconName(entityId, iconName, entityState, isOn) {
288 + if (iconName && iconName !== "home-assistant") {
289 + return iconName
290 + }
291 +
292 + // Auto-detect icon based on entity type and state
293 + var domain = entityId.split('.')[0]
294 + switch (domain) {
295 + case 'light':
296 + return isOn ? "lightbulb-on" : "lightbulb"
297 + case 'switch':
298 + return isOn ? "toggle-switch" : "toggle-switch-off"
299 + case 'fan':
300 + return isOn ? "weather-windy" : "fan"
301 + case 'automation':
302 + return isOn ? "playlist-play" : "playlist-remove"
303 + case 'climate':
304 + return "thermometer"
305 + case 'lock':
306 + return entityState && entityState.state === "locked" ? "lock" : "lock-open"
307 + case 'cover':
308 + return entityState && entityState.state === "open" ? "window-open" : "window-close"
309 + case 'input_boolean':
310 + return isOn ? "checkbox-marked" : "checkbox-blank-outline"
311 + default:
312 + return "home-assistant"
313 + }
314 + }
315 +
316 + function getEntityTooltipText(displayName, entityState, isLight, controlType, hasAdvancedControls) {
317 + var tooltip = displayName
318 +
319 + if (!entityState) {
320 + tooltip += "\nState: Unavailable"
321 + return tooltip
322 + }
323 +
324 + var stateText = entityState.state || "unknown"
325 +
326 + // Use friendly name if available
327 + if (entityState.attributes && entityState.attributes.friendly_name) {
328 + tooltip = entityState.attributes.friendly_name
329 + }
330 +
331 + tooltip += "\nState: " + stateText
332 +
333 + // Add additional info for specific entity types
334 + if (entityState.attributes) {
335 + if (entityState.attributes.brightness) {
336 + tooltip += "\nBrightness: " + Math.round(entityState.attributes.brightness / 255 * 100) + "%"
337 + }
338 + if (entityState.attributes.rgb_color) {
339 + tooltip += "\nRGB: " + entityState.attributes.rgb_color.join(", ")
340 + }
341 + if (entityState.attributes.color_temp) {
342 + tooltip += "\nColor Temp: " + Math.round(1000000 / entityState.attributes.color_temp) + "K"
343 + }
344 + if (entityState.attributes.temperature) {
345 + tooltip += "\nTemperature: " + entityState.attributes.temperature + "°"
346 + }
347 + if (entityState.attributes.humidity) {
348 + tooltip += "\nHumidity: " + entityState.attributes.humidity + "%"
349 + }
350 + if (entityState.attributes.battery_level) {
351 + tooltip += "\nBattery: " + entityState.attributes.battery_level + "%"
352 + }
353 + }
354 +
355 + // Add instruction for advanced controls
356 + if (hasAdvancedControls) {
357 + tooltip += "\n\nRight-click for advanced controls"
358 + } else {
359 + tooltip += "\n\nRight-click for details"
360 + }
361 +
362 + return tooltip
363 + }
364 +
365 + function handlePanelEntityClick(entityId, controlType, isOn) {
366 + switch (controlType) {
367 + case "toggle":
368 + homeAssistantAPI.toggleEntity(entityId)
369 + break
370 + case "switch":
371 + homeAssistantAPI.callService(isOn ? "turn_off" : "turn_on", entityId)
372 + break
373 + case "light":
374 + homeAssistantAPI.toggleEntity(entityId)
375 + break
376 + case "status":
377 + // Status displays don't perform actions, just refresh state
378 + homeAssistantAPI.getState(entityId)
379 + return
380 + default:
381 + homeAssistantAPI.toggleEntity(entityId)
382 + break
383 + }
384 +
385 + // Always try to refresh state after any action
386 + Qt.callLater(function() {
387 + homeAssistantAPI.getState(entityId)
388 + })
389 + }
390 +
391 + compactRepresentation: Item {
220 392 anchors.fill: parent
221 393
222 − // Show number of configured entities as badge
394 + // Configuration prompt when not configured
223 395 Rectangle {
224 − visible: root.hasEntities
225 − anchors.right: parent.right
226 − anchors.top: parent.top
227 − anchors.margins: 2
228 − width: Math.max(Kirigami.Units.gridUnit * 0.8, badgeText.implicitWidth + 4)
229 − height: Kirigami.Units.gridUnit * 0.8
230 − radius: height / 2
231 − color: Kirigami.Theme.highlightColor
396 + visible: !hasValidConfig || !hasEntities
397 + anchors.fill: parent
398 + color: "transparent"
399 + border.color: Kirigami.Theme.separatorColor
400 + border.width: 1
401 + radius: Kirigami.Units.cornerRadius
232 402
233 − PlasmaComponents3.Label {
234 − id: badgeText
403 + RowLayout {
235 404 anchors.centerIn: parent
236 − text: root.configuredEntities.length
237 − color: Kirigami.Theme.highlightedTextColor
238 − font.pointSize: Kirigami.Theme.smallFont.pointSize
239 − font.bold: true
405 + anchors.margins: Kirigami.Units.smallSpacing
406 +
407 + Kirigami.Icon {
408 + source: "home-assistant"
409 + Layout.preferredWidth: Kirigami.Units.iconSizes.small
410 + Layout.preferredHeight: Kirigami.Units.iconSizes.small
411 + color: Kirigami.Theme.disabledTextColor
412 + }
413 +
414 + PlasmaComponents3.Label {
415 + text: i18n("Setup")
416 + font.pointSize: Kirigami.Theme.smallFont.pointSize
417 + color: Kirigami.Theme.disabledTextColor
418 + }
419 + }
420 +
421 + PlasmaCore.ToolTipArea {
422 + anchors.fill: parent
423 + mainText: i18n("Home Assistant Control")
424 + subText: !hasValidConfig ? i18n("Click to configure connection") : i18n("Click to configure entities")
425 + }
426 +
427 + MouseArea {
428 + anchors.fill: parent
429 + onClicked: plasmoid.internalAction("configure").trigger()
240 430 }
241 431 }
242 432
243 − PlasmaCore.ToolTipArea {
244 − anchors.fill: parent
245 − mainText: i18n("Home Assistant Control")
246 − subText: root.hasEntities ?
247 − i18n("%1 entities configured", root.configuredEntities.length) :
248 − i18n("Click to configure")
249 − }
250 −
251 − MouseArea {
433 + // Panel entity controls when configured
434 + RowLayout {
435 + visible: hasValidConfig && hasEntities
252 436 anchors.fill: parent
253 − onClicked: {
254 − if (root.hasValidConfig && root.hasEntities) {
255 − root.expanded = !root.expanded
256 − if (root.expanded) {
257 − updateEntityStates()
437 + anchors.margins: Kirigami.Units.smallSpacing
438 + spacing: Kirigami.Units.smallSpacing
439 +
440 + Repeater {
441 + model: root.configuredEntities
442 +
443 + PlasmaComponents3.Button {
444 + id: panelButton
445 + Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
446 + Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
447 +
448 + property string entityId: modelData.entityId || ""
449 + property string displayName: modelData.displayName || modelData.entityId || ""
450 + property string controlType: modelData.controlType || "toggle"
451 + property string iconName: modelData.icon || "home-assistant"
452 + property var entityState: root.entityStates[entityId] || null
453 + property bool isOn: entityState ? (entityState.state === "on" || entityState.state === "home" || entityState.state === "open") : false
454 + property bool isLight: entityId.split('.')[0] === 'light'
455 + property bool hasAdvancedControls: isLight && controlType === "light" &&
456 + entityState && entityState.attributes && entityState.attributes.supported_color_modes &&
457 + (entityState.attributes.supported_color_modes.indexOf('rgb') !== -1 ||
458 + entityState.attributes.supported_color_modes.indexOf('color_temp') !== -1 ||
459 + entityState.attributes.supported_color_modes.indexOf('brightness') !== -1)
460 + property var currentRgbColor: entityState && entityState.attributes && entityState.attributes.rgb_color ?
461 + entityState.attributes.rgb_color : [255, 255, 255]
462 +
463 + // Visual state based on entity state
464 + property color normalColor: Kirigami.Theme.buttonBackgroundColor
465 + property color activeColor: isOn ?
466 + (isLight && entityState && entityState.attributes && entityState.attributes.rgb_color ?
467 + Qt.rgba(currentRgbColor[0]/255, currentRgbColor[1]/255, currentRgbColor[2]/255, 0.3) :
468 + Kirigami.Theme.positiveBackgroundColor) : normalColor
469 +
470 + background: Rectangle {
471 + radius: Kirigami.Units.cornerRadius
472 + color: panelButton.pressed ? Qt.darker(panelButton.activeColor, 1.1) :
473 + panelButton.hovered ? Qt.lighter(panelButton.activeColor, 1.1) :
474 + panelButton.activeColor
475 +
476 + border.color: isOn ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.buttonFocusColor
477 + border.width: isOn ? 2 : 1
478 +
479 + Behavior on color { ColorAnimation { duration: 150 } }
480 + Behavior on border.color { ColorAnimation { duration: 150 } }
481 +
482 + // Advanced controls indicator for lights
483 + Rectangle {
484 + visible: hasAdvancedControls
485 + anchors.top: parent.top
486 + anchors.right: parent.right
487 + anchors.margins: 1
488 + width: 4
489 + height: 4
490 + radius: 2
491 + color: Kirigami.Theme.disabledTextColor
492 + }
493 + }
494 +
495 + contentItem: Kirigami.Icon {
496 + source: getEntityIconName(entityId, iconName, entityState, isOn)
497 + color: isOn ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.textColor
498 +
499 + Behavior on color { ColorAnimation { duration: 150 } }
500 + }
501 +
502 + PlasmaComponents3.ToolTip {
503 + text: getEntityTooltipText(displayName, entityState, isLight, controlType, hasAdvancedControls)
504 + }
505 +
506 + onClicked: {
507 + handlePanelEntityClick(entityId, controlType, isOn)
508 + }
509 +
510 + // Right-click to show advanced controls popup for lights or expanded view
511 + MouseArea {
512 + anchors.fill: parent
513 + acceptedButtons: Qt.RightButton
514 + onClicked: {
515 + if (hasAdvancedControls) {
516 + // Store the current entity for the popup
517 + root.expandedEntityId = entityId
518 + root.expandedEntityData = modelData
519 + root.expanded = true
520 + } else {
521 + // For non-light entities, show expanded view
522 + root.expanded = !root.expanded
523 + }
524 + }
525 + }
526 +
527 + Component.onCompleted: {
528 + if (root.hasValidConfig && entityId) {
529 + homeAssistantAPI.getState(entityId)
530 + }
258 531 }
259 − } else {
260 − plasmoid.internalAction("configure").trigger()
261 532 }
262 533 }
263 534 }
modified metadata.json
+2 −1
@@ -18,5 +18,6 @@
18 18 "X-Plasma-API": "declarativeappletscript",
19 19 "X-Plasma-MainScript": "ui/main.qml",
20 20 "X-KDE-PlasmaDesktopFile": "Plasma/Applet",
21 − "X-Plasma-ConfigPlugins": ""
21 + "X-Plasma-ConfigPlugins": "",
22 + "X-Plasma-API-Minimum-Version": "6.0"
22 23 }

Parents: eaba37e