Use new light HS API for the color picker (#982)

This commit is contained in:
Adam Mills 2018-03-21 17:35:16 -04:00 committed by GitHub
parent 6bdf1c8b80
commit 0df4aa6117
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 49 additions and 141 deletions

View File

@ -82,23 +82,14 @@ class HaColorPicker extends window.hassMixins.EventsMixin(Polymer.Element) {
static get properties() { static get properties() {
return { return {
hsvColor: { hsColor: {
type: Object,
},
rgbColor: {
type: Object, type: Object,
}, },
// use these properties to update the state via attributes // use these properties to update the state via attributes
desiredHsvColor: { desiredHsColor: {
type: Object, type: Object,
observer: 'applyHsvColor' observer: 'applyHsColor'
},
desiredRgbColor: {
type: Object,
observer: 'applyRgbColor'
}, },
// width, height and radius apply to the coordinates of // width, height and radius apply to the coordinates of
@ -157,7 +148,6 @@ class HaColorPicker extends window.hassMixins.EventsMixin(Polymer.Element) {
ready() { ready() {
super.ready(); super.ready();
this.applyRgbColor = this.applyRgbColor.bind(this);
this.setupLayers(); this.setupLayers();
this.drawColorWheel(); this.drawColorWheel();
this.drawMarker(); this.drawMarker();
@ -250,18 +240,18 @@ class HaColorPicker extends window.hassMixins.EventsMixin(Polymer.Element) {
// Process user input to color // Process user input to color
processUserSelect(ev) { processUserSelect(ev) {
var canvasXY = this.convertToCanvasCoordinates(ev.clientX, ev.clientY); var canvasXY = this.convertToCanvasCoordinates(ev.clientX, ev.clientY);
var hsv = this.getColor(canvasXY.x, canvasXY.y); var hs = this.getColor(canvasXY.x, canvasXY.y);
this.onColorSelect(hsv); this.onColorSelect(hs);
} }
// apply color to marker position and canvas // apply color to marker position and canvas
onColorSelect(hsv) { onColorSelect(hs) {
this.setMarkerOnColor(hsv); // marker always follows mounse 'raw' hsv value (= mouse position) this.setMarkerOnColor(hs); // marker always follows mounse 'raw' hs value (= mouse position)
if (!this.ignoreSegments) { // apply segments if needed if (!this.ignoreSegments) { // apply segments if needed
hsv = this.applySegmentFilter(hsv); hs = this.applySegmentFilter(hs);
} }
// always apply the new color to the interface / canvas // always apply the new color to the interface / canvas
this.applyColorToCanvas(hsv); this.applyColorToCanvas(hs);
// throttling is applied to updating the exposed colors (properties) // throttling is applied to updating the exposed colors (properties)
// and firing of events // and firing of events
if (this.colorSelectIsThrottled) { if (this.colorSelectIsThrottled) {
@ -269,11 +259,11 @@ class HaColorPicker extends window.hassMixins.EventsMixin(Polymer.Element) {
// eventually after throttle limit has passed // eventually after throttle limit has passed
clearTimeout(this.ensureFinalSelect); clearTimeout(this.ensureFinalSelect);
this.ensureFinalSelect = setTimeout(() => { this.ensureFinalSelect = setTimeout(() => {
this.fireColorSelected(hsv); // do it for the final time this.fireColorSelected(hs); // do it for the final time
}, this.throttle); }, this.throttle);
return; return;
} }
this.fireColorSelected(hsv); // do it this.fireColorSelected(hs); // do it
this.colorSelectIsThrottled = true; this.colorSelectIsThrottled = true;
setTimeout(() => { setTimeout(() => {
this.colorSelectIsThrottled = false; this.colorSelectIsThrottled = false;
@ -281,10 +271,9 @@ class HaColorPicker extends window.hassMixins.EventsMixin(Polymer.Element) {
} }
// set color values and fire colorselected event // set color values and fire colorselected event
fireColorSelected(hsv) { fireColorSelected(hs) {
this.hsvColor = hsv; this.hsColor = hs;
this.rgbColor = this.HSVtoRGB(this.hsvColor); this.fire('colorselected', { hs: { h: hs.h, s: hs.s } });
this.fire('colorselected', { rgb: this.rgbColor, hsv: this.hsvColor });
} }
@ -293,9 +282,9 @@ class HaColorPicker extends window.hassMixins.EventsMixin(Polymer.Element) {
*/ */
// set marker position to the given color // set marker position to the given color
setMarkerOnColor(hsv) { setMarkerOnColor(hs) {
var dist = hsv.s * this.radius; var dist = hs.s * this.radius;
var theta = ((hsv.h - 180) / 180) * Math.PI; var theta = ((hs.h - 180) / 180) * Math.PI;
var markerdX = -dist * Math.cos(theta); var markerdX = -dist * Math.cos(theta);
var markerdY = -dist * Math.sin(theta); var markerdY = -dist * Math.sin(theta);
var translateString = `translate(${markerdX},${markerdY})`; var translateString = `translate(${markerdX},${markerdY})`;
@ -304,49 +293,27 @@ class HaColorPicker extends window.hassMixins.EventsMixin(Polymer.Element) {
} }
// apply given color to interface elements // apply given color to interface elements
applyColorToCanvas(hsv) { applyColorToCanvas(hs) {
// we're not really converting hsv to hsl here, but we keep it cheap // 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 // setting the color on the interactionLayer, the svg elements can inherit
this.interactionLayer.style.color = `hsl(${hsv.h}, ${hsv.v * 100}%, ${100 - (hsv.s * 50)}%)`; this.interactionLayer.style.color = `hsl(${hs.h}, 100%, ${100 - (hs.s * 50)}%)`;
} }
/* applyHsColor(hs) {
* applyRgbColor and applyHsvColor are used for external updates
* (to prevent observer loops on this.hsvColor and this.rgbColor)
*/
applyRgbColor(rgb) {
if (!rgb) {
return;
}
// do nothing is we already have the same color // do nothing is we already have the same color
if (this.rgbColor && if (this.hsColor &&
this.rgbColor.r === rgb.r && this.hsColor.h === hs.h &&
this.rgbColor.g === rgb.g && this.hsColor.s === hs.s) {
this.rgbColor.b === rgb.b) {
return; return;
} }
var hsv = this.RGBtoHSV(rgb); this.setMarkerOnColor(hs); // marker is always set on 'raw' hs position
this.applyHsvColor(hsv); // marker is always set on 'raw' hsv position
}
applyHsvColor(hsv) {
// do nothing is we already have the same color
if (this.hsvColor &&
this.hsvColor.h === hsv.h &&
this.hsvColor.s === hsv.s &&
this.hsvColor.v === hsv.v) {
return;
}
this.setMarkerOnColor(hsv); // marker is always set on 'raw' hsv position
if (!this.ignoreSegments) { // apply segments if needed if (!this.ignoreSegments) { // apply segments if needed
hsv = this.applySegmentFilter(hsv); hs = this.applySegmentFilter(hs);
} }
this.hsvColor = hsv; this.hsColor = hs;
this.rgbColor = this.HSVtoRGB(hsv);
// always apply the new color to the interface / canvas // always apply the new color to the interface / canvas
this.applyColorToCanvas(hsv); this.applyColorToCanvas(hs);
} }
@ -380,32 +347,32 @@ class HaColorPicker extends window.hassMixins.EventsMixin(Polymer.Element) {
var hue = this.getAngle(x, y); // degrees, clockwise from right var hue = this.getAngle(x, y); // degrees, clockwise from right
var relativeDistance = this.getDistance(x, y); // edge of radius = 1 var relativeDistance = this.getDistance(x, y); // edge of radius = 1
var sat = Math.min(relativeDistance, 1); // Distance from center var sat = Math.min(relativeDistance, 1); // Distance from center
return { h: hue, s: sat, v: 1 }; return { h: hue, s: sat };
} }
applySegmentFilter(hsv) { applySegmentFilter(hs) {
// apply hue segment steps // apply hue segment steps
if (this.hueSegments) { if (this.hueSegments) {
const angleStep = 360 / this.hueSegments; const angleStep = 360 / this.hueSegments;
const halfAngleStep = angleStep / 2; const halfAngleStep = angleStep / 2;
hsv.h -= halfAngleStep; // take the 'centered segemnts' into account hs.h -= halfAngleStep; // take the 'centered segemnts' into account
if (hsv.h < 0) { hsv.h += 360; } // don't end up below 0 if (hs.h < 0) { hs.h += 360; } // don't end up below 0
const rest = hsv.h % angleStep; const rest = hs.h % angleStep;
hsv.h -= rest - angleStep; hs.h -= rest - angleStep;
} }
// apply saturation segment steps // apply saturation segment steps
if (this.saturationSegments) { if (this.saturationSegments) {
if (this.saturationSegments === 1) { if (this.saturationSegments === 1) {
hsv.s = 1; hs.s = 1;
} else { } else {
var segmentSize = 1 / this.saturationSegments; var segmentSize = 1 / this.saturationSegments;
var saturationStep = 1 / (this.saturationSegments - 1); var saturationStep = 1 / (this.saturationSegments - 1);
var calculatedSat = Math.floor(hsv.s / segmentSize) * saturationStep; var calculatedSat = Math.floor(hs.s / segmentSize) * saturationStep;
hsv.s = Math.min(calculatedSat, 1); hs.s = Math.min(calculatedSat, 1);
} }
} }
return hsv; return hs;
} }
/* /*
@ -557,63 +524,6 @@ class HaColorPicker extends window.hassMixins.EventsMixin(Polymer.Element) {
this.tooltip = svgElement.tooltip; this.tooltip = svgElement.tooltip;
svgElement.appendChild(svgElement.tooltip); svgElement.appendChild(svgElement.tooltip);
} }
/**
* Color conversion helpers
*
* modified from:
* http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
* these take/return h = hue (0-360), s = saturation (0-1), v = value (0-1)
*/
/* eslint-disable */
HSVtoRGB(hsv) {
var r, g, b, i, f, p, q, t;
var h = hsv.h, s = hsv.s, v = hsv.v;
h /= 360;
i = Math.floor(h * 6);
f = h * 6 - i;
p = v * (1 - s);
q = v * (1 - f * s);
t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255)
};
}
RGBtoHSV(rgb) {
var r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, v = max;
var d = max - min;
s = max === 0 ? 0 : d / max;
if (max === min) {
h = 0; // achromatic
} else {
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h *= 60; // hue values 0-360
}
return {
h: h,
s: s,
v: v
};
}
/* eslint-enable */
} }
customElements.define(HaColorPicker.is, HaColorPicker); customElements.define(HaColorPicker.is, HaColorPicker);
</script> </script>

View File

@ -45,7 +45,7 @@
max-height: 84px; max-height: 84px;
} }
.has-rgb_color.is-on ha-color-picker { .has-color.is-on ha-color-picker {
max-height: 500px; max-height: 500px;
overflow: visible; overflow: visible;
--ha-color-picker-wheel-borderwidth: 5; --ha-color-picker-wheel-borderwidth: 5;
@ -89,7 +89,7 @@
<ha-color-picker <ha-color-picker
on-colorselected='colorPicked' on-colorselected='colorPicked'
desired-rgb-color='{{colorPickerColor}}' desired-hs-color='{{colorPickerColor}}'
throttle='500' throttle='500'
hue-segments='24' hue-segments='24'
saturation-segments='8' saturation-segments='8'
@ -107,7 +107,7 @@
</paper-dropdown-menu> </paper-dropdown-menu>
</div> </div>
<ha-attributes state-obj="[[stateObj]]" extra-filters="brightness,color_temp,white_value,effect_list,effect,rgb_color,xy_color,min_mireds,max_mireds"></ha-attributes> <ha-attributes state-obj="[[stateObj]]" extra-filters="brightness,color_temp,white_value,effect_list,effect,hs_color,rgb_color,xy_color,min_mireds,max_mireds"></ha-attributes>
</div> </div>
</template> </template>
</dom-module> </dom-module>
@ -118,7 +118,7 @@
1: 'has-brightness', 1: 'has-brightness',
2: 'has-color_temp', 2: 'has-color_temp',
4: 'has-effect_list', 4: 'has-effect_list',
16: 'has-rgb_color', 16: 'has-color',
128: 'has-white_value', 128: 'has-white_value',
}; };
class MoreInfoLight extends window.hassMixins.EventsMixin(Polymer.Element) { class MoreInfoLight extends window.hassMixins.EventsMixin(Polymer.Element) {
@ -171,8 +171,11 @@
props.brightnessSliderValue = newVal.attributes.brightness; props.brightnessSliderValue = newVal.attributes.brightness;
props.ctSliderValue = newVal.attributes.color_temp; props.ctSliderValue = newVal.attributes.color_temp;
props.wvSliderValue = newVal.attributes.white_value; props.wvSliderValue = newVal.attributes.white_value;
if (newVal.attributes.rgb_color) { if (newVal.attributes.hs_color) {
props.colorPickerColor = this.rgbArrToObj(newVal.attributes.rgb_color); props.colorPickerColor = {
h: newVal.attributes.hs_color[0],
s: newVal.attributes.hs_color[1] / 100,
};
} }
if (newVal.attributes.effect_list) { if (newVal.attributes.effect_list) {
props.effectIndex = newVal.attributes.effect_list.indexOf(newVal.attributes.effect); props.effectIndex = newVal.attributes.effect_list.indexOf(newVal.attributes.effect);
@ -254,21 +257,16 @@
serviceChangeColor(hass, entityId, color) { serviceChangeColor(hass, entityId, color) {
hass.callService('light', 'turn_on', { hass.callService('light', 'turn_on', {
entity_id: entityId, entity_id: entityId,
rgb_color: [color.r, color.g, color.b], hs_color: [color.h, color.s * 100],
}); });
} }
rgbArrToObj(rgbArr) {
return { r: rgbArr[0], g: rgbArr[1], b: rgbArr[2] };
}
/** /**
* Called when a new color has been picked. * Called when a new color has been picked.
* should be throttled with the 'throttle=' attribute of the color picker * should be throttled with the 'throttle=' attribute of the color picker
*/ */
colorPicked(ev) { colorPicked(ev) {
this.color = ev.detail.rgb; this.serviceChangeColor(this.hass, this.stateObj.entity_id, ev.detail.hs);
this.serviceChangeColor(this.hass, this.stateObj.entity_id, this.color);
} }
} }