commit
58eb4cfbed
@ -0,0 +1,128 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2010 Tim Baumann |
||||||
|
* |
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
* of this software and associated documentation files (the "Software"), to deal |
||||||
|
* in the Software without restriction, including without limitation the rights |
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
* copies of the Software, and to permit persons to whom the Software is |
||||||
|
* furnished to do so, subject to the following conditions: |
||||||
|
* |
||||||
|
* The above copyright notice and this permission notice shall be included in |
||||||
|
* all copies or substantial portions of the Software. |
||||||
|
* |
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
|
* THE SOFTWARE. |
||||||
|
*/ |
||||||
|
|
||||||
|
// NOTE:
|
||||||
|
// Extracted from ColorTriangle and
|
||||||
|
// Converted to ES6 by MightyPork (2017)
|
||||||
|
|
||||||
|
/******************* |
||||||
|
* Color conversion * |
||||||
|
*******************/ |
||||||
|
|
||||||
|
const M = Math |
||||||
|
const PI = M.PI |
||||||
|
|
||||||
|
exports.hue_to_rgb = function (v1, v2, h) { |
||||||
|
if (h < 0) h += 1 |
||||||
|
if (h > 1) h -= 1 |
||||||
|
|
||||||
|
if ((6 * h) < 1) return v1 + (v2 - v1) * 6 * h |
||||||
|
if ((2 * h) < 1) return v2 |
||||||
|
if ((3 * h) < 2) return v1 + (v2 - v1) * ((2 / 3) - h) * 6 |
||||||
|
return v1 |
||||||
|
} |
||||||
|
|
||||||
|
exports.hsl_to_rgb = function (h, s, l) { |
||||||
|
h /= 2 * PI |
||||||
|
let r, g, b |
||||||
|
|
||||||
|
if (s === 0) { |
||||||
|
r = g = b = l |
||||||
|
} else { |
||||||
|
let var_1, var_2 |
||||||
|
|
||||||
|
if (l < 0.5) var_2 = l * (1 + s) |
||||||
|
else var_2 = (l + s) - (s * l) |
||||||
|
|
||||||
|
var_1 = 2 * l - var_2 |
||||||
|
|
||||||
|
r = exports.hue_to_rgb(var_1, var_2, h + (1 / 3)) |
||||||
|
g = exports.hue_to_rgb(var_1, var_2, h) |
||||||
|
b = exports.hue_to_rgb(var_1, var_2, h - (1 / 3)) |
||||||
|
} |
||||||
|
return [r, g, b] |
||||||
|
} |
||||||
|
|
||||||
|
exports.rgb_to_hsl = function (r, g, b) { |
||||||
|
const min = M.min(r, g, b) |
||||||
|
const max = M.max(r, g, b) |
||||||
|
const d = max - min // delta
|
||||||
|
|
||||||
|
let h, s, l |
||||||
|
|
||||||
|
l = (max + min) / 2 |
||||||
|
|
||||||
|
if (d === 0) { |
||||||
|
// gray
|
||||||
|
h = s = 0 // HSL results from 0 to 1
|
||||||
|
} else { |
||||||
|
// chroma
|
||||||
|
if (l < 0.5) s = d / (max + min) |
||||||
|
else s = d / (2 - max - min) |
||||||
|
|
||||||
|
const d_r = (((max - r) / 6) + (d / 2)) / d |
||||||
|
const d_g = (((max - g) / 6) + (d / 2)) / d |
||||||
|
const d_b = (((max - b) / 6) + (d / 2)) / d // deltas
|
||||||
|
|
||||||
|
if (r === max) h = d_b - d_g |
||||||
|
else if (g === max) h = (1 / 3) + d_r - d_b |
||||||
|
else if (b === max) h = (2 / 3) + d_g - d_r |
||||||
|
|
||||||
|
if (h < 0) h += 1 |
||||||
|
else if (h > 1) h -= 1 |
||||||
|
} |
||||||
|
h *= 2 * PI |
||||||
|
return [h, s, l] |
||||||
|
} |
||||||
|
|
||||||
|
exports.hex_to_rgb = function (hex) { |
||||||
|
const groups = hex.match(/^#([A-Fa-f0-9]+)$/) |
||||||
|
if (groups && groups[1].length % 3 === 0) { |
||||||
|
hex = groups[1] |
||||||
|
const bytes = hex.length / 3 |
||||||
|
const max = Math.pow(16, bytes) - 1 |
||||||
|
const r = parseInt(hex.slice(0 * bytes, 1 * bytes), 16) / max |
||||||
|
const g = parseInt(hex.slice(1 * bytes, 2 * bytes), 16) / max |
||||||
|
const b = parseInt(hex.slice(2 * bytes, 3 * bytes), 16) / max |
||||||
|
return [r, g, b] |
||||||
|
} |
||||||
|
return [0, 0, 0] |
||||||
|
} |
||||||
|
|
||||||
|
function pad (n) { |
||||||
|
if (n.length === 1) n = '0' + n |
||||||
|
return n |
||||||
|
} |
||||||
|
|
||||||
|
exports.rgb255_to_hex = function (r, g, b) { |
||||||
|
r = r.toString(16) |
||||||
|
g = g.toString(16) |
||||||
|
b = b.toString(16) |
||||||
|
return `#${pad(r)}${pad(g)}${pad(b)}` |
||||||
|
} |
||||||
|
|
||||||
|
exports.rgb_to_hex = function (r, g, b) { |
||||||
|
r = Math.round(r * 255).toString(16) |
||||||
|
g = Math.round(g * 255).toString(16) |
||||||
|
b = Math.round(b * 255).toString(16) |
||||||
|
return `#${pad(r)}${pad(g)}${pad(b)}` |
||||||
|
} |
@ -0,0 +1,604 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2010 Tim Baumann |
||||||
|
* |
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
* of this software and associated documentation files (the "Software"), to deal |
||||||
|
* in the Software without restriction, including without limitation the rights |
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
* copies of the Software, and to permit persons to whom the Software is |
||||||
|
* furnished to do so, subject to the following conditions: |
||||||
|
* |
||||||
|
* The above copyright notice and this permission notice shall be included in |
||||||
|
* all copies or substantial portions of the Software. |
||||||
|
* |
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
|
* THE SOFTWARE. |
||||||
|
*/ |
||||||
|
|
||||||
|
// NOTE: Converted to ES6 by MightyPork (2017)
|
||||||
|
|
||||||
|
const { |
||||||
|
rgb_to_hex, |
||||||
|
hex_to_rgb, |
||||||
|
hsl_to_rgb, |
||||||
|
rgb_to_hsl |
||||||
|
} = require('./color_utils') |
||||||
|
|
||||||
|
const win = window |
||||||
|
const doc = document |
||||||
|
const M = Math |
||||||
|
const PI = M.PI |
||||||
|
|
||||||
|
function times (i, fn) { |
||||||
|
for (let j = 0; j < i; j++) { |
||||||
|
fn(j) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function each (obj, fn) { |
||||||
|
if (obj.length) { |
||||||
|
times(obj.length, function (i) { |
||||||
|
fn(obj[i], i) |
||||||
|
}) |
||||||
|
} else { |
||||||
|
for (let key in obj) { |
||||||
|
if (obj.hasOwnProperty(key)) { |
||||||
|
fn(obj[key], key) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function getOffsets (el) { |
||||||
|
let left = 0 |
||||||
|
let top = 0 |
||||||
|
|
||||||
|
while (el !== null) { |
||||||
|
console.log(el) |
||||||
|
console.log(el.offsetLeft, el.offsetTop) |
||||||
|
left += el.offsetLeft |
||||||
|
top += el.offsetTop |
||||||
|
el = el.offsetParent |
||||||
|
} |
||||||
|
|
||||||
|
return [left, top] |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = class ColorTriangle { |
||||||
|
/**************** |
||||||
|
* ColorTriangle * |
||||||
|
****************/ |
||||||
|
|
||||||
|
// Constructor function:
|
||||||
|
constructor (color, options) { |
||||||
|
this.options = { |
||||||
|
size: 150, |
||||||
|
padding: 8, |
||||||
|
triangleSize: 0.8, |
||||||
|
wheelPointerColor1: '#444', |
||||||
|
wheelPointerColor2: '#eee', |
||||||
|
trianglePointerSize: 16, |
||||||
|
// wheelPointerSize: 16,
|
||||||
|
trianglePointerColor1: '#eee', |
||||||
|
trianglePointerColor2: '#444', |
||||||
|
background: 'transparent' |
||||||
|
} |
||||||
|
|
||||||
|
this._setOptions(options) |
||||||
|
this._calculateProperties() |
||||||
|
|
||||||
|
this._createContainer() |
||||||
|
this._createTriangle() |
||||||
|
this._createWheel() |
||||||
|
this._createWheelPointer() |
||||||
|
this._createTrianglePointer() |
||||||
|
this._attachEvents() |
||||||
|
|
||||||
|
color = color || '#f00' |
||||||
|
if (typeof color == 'string') { |
||||||
|
this.setHEX(color) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
_calculateProperties () { |
||||||
|
let opts = this.options |
||||||
|
|
||||||
|
this.padding = opts.padding |
||||||
|
this.innerSize = opts.size - opts.padding * 2 |
||||||
|
this.triangleSize = opts.triangleSize * this.innerSize |
||||||
|
this.wheelThickness = (this.innerSize - this.triangleSize) / 2 |
||||||
|
this.wheelPointerSize = opts.wheelPointerSize || this.wheelThickness |
||||||
|
|
||||||
|
this.wheelRadius = (this.innerSize) / 2 |
||||||
|
this.triangleRadius = (this.triangleSize) / 2 |
||||||
|
this.triangleSideLength = M.sqrt(3) * this.triangleRadius |
||||||
|
} |
||||||
|
|
||||||
|
_calculatePositions () { |
||||||
|
const r = this.triangleRadius |
||||||
|
const hue = this.hue |
||||||
|
const third = (2 / 3) * PI |
||||||
|
const s = this.saturation |
||||||
|
const l = this.lightness |
||||||
|
|
||||||
|
// Colored point
|
||||||
|
const hx = this.hx = M.cos(hue) * r |
||||||
|
const hy = this.hy = -M.sin(hue) * r |
||||||
|
// Black point
|
||||||
|
const sx = this.sx = M.cos(hue - third) * r |
||||||
|
const sy = this.sy = -M.sin(hue - third) * r |
||||||
|
// White point
|
||||||
|
const vx = this.vx = M.cos(hue + third) * r |
||||||
|
const vy = this.vy = -M.sin(hue + third) * r |
||||||
|
// Current point
|
||||||
|
const mx = (sx + vx) / 2 |
||||||
|
const my = (sy + vy) / 2 |
||||||
|
const a = (1 - 2 * M.abs(l - 0.5)) * s |
||||||
|
this.x = sx + (vx - sx) * l + (hx - mx) * a |
||||||
|
this.y = sy + (vy - sy) * l + (hy - my) * a |
||||||
|
} |
||||||
|
|
||||||
|
_createContainer () { |
||||||
|
let c = this.container = doc.createElement('div') |
||||||
|
c.className = 'color-triangle' |
||||||
|
|
||||||
|
c.style.display = 'block' |
||||||
|
c.style.padding = `${this.padding}px` |
||||||
|
c.style.position = 'relative' |
||||||
|
c.style.boxShadow = '0 1px 10px black' |
||||||
|
c.style.borderRadius = '5px' |
||||||
|
c.style.width = c.style.height = `${this.innerSize + 2 * this.padding}px` |
||||||
|
c.style.background = this.options.background |
||||||
|
} |
||||||
|
|
||||||
|
_createWheel () { |
||||||
|
let c = this.wheel = doc.createElement('canvas') |
||||||
|
c.width = c.height = this.innerSize |
||||||
|
c.style.position = 'absolute' |
||||||
|
c.style.margin = c.style.padding = '0' |
||||||
|
c.style.left = c.style.top = `${this.padding}px` |
||||||
|
|
||||||
|
this._drawWheel(c.getContext('2d')) |
||||||
|
this.container.appendChild(c) |
||||||
|
} |
||||||
|
|
||||||
|
_drawWheel (ctx) { |
||||||
|
let s, i |
||||||
|
|
||||||
|
ctx.save() |
||||||
|
ctx.translate(this.wheelRadius, this.wheelRadius) |
||||||
|
s = this.wheelRadius - this.triangleRadius |
||||||
|
// Draw a circle for every color
|
||||||
|
for (i = 0; i < 360; i++) { |
||||||
|
ctx.rotate(PI / -180) // rotate one degree
|
||||||
|
ctx.beginPath() |
||||||
|
ctx.fillStyle = 'hsl(' + i + ', 100%, 50%)' |
||||||
|
ctx.arc(this.wheelRadius - (s / 2), 0, s / 2, 0, PI * 2, true) |
||||||
|
ctx.fill() |
||||||
|
} |
||||||
|
ctx.restore() |
||||||
|
} |
||||||
|
|
||||||
|
_createTriangle () { |
||||||
|
let c = this.triangle = doc.createElement('canvas') |
||||||
|
|
||||||
|
c.width = c.height = this.innerSize |
||||||
|
c.style.position = 'absolute' |
||||||
|
c.style.margin = c.style.padding = '0' |
||||||
|
c.style.left = c.style.top = this.padding + 'px' |
||||||
|
|
||||||
|
this.triangleCtx = c.getContext('2d') |
||||||
|
|
||||||
|
this.container.appendChild(c) |
||||||
|
} |
||||||
|
|
||||||
|
_drawTriangle () { |
||||||
|
const hx = this.hx |
||||||
|
const hy = this.hy |
||||||
|
const sx = this.sx |
||||||
|
const sy = this.sy |
||||||
|
const vx = this.vx |
||||||
|
const vy = this.vy |
||||||
|
const size = this.innerSize |
||||||
|
|
||||||
|
let ctx = this.triangleCtx |
||||||
|
|
||||||
|
// clear
|
||||||
|
ctx.clearRect(0, 0, size, size) |
||||||
|
|
||||||
|
ctx.save() |
||||||
|
ctx.translate(this.wheelRadius, this.wheelRadius) |
||||||
|
|
||||||
|
// make a triangle
|
||||||
|
ctx.beginPath() |
||||||
|
ctx.moveTo(hx, hy) |
||||||
|
ctx.lineTo(sx, sy) |
||||||
|
ctx.lineTo(vx, vy) |
||||||
|
ctx.closePath() |
||||||
|
ctx.clip() |
||||||
|
|
||||||
|
ctx.fillStyle = '#000' |
||||||
|
ctx.fillRect(-this.wheelRadius, -this.wheelRadius, size, size) |
||||||
|
// => black triangle
|
||||||
|
|
||||||
|
// create gradient from hsl(hue, 1, 1) to transparent
|
||||||
|
let grad0 = ctx.createLinearGradient(hx, hy, (sx + vx) / 2, (sy + vy) / 2) |
||||||
|
const hsla = 'hsla(' + M.round(this.hue * (180 / PI)) + ', 100%, 50%, ' |
||||||
|
grad0.addColorStop(0, hsla + '1)') |
||||||
|
grad0.addColorStop(1, hsla + '0)') |
||||||
|
ctx.fillStyle = grad0 |
||||||
|
ctx.fillRect(-this.wheelRadius, -this.wheelRadius, size, size) |
||||||
|
// => gradient: one side of the triangle is black, the opponent angle is $color
|
||||||
|
|
||||||
|
// create color gradient from white to transparent
|
||||||
|
let grad1 = ctx.createLinearGradient(vx, vy, (hx + sx) / 2, (hy + sy) / 2) |
||||||
|
grad1.addColorStop(0, '#fff') |
||||||
|
grad1.addColorStop(1, 'rgba(255, 255, 255, 0)') |
||||||
|
ctx.globalCompositeOperation = 'lighter' |
||||||
|
ctx.fillStyle = grad1 |
||||||
|
ctx.fillRect(-this.wheelRadius, -this.wheelRadius, size, size) |
||||||
|
// => white angle
|
||||||
|
|
||||||
|
ctx.restore() |
||||||
|
} |
||||||
|
|
||||||
|
// The two pointers
|
||||||
|
_createWheelPointer () { |
||||||
|
let c = this.wheelPointer = doc.createElement('canvas') |
||||||
|
const size = this.wheelPointerSize |
||||||
|
c.width = c.height = size |
||||||
|
c.style.position = 'absolute' |
||||||
|
c.style.margin = c.style.padding = '0' |
||||||
|
this._drawPointer(c.getContext('2d'), size / 2, this.options.wheelPointerColor1, this.options.wheelPointerColor2) |
||||||
|
this.container.appendChild(c) |
||||||
|
} |
||||||
|
|
||||||
|
_moveWheelPointer () { |
||||||
|
const r = this.wheelPointerSize / 2 |
||||||
|
const s = this.wheelPointer.style |
||||||
|
s.top = this.padding + this.wheelRadius - M.sin(this.hue) * (this.triangleRadius + this.wheelThickness / 2) - r + 'px' |
||||||
|
s.left = this.padding + this.wheelRadius + M.cos(this.hue) * (this.triangleRadius + this.wheelThickness / 2) - r + 'px' |
||||||
|
} |
||||||
|
|
||||||
|
_createTrianglePointer () { // create pointer in the triangle
|
||||||
|
let c = this.trianglePointer = doc.createElement('canvas') |
||||||
|
const size = this.options.trianglePointerSize |
||||||
|
|
||||||
|
c.width = c.height = size |
||||||
|
c.style.position = 'absolute' |
||||||
|
c.style.margin = c.style.padding = '0' |
||||||
|
this._drawPointer(c.getContext('2d'), size / 2, this.options.trianglePointerColor1, this.options.trianglePointerColor2) |
||||||
|
this.container.appendChild(c) |
||||||
|
} |
||||||
|
|
||||||
|
_moveTrianglePointer (x, y) { |
||||||
|
const s = this.trianglePointer.style |
||||||
|
const r = this.options.trianglePointerSize / 2 |
||||||
|
s.top = (this.y + this.wheelRadius + this.padding - r) + 'px' |
||||||
|
s.left = (this.x + this.wheelRadius + this.padding - r) + 'px' |
||||||
|
} |
||||||
|
|
||||||
|
_drawPointer (ctx, r, color1, color2) { |
||||||
|
ctx.fillStyle = color2 |
||||||
|
ctx.beginPath() |
||||||
|
ctx.arc(r, r, r, 0, PI * 2, true) |
||||||
|
ctx.fill() // => black circle
|
||||||
|
ctx.fillStyle = color1 |
||||||
|
ctx.beginPath() |
||||||
|
ctx.arc(r, r, r - 2, 0, PI * 2, true) |
||||||
|
ctx.fill() // => white circle with 1px black border
|
||||||
|
ctx.fillStyle = color2 |
||||||
|
ctx.beginPath() |
||||||
|
ctx.arc(r, r, r / 4 + 2, 0, PI * 2, true) |
||||||
|
ctx.fill() // => black circle with big white border and a small black border
|
||||||
|
ctx.globalCompositeOperation = 'destination-out' |
||||||
|
ctx.beginPath() |
||||||
|
ctx.arc(r, r, r / 4, 0, PI * 2, true) |
||||||
|
ctx.fill() // => transparent center
|
||||||
|
} |
||||||
|
|
||||||
|
// The Element and the DOM
|
||||||
|
inject (parent) { |
||||||
|
parent.appendChild(this.container) |
||||||
|
|
||||||
|
// calculate canvas position on page
|
||||||
|
const offsets = getOffsets(this.triangle) |
||||||
|
this.offset = { |
||||||
|
x: offsets[0], |
||||||
|
y: offsets[1] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
dispose () { |
||||||
|
let parent = this.container.parentNode |
||||||
|
if (parent) { |
||||||
|
parent.removeChild(this.container) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
getElement () { |
||||||
|
return this.container |
||||||
|
} |
||||||
|
|
||||||
|
// Color accessors
|
||||||
|
getCSS () { |
||||||
|
const h = Math.round(this.hue * (180 / PI)) |
||||||
|
const s = Math.round(this.saturation * 100) |
||||||
|
const l = Math.round(this.lightness * 100) |
||||||
|
|
||||||
|
return `hsl(${h}, ${s}%, ${l}%)` |
||||||
|
} |
||||||
|
|
||||||
|
getHEX () { |
||||||
|
return rgb_to_hex(...this.getRGB()) |
||||||
|
} |
||||||
|
|
||||||
|
setHEX (hex) { |
||||||
|
this.setRGB(...hex_to_rgb(hex)) |
||||||
|
} |
||||||
|
|
||||||
|
getRGB () { |
||||||
|
return hsl_to_rgb(...this.getHSL()) |
||||||
|
} |
||||||
|
|
||||||
|
setRGB (r, g, b) { |
||||||
|
this.setHSL(...rgb_to_hsl(r, g, b)) |
||||||
|
} |
||||||
|
|
||||||
|
getHSL () { |
||||||
|
return [this.hue, this.saturation, this.lightness] |
||||||
|
} |
||||||
|
|
||||||
|
setHSL (h, s, l) { |
||||||
|
this.hue = h |
||||||
|
this.saturation = s |
||||||
|
this.lightness = l |
||||||
|
|
||||||
|
this._initColor() |
||||||
|
} |
||||||
|
|
||||||
|
_initColor () { |
||||||
|
this._calculatePositions() |
||||||
|
this._moveWheelPointer() |
||||||
|
this._drawTriangle() |
||||||
|
this._moveTrianglePointer() |
||||||
|
} |
||||||
|
|
||||||
|
// Mouse event handling
|
||||||
|
_attachEvents () { |
||||||
|
this.down = null |
||||||
|
|
||||||
|
let mousedown = (evt) => { |
||||||
|
evt.stopPropagation() |
||||||
|
evt.preventDefault() |
||||||
|
|
||||||
|
doc.body.addEventListener('mousemove', mousemove, false) |
||||||
|
doc.body.addEventListener('mouseup', mouseup, false) |
||||||
|
this._map(evt.pageX, evt.pageY) |
||||||
|
} |
||||||
|
|
||||||
|
let mousemove = (evt) => { |
||||||
|
this._move(evt.pageX, evt.pageY) |
||||||
|
} |
||||||
|
|
||||||
|
let mouseup = (evt) => { |
||||||
|
if (this.down) { |
||||||
|
this.down = null |
||||||
|
this._fireEvent('dragend') |
||||||
|
} |
||||||
|
doc.body.removeEventListener('mousemove', mousemove, false) |
||||||
|
doc.body.removeEventListener('mouseup', mouseup, false) |
||||||
|
} |
||||||
|
|
||||||
|
this.container.addEventListener('mousedown', mousedown, false) |
||||||
|
this.container.addEventListener('mousemove', mousemove, false) |
||||||
|
} |
||||||
|
|
||||||
|
_map (x, y) { |
||||||
|
const ox = x |
||||||
|
const oy = y |
||||||
|
|
||||||
|
x -= this.offset.x + this.wheelRadius |
||||||
|
y -= this.offset.y + this.wheelRadius |
||||||
|
|
||||||
|
const r = M.sqrt(x * x + y * y) // Pythagoras
|
||||||
|
if (r > this.triangleRadius && r < this.wheelRadius) { |
||||||
|
// Wheel
|
||||||
|
this.down = 'wheel' |
||||||
|
this._fireEvent('dragstart') |
||||||
|
this._move(ox, oy) |
||||||
|
} else if (r < this.triangleRadius) { |
||||||
|
// Inner circle
|
||||||
|
this.down = 'triangle' |
||||||
|
this._fireEvent('dragstart') |
||||||
|
this._move(ox, oy) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
_move (x, y) { |
||||||
|
if (!this.down) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
x -= this.offset.x + this.wheelRadius |
||||||
|
y -= this.offset.y + this.wheelRadius |
||||||
|
|
||||||
|
let rad = M.atan2(-y, x) |
||||||
|
if (rad < 0) { |
||||||
|
rad += 2 * PI |
||||||
|
} |
||||||
|
|
||||||
|
if (this.down === 'wheel') { |
||||||
|
this.hue = rad |
||||||
|
this._initColor() |
||||||
|
this._fireEvent('drag') |
||||||
|
} else if (this.down === 'triangle') { |
||||||
|
// get radius and max radius
|
||||||
|
let rad0 = (rad + 2 * PI - this.hue) % (2 * PI) |
||||||
|
let rad1 = rad0 % ((2 / 3) * PI) - (PI / 3) |
||||||
|
let a = 0.5 * this.triangleRadius |
||||||
|
let b = M.tan(rad1) * a |
||||||
|
let r = M.sqrt(x * x + y * y) // Pythagoras
|
||||||
|
let maxR = M.sqrt(a * a + b * b) // Pythagoras
|
||||||
|
|
||||||
|
if (r > maxR) { |
||||||
|
const dx = M.tan(rad1) * r |
||||||
|
let rad2 = M.atan(dx / maxR) |
||||||
|
if (rad2 > PI / 3) { |
||||||
|
rad2 = PI / 3 |
||||||
|
} else if (rad2 < -PI / 3) { |
||||||
|
rad2 = -PI / 3 |
||||||
|
} |
||||||
|
rad += rad2 - rad1 |
||||||
|
|
||||||
|
rad0 = (rad + 2 * PI - this.hue) % (2 * PI) |
||||||
|
rad1 = rad0 % ((2 / 3) * PI) - (PI / 3) |
||||||
|
b = M.tan(rad1) * a |
||||||
|
r = maxR = M.sqrt(a * a + b * b) // Pythagoras
|
||||||
|
} |
||||||
|
|
||||||
|
x = M.round(M.cos(rad) * r) |
||||||
|
y = M.round(-M.sin(rad) * r) |
||||||
|
|
||||||
|
const l = this.lightness = ((M.sin(rad0) * r) / this.triangleSideLength) + 0.5 |
||||||
|
|
||||||
|
const widthShare = 1 - (M.abs(l - 0.5) * 2) |
||||||
|
let s = this.saturation = (((M.cos(rad0) * r) + (this.triangleRadius / 2)) / (1.5 * this.triangleRadius)) / widthShare |
||||||
|
s = M.max(0, s) // cannot be lower than 0
|
||||||
|
s = M.min(1, s) // cannot be greater than 1
|
||||||
|
|
||||||
|
this.lightness = l |
||||||
|
this.saturation = s |
||||||
|
|
||||||
|
this.x = x |
||||||
|
this.y = y |
||||||
|
this._moveTrianglePointer() |
||||||
|
|
||||||
|
this._fireEvent('drag') |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/*************** |
||||||
|
* Init helpers * |
||||||
|
***************/ |
||||||
|
|
||||||
|
static initInput (input, options) { |
||||||
|
options = options || {} |
||||||
|
|
||||||
|
let ct |
||||||
|
let openColorTriangle = function () { |
||||||
|
let hex = input.value |
||||||
|
if (options.parseColor) hex = options.parseColor(hex) |
||||||
|
if (!ct) { |
||||||
|
options.size = options.size || input.offsetWidth |
||||||
|
options.background = win.getComputedStyle(input, null).backgroundColor |
||||||
|
options.margin = options.margin || 10 |
||||||
|
options.event = options.event || 'dragend' |
||||||
|
|
||||||
|
ct = new ColorTriangle(hex, options) |
||||||
|
ct.addEventListener(options.event, () => { |
||||||
|
const hex = ct.getHEX() |
||||||
|
input.value = options.uppercase ? hex.toUpperCase() : hex |
||||||
|
fireChangeEvent() |
||||||
|
}) |
||||||
|
} else { |
||||||
|
ct.setHEX(hex) |
||||||
|
} |
||||||
|
|
||||||
|
let top = input.offsetTop |
||||||
|
if (win.innerHeight - input.getBoundingClientRect().top > input.offsetHeight + options.margin + options.size) { |
||||||
|
top += input.offsetHeight + options.margin // below
|
||||||
|
} else { |
||||||
|
top -= options.margin + options.size // above
|
||||||
|
} |
||||||
|
|
||||||
|
let el = ct.getElement() |
||||||
|
el.style.position = 'absolute' |
||||||
|
el.style.left = input.offsetLeft + 'px' |
||||||
|
el.style.top = top + 'px' |
||||||
|
el.style.zIndex = '1338' // above everything
|
||||||
|
|
||||||
|
ct.inject(input.parentNode) |
||||||
|
} |
||||||
|
|
||||||
|
let closeColorTriangle = () => { |
||||||
|
if (ct) { |
||||||
|
ct.dispose() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
let fireChangeEvent = () => { |
||||||
|
let evt = doc.createEvent('HTMLEvents') |
||||||
|
evt.initEvent('input', true, false) // bubbles = true, cancable = false
|
||||||
|
input.dispatchEvent(evt) // fire event
|
||||||
|
} |
||||||
|
|
||||||
|
input.addEventListener('focus', openColorTriangle, false) |
||||||
|
input.addEventListener('blur', closeColorTriangle, false) |
||||||
|
input.addEventListener('keyup', () => { |
||||||
|
const val = input.value |
||||||
|
if (val.match(/^#((?:[0-9A-Fa-f]{3})|(?:[0-9A-Fa-f]{6}))$/)) { |
||||||
|
openColorTriangle() |
||||||
|
fireChangeEvent() |
||||||
|
} else { |
||||||
|
closeColorTriangle() |
||||||
|
} |
||||||
|
}, false) |
||||||
|
} |
||||||
|
|
||||||
|
/******************* |
||||||
|
* Helper functions * |
||||||
|
*******************/ |
||||||
|
|
||||||
|
_setOptions (opts) { |
||||||
|
opts = opts || {} |
||||||
|
let dflt = this.options |
||||||
|
let options = this.options = {} |
||||||
|
|
||||||
|
each(dflt, function (val, key) { |
||||||
|
options[key] = (opts.hasOwnProperty(key)) |
||||||
|
? opts[key] |
||||||
|
: val |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
addEventListener (type, fn) { |
||||||
|
if (!this.events) { |
||||||
|
this.events = {} |
||||||
|
} |
||||||
|
if (!this.events[type]) { |
||||||
|
this.events[type] = [] |
||||||
|
} |
||||||
|
this.events[type].push(fn) |
||||||
|
} |
||||||
|
|
||||||
|
removeEventListener (type, fn) { |
||||||
|
if (this.events) { |
||||||
|
let fns = this.events[type] |
||||||
|
const l = fns.length |
||||||
|
for (let i = 0; i < l; i += 1) { |
||||||
|
if (fns[i] === fn) { |
||||||
|
fns.splice(i, 1) |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
_fireEvent (type) { |
||||||
|
if (this.events) { |
||||||
|
let evts = this.events[type] |
||||||
|
if (evts) { |
||||||
|
const args = Array.prototype.slice.call(arguments, 1) |
||||||
|
each(evts, function (evt) { |
||||||
|
evt(...args) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
const ColorTriangle = require('./lib/colortriangle') |
||||||
|
const $ = require('./lib/chibi') |
||||||
|
const themes = require('./term/themes') |
||||||
|
const { qs } = require('./utils') |
||||||
|
|
||||||
|
function selectedTheme () { |
||||||
|
return +$('#theme').val() |
||||||
|
} |
||||||
|
|
||||||
|
exports.init = function () { |
||||||
|
$('#theme').on('change', showColor) |
||||||
|
|
||||||
|
$('#default_fg').on('input', showColor) |
||||||
|
$('#default_bg').on('input', showColor) |
||||||
|
|
||||||
|
let opts = { |
||||||
|
padding: 10, |
||||||
|
event: 'drag', |
||||||
|
uppercase: true, |
||||||
|
trianglePointerSize: 20, |
||||||
|
// wheelPointerSize: 12,
|
||||||
|
size: 200, |
||||||
|
parseColor: (color) => { |
||||||
|
return themes.toHex(color, selectedTheme()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ColorTriangle.initInput(qs('#default_fg'), opts) |
||||||
|
ColorTriangle.initInput(qs('#default_bg'), opts) |
||||||
|
|
||||||
|
$('.colorprev.bg span').on('click', function () { |
||||||
|
const bg = this.dataset.bg |
||||||
|
if (typeof bg != 'undefined') $('#default_bg').val(bg) |
||||||
|
showColor() |
||||||
|
}) |
||||||
|
|
||||||
|
$('.colorprev.fg span').on('click', function () { |
||||||
|
const fg = this.dataset.fg |
||||||
|
if (typeof fg != 'undefined') $('#default_fg').val(fg) |
||||||
|
showColor() |
||||||
|
}) |
||||||
|
|
||||||
|
let $presets = $('#fgbg_presets') |
||||||
|
for (let i = 0; i < themes.fgbgThemes.length; i++) { |
||||||
|
const thm = themes.fgbgThemes[i] |
||||||
|
const fg = thm[0] |
||||||
|
const bg = thm[1] |
||||||
|
const lbl = thm[2] |
||||||
|
const tit = thm[3] |
||||||
|
$presets.htmlAppend( |
||||||
|
'<span class="preset" ' + |
||||||
|
'data-xfg="' + fg + '" data-xbg="' + bg + '" ' + |
||||||
|
'style="color:' + fg + ';background:' + bg + '" title="' + tit + '"> ' + lbl + ' </span>') |
||||||
|
|
||||||
|
if ((i + 1) % 5 === 0) $presets.htmlAppend('<br>') |
||||||
|
} |
||||||
|
|
||||||
|
$('.preset').on('click', function () { |
||||||
|
$('#default_fg').val(this.dataset.xfg) |
||||||
|
$('#default_bg').val(this.dataset.xbg) |
||||||
|
showColor() |
||||||
|
}) |
||||||
|
|
||||||
|
showColor() |
||||||
|
} |
||||||
|
|
||||||
|
function showColor () { |
||||||
|
let ex = qs('.color-example') |
||||||
|
let fg = $('#default_fg').val() |
||||||
|
let bg = $('#default_bg').val() |
||||||
|
|
||||||
|
if (/^\d+$/.test(fg)) { |
||||||
|
fg = +fg |
||||||
|
} else if (!/^#[\da-f]{6}$/i.test(fg)) { |
||||||
|
fg = 'black' |
||||||
|
} |
||||||
|
|
||||||
|
if (/^\d+$/.test(bg)) { |
||||||
|
bg = +bg |
||||||
|
} else if (!/^#[\da-f]{6}$/i.test(bg)) { |
||||||
|
bg = 'black' |
||||||
|
} |
||||||
|
|
||||||
|
const themeN = selectedTheme() |
||||||
|
ex.dataset.fg = fg |
||||||
|
ex.dataset.bg = bg |
||||||
|
|
||||||
|
themes.themePreview(themeN) |
||||||
|
|
||||||
|
$('.colorprev.fg span').css('background', themes.toHex(bg, themeN)) |
||||||
|
} |
||||||
|
|
||||||
|
exports.nextTheme = () => { |
||||||
|
let sel = qs('#theme') |
||||||
|
let i = sel.selectedIndex |
||||||
|
sel.options[++i % sel.options.length].selected = true |
||||||
|
showColor() |
||||||
|
} |
||||||
|
|
||||||
|
exports.prevTheme = () => { |
||||||
|
let sel = qs('#theme') |
||||||
|
let i = sel.selectedIndex |
||||||
|
sel.options[(sel.options.length + (--i)) % sel.options.length].selected = true |
||||||
|
showColor() |
||||||
|
} |
Loading…
Reference in new issue