Theme color fixes (#14672)

* Split off and unavailable colors

* off and unvailable in history panel

* Refactor tile card color code

* Use new color in state badge

* Use new colors in button and entity card

* Rename off to inactive

* Add inactive color to color picker

* Use amber instead of blue
This commit is contained in:
Paul Bottein 2022-12-09 16:04:14 +01:00 committed by GitHub
parent 3c8c1260b1
commit 23573d8c26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 105 additions and 87 deletions

View File

@ -4,6 +4,7 @@ export const THEME_COLORS = new Set([
"primary",
"accent",
"disabled",
"inactive",
"red",
"pink",
"purple",

View File

@ -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;

View File

@ -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) => {

View File

@ -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));
}
`;

View File

@ -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<string, Record<string, number>> = {
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;

View File

@ -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) {

View File

@ -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`
<ha-card
@ -187,9 +192,8 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
.icon=${this._config.icon}
.state=${stateObj}
style=${styleMap({
color: stateObj && active ? this._computeColor(stateObj) : "",
filter:
stateObj && active ? this._computeBrightness(stateObj) : "",
color: colored ? this._computeColor(stateObj) : "",
filter: colored ? this._computeBrightness(stateObj) : "",
height: this._config.icon_height
? this._config.icon_height
: "",
@ -305,7 +309,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
}
private _computeBrightness(stateObj: HassEntity | LightEntity): string {
if (!stateObj.attributes.brightness) {
if (!stateObj.attributes.brightness && stateActive(stateObj)) {
const brightness = stateObj.attributes.brightness;
return `brightness(${(brightness + 245) / 5}%)`;
}
@ -313,12 +317,12 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
}
private _computeColor(stateObj: HassEntity | LightEntity): string {
if (stateObj.attributes.rgb_color) {
if (stateObj.attributes.rgb_color && stateActive(stateObj)) {
return `rgb(${stateObj.attributes.rgb_color.join(",")})`;
}
const iconColor = stateColor(stateObj);
const iconColor = stateColorCss(stateObj);
if (iconColor) {
return `rgb(var(--rgb-state-${iconColor}-color))`;
return `rgb(${iconColor})`;
}
return "";
}

View File

@ -16,7 +16,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 {
formatNumber,
@ -76,6 +76,15 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
private _footerElement?: HuiErrorCard | LovelaceHeaderFooter;
private getStateColor(stateObj: HassEntity, config: EntityCardConfig) {
const domain = stateObj ? computeStateDomain(stateObj) : undefined;
return (
config &&
(config.state_color ||
(domain === "light" && config.state_color !== false))
);
}
public setConfig(config: EntityCardConfig): void {
if (!config.entity) {
throw new Error("Entity must be specified");
@ -124,10 +133,8 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
const name = this._config.name || computeStateName(stateObj);
const active =
(this._config.state_color ||
(domain === "light" && this._config.state_color !== false)) &&
stateActive(stateObj);
const colored = stateObj && this.getStateColor(stateObj, this._config);
const active = stateObj && colored && stateActive(stateObj);
return html`
<ha-card @click=${this._handleClick} tabindex="0">
@ -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 "";
}

View File

@ -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;

View File

@ -107,6 +107,7 @@ documentContainer.innerHTML = `<custom-style>
--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 = `<custom-style>
--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 = `<custom-style>
--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 = `<custom-style>
--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);

View File

@ -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 = {

View File

@ -4349,6 +4349,7 @@
"primary": "Primary",
"accent": "Accent",
"disabled": "Disabled",
"inactive": "Inactive",
"red": "Red",
"pink": "Pink",
"purple": "Purple",