diff --git a/src/common/color/compute-color.ts b/src/common/color/compute-color.ts index e7e3409a9d..40b43c44d4 100644 --- a/src/common/color/compute-color.ts +++ b/src/common/color/compute-color.ts @@ -4,6 +4,7 @@ export const THEME_COLORS = new Set([ "primary", "accent", "disabled", + "inactive", "red", "pink", "purple", diff --git a/src/common/entity/color/sensor_color.ts b/src/common/entity/color/sensor_color.ts index 5da0eeb0a2..298f996f26 100644 --- a/src/common/entity/color/sensor_color.ts +++ b/src/common/entity/color/sensor_color.ts @@ -3,12 +3,12 @@ import { batteryStateColor } from "./battery_color"; export const sensorColor = ( stateObj: HassEntity, - compareState: string + state: string ): string | undefined => { const deviceClass = stateObj?.attributes.device_class; if (deviceClass === "battery") { - return batteryStateColor(compareState); + return batteryStateColor(state); } return undefined; diff --git a/src/common/entity/state_color.ts b/src/common/entity/state_color.ts index 4cf278e231..6da45f1ee1 100644 --- a/src/common/entity/state_color.ts +++ b/src/common/entity/state_color.ts @@ -1,5 +1,6 @@ /** Return an color representing a state. */ import { HassEntity } from "home-assistant-js-websocket"; +import { UNAVAILABLE } from "../../data/entity"; import { UpdateEntity, updateIsInstalling } from "../../data/update"; import { alarmControlPanelColor } from "./color/alarm_control_panel_color"; import { binarySensorColor } from "./color/binary_sensor_color"; @@ -30,18 +31,23 @@ const STATIC_COLORED_DOMAIN = new Set([ "vacuum", ]); -export const stateColorCss = (stateObj?: HassEntity, state?: string) => { - if (!stateObj || !stateActive(stateObj, state)) { - return `var(--rgb-disabled-color)`; +export const stateColorCss = (stateObj: HassEntity, state?: string) => { + const compareState = state !== undefined ? state : stateObj?.state; + if (compareState === UNAVAILABLE) { + return `var(--rgb-state-unavailable-color)`; } - const color = stateColor(stateObj, state); - - if (color) { - return `var(--rgb-state-${color}-color)`; + if (!stateActive(stateObj, state)) { + return `var(--rgb-state-inactive-color)`; } - return `var(--rgb-state-default-color)`; + const domainColor = stateColor(stateObj, state); + + if (domainColor) { + return `var(--rgb-state-${domainColor}-color)`; + } + + return undefined; }; export const stateColor = (stateObj: HassEntity, state?: string) => { diff --git a/src/common/style/icon_color_css.ts b/src/common/style/icon_color_css.ts index 8e5f66d90c..fef56682b5 100644 --- a/src/common/style/icon_color_css.ts +++ b/src/common/style/icon_color_css.ts @@ -21,6 +21,6 @@ export const iconColorCSS = css` /* Color the icon if unavailable */ ha-state-icon[data-state="unavailable"] { - color: var(--state-unavailable-color); + color: rgb(var(--rgb-state-unavailable-color)); } `; diff --git a/src/components/chart/timeline-chart/timeline-color.ts b/src/components/chart/timeline-chart/timeline-color.ts index 012c9315f6..52e2283aa5 100644 --- a/src/components/chart/timeline-chart/timeline-color.ts +++ b/src/components/chart/timeline-chart/timeline-color.ts @@ -5,6 +5,7 @@ import { labBrighten } from "../../../common/color/lab"; import { computeDomain } from "../../../common/entity/compute_domain"; import { stateActive } from "../../../common/entity/state_active"; import { stateColor } from "../../../common/entity/state_color"; +import { UNAVAILABLE } from "../../../data/entity"; const DOMAIN_STATE_SHADES: Record> = { media_player: { @@ -49,11 +50,18 @@ function computeTimelineStateColor( computedStyles: CSSStyleDeclaration, stateObj?: HassEntity ): string | undefined { - if (!stateObj || !stateActive(stateObj, state)) { - const rgb = cssToRgb("--rgb-disabled-color", computedStyles); + if (!stateObj || state === UNAVAILABLE) { + const rgb = cssToRgb("--rgb-state-unavailable-color", computedStyles); if (!rgb) return undefined; return rgb2hex(rgb); } + + if (!stateActive(stateObj, state)) { + const rgb = cssToRgb("--rgb-state-inactive-color", computedStyles); + if (!rgb) return undefined; + return rgb2hex(rgb); + } + const color = stateColor(stateObj, state); if (!color) return undefined; diff --git a/src/components/entity/state-badge.ts b/src/components/entity/state-badge.ts index 30022f677f..50458e1f88 100644 --- a/src/components/entity/state-badge.ts +++ b/src/components/entity/state-badge.ts @@ -14,7 +14,7 @@ import { styleMap } from "lit/directives/style-map"; import { computeDomain } from "../../common/entity/compute_domain"; import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { stateActive } from "../../common/entity/state_active"; -import { stateColor } from "../../common/entity/state_color"; +import { stateColorCss } from "../../common/entity/state_color"; import { iconColorCSS } from "../../common/style/icon_color_css"; import { cameraUrlWithWidthHeight } from "../../data/camera"; import type { HomeAssistant } from "../../types"; @@ -107,24 +107,27 @@ export class StateBadge extends LitElement { } hostStyle.backgroundImage = `url(${imageUrl})`; this._showIcon = false; - } else if (stateActive(stateObj) && this._stateColor) { - const iconColor = stateColor(stateObj); - if (stateObj.attributes.rgb_color) { - iconStyle.color = `rgb(${stateObj.attributes.rgb_color.join(",")})`; - } else if (iconColor) { - iconStyle.color = `rgb(var(--rgb-state-${iconColor}-color))`; + } else if (this._stateColor) { + const color = stateColorCss(stateObj); + if (color) { + iconStyle.color = `rgb(${color})`; } - if (stateObj.attributes.brightness) { - const brightness = stateObj.attributes.brightness; - if (typeof brightness !== "number") { - const errorMessage = `Type error: state-badge expected number, but type of ${ - stateObj.entity_id - }.attributes.brightness is ${typeof brightness} (${brightness})`; - // eslint-disable-next-line - console.warn(errorMessage); + if (stateActive(stateObj)) { + if (stateObj.attributes.rgb_color) { + iconStyle.color = `rgb(${stateObj.attributes.rgb_color.join(",")})`; + } + if (stateObj.attributes.brightness) { + const brightness = stateObj.attributes.brightness; + if (typeof brightness !== "number") { + const errorMessage = `Type error: state-badge expected number, but type of ${ + stateObj.entity_id + }.attributes.brightness is ${typeof brightness} (${brightness})`; + // eslint-disable-next-line + console.warn(errorMessage); + } + // lowest brightness will be around 50% (that's pretty dark) + iconStyle.filter = `brightness(${(brightness + 245) / 5}%)`; } - // lowest brightness will be around 50% (that's pretty dark) - iconStyle.filter = `brightness(${(brightness + 245) / 5}%)`; } } } else if (this.overrideImage) { diff --git a/src/panels/lovelace/cards/hui-button-card.ts b/src/panels/lovelace/cards/hui-button-card.ts index 3e274f957a..480d86f542 100644 --- a/src/panels/lovelace/cards/hui-button-card.ts +++ b/src/panels/lovelace/cards/hui-button-card.ts @@ -26,7 +26,7 @@ import { computeStateDisplay } from "../../../common/entity/compute_state_displa import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { stateActive } from "../../../common/entity/state_active"; -import { stateColor } from "../../../common/entity/state_color"; +import { stateColorCss } from "../../../common/entity/state_color"; import { isValidEntityId } from "../../../common/entity/valid_entity_id"; import { iconColorCSS } from "../../../common/style/icon_color_css"; import "../../../components/ha-card"; @@ -79,6 +79,15 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { @state() private _shouldRenderRipple = false; + private getStateColor(stateObj: HassEntity, config: ButtonCardConfig) { + const domain = stateObj ? computeStateDomain(stateObj) : undefined; + return ( + config && + (config.state_color || + (domain === "light" && config.state_color !== false)) + ); + } + public getCardSize(): number { return ( (this._config?.show_icon ? 4 : 0) + (this._config?.show_name ? 1 : 0) @@ -146,13 +155,9 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { const name = this._config.show_name ? this._config.name || (stateObj ? computeStateName(stateObj) : "") : ""; - const domain = stateObj ? computeStateDomain(stateObj) : undefined; - const active = - (this._config.state_color || - (domain === "light" && this._config.state_color !== false)) && - stateObj && - stateActive(stateObj); + const colored = stateObj && this.getStateColor(stateObj, this._config); + const active = stateObj && colored && stateActive(stateObj); return html` @@ -141,7 +148,7 @@ export class HuiEntityCard extends LitElement implements LovelaceCard { data-domain=${ifDefined(domain)} data-state=${stateObj.state} style=${styleMap({ - color: active ? this._computeColor(stateObj) : "", + color: colored ? this._computeColor(stateObj) : undefined, height: this._config.icon_height ? this._config.icon_height : "", @@ -187,19 +194,9 @@ export class HuiEntityCard extends LitElement implements LovelaceCard { } private _computeColor(stateObj: HassEntity | LightEntity): string { - const domain = computeStateDomain(stateObj); - if ( - !( - this._config?.state_color || - (domain === "light" && this._config?.state_color !== false) - ) || - !stateActive(stateObj) - ) { - return ""; - } - const iconColor = stateColor(stateObj); + const iconColor = stateColorCss(stateObj); if (iconColor) { - return `rgb(var(--rgb-state-${iconColor}-color))`; + return `rgb(${iconColor})`; } return ""; } diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index 52c8b9b96a..4ca720e34b 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -9,7 +9,6 @@ import { hsv2rgb, rgb2hsv } from "../../../common/color/convert-color"; import { DOMAINS_TOGGLE } from "../../../common/const"; import { computeDomain } from "../../../common/entity/compute_domain"; import { computeStateDisplay } from "../../../common/entity/compute_state_display"; -import { stateActive } from "../../../common/entity/state_active"; import { stateColorCss } from "../../../common/entity/state_color"; import { stateIconPath } from "../../../common/entity/state_icon_path"; import { blankBeforePercent } from "../../../common/translations/blank_before_percent"; @@ -129,8 +128,9 @@ export class HuiTileCard extends LitElement implements LovelaceCard { } private _computeStateColor = memoize((entity: HassEntity, color?: string) => { - if (UNAVAILABLE_STATES.includes(entity.state)) { - return undefined; + // Use custom color + if (color) { + return computeRgbColor(color); } // Use default color for person/device_tracker because color is on the badge @@ -141,16 +141,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard { return "var(--rgb-state-default-color)"; } - if (!stateActive(entity)) { - return undefined; - } - - if (color) { - return computeRgbColor(color); - } - - let stateColor = stateColorCss(entity); - + // Use light color if the light support rgb if ( computeDomain(entity.entity_id) === "light" && entity.attributes.rgb_color @@ -166,10 +157,11 @@ export class HuiTileCard extends LitElement implements LovelaceCard { hsvColor[1] = 0.4; } } - stateColor = hsv2rgb(hsvColor).join(","); + return hsv2rgb(hsvColor).join(","); } - return stateColor; + // Fallback to state color + return stateColorCss(entity) ?? "var(--rgb-state-default-color)"; }); private _computeStateDisplay(stateObj: HassEntity): TemplateResult | string { @@ -360,7 +352,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard { static get styles(): CSSResultGroup { return css` :host { - --tile-color: var(--rgb-disabled-color); + --tile-color: var(--rgb-state-default-color); --tile-tap-padding: 6px; -webkit-tap-highlight-color: transparent; } @@ -368,7 +360,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard { height: 100%; } ha-card.disabled { - background: rgba(var(--rgb-disabled-color), 0.1); + background: rgba(var(--rgb-state-unavailable-color), 0.1); } [role="button"] { cursor: pointer; diff --git a/src/resources/ha-style.ts b/src/resources/ha-style.ts index 63ed84aa9a..b8adeee7d3 100644 --- a/src/resources/ha-style.ts +++ b/src/resources/ha-style.ts @@ -107,6 +107,7 @@ documentContainer.innerHTML = ` --rgb-primary-color: 3, 169, 244; --rgb-accent-color: 255, 152, 0; --rgb-disabled-color: 189, 189, 189; + --rgb-inactive-color: 114, 114, 114; --rgb-primary-text-color: 33, 33, 33; --rgb-secondary-text-color: 114, 114, 114; --rgb-text-primary-color: 255, 255, 255; @@ -134,7 +135,11 @@ documentContainer.innerHTML = ` --rgb-white-color: 255, 255, 255; /* rgb state color */ - --rgb-state-default-color: 68, 115, 158; + --rgb-state-default-color: var(--rgb-dark-primary-color, 68, 115, 158); + --rgb-state-unavailable-color: var(--rgb-disabled-color); + --rgb-state-inactive-color: var(--rgb-inactive-color); + + /* rgb state color */ --rgb-state-alarm-armed-color: var(--rgb-red-color); --rgb-state-alarm-arming-color: var(--rgb-orange-color); --rgb-state-alarm-pending-color: var(--rgb-orange-color); @@ -142,16 +147,16 @@ documentContainer.innerHTML = ` --rgb-state-alert-color: var(--rgb-red-color); --rgb-state-automation-color: var(--rgb-amber-color); --rgb-state-binary-sensor-alerting-color: var(--rgb-red-color); - --rgb-state-binary-sensor-color: var(--rgb-blue-color); - --rgb-state-calendar-color: var(--rgb-blue-color); - --rgb-state-camera-color: var(--rgb-blue-color); + --rgb-state-binary-sensor-color: var(--rgb-amber-color); + --rgb-state-calendar-color: var(--rgb-amber-color); + --rgb-state-camera-color: var(--rgb-amber-color); --rgb-state-climate-auto-color: var(--rgb-green-color); --rgb-state-climate-cool-color: var(--rgb-blue-color); --rgb-state-climate-dry-color: var(--rgb-orange-color); --rgb-state-climate-fan-only-color: var(--rgb-cyan-color); --rgb-state-climate-heat-color: var(--rgb-deep-orange-color); --rgb-state-climate-heat-cool-color: var(--rgb-amber-color); - --rgb-state-climate-idle-color: var(--rgb-disabled-color); + --rgb-state-climate-idle-color: var(--rgb-off-color); --rgb-state-cover-color: var(--rgb-purple-color); --rgb-state-fan-color: var(--rgb-cyan-color); --rgb-state-group-color: var(--rgb-amber-color); @@ -164,12 +169,12 @@ documentContainer.innerHTML = ` --rgb-state-media-player-color: var(--rgb-indigo-color); --rgb-state-person-home-color: var(--rgb-green-color); --rgb-state-person-zone-color: var(--rgb-blue-color); - --rgb-state-remote-color: var(--rgb-blue-color); + --rgb-state-remote-color: var(--rgb-amber-color); --rgb-state-script-color: var(--rgb-amber-color); --rgb-state-sensor-battery-high-color: var(--rgb-green-color); --rgb-state-sensor-battery-low-color: var(--rgb-red-color); --rgb-state-sensor-battery-medium-color: var(--rgb-orange-color); - --rgb-state-sensor-battery-unknown-color: var(--rgb-disabled-color); + --rgb-state-sensor-battery-unknown-color: var(--rgb-off-color); --rgb-state-siren-color: var(--rgb-red-color); --rgb-state-sun-day-color: var(--rgb-amber-color); --rgb-state-sun-night-color: var(--rgb-deep-purple-color); diff --git a/src/resources/styles.ts b/src/resources/styles.ts index 83ddd066dc..4b49cf06aa 100644 --- a/src/resources/styles.ts +++ b/src/resources/styles.ts @@ -48,7 +48,8 @@ export const darkStyles = { "energy-grid-return-color": "#a280db", "map-filter": "invert(.9) hue-rotate(170deg) brightness(1.5) contrast(1.2) saturate(.3)", - "rgb-disabled-color": "111, 111, 111", + "rgb-disabled-color": "70, 70, 70", + "rgb-inactive-color": "141, 141, 141", }; export const derivedStyles = { diff --git a/src/translations/en.json b/src/translations/en.json index 008434e0c5..029113a66c 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4349,6 +4349,7 @@ "primary": "Primary", "accent": "Accent", "disabled": "Disabled", + "inactive": "Inactive", "red": "Red", "pink": "Pink", "purple": "Purple",