Use supported_color_modes to determine what UI elements to show (#8961)

This commit is contained in:
Bram Kragten 2021-04-27 19:43:25 +02:00 committed by GitHub
parent 20c351949f
commit 36586b798e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 587 additions and 120 deletions

View File

@ -9,13 +9,10 @@ import {
} from "lit-element";
import "../../../src/components/ha-card";
import {
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
LightColorModes,
SUPPORT_EFFECT,
SUPPORT_FLASH,
SUPPORT_TRANSITION,
SUPPORT_WHITE_VALUE,
} from "../../../src/data/light";
import "../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../src/fake_data/entity";
@ -32,7 +29,8 @@ const ENTITIES = [
getEntity("light", "kitchen_light", "on", {
friendly_name: "Brightness Light",
brightness: 200,
supported_features: SUPPORT_BRIGHTNESS,
supported_color_modes: [LightColorModes.BRIGHTNESS],
color_mode: LightColorModes.BRIGHTNESS,
}),
getEntity("light", "color_temperature_light", "on", {
friendly_name: "White Color Temperature Light",
@ -40,20 +38,96 @@ const ENTITIES = [
color_temp: 75,
min_mireds: 30,
max_mireds: 150,
supported_features: SUPPORT_BRIGHTNESS + SUPPORT_COLOR_TEMP,
supported_color_modes: [
LightColorModes.BRIGHTNESS,
LightColorModes.COLOR_TEMP,
],
color_mode: LightColorModes.COLOR_TEMP,
}),
getEntity("light", "color_effectslight", "on", {
friendly_name: "Color Effets Light",
getEntity("light", "color_hs_light", "on", {
friendly_name: "Color HS Light",
brightness: 255,
hs_color: [30, 100],
white_value: 36,
supported_features:
SUPPORT_BRIGHTNESS +
SUPPORT_EFFECT +
SUPPORT_FLASH +
SUPPORT_COLOR +
SUPPORT_TRANSITION +
SUPPORT_WHITE_VALUE,
rgb_color: [30, 100, 255],
min_mireds: 30,
max_mireds: 150,
supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
supported_color_modes: [
LightColorModes.BRIGHTNESS,
LightColorModes.COLOR_TEMP,
LightColorModes.HS,
],
color_mode: LightColorModes.HS,
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_rgb_ct_light", "on", {
friendly_name: "Color RGB + CT Light",
brightness: 255,
color_temp: 75,
min_mireds: 30,
max_mireds: 150,
supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
supported_color_modes: [
LightColorModes.BRIGHTNESS,
LightColorModes.COLOR_TEMP,
LightColorModes.RGB,
],
color_mode: LightColorModes.COLOR_TEMP,
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_RGB_light", "on", {
friendly_name: "Color Effets Light",
brightness: 255,
rgb_color: [30, 100, 255],
supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
supported_color_modes: [LightColorModes.BRIGHTNESS, LightColorModes.RGB],
color_mode: LightColorModes.RGB,
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_rgbw_light", "on", {
friendly_name: "Color RGBW Light",
brightness: 255,
rgbw_color: [30, 100, 255, 125],
min_mireds: 30,
max_mireds: 150,
supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
supported_color_modes: [
LightColorModes.BRIGHTNESS,
LightColorModes.COLOR_TEMP,
LightColorModes.RGBW,
],
color_mode: LightColorModes.RGBW,
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_rgbww_light", "on", {
friendly_name: "Color RGBWW Light",
brightness: 255,
rgbww_color: [30, 100, 255, 125, 10],
min_mireds: 30,
max_mireds: 150,
supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
supported_color_modes: [
LightColorModes.BRIGHTNESS,
LightColorModes.COLOR_TEMP,
LightColorModes.RGBWW,
],
color_mode: LightColorModes.RGBWW,
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_xy_light", "on", {
friendly_name: "Color XY Light",
brightness: 255,
xy_color: [30, 100],
rgb_color: [30, 100, 255],
min_mireds: 30,
max_mireds: 150,
supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
supported_color_modes: [
LightColorModes.BRIGHTNESS,
LightColorModes.COLOR_TEMP,
LightColorModes.XY,
],
color_mode: LightColorModes.XY,
effect_list: ["random", "colorloop"],
}),
];

View File

@ -102,3 +102,18 @@ export const lab2hex = (lab: [number, number, number]): string => {
const rgb = lab2rgb(lab);
return rgb2hex(rgb);
};
export const rgb2hsv = (
rgb: [number, number, number]
): [number, number, number] => {
const [r, g, b] = rgb;
const v = Math.max(r, g, b);
const c = v - Math.min(r, g, b);
const h =
c && (v === r ? (g - b) / c : v === g ? 2 + (b - r) / c : 4 + (r - g) / c);
return [60 * (h < 0 ? h + 6 : h), v && c / v, v];
};
export const rgb2hs = (rgb: [number, number, number]): [number, number] => {
return rgb2hsv(rgb).slice(0, 2) as [number, number];
};

View File

@ -15,6 +15,7 @@ import { computeActiveState } from "../../common/entity/compute_active_state";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { stateIcon } from "../../common/entity/state_icon";
import { iconColorCSS } from "../../common/style/icon_color_css";
import { getLightRgbColor, LightEntity } from "../../data/light";
import type { HomeAssistant } from "../../types";
import "../ha-icon";
@ -99,11 +100,13 @@ export class StateBadge extends LitElement {
hostStyle.backgroundImage = `url(${imageUrl})`;
this._showIcon = false;
} else if (stateObj.state === "on") {
if (stateObj.attributes.hs_color && this.stateColor !== false) {
const hue = stateObj.attributes.hs_color[0];
const sat = stateObj.attributes.hs_color[1];
if (sat > 10) {
iconStyle.color = `hsl(${hue}, 100%, ${100 - sat / 2}%)`;
if (
computeStateDomain(stateObj) === "light" &&
this.stateColor !== false
) {
const rgb = getLightRgbColor(stateObj as LightEntity);
if (rgb) {
iconStyle.color = `rgb(${rgb.slice(0, 3).join(",")})`;
}
}
if (stateObj.attributes.brightness && this.stateColor !== false) {

View File

@ -9,6 +9,7 @@ import {
property,
TemplateResult,
} from "lit-element";
import { styleMap } from "lit-html/directives/style-map";
import { fireEvent } from "../common/dom/fire_event";
import type { ToggleButton } from "../types";
import "./ha-svg-icon";
@ -19,6 +20,8 @@ export class HaButtonToggleGroup extends LitElement {
@property() public active?: string;
@property({ type: Boolean }) public fullWidth = false;
protected render(): TemplateResult {
return html`
<div>
@ -33,6 +36,11 @@ export class HaButtonToggleGroup extends LitElement {
<ha-svg-icon .path=${button.iconPath}></ha-svg-icon>
</mwc-icon-button>`
: html`<mwc-button
style=${styleMap({
width: this.fullWidth
? `${100 / this.buttons.length}%`
: "initial",
})}
.value=${button.value}
?active=${this.active === button.value}
@click=${this._handleClick}

View File

@ -2,7 +2,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { EventsMixin } from "../mixins/events-mixin";
import { rgb2hs } from "../common/color/convert-color";
/**
* Color-picker custom element
*
@ -114,6 +114,12 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
observer: "applyHsColor",
},
// use these properties to update the state via attributes
desiredRgbColor: {
type: Object,
observer: "applyRgbColor",
},
// width, height and radius apply to the coordinates of
// of the canvas.
// border width are relative to these numbers
@ -177,8 +183,11 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
this.drawMarker();
if (this.desiredHsColor) {
this.setMarkerOnColor(this.desiredHsColor);
this.applyColorToCanvas(this.desiredHsColor);
this.applyHsColor(this.desiredHsColor);
}
if (this.desiredRgbColor) {
this.applyRgbColor(this.desiredRgbColor);
}
this.interactionLayer.addEventListener("mousedown", (ev) =>
@ -282,12 +291,13 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
processUserSelect(ev) {
const canvasXY = this.convertToCanvasCoordinates(ev.clientX, ev.clientY);
const hs = this.getColor(canvasXY.x, canvasXY.y);
this.onColorSelect(hs);
const rgb = this.getRgbColor(canvasXY.x, canvasXY.y);
this.onColorSelect(hs, rgb);
}
// apply color to marker position and canvas
onColorSelect(hs) {
this.setMarkerOnColor(hs); // marker always follows mounse 'raw' hs value (= mouse position)
onColorSelect(hs, rgb) {
this.setMarkerOnColor(hs); // marker always follows mouse 'raw' hs value (= mouse position)
if (!this.ignoreSegments) {
// apply segments if needed
hs = this.applySegmentFilter(hs);
@ -301,11 +311,11 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
// eventually after throttle limit has passed
clearTimeout(this.ensureFinalSelect);
this.ensureFinalSelect = setTimeout(() => {
this.fireColorSelected(hs); // do it for the final time
this.fireColorSelected(hs, rgb); // do it for the final time
}, this.throttle);
return;
}
this.fireColorSelected(hs); // do it
this.fireColorSelected(hs, rgb); // do it
this.colorSelectIsThrottled = true;
setTimeout(() => {
this.colorSelectIsThrottled = false;
@ -313,9 +323,9 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
}
// set color values and fire colorselected event
fireColorSelected(hs) {
fireColorSelected(hs, rgb) {
this.hsColor = hs;
this.fire("colorselected", { hs: { h: hs.h, s: hs.s } });
this.fire("colorselected", { hs, rgb });
}
/*
@ -363,6 +373,11 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
this.applyColorToCanvas(hs);
}
applyRgbColor(rgb) {
const [h, s] = rgb2hs(rgb);
this.applyHsColor({ h, s });
}
/*
* input processing helpers
*/
@ -395,6 +410,15 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
return { h: hue, s: sat };
}
getRgbColor(x, y) {
// get current pixel
const imageData = this.backgroundLayer
.getContext("2d")
.getImageData(x + 250, y + 250, 1, 1);
const pixel = imageData.data;
return { r: pixel[0], g: pixel[1], b: pixel[2] };
}
applySegmentFilter(hs) {
// apply hue segment steps
if (this.hueSegments) {
@ -468,7 +492,7 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
.getPropertyValue("--wheel-bordercolor")
.trim();
const wheelShadow = wheelStyle.getPropertyValue("--wheel-shadow").trim();
// extract shadow properties from CCS variable
// extract shadow properties from CSS variable
// the shadow should be defined as: "10px 5px 5px 0px COLOR"
if (wheelShadow !== "none") {
const values = wheelShadow.split("px ");

View File

@ -3,26 +3,82 @@ import {
HassEntityBase,
} from "home-assistant-js-websocket";
export enum LightColorModes {
UNKNOWN = "unknown",
ONOFF = "onoff",
BRIGHTNESS = "brightness",
COLOR_TEMP = "color_temp",
HS = "hs",
XY = "xy",
RGB = "rgb",
RGBW = "rgbw",
RGBWW = "rgbww",
}
const modesSupportingColor = [
LightColorModes.HS,
LightColorModes.XY,
LightColorModes.RGB,
LightColorModes.RGBW,
LightColorModes.RGBWW,
];
const modesSupportingDimming = [
...modesSupportingColor,
LightColorModes.COLOR_TEMP,
LightColorModes.BRIGHTNESS,
];
export const SUPPORT_EFFECT = 4;
export const SUPPORT_FLASH = 8;
export const SUPPORT_TRANSITION = 32;
export const lightSupportsColorMode = (
entity: LightEntity,
mode: LightColorModes
) => {
return entity.attributes.supported_color_modes?.includes(mode);
};
export const lightIsInColorMode = (entity: LightEntity) => {
return modesSupportingColor.includes(entity.attributes.color_mode);
};
export const lightSupportsColor = (entity: LightEntity) => {
return entity.attributes.supported_color_modes?.some((mode) =>
modesSupportingColor.includes(mode)
);
};
export const lightSupportsDimming = (entity: LightEntity) => {
return entity.attributes.supported_color_modes?.some((mode) =>
modesSupportingDimming.includes(mode)
);
};
export const getLightRgbColor = (entity: LightEntity): number[] | undefined =>
entity.attributes.color_mode === LightColorModes.RGBWW
? entity.attributes.rgbww_color
: entity.attributes.color_mode === LightColorModes.RGBW
? entity.attributes.rgbw_color
: entity.attributes.rgb_color;
interface LightEntityAttributes extends HassEntityAttributeBase {
min_mireds: number;
max_mireds: number;
friendly_name: string;
brightness: number;
hs_color: number[];
hs_color: [number, number];
rgb_color: [number, number, number];
rgbw_color: [number, number, number, number];
rgbww_color: [number, number, number, number, number];
color_temp: number;
white_value: number;
effect?: string;
effect_list: string[] | null;
supported_color_modes: LightColorModes[];
color_mode: LightColorModes;
}
export interface LightEntity extends HassEntityBase {
attributes: LightEntityAttributes;
}
export const SUPPORT_BRIGHTNESS = 1;
export const SUPPORT_COLOR_TEMP = 2;
export const SUPPORT_EFFECT = 4;
export const SUPPORT_FLASH = 8;
export const SUPPORT_COLOR = 16;
export const SUPPORT_TRANSITION = 32;
export const SUPPORT_WHITE_VALUE = 128;

View File

@ -11,7 +11,6 @@ import {
PropertyValues,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attributes";
import "../../../components/ha-color-picker";
@ -19,20 +18,22 @@ import "../../../components/ha-icon-button";
import "../../../components/ha-labeled-slider";
import "../../../components/ha-paper-dropdown-menu";
import {
getLightRgbColor,
LightColorModes,
LightEntity,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
lightIsInColorMode,
lightSupportsColor,
lightSupportsColorMode,
lightSupportsDimming,
SUPPORT_EFFECT,
SUPPORT_WHITE_VALUE,
} from "../../../data/light";
import type { HomeAssistant } from "../../../types";
import "../../../components/ha-button-toggle-group";
interface HueSatColor {
h: number;
s: number;
}
const toggleButtons = [
{ label: "Color", value: "color" },
{ label: "Temperature", value: LightColorModes.COLOR_TEMP },
];
@customElement("more-info-light")
class MoreInfoLight extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@ -41,28 +42,51 @@ class MoreInfoLight extends LitElement {
@internalProperty() private _brightnessSliderValue = 0;
@internalProperty() private _ctSliderValue = 0;
@internalProperty() private _ctSliderValue?: number;
@internalProperty() private _wvSliderValue = 0;
@internalProperty() private _cwSliderValue?: number;
@internalProperty() private _wwSliderValue?: number;
@internalProperty() private _wvSliderValue?: number;
@internalProperty() private _colorBrightnessSliderValue?: number;
@internalProperty() private _brightnessAdjusted?: number;
@internalProperty() private _hueSegments = 24;
@internalProperty() private _saturationSegments = 8;
@internalProperty() private _colorPickerColor?: HueSatColor;
@internalProperty() private _colorPickerColor?: [number, number, number];
@internalProperty() private _mode?: "color" | LightColorModes.COLOR_TEMP;
protected render(): TemplateResult {
if (!this.hass || !this.stateObj) {
return html``;
}
const supportsTemp = lightSupportsColorMode(
this.stateObj,
LightColorModes.COLOR_TEMP
);
const supportsRgbww = lightSupportsColorMode(
this.stateObj,
LightColorModes.RGBWW
);
const supportsRgbw =
!supportsRgbww &&
lightSupportsColorMode(this.stateObj, LightColorModes.RGBW);
const supportsColor =
supportsRgbww || supportsRgbw || lightSupportsColor(this.stateObj);
return html`
<div
class="content ${classMap({
"is-on": this.stateObj.state === "on",
})}"
>
${supportsFeature(this.stateObj!, SUPPORT_BRIGHTNESS)
<div class="content">
${lightSupportsDimming(this.stateObj)
? html`
<ha-labeled-slider
caption=${this.hass.localize("ui.card.light.brightness")}
@ -77,7 +101,17 @@ class MoreInfoLight extends LitElement {
: ""}
${this.stateObj.state === "on"
? html`
${supportsFeature(this.stateObj, SUPPORT_COLOR_TEMP)
${supportsTemp || supportsColor ? html`<hr></hr>` : ""}
${supportsTemp && supportsColor
? html`<ha-button-toggle-group
fullWidth
.buttons=${toggleButtons}
.active=${this._mode}
@value-changed=${this._modeChanged}
></ha-button-toggle-group>`
: ""}
${supportsTemp &&
(!supportsColor || this._mode === LightColorModes.COLOR_TEMP)
? html`
<ha-labeled-slider
class="color_temp"
@ -91,27 +125,16 @@ class MoreInfoLight extends LitElement {
@change=${this._ctSliderChanged}
pin
></ha-labeled-slider>
<hr></hr>
`
: ""}
${supportsFeature(this.stateObj, SUPPORT_WHITE_VALUE)
? html`
<ha-labeled-slider
caption=${this.hass.localize("ui.card.light.white_value")}
icon="hass:file-word-box"
max="255"
.value=${this._wvSliderValue}
@change=${this._wvSliderChanged}
pin
></ha-labeled-slider>
`
: ""}
${supportsFeature(this.stateObj, SUPPORT_COLOR)
${supportsColor && (!supportsTemp || this._mode === "color")
? html`
<div class="segmentationContainer">
<ha-color-picker
class="color"
@colorselected=${this._colorPicked}
.desiredHsColor=${this._colorPickerColor}
.desiredRgbColor=${this._colorPickerColor}
throttle="500"
.hueSegments=${this._hueSegments}
.saturationSegments=${this._saturationSegments}
@ -123,6 +146,67 @@ class MoreInfoLight extends LitElement {
class="segmentationButton"
></ha-icon-button>
</div>
${
supportsRgbw || supportsRgbww
? html`<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.color_brightness"
)}
icon="hass:brightness-7"
max="100"
.value=${this._colorBrightnessSliderValue ?? 255}
@change=${this._colorBrightnessSliderChanged}
pin
></ha-labeled-slider>`
: ""
}
${
supportsRgbw
? html`
<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.white_value"
)}
icon="hass:file-word-box"
max="100"
.name=${"wv"}
.value=${this._wvSliderValue}
@change=${this._wvSliderChanged}
pin
></ha-labeled-slider>
`
: ""
}
${
supportsRgbww
? html`
<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.cold_white_value"
)}
icon="hass:file-word-box-outline"
max="100"
.name=${"cw"}
.value=${this._cwSliderValue}
@change=${this._wvSliderChanged}
pin
></ha-labeled-slider>
<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.warm_white_value"
)}
icon="hass:file-word-box"
max="100"
.name=${"ww"}
.value=${this._wwSliderValue}
@change=${this._wvSliderChanged}
pin
></ha-labeled-slider>
`
: ""
}
<hr></hr>
`
: ""}
${supportsFeature(this.stateObj, SUPPORT_EFFECT) &&
@ -151,34 +235,85 @@ class MoreInfoLight extends LitElement {
: ""}
<ha-attributes
.stateObj=${this.stateObj}
extra-filters="brightness,color_temp,white_value,effect_list,effect,hs_color,rgb_color,xy_color,min_mireds,max_mireds,entity_id,supported_color_modes,color_mode"
extra-filters="brightness,color_temp,white_value,effect_list,effect,hs_color,rgb_color,rgbw_color,rgbww_color,xy_color,min_mireds,max_mireds,entity_id,supported_color_modes,color_mode"
></ha-attributes>
</div>
`;
}
protected updated(changedProps: PropertyValues): void {
protected updated(changedProps: PropertyValues<this>) {
if (!changedProps.has("stateObj")) {
return;
}
const stateObj = this.stateObj! as LightEntity;
if (changedProps.has("stateObj")) {
if (stateObj.state === "on") {
this._brightnessSliderValue = Math.round(
(stateObj.attributes.brightness * 100) / 255
);
this._ctSliderValue = stateObj.attributes.color_temp;
this._wvSliderValue = stateObj.attributes.white_value;
const oldStateObj = changedProps.get("stateObj") as LightEntity | undefined;
if (stateObj.attributes.hs_color) {
this._colorPickerColor = {
h: stateObj.attributes.hs_color[0],
s: stateObj.attributes.hs_color[1] / 100,
};
if (stateObj.state === "on") {
// Don't change tab when the color mode changes
if (
oldStateObj?.entity_id !== stateObj.entity_id ||
oldStateObj?.state !== stateObj.state
) {
this._mode = lightIsInColorMode(this.stateObj!)
? "color"
: LightColorModes.COLOR_TEMP;
}
let brightnessAdjust = 100;
if (
stateObj.attributes.color_mode === LightColorModes.RGB &&
!lightSupportsColorMode(stateObj, LightColorModes.RGBWW) &&
!lightSupportsColorMode(stateObj, LightColorModes.RGBW)
) {
const maxVal = Math.max(...stateObj.attributes.rgb_color);
if (maxVal < 255) {
this._brightnessAdjusted = maxVal;
brightnessAdjust = (this._brightnessAdjusted / 255) * 100;
}
} else {
this._brightnessSliderValue = 0;
this._brightnessAdjusted = undefined;
}
this._brightnessSliderValue = Math.round(
(stateObj.attributes.brightness * brightnessAdjust) / 255
);
this._ctSliderValue = stateObj.attributes.color_temp;
this._wvSliderValue =
stateObj.attributes.color_mode === LightColorModes.RGBW
? Math.round((stateObj.attributes.rgbw_color[3] * 100) / 255)
: undefined;
this._cwSliderValue =
stateObj.attributes.color_mode === LightColorModes.RGBWW
? Math.round((stateObj.attributes.rgbww_color[3] * 100) / 255)
: undefined;
this._wwSliderValue =
stateObj.attributes.color_mode === LightColorModes.RGBWW
? Math.round((stateObj.attributes.rgbww_color[4] * 100) / 255)
: undefined;
this._colorBrightnessSliderValue =
stateObj.attributes.color_mode === LightColorModes.RGBWW
? Math.round(
(Math.max(...stateObj.attributes.rgbww_color.slice(0, 3)) * 100) /
255
)
: stateObj.attributes.color_mode === LightColorModes.RGBW
? Math.round(
(Math.max(...stateObj.attributes.rgbw_color.slice(0, 3)) * 100) /
255
)
: undefined;
this._colorPickerColor = getLightRgbColor(stateObj)?.slice(0, 3) as
| [number, number, number]
| undefined;
} else {
this._brightnessSliderValue = 0;
}
}
private _modeChanged(ev: CustomEvent) {
this._mode = ev.detail.value;
}
private _effectChanged(ev: CustomEvent) {
const newVal = ev.detail.item.itemName;
@ -193,12 +328,29 @@ class MoreInfoLight extends LitElement {
}
private _brightnessSliderChanged(ev: CustomEvent) {
const bri = parseInt((ev.target as any).value, 10);
const bri = Number((ev.target as any).value);
if (isNaN(bri)) {
return;
}
if (this._brightnessAdjusted) {
const rgb =
this.stateObj!.attributes.rgb_color ||
([0, 0, 0] as [number, number, number]);
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
brightness_pct: bri,
rgb_color: this._adjustColorBrightness(
rgb,
this._brightnessAdjusted,
true
),
});
return;
}
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
brightness_pct: bri,
@ -206,7 +358,7 @@ class MoreInfoLight extends LitElement {
}
private _ctSliderChanged(ev: CustomEvent) {
const ct = parseInt((ev.target as any).value, 10);
const ct = Number((ev.target as any).value);
if (isNaN(ct)) {
return;
@ -219,18 +371,64 @@ class MoreInfoLight extends LitElement {
}
private _wvSliderChanged(ev: CustomEvent) {
const wv = parseInt((ev.target as any).value, 10);
const target = ev.target as any;
let wv = Number(target.value);
const name = target.name;
if (isNaN(wv)) {
return;
}
wv = (wv * 255) / 100;
const rgb = getLightRgbColor(this.stateObj!);
if (name === "wv") {
const rgbw_color = rgb || [0, 0, 0, 0];
rgbw_color[3] = wv;
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
rgbw_color,
});
return;
}
const rgbww_color = rgb || [0, 0, 0, 0, 0];
while (rgbww_color.length < 5) {
rgbww_color.push(0);
}
rgbww_color[name === "cw" ? 3 : 4] = wv;
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
white_value: wv,
rgbww_color,
});
}
private _colorBrightnessSliderChanged(ev: CustomEvent) {
const target = ev.target as any;
const value = Number(target.value);
const rgb = (getLightRgbColor(this.stateObj!)?.slice(0, 3) || [
255,
255,
255,
]) as [number, number, number];
this._setRgbColor(
this._adjustColorBrightness(
// first normalize the value
this._colorBrightnessSliderValue
? this._adjustColorBrightness(
rgb,
this._colorBrightnessSliderValue,
true
)
: rgb,
value
)
);
}
private _segmentClick() {
if (this._hueSegments === 24 && this._saturationSegments === 8) {
this._hueSegments = 0;
@ -241,15 +439,90 @@ class MoreInfoLight extends LitElement {
}
}
private _adjustColorBrightness(
rgbColor: [number, number, number],
value?: number,
invert = false
) {
if (value !== undefined && value !== 255) {
let ratio = value / 255;
if (invert) {
ratio = 1 / ratio;
}
rgbColor[0] *= ratio;
rgbColor[1] *= ratio;
rgbColor[2] *= ratio;
}
return rgbColor;
}
private _setRgbColor(rgbColor: [number, number, number]) {
if (lightSupportsColorMode(this.stateObj!, LightColorModes.RGBWW)) {
const rgbww_color: [number, number, number, number, number] = this
.stateObj!.attributes.rgbww_color
? [...this.stateObj!.attributes.rgbww_color]
: [0, 0, 0, 0, 0];
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
rgbww_color: rgbColor.concat(rgbww_color.slice(3)),
});
} else if (lightSupportsColorMode(this.stateObj!, LightColorModes.RGBW)) {
const rgbw_color: [number, number, number, number] = this.stateObj!
.attributes.rgbw_color
? [...this.stateObj!.attributes.rgbw_color]
: [0, 0, 0, 0];
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
rgbw_color: rgbColor.concat(rgbw_color.slice(3)),
});
}
}
/**
* Called when a new color has been picked.
* should be throttled with the 'throttle=' attribute of the color picker
*/
private _colorPicked(ev: CustomEvent) {
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
hs_color: [ev.detail.hs.h, ev.detail.hs.s * 100],
});
if (
lightSupportsColorMode(this.stateObj!, LightColorModes.RGBWW) ||
lightSupportsColorMode(this.stateObj!, LightColorModes.RGBW)
) {
this._setRgbColor(
this._colorBrightnessSliderValue
? this._adjustColorBrightness(
[ev.detail.rgb.r, ev.detail.rgb.g, ev.detail.rgb.b],
this._colorBrightnessSliderValue
)
: [ev.detail.rgb.r, ev.detail.rgb.g, ev.detail.rgb.b]
);
} else if (lightSupportsColorMode(this.stateObj!, LightColorModes.RGB)) {
const rgb_color = [ev.detail.rgb.r, ev.detail.rgb.g, ev.detail.rgb.b] as [
number,
number,
number
];
if (this._brightnessAdjusted) {
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
brightness_pct: this._brightnessSliderValue,
rgb_color: this._adjustColorBrightness(
rgb_color,
this._brightnessAdjusted,
true
),
});
} else {
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
rgb_color,
});
}
} else {
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
hs_color: [ev.detail.hs.h, ev.detail.hs.s * 100],
});
}
}
static get styles(): CSSResult {
@ -275,11 +548,18 @@ class MoreInfoLight extends LitElement {
);
/* The color temp minimum value shouldn't be rendered differently. It's not "off". */
--paper-slider-knob-start-border-color: var(--primary-color);
margin-bottom: 4px;
}
.segmentationContainer {
position: relative;
max-height: 500px;
display: flex;
justify-content: center;
}
ha-button-toggle-group {
margin: 8px 0px;
}
ha-color-picker {
@ -293,12 +573,19 @@ class MoreInfoLight extends LitElement {
.segmentationButton {
position: absolute;
top: 5%;
left: 0;
color: var(--secondary-text-color);
}
paper-item {
cursor: pointer;
}
hr {
border-color: var(--divider-color);
border-bottom: none;
margin: 8px 0;
}
`;
}
}

View File

@ -28,7 +28,7 @@ import { stateIcon } from "../../../common/entity/state_icon";
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
import { iconColorCSS } from "../../../common/style/icon_color_css";
import "../../../components/ha-card";
import { LightEntity } from "../../../data/light";
import { getLightRgbColor, LightEntity } from "../../../data/light";
import { ActionHandlerEvent } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive";
@ -301,14 +301,14 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
}
private _computeColor(stateObj: HassEntity | LightEntity): string {
if (!stateObj.attributes.hs_color || !this._config?.state_color) {
if (
!this._config?.state_color ||
computeStateDomain(stateObj) !== "light"
) {
return "";
}
const [hue, sat] = stateObj.attributes.hs_color;
if (sat <= 10) {
return "";
}
return `hsl(${hue}, 100%, ${100 - sat / 2}%)`;
const rgb = getLightRgbColor(stateObj as LightEntity);
return rgb ? `rgb(${rgb.slice(0, 3).join(",")})` : "";
}
private _handleAction(ev: ActionHandlerEvent) {

View File

@ -18,11 +18,14 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { stateIcon } from "../../../common/entity/state_icon";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import { UNAVAILABLE, UNAVAILABLE_STATES } from "../../../data/entity";
import { LightEntity, SUPPORT_BRIGHTNESS } from "../../../data/light";
import {
getLightRgbColor,
LightEntity,
lightSupportsDimming,
} from "../../../data/light";
import { ActionHandlerEvent } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive";
@ -121,17 +124,14 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
@value-changing=${this._dragEvent}
@value-changed=${this._setBrightness}
style=${styleMap({
visibility: supportsFeature(stateObj, SUPPORT_BRIGHTNESS)
visibility: lightSupportsDimming(stateObj)
? "visible"
: "hidden",
})}
></round-slider>
<ha-icon-button
class="light-button ${classMap({
"slider-center": supportsFeature(
stateObj,
SUPPORT_BRIGHTNESS
),
"slider-center": lightSupportsDimming(stateObj),
"state-on": stateObj.state === "on",
"state-unavailable": stateObj.state === UNAVAILABLE,
})}"
@ -244,14 +244,11 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
}
private _computeColor(stateObj: LightEntity): string {
if (stateObj.state === "off" || !stateObj.attributes.hs_color) {
if (stateObj.state === "off") {
return "";
}
const [hue, sat] = stateObj.attributes.hs_color;
if (sat <= 10) {
return "";
}
return `hsl(${hue}, 100%, ${100 - sat / 2}%)`;
const rgb = getLightRgbColor(stateObj);
return rgb ? `rgb(${rgb.slice(0, 3).join(",")})` : "";
}
private _handleAction(ev: ActionHandlerEvent) {

View File

@ -172,7 +172,10 @@
"light": {
"brightness": "Brightness",
"color_temperature": "Color temperature",
"white_value": "White value",
"white_value": "White brightness",
"color_brightness": "Color brightness",
"cold_white_value": "Cold white brightness",
"warm_white_value": "Warm white brightness",
"effect": "Effect"
},
"lock": {