mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 15:26:36 +00:00
New color picker for HS and color temp (#16549)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
a5b5e61ed4
commit
e9d9d89d79
3
gallery/src/pages/components/ha-hs-color-picker.markdown
Normal file
3
gallery/src/pages/components/ha-hs-color-picker.markdown
Normal file
@ -0,0 +1,3 @@
|
||||
---
|
||||
title: HS Color Picker
|
||||
---
|
120
gallery/src/pages/components/ha-hs-color-picker.ts
Normal file
120
gallery/src/pages/components/ha-hs-color-picker.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Temp Color Picker
|
||||
---
|
117
gallery/src/pages/components/ha-temp-color-picker.ts
Normal file
117
gallery/src/pages/components/ha-temp-color-picker.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
329
src/components/ha-hs-color-picker.ts
Normal file
329
src/components/ha-hs-color-picker.ts
Normal 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;
|
||||
}
|
||||
}
|
354
src/components/ha-temp-color-picker.ts
Normal file
354
src/components/ha-temp-color-picker.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user