New color picker for HS and color temp (#16549)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Paul Bottein 2023-05-23 14:45:23 +02:00 committed by GitHub
parent a5b5e61ed4
commit e9d9d89d79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1036 additions and 810 deletions

View File

@ -0,0 +1,3 @@
---
title: HS Color Picker
---

View File

@ -0,0 +1,120 @@
import "../../../../src/components/ha-hs-color-picker";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-slider";
import { hsv2rgb } from "../../../../src/common/color/convert-color";
@customElement("demo-components-ha-hs-color-picker")
export class DemoHaHsColorPicker extends LitElement {
@state()
brightness = 255;
@state()
value: [number, number] = [0, 0];
@state()
liveValue?: [number, number];
private _brightnessChanged(ev) {
this.brightness = Number(ev.target.value);
}
private _hsColorCursor(ev) {
this.liveValue = ev.detail.value;
}
private _hsColorChanged(ev) {
this.value = ev.detail.value;
}
private _hueChanged(ev) {
this.value = [ev.target.value, this.value[1]];
}
private _saturationChanged(ev) {
this.value = [this.value[0], ev.target.value];
}
protected render(): TemplateResult {
const h = (this.liveValue ?? this.value)[0];
const s = (this.liveValue ?? this.value)[1];
const rgb = hsv2rgb([h, s, this.brightness]);
return html`
<ha-card>
<div class="card-content">
<p class="value">${h}° - ${Math.round(s * 100)}%</p>
<p class="value">${rgb.map((v) => Math.round(v)).join(", ")}</p>
<ha-hs-color-picker
colorBrightness=${this.brightness}
.value=${this.value}
@value-changed=${this._hsColorChanged}
@cursor-moved=${this._hsColorCursor}
></ha-hs-color-picker>
<p>Hue : ${this.value[0]}</p>
<ha-slider
step="1"
pin
min="0"
max="360"
.value=${this.value[0]}
@change=${this._hueChanged}
>
</ha-slider>
<p>Saturation : ${this.value[1]}</p>
<ha-slider
step="0.01"
pin
min="0"
max="1"
.value=${this.value[1]}
@change=${this._saturationChanged}
>
</ha-slider>
<p>Color Brighness : ${this.brightness}</p>
<ha-slider
step="1"
pin
min="0"
max="255"
.value=${this.brightness}
@change=${this._brightnessChanged}
>
</ha-slider>
</div>
</ha-card>
`;
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
.card-content {
display: flex;
align-items: center;
flex-direction: column;
}
ha-hs-color-picker {
width: 400px;
}
.value {
font-size: 22px;
font-weight: bold;
margin: 0 0 12px 0;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-components-ha-hs-color-picker": DemoHaHsColorPicker;
}
}

View File

@ -0,0 +1,3 @@
---
title: Temp Color Picker
---

View File

@ -0,0 +1,117 @@
import "../../../../src/components/ha-temp-color-picker";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-slider";
@customElement("demo-components-ha-temp-color-picker")
export class DemoHaTempColorPicker extends LitElement {
@state()
min = 3000;
@state()
max = 7000;
@state()
value = 4000;
@state()
liveValue?: number;
private _minChanged(ev) {
this.min = Number(ev.target.value);
}
private _maxChanged(ev) {
this.max = Number(ev.target.value);
}
private _valueChanged(ev) {
this.value = Number(ev.target.value);
}
private _tempColorCursor(ev) {
this.liveValue = ev.detail.value;
}
private _tempColorChanged(ev) {
this.value = ev.detail.value;
}
protected render(): TemplateResult {
return html`
<ha-card>
<div class="card-content">
<p class="value">${this.liveValue ?? this.value} K</p>
<ha-temp-color-picker
.min=${this.min}
.max=${this.max}
.value=${this.value}
@value-changed=${this._tempColorChanged}
@cursor-moved=${this._tempColorCursor}
></ha-temp-color-picker>
<p>Min temp : ${this.min} K</p>
<ha-slider
step="1"
pin
min="2000"
max="10000"
.value=${this.min}
@change=${this._minChanged}
>
</ha-slider>
<p>Max temp : ${this.max} K</p>
<ha-slider
step="1"
pin
min="2000"
max="10000"
.value=${this.max}
@change=${this._maxChanged}
>
</ha-slider>
<p>Value : ${this.value} K</p>
<ha-slider
step="1"
pin
min=${this.min}
max=${this.max}
.value=${this.value}
@change=${this._valueChanged}
>
</ha-slider>
</div>
</ha-card>
`;
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
.card-content {
display: flex;
align-items: center;
flex-direction: column;
}
ha-temp-color-picker {
width: 400px;
}
.value {
font-size: 22px;
font-weight: bold;
margin: 0 0 12px 0;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-components-ha-temp-color-picker": DemoHaTempColorPicker;
}
}

View File

@ -1,644 +0,0 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { hs2rgb, rgb2hs } from "../common/color/convert-color";
import { EventsMixin } from "../mixins/events-mixin";
/**
* Color-picker custom element
*
* @appliesMixin EventsMixin
*/
class HaColorPicker extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style>
:host {
user-select: none;
-webkit-user-select: none;
}
#canvas {
position: relative;
width: 100%;
max-width: 330px;
}
#canvas > * {
display: block;
}
#interactionLayer {
color: white;
position: absolute;
cursor: crosshair;
width: 100%;
height: 100%;
overflow: visible;
}
#backgroundLayer {
width: 100%;
overflow: visible;
--wheel-bordercolor: var(--ha-color-picker-wheel-bordercolor, white);
--wheel-borderwidth: var(--ha-color-picker-wheel-borderwidth, 3);
--wheel-shadow: var(
--ha-color-picker-wheel-shadow,
rgb(15, 15, 15) 10px 5px 5px 0px
);
}
#marker {
fill: currentColor;
stroke: var(--ha-color-picker-marker-bordercolor, white);
stroke-width: var(--ha-color-picker-marker-borderwidth, 3);
filter: url(#marker-shadow);
}
.dragging #marker {
}
#colorTooltip {
display: none;
fill: currentColor;
stroke: var(--ha-color-picker-tooltip-bordercolor, white);
stroke-width: var(--ha-color-picker-tooltip-borderwidth, 3);
}
.touch.dragging #colorTooltip {
display: inherit;
}
</style>
<div id="canvas">
<svg id="interactionLayer">
<defs>
<filter
id="marker-shadow"
x="-50%"
y="-50%"
width="200%"
height="200%"
filterUnits="objectBoundingBox"
>
<feOffset
result="offOut"
in="SourceAlpha"
dx="2"
dy="2"
></feOffset>
<feGaussianBlur
result="blurOut"
in="offOut"
stdDeviation="2"
></feGaussianBlur>
<feComponentTransfer in="blurOut" result="alphaOut">
<feFuncA type="linear" slope="0.3"></feFuncA>
</feComponentTransfer>
<feBlend
in="SourceGraphic"
in2="alphaOut"
mode="normal"
></feBlend>
</filter>
</defs>
</svg>
<canvas id="backgroundLayer"></canvas>
</div>
`;
}
static get properties() {
return {
hsColor: {
type: Object,
},
// use these properties to update the state via attributes
desiredHsColor: {
type: Object,
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
// the onscreen displayed size should be controlled with css
// and should be the same or smaller
width: {
type: Number,
value: 500,
},
height: {
type: Number,
value: 500,
},
radius: {
type: Number,
value: 225,
},
// the amount segments for the hue
// 0 = continuous gradient
// other than 0 gives 'pie-pieces'
hueSegments: {
type: Number,
value: 0,
observer: "segmentationChange",
},
// the amount segments for the hue
// 0 = continuous gradient
// 1 = only fully saturated
// > 1 = segments from white to fully saturated
saturationSegments: {
type: Number,
value: 0,
observer: "segmentationChange",
},
// set to true to make the segments purely esthetical
// this allows selection off all collors, also
// interpolated between the segments
ignoreSegments: {
type: Boolean,
value: false,
},
// throttle te amount of 'colorselected' events fired
// value is timeout in milliseconds
throttle: {
type: Number,
value: 500,
},
};
}
ready() {
super.ready();
this.setupLayers();
this.drawColorWheel();
this.drawMarker();
if (this.desiredHsColor) {
this.applyHsColor(this.desiredHsColor);
}
if (this.desiredRgbColor) {
this.applyRgbColor(this.desiredRgbColor);
}
this.interactionLayer.addEventListener("mousedown", (ev) =>
this.onMouseDown(ev)
);
this.interactionLayer.addEventListener("touchstart", (ev) =>
this.onTouchStart(ev)
);
}
// converts browser coordinates to canvas canvas coordinates
// origin is wheel center
// returns {x: X, y: Y} object
convertToCanvasCoordinates(clientX, clientY) {
const svgPoint = this.interactionLayer.createSVGPoint();
svgPoint.x = clientX;
svgPoint.y = clientY;
const cc = svgPoint.matrixTransform(
this.interactionLayer.getScreenCTM().inverse()
);
return { x: cc.x, y: cc.y };
}
// Mouse events
onMouseDown(ev) {
const cc = this.convertToCanvasCoordinates(ev.clientX, ev.clientY);
// return if we're not on the wheel
if (!this.isInWheel(cc.x, cc.y)) {
return;
}
// a mousedown in wheel is always a color select action
this.onMouseSelect(ev);
// allow dragging
this.canvas.classList.add("mouse", "dragging");
this.addEventListener("mousemove", this.onMouseSelect);
this.addEventListener("mouseup", this.onMouseUp);
}
onMouseUp() {
this.canvas.classList.remove("mouse", "dragging");
this.removeEventListener("mousemove", this.onMouseSelect);
}
onMouseSelect(ev) {
requestAnimationFrame(() => this.processUserSelect(ev));
}
// Touch events
onTouchStart(ev) {
const touch = ev.changedTouches[0];
const cc = this.convertToCanvasCoordinates(touch.clientX, touch.clientY);
// return if we're not on the wheel
if (!this.isInWheel(cc.x, cc.y)) {
return;
}
if (ev.target === this.marker) {
// drag marker
ev.preventDefault();
this.canvas.classList.add("touch", "dragging");
this.addEventListener("touchmove", this.onTouchSelect);
this.addEventListener("touchend", this.onTouchEnd);
return;
}
// don't fire color selection immediately,
// wait for touchend and invalidate when we scroll
this.tapBecameScroll = false;
this.addEventListener("touchend", this.onTap);
this.addEventListener(
"touchmove",
() => {
this.tapBecameScroll = true;
},
{ passive: true }
);
}
onTap(ev) {
if (this.tapBecameScroll) {
return;
}
ev.preventDefault();
this.onTouchSelect(ev);
}
onTouchEnd() {
this.canvas.classList.remove("touch", "dragging");
this.removeEventListener("touchmove", this.onTouchSelect);
}
onTouchSelect(ev) {
requestAnimationFrame(() => this.processUserSelect(ev.changedTouches[0]));
}
/*
* General event/selection handling
*/
// Process user input to color
processUserSelect(ev) {
const canvasXY = this.convertToCanvasCoordinates(ev.clientX, ev.clientY);
const hs = this.getColor(canvasXY.x, canvasXY.y);
let rgb;
if (!this.isInWheel(canvasXY.x, canvasXY.y)) {
const [r, g, b] = hs2rgb([hs.h, hs.s]);
rgb = { r, g, b };
} else {
rgb = this.getRgbColor(canvasXY.x, canvasXY.y);
}
this.onColorSelect(hs, rgb);
}
// apply color to marker position and canvas
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);
}
// always apply the new color to the interface / canvas
this.applyColorToCanvas(hs);
// throttling is applied to updating the exposed colors (properties)
// and firing of events
if (this.colorSelectIsThrottled) {
// make sure we apply the last selected color
// eventually after throttle limit has passed
clearTimeout(this.ensureFinalSelect);
this.ensureFinalSelect = setTimeout(() => {
this.fireColorSelected(hs, rgb); // do it for the final time
}, this.throttle);
return;
}
this.fireColorSelected(hs, rgb); // do it
this.colorSelectIsThrottled = true;
setTimeout(() => {
this.colorSelectIsThrottled = false;
}, this.throttle);
}
// set color values and fire colorselected event
fireColorSelected(hs, rgb) {
this.hsColor = hs;
this.fire("colorselected", { hs, rgb });
}
/*
* Interface updating
*/
// set marker position to the given color
setMarkerOnColor(hs) {
if (!this.marker || !this.tooltip) {
return;
}
const dist = hs.s * this.radius;
const theta = ((hs.h - 180) / 180) * Math.PI;
const markerdX = -dist * Math.cos(theta);
const markerdY = -dist * Math.sin(theta);
const translateString = `translate(${markerdX},${markerdY})`;
this.marker.setAttribute("transform", translateString);
this.tooltip.setAttribute("transform", translateString);
}
// apply given color to interface elements
applyColorToCanvas(hs) {
if (!this.interactionLayer) {
return;
}
// we're not really converting hs to hsl here, but we keep it cheap
// setting the color on the interactionLayer, the svg elements can inherit
this.interactionLayer.style.color = `hsl(${hs.h}, 100%, ${
100 - hs.s * 50
}%)`;
}
applyHsColor(hs) {
// do nothing is we already have the same color
if (this.hsColor && this.hsColor.h === hs.h && this.hsColor.s === hs.s) {
return;
}
this.setMarkerOnColor(hs); // marker is always set on 'raw' hs position
if (!this.ignoreSegments) {
// apply segments if needed
hs = this.applySegmentFilter(hs);
}
this.hsColor = hs;
// always apply the new color to the interface / canvas
this.applyColorToCanvas(hs);
}
applyRgbColor(rgb) {
const [h, s] = rgb2hs(rgb);
this.applyHsColor({ h, s });
}
/*
* input processing helpers
*/
// get angle (degrees)
getAngle(dX, dY) {
const theta = Math.atan2(-dY, -dX); // radians from the left edge, clockwise = positive
const angle = (theta / Math.PI) * 180 + 180; // degrees, clockwise from right
return angle;
}
// returns true when coordinates are in the colorwheel
isInWheel(x, y) {
return this.getDistance(x, y) <= 1;
}
// returns distance from wheel center, 0 = center, 1 = edge, >1 = outside
getDistance(dX, dY) {
return Math.sqrt(dX * dX + dY * dY) / this.radius;
}
/*
* Getting colors
*/
getColor(x, y) {
const hue = this.getAngle(x, y); // degrees, clockwise from right
const relativeDistance = this.getDistance(x, y); // edge of radius = 1
const sat = Math.min(relativeDistance, 1); // Distance from center
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) {
const angleStep = 360 / this.hueSegments;
const halfAngleStep = angleStep / 2;
hs.h -= halfAngleStep; // take the 'centered segemnts' into account
if (hs.h < 0) {
hs.h += 360;
} // don't end up below 0
const rest = hs.h % angleStep;
hs.h -= rest - angleStep;
}
// apply saturation segment steps
if (this.saturationSegments) {
if (this.saturationSegments === 1) {
hs.s = 1;
} else {
const segmentSize = 1 / this.saturationSegments;
const saturationStep = 1 / (this.saturationSegments - 1);
const calculatedSat = Math.floor(hs.s / segmentSize) * saturationStep;
hs.s = Math.min(calculatedSat, 1);
}
}
return hs;
}
/*
* Drawing related stuff
*/
setupLayers() {
this.canvas = this.$.canvas;
this.backgroundLayer = this.$.backgroundLayer;
this.interactionLayer = this.$.interactionLayer;
// coordinate origin position (center of the wheel)
this.originX = this.width / 2;
this.originY = this.originX;
// synchronise width/height coordinates
this.backgroundLayer.width = this.width;
this.backgroundLayer.height = this.height;
this.interactionLayer.setAttribute(
"viewBox",
`${-this.originX} ${-this.originY} ${this.width} ${this.height}`
);
}
drawColorWheel() {
/*
* Setting up all paremeters
*/
let shadowColor;
let shadowOffsetX;
let shadowOffsetY;
let shadowBlur;
const context = this.backgroundLayer.getContext("2d");
// postioning and sizing
const cX = this.originX;
const cY = this.originY;
const radius = this.radius;
const counterClockwise = false;
// styling of the wheel
const wheelStyle = window.getComputedStyle(this.backgroundLayer, null);
const borderWidth = parseInt(
wheelStyle.getPropertyValue("--wheel-borderwidth"),
10
);
const borderColor = wheelStyle
.getPropertyValue("--wheel-bordercolor")
.trim();
const wheelShadow = wheelStyle.getPropertyValue("--wheel-shadow").trim();
// 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 ");
shadowColor = values.pop();
shadowOffsetX = parseInt(values[0], 10);
shadowOffsetY = parseInt(values[1], 10);
shadowBlur = parseInt(values[2], 10) || 0;
}
const borderRadius = radius + borderWidth / 2;
const wheelRadius = radius;
const shadowRadius = radius + borderWidth;
/*
* Drawing functions
*/
function drawCircle(hueSegments, saturationSegments) {
hueSegments = hueSegments || 360; // reset 0 segments to 360
const angleStep = 360 / hueSegments;
const halfAngleStep = angleStep / 2; // center segments on color
for (let angle = 0; angle <= 360; angle += angleStep) {
const startAngle = (angle - halfAngleStep) * (Math.PI / 180);
const endAngle = (angle + halfAngleStep + 1) * (Math.PI / 180);
context.beginPath();
context.moveTo(cX, cY);
context.arc(
cX,
cY,
wheelRadius,
startAngle,
endAngle,
counterClockwise
);
context.closePath();
// gradient
const gradient = context.createRadialGradient(
cX,
cY,
0,
cX,
cY,
wheelRadius
);
let lightness = 100;
// first gradient stop
gradient.addColorStop(0, `hsl(${angle}, 100%, ${lightness}%)`);
// segment gradient stops
if (saturationSegments > 0) {
const ratioStep = 1 / saturationSegments;
let ratio = 0;
for (let stop = 1; stop < saturationSegments; stop += 1) {
const prevLighness = lightness;
ratio = stop * ratioStep;
lightness = 100 - 50 * ratio;
gradient.addColorStop(
ratio,
`hsl(${angle}, 100%, ${prevLighness}%)`
);
gradient.addColorStop(ratio, `hsl(${angle}, 100%, ${lightness}%)`);
}
gradient.addColorStop(ratio, `hsl(${angle}, 100%, 50%)`);
}
// last gradient stop
gradient.addColorStop(1, `hsl(${angle}, 100%, 50%)`);
context.fillStyle = gradient;
context.fill();
}
}
function drawShadow() {
context.save();
context.beginPath();
context.arc(cX, cY, shadowRadius, 0, 2 * Math.PI, false);
context.shadowColor = shadowColor;
context.shadowOffsetX = shadowOffsetX;
context.shadowOffsetY = shadowOffsetY;
context.shadowBlur = shadowBlur;
context.fillStyle = "white";
context.fill();
context.restore();
}
function drawBorder() {
context.beginPath();
context.arc(cX, cY, borderRadius, 0, 2 * Math.PI, false);
context.lineWidth = borderWidth;
context.strokeStyle = borderColor;
context.stroke();
}
/*
* Call the drawing functions
* draws the shadow, wheel and border
*/
if (wheelStyle.shadow !== "none") {
drawShadow();
}
drawCircle(this.hueSegments, this.saturationSegments);
if (borderWidth > 0) {
drawBorder();
}
}
/*
* Draw the (draggable) marker and tooltip
* on the interactionLayer)
*/
drawMarker() {
const svgElement = this.interactionLayer;
const markerradius = this.radius * 0.08;
const tooltipradius = this.radius * 0.15;
const TooltipOffsetY = -(tooltipradius * 3);
const TooltipOffsetX = 0;
svgElement.marker = document.createElementNS(
"http://www.w3.org/2000/svg",
"circle"
);
svgElement.marker.setAttribute("id", "marker");
svgElement.marker.setAttribute("r", markerradius);
this.marker = svgElement.marker;
svgElement.appendChild(svgElement.marker);
svgElement.tooltip = document.createElementNS(
"http://www.w3.org/2000/svg",
"circle"
);
svgElement.tooltip.setAttribute("id", "colorTooltip");
svgElement.tooltip.setAttribute("r", tooltipradius);
svgElement.tooltip.setAttribute("cx", TooltipOffsetX);
svgElement.tooltip.setAttribute("cy", TooltipOffsetY);
this.tooltip = svgElement.tooltip;
svgElement.appendChild(svgElement.tooltip);
}
segmentationChange() {
if (this.backgroundLayer) {
this.drawColorWheel();
}
}
}
customElements.define("ha-color-picker", HaColorPicker);

View File

@ -0,0 +1,329 @@
import { DIRECTION_ALL, Manager, Pan, Tap } from "@egjs/hammerjs";
import { css, html, LitElement, PropertyValues, svg } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import { hsv2rgb, rgb2hex } from "../common/color/convert-color";
import { fireEvent } from "../common/dom/fire_event";
function xy2polar(x: number, y: number) {
const r = Math.sqrt(x * x + y * y);
const phi = Math.atan2(y, x);
return [r, phi];
}
function polar2xy(r: number, phi: number) {
const x = Math.cos(phi) * r;
const y = Math.sin(phi) * r;
return [x, y];
}
function rad2deg(rad: number) {
return (rad / (2 * Math.PI)) * 360;
}
function deg2rad(deg: number) {
return (deg / 360) * 2 * Math.PI;
}
function drawColorWheel(ctx: CanvasRenderingContext2D, colorBrightness = 255) {
const radius = ctx.canvas.width / 2;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
const cX = ctx.canvas.width / 2;
const cY = ctx.canvas.width / 2;
for (let angle = 0; angle < 360; angle += 1) {
const startAngle = deg2rad(angle - 0.5);
const endAngle = deg2rad(angle + 1.5);
ctx.beginPath();
ctx.moveTo(cX, cY);
ctx.arc(cX, cY, radius, startAngle, endAngle);
ctx.closePath();
const gradient = ctx.createRadialGradient(cX, cY, 0, cX, cY, radius);
const start = rgb2hex(hsv2rgb([angle, 0, colorBrightness]));
const end = rgb2hex(hsv2rgb([angle, 1, colorBrightness]));
gradient.addColorStop(0, start);
gradient.addColorStop(1, end);
ctx.fillStyle = gradient;
ctx.fill();
}
}
@customElement("ha-hs-color-picker")
class HaHsColorPicker extends LitElement {
@property({ type: Boolean, reflect: true })
public disabled = false;
@property({ type: Number, attribute: false })
public renderSize?: number;
@property({ type: Number })
public value?: [number, number];
@property({ type: Number })
public colorBrightness?: number;
@query("#canvas") private _canvas!: HTMLCanvasElement;
private _mc?: HammerManager;
@state()
private _pressed?: string;
@state()
private _cursorPosition?: [number, number];
@state()
private _localValue?: [number, number];
protected firstUpdated(changedProps: PropertyValues): void {
super.firstUpdated(changedProps);
this._setupListeners();
this._generateColorWheel();
}
private _generateColorWheel() {
const ctx = this._canvas.getContext("2d")!;
drawColorWheel(ctx, this.colorBrightness);
}
connectedCallback(): void {
super.connectedCallback();
this._setupListeners();
}
disconnectedCallback(): void {
super.disconnectedCallback();
this._destroyListeners();
}
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
if (changedProps.has("colorBrightness")) {
this._generateColorWheel();
}
if (changedProps.has("value")) {
if (
this.value !== undefined &&
(this._localValue?.[0] !== this.value[0] ||
this._localValue?.[1] !== this.value[1])
) {
this._resetPosition();
}
}
}
_setupListeners() {
if (this._canvas && !this._mc) {
this._mc = new Manager(this._canvas);
this._mc.add(
new Pan({
direction: DIRECTION_ALL,
enable: true,
})
);
this._mc.add(new Tap({ event: "singletap" }));
let savedPosition;
this._mc.on("panstart", (e) => {
if (this.disabled) return;
this._pressed = e.pointerType;
savedPosition = this._cursorPosition;
});
this._mc.on("pancancel", () => {
if (this.disabled) return;
this._pressed = undefined;
this._cursorPosition = savedPosition;
});
this._mc.on("panmove", (e) => {
if (this.disabled) return;
this._cursorPosition = this._getPositionFromEvent(e);
this._localValue = this._getValueFromCoord(...this._cursorPosition);
fireEvent(this, "cursor-moved", { value: this._localValue });
});
this._mc.on("panend", (e) => {
if (this.disabled) return;
this._pressed = undefined;
this._cursorPosition = this._getPositionFromEvent(e);
this._localValue = this._getValueFromCoord(...this._cursorPosition);
fireEvent(this, "cursor-moved", { value: undefined });
fireEvent(this, "value-changed", { value: this._localValue });
});
this._mc.on("singletap", (e) => {
if (this.disabled) return;
this._cursorPosition = this._getPositionFromEvent(e);
this._localValue = this._getValueFromCoord(...this._cursorPosition);
fireEvent(this, "value-changed", { value: this._localValue });
});
}
}
private _resetPosition() {
if (this.value === undefined) return;
this._cursorPosition = this._getCoordsFromValue(this.value);
this._localValue = this.value;
}
private _getCoordsFromValue = (value: [number, number]): [number, number] => {
const phi = deg2rad(value[0]);
const r = Math.min(value[1], 1);
const [x, y] = polar2xy(r, phi);
return [x, y];
};
private _getValueFromCoord = (x: number, y: number): [number, number] => {
const [r, phi] = xy2polar(x, y);
const deg = Math.round(rad2deg(phi)) % 360;
const hue = (deg + 360) % 360;
const saturation = Math.round(Math.min(r, 1) * 100) / 100;
return [hue, saturation];
};
private _getPositionFromEvent = (e: HammerInput): [number, number] => {
const x = e.center.x;
const y = e.center.y;
const boundingRect = e.target.getBoundingClientRect();
const offsetX = boundingRect.left;
const offsetY = boundingRect.top;
const maxX = e.target.clientWidth;
const maxY = e.target.clientHeight;
const _x = (2 * (x - offsetX)) / maxX - 1;
const _y = (2 * (y - offsetY)) / maxY - 1;
const [r, phi] = xy2polar(_x, _y);
const [__x, __y] = polar2xy(Math.min(1, r), phi);
return [__x, __y];
};
_destroyListeners() {
if (this._mc) {
this._mc.destroy();
this._mc = undefined;
}
}
render() {
const size = this.renderSize || 400;
const canvasSize = size * window.devicePixelRatio;
const rgb =
this._localValue !== undefined
? hsv2rgb([
this._localValue[0],
this._localValue[1],
this.colorBrightness ?? 255,
])
: ([255, 255, 255] as [number, number, number]);
const [x, y] = this._cursorPosition ?? [0, 0];
const cx = ((x + 1) * size) / 2;
const cy = ((y + 1) * size) / 2;
const markerPosition = `${cx}px, ${cy}px`;
const markerScale = this._pressed ? "1.5" : "1";
const markerOffset =
this._pressed === "touch" ? `0px, -${size / 8}px` : "0px, 0px";
return html`
<div class="container ${classMap({ pressed: Boolean(this._pressed) })}">
<canvas id="canvas" .width=${canvasSize} .height=${canvasSize}></canvas>
<svg id="interaction" viewBox="0 0 ${size} ${size}" overflow="visible">
<defs>${this.renderSVGFilter()}</defs>
<g
style=${styleMap({
fill: rgb2hex(rgb),
transform: `translate(${markerPosition})`,
})}
class="cursor"
>
<circle
cx="0"
cy="0"
r="16"
style=${styleMap({
fill: rgb2hex(rgb),
transform: `translate(${markerOffset}) scale(${markerScale})`,
visibility: this._cursorPosition ? undefined : "hidden",
})}
></circle>
</g>
</svg>
</div>
`;
}
renderSVGFilter() {
return svg`
<filter
id="marker-shadow"
x="-50%"
y="-50%"
width="200%"
height="200%"
filterUnits="objectBoundingBox"
>
<feDropShadow dx="0" dy="1" stdDeviation="2" flood-opacity="0.3" flood-color="rgba(0, 0, 0, 1)"/>
<feDropShadow dx="0" dy="1" stdDeviation="3" flood-opacity="0.15" flood-color="rgba(0, 0, 0, 1)"/>
</filter>
`;
}
static get styles() {
return css`
:host {
display: block;
}
.container {
position: relative;
width: 100%;
height: 100%;
cursor: pointer;
display: flex;
}
canvas {
width: 100%;
height: 100%;
}
svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
circle {
fill: black;
stroke: white;
stroke-width: 2;
filter: url(#marker-shadow);
}
.container:not(.pressed) circle {
transition: transform 100ms ease-in-out, fill 100ms ease-in-out;
}
.container:not(.pressed) .cursor {
transition: transform 200ms ease-in-out;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-hs-color-picker": HaHsColorPicker;
}
}

View File

@ -0,0 +1,354 @@
import { DIRECTION_ALL, Manager, Pan, Tap } from "@egjs/hammerjs";
import { css, html, LitElement, PropertyValues, svg } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import { rgb2hex } from "../common/color/convert-color";
import { fireEvent } from "../common/dom/fire_event";
declare global {
interface HASSDomEvents {
"cursor-moved": { value?: any };
}
}
function xy2polar(x: number, y: number) {
const r = Math.sqrt(x * x + y * y);
const phi = Math.atan2(y, x);
return [r, phi];
}
function polar2xy(r: number, phi: number) {
const x = Math.cos(phi) * r;
const y = Math.sin(phi) * r;
return [x, y];
}
function temperature2rgb(temperature: number): [number, number, number] {
const value = temperature / 100;
return [getRed(value), getGreen(value), getBlue(value)];
}
function getRed(temperature: number): number {
if (temperature <= 66) {
return 255;
}
const tmp_red = 329.698727446 * (temperature - 60) ** -0.1332047592;
return clamp(tmp_red);
}
function getGreen(temperature: number): number {
let green: number;
if (temperature <= 66) {
green = 99.4708025861 * Math.log(temperature) - 161.1195681661;
} else {
green = 288.1221695283 * (temperature - 60) ** -0.0755148492;
}
return clamp(green);
}
function getBlue(temperature: number): number {
if (temperature >= 66) {
return 255;
}
if (temperature <= 19) {
return 0;
}
const blue = 138.5177312231 * Math.log(temperature - 10) - 305.0447927307;
return clamp(blue);
}
function clamp(value: number): number {
return Math.max(0, Math.min(255, value));
}
function drawColorWheel(
ctx: CanvasRenderingContext2D,
minTemp: number,
maxTemp: number
) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
const radius = ctx.canvas.width / 2;
const min = Math.max(minTemp, 2000);
const max = Math.min(maxTemp, 40000);
for (let y = -radius; y < radius; y += 1) {
const x = radius * Math.sqrt(1 - (y / radius) ** 2);
const fraction = (y / radius + 1) / 2;
const temperature = min + fraction * (max - min);
const color = rgb2hex(temperature2rgb(temperature));
ctx.fillStyle = color;
ctx.fillRect(radius - x, radius + y - 0.5, 2 * x, 2);
ctx.fill();
}
}
@customElement("ha-temp-color-picker")
class HaTempColorPicker extends LitElement {
@property({ type: Boolean, reflect: true })
public disabled = false;
@property({ type: Number, attribute: false })
public renderSize?: number;
@property({ type: Number })
public value?: number;
@property() min = 2000;
@property() max = 10000;
@query("#canvas") private _canvas!: HTMLCanvasElement;
private _mc?: HammerManager;
@state()
private _pressed?: string;
@state()
private _cursorPosition?: [number, number];
@state()
private _localValue?: number;
protected firstUpdated(changedProps: PropertyValues): void {
super.firstUpdated(changedProps);
this._setupListeners();
this._generateColorWheel();
}
private _generateColorWheel() {
const ctx = this._canvas.getContext("2d")!;
drawColorWheel(ctx, this.min, this.max);
}
connectedCallback(): void {
super.connectedCallback();
this._setupListeners();
}
disconnectedCallback(): void {
super.disconnectedCallback();
this._destroyListeners();
}
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
if (changedProps.has("min") || changedProps.has("max")) {
this._generateColorWheel();
this._resetPosition();
}
if (changedProps.has("value")) {
if (this.value !== undefined && this._localValue !== this.value) {
this._resetPosition();
}
}
}
_setupListeners() {
if (this._canvas && !this._mc) {
this._mc = new Manager(this._canvas);
this._mc.add(
new Pan({
direction: DIRECTION_ALL,
enable: true,
threshold: 0,
})
);
this._mc.add(new Tap({ event: "singletap" }));
let savedPosition;
this._mc.on("panstart", (e) => {
if (this.disabled) return;
this._pressed = e.pointerType;
savedPosition = this._cursorPosition;
});
this._mc.on("pancancel", () => {
if (this.disabled) return;
this._pressed = undefined;
this._cursorPosition = savedPosition;
});
this._mc.on("panmove", (e) => {
if (this.disabled) return;
this._cursorPosition = this._getPositionFromEvent(e);
this._localValue = this._getValueFromCoord(...this._cursorPosition);
fireEvent(this, "cursor-moved", { value: this._localValue });
});
this._mc.on("panend", (e) => {
if (this.disabled) return;
this._pressed = undefined;
this._cursorPosition = this._getPositionFromEvent(e);
this._localValue = this._getValueFromCoord(...this._cursorPosition);
fireEvent(this, "cursor-moved", { value: undefined });
fireEvent(this, "value-changed", { value: this._localValue });
});
this._mc.on("singletap", (e) => {
if (this.disabled) return;
this._cursorPosition = this._getPositionFromEvent(e);
this._localValue = this._getValueFromCoord(...this._cursorPosition);
fireEvent(this, "value-changed", { value: this._localValue });
});
}
}
private _resetPosition() {
if (this.value === undefined) return;
const [, y] = this._getCoordsFromValue(this.value);
const currentX = this._cursorPosition?.[0] ?? 0;
const x =
Math.sign(currentX) * Math.min(Math.sqrt(1 - y ** 2), Math.abs(currentX));
this._cursorPosition = [x, y];
this._localValue = this.value;
}
private _getCoordsFromValue = (temperature: number): [number, number] => {
const fraction = (temperature - this.min) / (this.max - this.min);
const y = 2 * fraction - 1;
return [0, y];
};
private _getValueFromCoord = (_x: number, y: number): number => {
const fraction = (y + 1) / 2;
const temperature = this.min + fraction * (this.max - this.min);
return Math.round(temperature);
};
private _getPositionFromEvent = (e: HammerInput): [number, number] => {
const x = e.center.x;
const y = e.center.y;
const boundingRect = e.target.getBoundingClientRect();
const offsetX = boundingRect.left;
const offsetY = boundingRect.top;
const maxX = e.target.clientWidth;
const maxY = e.target.clientHeight;
const _x = (2 * (x - offsetX)) / maxX - 1;
const _y = (2 * (y - offsetY)) / maxY - 1;
const [r, phi] = xy2polar(_x, _y);
const [__x, __y] = polar2xy(Math.min(1, r), phi);
return [__x, __y];
};
_destroyListeners() {
if (this._mc) {
this._mc.destroy();
this._mc = undefined;
}
}
render() {
const size = this.renderSize || 400;
const canvasSize = size * window.devicePixelRatio;
const rgb =
this._localValue !== undefined
? temperature2rgb(this._localValue)
: ([255, 255, 255] as [number, number, number]);
const [x, y] = this._cursorPosition ?? [0, 0];
const cx = ((x + 1) * size) / 2;
const cy = ((y + 1) * size) / 2;
const markerPosition = `${cx}px, ${cy}px`;
const markerScale = this._pressed ? "1.5" : "1";
const markerOffset =
this._pressed === "touch" ? `0px, -${size / 8}px` : "0px, 0px";
return html`
<div class="container ${classMap({ pressed: Boolean(this._pressed) })}">
<canvas id="canvas" .width=${canvasSize} .height=${canvasSize}></canvas>
<svg id="interaction" viewBox="0 0 ${size} ${size}" overflow="visible">
<defs>${this.renderSVGFilter()}</defs>
<g
style=${styleMap({
fill: rgb2hex(rgb),
transform: `translate(${markerPosition})`,
})}
class="cursor"
>
<circle
cx="0"
cy="0"
r="16"
style=${styleMap({
fill: rgb2hex(rgb),
transform: `translate(${markerOffset}) scale(${markerScale})`,
visibility: this._cursorPosition ? undefined : "hidden",
})}
></circle>
</g>
</svg>
</div>
`;
}
renderSVGFilter() {
return svg`
<filter
id="marker-shadow"
x="-50%"
y="-50%"
width="200%"
height="200%"
filterUnits="objectBoundingBox"
>
<feDropShadow dx="0" dy="1" stdDeviation="2" flood-opacity="0.3" flood-color="rgba(0, 0, 0, 1)"/>
<feDropShadow dx="0" dy="1" stdDeviation="3" flood-opacity="0.15" flood-color="rgba(0, 0, 0, 1)"/>
</filter>
`;
}
static get styles() {
return css`
:host {
display: block;
}
.container {
position: relative;
width: 100%;
height: 100%;
cursor: pointer;
display: flex;
}
canvas {
width: 100%;
height: 100%;
}
svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
circle {
fill: black;
stroke: white;
stroke-width: 2;
filter: url(#marker-shadow);
}
.container:not(.pressed) circle {
transition: transform 100ms ease-in-out, fill 100ms ease-in-out;
}
.container:not(.pressed) .cursor {
transition: transform 200ms ease-in-out;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-temp-color-picker": HaTempColorPicker;
}
}

View File

@ -1,7 +1,6 @@
import "@material/mwc-button";
import "@material/mwc-tab-bar/mwc-tab-bar";
import "@material/mwc-tab/mwc-tab";
import { mdiPalette } from "@mdi/js";
import {
css,
CSSResultGroup,
@ -11,12 +10,13 @@ import {
PropertyValues,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { hs2rgb, rgb2hs } from "../../../../common/color/convert-color";
import { throttle } from "../../../../common/util/throttle";
import "../../../../components/ha-button-toggle-group";
import "../../../../components/ha-color-picker";
import "../../../../components/ha-control-slider";
import "../../../../components/ha-hs-color-picker";
import "../../../../components/ha-icon-button-prev";
import "../../../../components/ha-labeled-slider";
import "../../../../components/ha-temp-color-picker";
import {
getLightCurrentModeRgbColor,
LightColorMode,
@ -35,8 +35,6 @@ class MoreInfoViewLightColorPicker extends LitElement {
@property() public params?: LightColorPickerViewParams;
@state() private _ctSliderValue?: number;
@state() private _cwSliderValue?: number;
@state() private _wwSliderValue?: number;
@ -47,11 +45,9 @@ class MoreInfoViewLightColorPicker extends LitElement {
@state() private _brightnessAdjusted?: number;
@state() private _hueSegments = 24;
@state() private _hsPickerValue?: [number, number];
@state() private _saturationSegments = 8;
@state() private _colorPickerColor?: [number, number, number];
@state() private _ctPickerValue?: number;
@state() private _mode?: Mode;
@ -94,47 +90,34 @@ class MoreInfoViewLightColorPicker extends LitElement {
)}
</mwc-tab-bar>
`
: ""}
: nothing}
<div class="content">
${this._mode === LightColorMode.COLOR_TEMP
? html`
<p class="color-temp-value">
${this._ctSliderValue ? `${this._ctSliderValue} K` : nothing}
${this._ctPickerValue ? `${this._ctPickerValue} K` : nothing}
</p>
<ha-control-slider
vertical
class="color-temp"
label=${this.hass.localize("ui.card.light.color_temperature")}
min="1"
max="100"
mode="cursor"
.value=${this._ctSliderValue}
@value-changed=${this._ctSliderChanged}
@slider-moved=${this._ctSliderMoved}
<ha-temp-color-picker
@value-changed=${this._ctColorChanged}
@cursor-moved=${this._ctColorCursorMoved}
.min=${this.stateObj.attributes.min_color_temp_kelvin!}
.max=${this.stateObj.attributes.max_color_temp_kelvin!}
.value=${this._ctPickerValue}
>
</ha-control-slider>
</ha-temp-color-picker>
`
: ""}
: nothing}
${this._mode === "color"
? html`
<div class="segmentation-container">
<ha-color-picker
class="color"
@colorselected=${this._colorPicked}
.desiredRgbColor=${this._colorPickerColor}
throttle="500"
.hueSegments=${this._hueSegments}
.saturationSegments=${this._saturationSegments}
>
</ha-color-picker>
<ha-icon-button
.path=${mdiPalette}
@click=${this._segmentClick}
class="segmentation-button"
></ha-icon-button>
</div>
<ha-hs-color-picker
@value-changed=${this._hsColorChanged}
@cursor-moved=${this._hsColorCursorMoved}
.value=${this._hsPickerValue}
.colorBrightness=${this._colorBrightnessSliderValue != null
? (this._colorBrightnessSliderValue * 255) / 100
: undefined}
>
</ha-hs-color-picker>
${supportsRgbw || supportsRgbww
? html`<ha-labeled-slider
@ -188,9 +171,9 @@ class MoreInfoViewLightColorPicker extends LitElement {
pin
></ha-labeled-slider>
`
: ""}
: nothing}
`
: ""}
: nothing}
</div>
`;
}
@ -212,7 +195,7 @@ class MoreInfoViewLightColorPicker extends LitElement {
this._brightnessAdjusted = maxVal;
}
}
this._ctSliderValue =
this._ctPickerValue =
stateObj.attributes.color_mode === LightColorMode.COLOR_TEMP
? stateObj.attributes.color_temp_kelvin
: undefined;
@ -239,14 +222,12 @@ class MoreInfoViewLightColorPicker extends LitElement {
? Math.round((Math.max(...currentRgbColor.slice(0, 3)) * 100) / 255)
: undefined;
this._colorPickerColor = currentRgbColor?.slice(0, 3) as [
number,
number,
number
];
this._hsPickerValue = currentRgbColor
? rgb2hs(currentRgbColor.slice(0, 3) as [number, number, number])
: undefined;
} else {
this._colorPickerColor = [0, 0, 0];
this._ctSliderValue = undefined;
this._hsPickerValue = [0, 0];
this._ctPickerValue = undefined;
this._wvSliderValue = undefined;
this._cwSliderValue = undefined;
this._wwSliderValue = undefined;
@ -295,14 +276,79 @@ class MoreInfoViewLightColorPicker extends LitElement {
this._mode = newMode;
}
private _ctSliderMoved(ev: CustomEvent) {
private _hsColorCursorMoved(ev: CustomEvent) {
if (!ev.detail.value) {
return;
}
this._hsPickerValue = ev.detail.value;
this._throttleUpdateColor();
}
private _throttleUpdateColor = throttle(() => this._updateColor(), 500);
private _updateColor() {
const hs_color = this._hsPickerValue!;
const rgb_color = hs2rgb(hs_color);
if (
lightSupportsColorMode(this.stateObj!, LightColorMode.RGBWW) ||
lightSupportsColorMode(this.stateObj!, LightColorMode.RGBW)
) {
this._setRgbWColor(
this._colorBrightnessSliderValue
? this._adjustColorBrightness(
rgb_color,
(this._colorBrightnessSliderValue * 255) / 100
)
: rgb_color
);
} else if (lightSupportsColorMode(this.stateObj!, LightColorMode.RGB)) {
if (this._brightnessAdjusted) {
const brightnessAdjust = (this._brightnessAdjusted / 255) * 100;
const brightnessPercentage = Math.round(
((this.stateObj!.attributes.brightness || 0) * brightnessAdjust) / 255
);
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
brightness_pct: brightnessPercentage,
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: [hs_color[0], hs_color[1] * 100],
});
}
}
private _hsColorChanged(ev: CustomEvent) {
if (!ev.detail.value) {
return;
}
this._hsPickerValue = ev.detail.value;
this._updateColor();
}
private _ctColorCursorMoved(ev: CustomEvent) {
const ct = ev.detail.value;
if (isNaN(ct) || this._ctSliderValue === ct) {
if (isNaN(ct) || this._ctPickerValue === ct) {
return;
}
this._ctSliderValue = ct;
this._ctPickerValue = ct;
this._throttleUpdateColorTemp();
}
@ -310,18 +356,18 @@ class MoreInfoViewLightColorPicker extends LitElement {
private _throttleUpdateColorTemp = throttle(() => {
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
color_temp_kelvin: this._ctSliderValue,
color_temp_kelvin: this._ctPickerValue,
});
}, 500);
private _ctSliderChanged(ev: CustomEvent) {
private _ctColorChanged(ev: CustomEvent) {
const ct = ev.detail.value;
if (isNaN(ct) || this._ctSliderValue === ct) {
if (isNaN(ct) || this._ctPickerValue === ct) {
return;
}
this._ctSliderValue = ct;
this._ctPickerValue = ct;
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
@ -399,16 +445,6 @@ class MoreInfoViewLightColorPicker extends LitElement {
);
}
private _segmentClick() {
if (this._hueSegments === 24 && this._saturationSegments === 8) {
this._hueSegments = 0;
this._saturationSegments = 0;
} else {
this._hueSegments = 24;
this._saturationSegments = 8;
}
}
private _adjustColorBrightness(
rgbColor: [number, number, number],
value?: number,
@ -448,68 +484,6 @@ class MoreInfoViewLightColorPicker extends LitElement {
}
}
/**
* Called when a new color has been picked.
* should be throttled with the 'throttle=' attribute of the color picker
*/
private _colorPicked(
ev: CustomEvent<{
hs: { h: number; s: number };
rgb: { r: number; g: number; b: number };
}>
) {
this._colorPickerColor = [
ev.detail.rgb.r,
ev.detail.rgb.g,
ev.detail.rgb.b,
];
if (
lightSupportsColorMode(this.stateObj!, LightColorMode.RGBWW) ||
lightSupportsColorMode(this.stateObj!, LightColorMode.RGBW)
) {
this._setRgbWColor(
this._colorBrightnessSliderValue
? this._adjustColorBrightness(
[ev.detail.rgb.r, ev.detail.rgb.g, ev.detail.rgb.b],
(this._colorBrightnessSliderValue * 255) / 100
)
: [ev.detail.rgb.r, ev.detail.rgb.g, ev.detail.rgb.b]
);
} else if (lightSupportsColorMode(this.stateObj!, LightColorMode.RGB)) {
const rgb_color: [number, number, number] = [
ev.detail.rgb.r,
ev.detail.rgb.g,
ev.detail.rgb.b,
];
if (this._brightnessAdjusted) {
const brightnessAdjust = (this._brightnessAdjusted / 255) * 100;
const brightnessPercentage = Math.round(
((this.stateObj!.attributes.brightness || 0) * brightnessAdjust) / 255
);
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
brightness_pct: brightnessPercentage,
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(): CSSResultGroup {
return [
css`
@ -526,35 +500,16 @@ class MoreInfoViewLightColorPicker extends LitElement {
flex: 1;
}
.segmentation-container {
position: relative;
max-height: 500px;
display: flex;
justify-content: center;
ha-hs-color-picker {
max-width: 320px;
min-width: 200px;
margin: 44px 0 44px 0;
}
.segmentation-button {
position: absolute;
top: 5%;
left: 0;
color: var(--secondary-text-color);
}
ha-color-picker {
--ha-color-picker-wheel-borderwidth: 5;
--ha-color-picker-wheel-bordercolor: white;
--ha-color-picker-wheel-shadow: none;
--ha-color-picker-marker-borderwidth: 2;
--ha-color-picker-marker-bordercolor: white;
}
ha-control-slider {
height: 45vh;
max-height: 320px;
min-height: 200px;
margin: 20px 0;
--control-slider-thickness: 100px;
--control-slider-border-radius: 24px;
ha-temp-color-picker {
max-width: 320px;
min-width: 200px;
margin: 20px 0 44px 0;
}
ha-labeled-slider {
@ -572,17 +527,6 @@ class MoreInfoViewLightColorPicker extends LitElement {
direction: ltr;
}
.color-temp {
--control-slider-background: -webkit-linear-gradient(
top,
rgb(166, 209, 255) 0%,
white 50%,
rgb(255, 160, 0) 100%
);
--control-slider-background-opacity: 1;
margin-bottom: 44px;
}
hr {
border-color: var(--divider-color);
border-bottom: none;