New color picker component
This commit is contained in:
parent
b5a5178ec6
commit
48c38d630b
11 changed files with 121 additions and 182 deletions
|
|
@ -107,7 +107,7 @@ export default Em.Component.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
tabList: ["Lights", "Music"],
|
tabList: ["Lights", "Music"],
|
||||||
selectedTab: 0,
|
selectedTab: 1,
|
||||||
tabData: function(){
|
tabData: function(){
|
||||||
var tabData = [], selectedTab = this.get('selectedTab');
|
var tabData = [], selectedTab = this.get('selectedTab');
|
||||||
|
|
||||||
|
|
|
||||||
66
app/components/color-picker.js
Normal file
66
app/components/color-picker.js
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import Em from 'ember';
|
||||||
|
|
||||||
|
export default Em.Component.extend({
|
||||||
|
classNames:['colorpicker'],
|
||||||
|
|
||||||
|
// https://dzone.com/articles/creating-your-own-html5
|
||||||
|
didInsertElement: function(){
|
||||||
|
// handle color changes
|
||||||
|
var self = this,
|
||||||
|
canvas = Em.$('#picker')[0].getContext('2d'),
|
||||||
|
image = new Image();
|
||||||
|
|
||||||
|
image.src ='assets/images/colorwheel.png';
|
||||||
|
image.onload = function () {
|
||||||
|
canvas.drawImage(image, 0, 0, image.width, image.height); // draw the image on the canvas
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// http://www.developers.meethue.com/documentation/color-conversions-rgb-xy
|
||||||
|
rgbToXy: function(red, green, blue){
|
||||||
|
var X, Y, Z, x, y;
|
||||||
|
|
||||||
|
// normalize
|
||||||
|
red = Number((red/255).toFixed(2));
|
||||||
|
green = Number((green/255).toFixed(2));
|
||||||
|
blue = Number((blue/255).toFixed(2));
|
||||||
|
|
||||||
|
// gamma correction
|
||||||
|
red = (red > 0.04045) ? Math.pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92);
|
||||||
|
green = (green > 0.04045) ? Math.pow((green + 0.055) / (1.0 + 0.055), 2.4) : (green / 12.92);
|
||||||
|
blue = (blue > 0.04045) ? Math.pow((blue + 0.055) / (1.0 + 0.055), 2.4) : (blue / 12.92);
|
||||||
|
|
||||||
|
// RGB to XYZ
|
||||||
|
X = red * 0.664511 + green * 0.154324 + blue * 0.162028;
|
||||||
|
Y = red * 0.283881 + green * 0.668433 + blue * 0.047685;
|
||||||
|
Z = red * 0.000088 + green * 0.072310 + blue * 0.986039;
|
||||||
|
|
||||||
|
x = X / (X + Y + Z);
|
||||||
|
y = Y / (X + Y + Z);
|
||||||
|
|
||||||
|
return [x,y];
|
||||||
|
},
|
||||||
|
|
||||||
|
xyTorgb: function(x, y){
|
||||||
|
var r, g, b, X, Y, Z, activeLights = this.get('activeLights'), lightsData = this.get('lightsData');
|
||||||
|
|
||||||
|
z = 1 - x - y;
|
||||||
|
Y = lightsData[activeLights[0]].state.bri;
|
||||||
|
X = (Y / y) * x;
|
||||||
|
Z = (Y / y) * z;
|
||||||
|
|
||||||
|
r = X * 1.656492 - Y * 0.354851 - Z * 0.255038;
|
||||||
|
g = -X * 0.707196 + Y * 1.655397 + Z * 0.036152;
|
||||||
|
b = X * 0.051713 - Y * 0.121364 + Z * 1.011530;
|
||||||
|
|
||||||
|
r = r <= 0.0031308 ? 12.92 * r : (1.0 + 0.055) * Math.pow(r, (1.0 / 2.4)) - 0.055;
|
||||||
|
g = g <= 0.0031308 ? 12.92 * g : (1.0 + 0.055) * Math.pow(g, (1.0 / 2.4)) - 0.055;
|
||||||
|
b = b <= 0.0031308 ? 12.92 * b : (1.0 + 0.055) * Math.pow(b, (1.0 / 2.4)) - 0.055;
|
||||||
|
|
||||||
|
r = Math.floor(r * 255);
|
||||||
|
g = Math.floor(g * 255);
|
||||||
|
b = Math.floor(b * 255);
|
||||||
|
|
||||||
|
return [r, g, b];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -12,18 +12,6 @@ export default Em.Component.extend({
|
||||||
|
|
||||||
isShowingColorPicker: false,
|
isShowingColorPicker: false,
|
||||||
|
|
||||||
didInsertElement: function(){
|
|
||||||
// handle color changes
|
|
||||||
var self = this,
|
|
||||||
canvas = $('#picker')[0].getContext('2d'),
|
|
||||||
image = new Image();
|
|
||||||
|
|
||||||
image.src ='assets/images/colorwheel.png';
|
|
||||||
image.onload = function () {
|
|
||||||
canvas.drawImage(image, 0, 0, image.width, image.height); // draw the image on the canvas
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
clickLight: function(){
|
clickLight: function(){
|
||||||
console.log('clickLight');
|
console.log('clickLight');
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,12 @@ export default Em.Component.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
didInsertElement: function() {
|
||||||
|
if(this.get('lightsData')){
|
||||||
|
this.onLightsDataChange();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// list of all the lights in the hue system
|
// list of all the lights in the hue system
|
||||||
onLightsDataChange: function(){
|
onLightsDataChange: function(){
|
||||||
if(!this.get('isHovering')){
|
if(!this.get('isHovering')){
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,10 @@ body {
|
||||||
position: static;
|
position: static;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.relative {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
#settings {
|
#settings {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -62,7 +66,7 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.appSettingsItem:hover {
|
.appSettingsItem:hover {
|
||||||
background: darken(white, 10%);
|
background: darken(white, 20%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settingsItem.on md-icon.md-default-theme {
|
.settingsItem.on md-icon.md-default-theme {
|
||||||
|
|
@ -188,20 +192,19 @@ md-list-item .md-no-style {
|
||||||
}
|
}
|
||||||
|
|
||||||
.colorpicker {
|
.colorpicker {
|
||||||
background-color: #222222;
|
padding: 10px;
|
||||||
border-radius: 5px 5px 5px 5px;
|
background: rgba(0, 0, 0, 0.6);
|
||||||
box-shadow: 2px 2px 2px #444444;
|
box-shadow: 5px 10px 15px 5px rgba(0, 0, 0, 0.3);
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
font-size: 12px;
|
z-index: 2;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 460px;
|
width: 220px;
|
||||||
|
right: 6px;
|
||||||
|
top: -6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#picker {
|
#picker {
|
||||||
cursor: crosshair;
|
cursor: crosshair;
|
||||||
float: left;
|
|
||||||
margin: 10px;
|
|
||||||
border: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LIGHT GROUP
|
// LIGHT GROUP
|
||||||
|
|
@ -239,7 +242,8 @@ md-slider.md-default-theme .md-thumb:after {
|
||||||
.lightGroup {
|
.lightGroup {
|
||||||
margin: 0 auto 0 auto;
|
margin: 0 auto 0 auto;
|
||||||
.tooltip.top {
|
.tooltip.top {
|
||||||
margin-top: 8px;
|
margin-top: -10px;
|
||||||
|
margin-left: -3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,7 +275,8 @@ md-slider.md-default-theme .md-thumb:after {
|
||||||
left: 6px;
|
left: 6px;
|
||||||
}
|
}
|
||||||
.tooltip.top {
|
.tooltip.top {
|
||||||
margin-top: -10px;
|
margin-top: 6px;
|
||||||
|
margin-left: -5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
1
app/templates/components/color-picker.hbs
Normal file
1
app/templates/components/color-picker.hbs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<canvas id="picker" width="200" height="200"></canvas>
|
||||||
|
|
@ -19,12 +19,12 @@
|
||||||
{{paper-icon icon="color-lens"}}
|
{{paper-icon icon="color-lens"}}
|
||||||
<p>Color</p>
|
<p>Color</p>
|
||||||
{{#paper-button raised=true class="color" action="toggleColorpicker"}}{{/paper-button}}
|
{{#paper-button raised=true class="color" action="toggleColorpicker"}}{{/paper-button}}
|
||||||
{{#if isShowingColorPicker}}
|
|
||||||
<div class="colorpicker">
|
|
||||||
<canvas id="picker" width="300" height="300"></canvas>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
{{/paper-item}}
|
{{/paper-item}}
|
||||||
|
<div class="relative">
|
||||||
|
{{#if isShowingColorPicker}}
|
||||||
|
{{color-picker lightsData=lightsData activeLights=activeLights}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
{{#paper-item class="item"}}
|
{{#paper-item class="item"}}
|
||||||
{{paper-icon icon="flare"}}
|
{{paper-icon icon="flare"}}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ module.exports = function(defaults) {
|
||||||
});
|
});
|
||||||
|
|
||||||
app.import('vendor/dancer.js');
|
app.import('vendor/dancer.js');
|
||||||
app.import('vendor/colorpicker.js');
|
|
||||||
app.import('bower_components/bootstrap-sass/assets/javascripts/bootstrap/tooltip.js');
|
app.import('bower_components/bootstrap-sass/assets/javascripts/bootstrap/tooltip.js');
|
||||||
app.import('bower_components/JavaScript-ID3-Reader/dist/id3-minimized.js');
|
app.import('bower_components/JavaScript-ID3-Reader/dist/id3-minimized.js');
|
||||||
app.import('bower_components/jquery-mousewheel/jquery.mousewheel.js');
|
app.import('bower_components/jquery-mousewheel/jquery.mousewheel.js');
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 35 KiB |
26
tests/integration/components/color-picker-test.js
Normal file
26
tests/integration/components/color-picker-test.js
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { moduleForComponent, test } from 'ember-qunit';
|
||||||
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
|
||||||
|
moduleForComponent('color-picker', 'Integration | Component | color picker', {
|
||||||
|
integration: true
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it renders', function(assert) {
|
||||||
|
assert.expect(2);
|
||||||
|
|
||||||
|
// Set any properties with this.set('myProperty', 'value');
|
||||||
|
// Handle any actions with this.on('myAction', function(val) { ... });
|
||||||
|
|
||||||
|
this.render(hbs`{{color-picker}}`);
|
||||||
|
|
||||||
|
assert.equal(this.$().text().trim(), '');
|
||||||
|
|
||||||
|
// Template block usage:
|
||||||
|
this.render(hbs`
|
||||||
|
{{#color-picker}}
|
||||||
|
template block text
|
||||||
|
{{/color-picker}}
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.equal(this.$().text().trim(), 'template block text');
|
||||||
|
});
|
||||||
152
vendor/colorpicker.js
vendored
152
vendor/colorpicker.js
vendored
|
|
@ -1,152 +0,0 @@
|
||||||
//https://github.com/bgrins/colorwheel-1k/tree/gh-pages
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
// Declare constants and variables to help with minification
|
|
||||||
// Some of these are inlined (with comments to the side with the actual equation)
|
|
||||||
var doc = document;
|
|
||||||
doc.c = doc.createElement;
|
|
||||||
b.a = b.appendChild;
|
|
||||||
|
|
||||||
var width = c.width = c.height = 400,
|
|
||||||
label = b.a(doc.c("p")),
|
|
||||||
input = b.a(doc.c("input")),
|
|
||||||
imageData = a.createImageData(width, width),
|
|
||||||
pixels = imageData.data,
|
|
||||||
oneHundred = input.value = input.max = 100,
|
|
||||||
circleOffset = 10,
|
|
||||||
diameter = 380, //width-circleOffset*2,
|
|
||||||
radius = 190, //diameter / 2,
|
|
||||||
radiusPlusOffset = 200, //radius + circleOffset
|
|
||||||
radiusSquared = radius * radius,
|
|
||||||
two55 = 255,
|
|
||||||
currentY = oneHundred,
|
|
||||||
currentX = -currentY,
|
|
||||||
wheelPixel = 16040; // circleOffset*4*width+circleOffset*4;
|
|
||||||
|
|
||||||
// Math helpers
|
|
||||||
var math = Math,
|
|
||||||
PI = math.PI,
|
|
||||||
PI2 = PI * 2,
|
|
||||||
sqrt = math.sqrt,
|
|
||||||
atan2 = math.atan2;
|
|
||||||
|
|
||||||
// Setup DOM properties
|
|
||||||
b.style.textAlign="center";
|
|
||||||
label.style.font = "2em courier";
|
|
||||||
input.type = "range";
|
|
||||||
|
|
||||||
// Load color wheel data into memory.
|
|
||||||
for (y = input.min = 0; y < width; y++) {
|
|
||||||
for (x = 0; x < width; x++) {
|
|
||||||
var rx = x - radius,
|
|
||||||
ry = y - radius,
|
|
||||||
d = rx * rx + ry * ry,
|
|
||||||
rgb = hsvToRgb(
|
|
||||||
(atan2(ry, rx) + PI) / PI2, // Hue
|
|
||||||
sqrt(d) / radius, // Saturation
|
|
||||||
1 // Value
|
|
||||||
);
|
|
||||||
|
|
||||||
// Print current color, but hide if outside the area of the circle
|
|
||||||
pixels[wheelPixel++] = rgb[0];
|
|
||||||
pixels[wheelPixel++] = rgb[1];
|
|
||||||
pixels[wheelPixel++] = rgb[2];
|
|
||||||
pixels[wheelPixel++] = d > radiusSquared ? 0 : two55;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind Event Handlers
|
|
||||||
input.onchange = redraw;
|
|
||||||
c.onmousedown = doc.onmouseup = function(e) {
|
|
||||||
// Unbind mousemove if this is a mouseup event, or bind mousemove if this a mousedown event
|
|
||||||
doc.onmousemove = /p/.test(e.type) ? 0 : (redraw(e), redraw);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle manual calls + mousemove event handler + input change event handler all in one place.
|
|
||||||
function redraw(e) {
|
|
||||||
|
|
||||||
// Only process an actual change if it is triggered by the mousemove or mousedown event.
|
|
||||||
// Otherwise e.pageX will be undefined, which will cause the result to be NaN, so it will fallback to the current value
|
|
||||||
currentX = e.pageX - c.offsetLeft - radiusPlusOffset || currentX;
|
|
||||||
currentY = e.pageY - c.offsetTop - radiusPlusOffset || currentY;
|
|
||||||
|
|
||||||
// Scope these locally so the compiler will minify the names. Will manually remove the 'var' keyword in the minified version.
|
|
||||||
var theta = atan2(currentY, currentX),
|
|
||||||
d = currentX * currentX + currentY * currentY;
|
|
||||||
|
|
||||||
// If the x/y is not in the circle, find angle between center and mouse point:
|
|
||||||
// Draw a line at that angle from center with the distance of radius
|
|
||||||
// Use that point on the circumference as the draggable location
|
|
||||||
if (d > radiusSquared) {
|
|
||||||
currentX = radius * math.cos(theta);
|
|
||||||
currentY = radius * math.sin(theta);
|
|
||||||
theta = atan2(currentY, currentX);
|
|
||||||
d = currentX * currentX + currentY * currentY;
|
|
||||||
}
|
|
||||||
|
|
||||||
label.textContent = b.style.background = hsvToRgb(
|
|
||||||
(theta + PI) / PI2, // Current hue (how many degrees along the circle)
|
|
||||||
sqrt(d) / radius, // Current saturation (how close to the middle)
|
|
||||||
input.value / oneHundred // Current value (input type="range" slider value)
|
|
||||||
)[3];
|
|
||||||
|
|
||||||
// Reset to color wheel and draw a spot on the current location.
|
|
||||||
a.putImageData(imageData, 0, 0);
|
|
||||||
|
|
||||||
// Draw the current spot.
|
|
||||||
// I have tried a rectangle, circle, and heart shape.
|
|
||||||
/*
|
|
||||||
// Rectangle:
|
|
||||||
a.fillStyle = '#000';
|
|
||||||
a.fillRect(currentX+radiusPlusOffset,currentY+radiusPlusOffset, 6, 6);
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
// Circle:
|
|
||||||
a.beginPath();
|
|
||||||
a.strokeStyle = '#000';
|
|
||||||
a.arc(~~currentX+radiusPlusOffset,~~currentY+radiusPlusOffset, 4, 0, PI2);
|
|
||||||
a.stroke();
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Heart:
|
|
||||||
a.font = "1em arial";
|
|
||||||
a.fillText("♥", currentX+radiusPlusOffset-4,currentY+radiusPlusOffset+4);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Created a shorter version of the HSV to RGB conversion function in TinyColor
|
|
||||||
// https://github.com/bgrins/TinyColor/blob/master/tinycolor.js
|
|
||||||
function hsvToRgb(h, s, v) {
|
|
||||||
h*=6;
|
|
||||||
var i = ~~h,
|
|
||||||
f = h - i,
|
|
||||||
p = v * (1 - s),
|
|
||||||
q = v * (1 - f * s),
|
|
||||||
t = v * (1 - (1 - f) * s),
|
|
||||||
mod = i % 6,
|
|
||||||
r = [v, q, p, p, t, v][mod] * two55,
|
|
||||||
g = [t, v, v, q, p, p][mod] * two55,
|
|
||||||
b = [p, p, t, v, v, q][mod] * two55;
|
|
||||||
|
|
||||||
return [r, g, b, "rgb("+ ~~r + "," + ~~g + "," + ~~b + ")"];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kick everything off
|
|
||||||
redraw(0);
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Just an idea I had to kick everything off with some changing colors…
|
|
||||||
// Probably no way to squeeze this into 1k, but it could probably be a lot smaller than this:
|
|
||||||
currentX = currentY = 1;
|
|
||||||
var interval = setInterval(function() {
|
|
||||||
currentX--;
|
|
||||||
currentY*=1.05;
|
|
||||||
redraw(0)
|
|
||||||
}, 7);
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
clearInterval(interval)
|
|
||||||
}, 700)
|
|
||||||
*/
|
|
||||||
|
|
||||||
})();
|
|
||||||
Reference in a new issue