diff --git a/README.md b/README.md
index 4f0bf3b..5f2ce3f 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,27 @@
# litsimaja
-Projekt Litsimaja - Lapikute tagatoa seintele programmeeritavad ARGB ribad
\ No newline at end of file
+Projekt Litsimaja - Lapikute tagatoa seintele programmeeritavad ARGB ribad
+
+#### Running the program
+```python run.py```
+
+#### Running with emulation
+This is mainly for testing, development.
+
+In ``lib/Litsimaja.py`` change the following:
+```
+# from lib.strip.TkinterStrip import TkinterStrip
+
+ def __init__(self):
+ self._strip = PixelStrip(290, 18, 800000, 10, False, 255, 0, 4104)
+```
+to
+```
+from lib.strip.TkinterStrip import TkinterStrip
+
+ def __init__(self):
+ self._strip = TkinterStrip(290, 18, 800000, 10, False, 255, 0, 4104)
+```
+Now when you run the program, you will see a Tkinter window pop up with a rectangle simulating the LED strip.
+
+Don't commit this change.
\ No newline at end of file
diff --git a/pyleds/lib/Litsimaja.py b/pyleds/lib/Litsimaja.py
index 448fd33..feca951 100644
--- a/pyleds/lib/Litsimaja.py
+++ b/pyleds/lib/Litsimaja.py
@@ -25,6 +25,7 @@ class Litsimaja(object):
self._strip.show()
def add_loop(self, loop: LoopSwitch):
+ self.clear_loops()
self._loops.append(loop)
def clear_loops(self):
diff --git a/pyleds/program/peter/MegaMix.py b/pyleds/program/peter/MegaMix.py
new file mode 100644
index 0000000..c498f67
--- /dev/null
+++ b/pyleds/program/peter/MegaMix.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+# MegaMix
+# Mis m6tted tulevad kui kuuled "MegaMix"?
+# m6tlen et millal jooma saaks hakata
+
+from lib.Program import Program
+import time
+import random
+
+
+def name():
+ return 'MegaMix'
+
+
+class MegaMix(Program):
+
+ preset_colors = ["ffa500", "e56717", "e18b6b", "c36241", "ed9121", "f5deb3", "c35817", "ff8400", "efb261",
+ "c78023", "cc6600", "e66c2c", "ff8040", "ff7f50", "ffc594", "f88158", "e67451", "fc7f03",
+ "deaa88", "f87217", "db4200", "ff7e75"]
+ current_colors = []
+
+ # from https://chase-seibert.github.io/blog/2011/07/29/python-calculate-lighterdarker-rgb-colors.html
+ # edited to my own liking
+ def color_variant(self, hex_color, brightness_offset = 1):
+ """ takes a color like #87c95f and produces a lighter or darker variant """
+ if len(hex_color) != 6:
+ print("Passed %s into color_variant(), needs to be in 87c95f format." % hex_color)
+ return "000000"
+ rgb_hex = [hex_color[x:x+2] for x in [0, 2, 4]]
+ new_rgb_int = [int(hex_value, 16) + brightness_offset for hex_value in rgb_hex]
+ new_rgb_int = [min([255, max([0, i])]) for i in new_rgb_int] # make sure new values are between 0 and 255
+ # hex() produces "0x88", we want just "88"
+ new_hex_list = [hex(i)[2:].zfill(2) for i in new_rgb_int]
+ new_hex = "".join(new_hex_list)
+ return new_hex
+
+ def disco(self, wait_ms):
+ total_length = self._lm.count_pixels()
+ for p in range(total_length):
+ if len(self.current_colors) == total_length:
+ color = self.current_colors[p]
+ color = int(self.color_variant(color, random.randint(1, 10)), 16)
+ if color >= 16777215:
+ color = int(random.choice(self.preset_colors), 16)
+ self.current_colors[p] = hex(color)[2:]
+ else:
+ color = int(random.choice(self.preset_colors), 16)
+ self.current_colors.append(hex(color)[2:])
+ self._lm.set_pixel_color(p, color)
+ self._lm.show()
+ time.sleep(wait_ms / 1000.0)
+
+ # Main program logic follows:
+ def run(self, args=None):
+ loop = False
+ if 'loop' in args and args['loop']:
+ loop = args['loop']
+
+ while self.get_loop().status():
+ self.disco(300)
+
+ if not loop:
+ break
diff --git a/pyleds/run.py b/pyleds/run.py
index 73a367a..82c2dca 100755
--- a/pyleds/run.py
+++ b/pyleds/run.py
@@ -1,7 +1,6 @@
#!/usr/bin/env python3
import logging
import sys
-import json
import lib.ProgramLoading as Pl
from lib.Litsimaja import Litsimaja
@@ -20,8 +19,7 @@ logger.addHandler(stdout_handler)
# start litsimaja
lm = Litsimaja()
-app = Flask(__name__)
-# Pl.run('siinus', 'Static', lm, logger, {'color': [50, 50, 50]})
+app = Flask(__name__, static_url_path='', static_folder='templates')
@app.route('/', methods=['GET'])
@@ -29,11 +27,6 @@ def respondroot():
return render_template('index.html', programs=Pl.list_all(True))
-@app.route('/jscolor.js', methods=['GET'])
-def respondjs():
- return render_template('jscolor.js')
-
-
@app.route('/run')
def run():
Pl.run('siinus', 'MyProgram', lm, logger, {'color': [50, 50, 50]})
diff --git a/pyleds/templates/custom.css b/pyleds/templates/custom.css
new file mode 100644
index 0000000..00fd597
--- /dev/null
+++ b/pyleds/templates/custom.css
@@ -0,0 +1,30 @@
+.section-rgb {
+ height: 20rem;
+ text-align: center;
+}
+
+select {
+ width: 100%;
+ height: calc(100% - 1.5rem);
+}
+
+.colorpicker {
+ display: inline-block;
+ height: 38px;
+ padding: 0 30px;
+ width: 100%;
+ color: #555;
+ background-color: transparent;
+ border-radius: 4px;
+ border: 1px solid #bbb;
+ cursor: pointer;
+ box-sizing: border-box; }
+.colorpicker:hover,
+.colorpicker:focus {
+ color: #333;
+ border-color: #888;
+ outline: 0; }
+
+.spacer-row {
+ height: 2rem;
+}
\ No newline at end of file
diff --git a/pyleds/templates/index.html b/pyleds/templates/index.html
index 95f1640..aec4611 100644
--- a/pyleds/templates/index.html
+++ b/pyleds/templates/index.html
@@ -1,103 +1,109 @@
- litsimaja
-
+ Litsimaja
+
+
+
+
-
-
-
-
-
-LOADING!
-
-
-
+
+
+
+
+
+
+
+
+
+
LOADING!
+
+
+
+
+
+
+
+
+
diff --git a/pyleds/templates/jscolor.js b/pyleds/templates/jscolor.js
deleted file mode 100644
index 9e78392..0000000
--- a/pyleds/templates/jscolor.js
+++ /dev/null
@@ -1,3180 +0,0 @@
-/**
- * jscolor - JavaScript Color Picker
- *
- * @link http://jscolor.com
- * @license For open source use: GPLv3
- * For commercial use: JSColor Commercial License
- * @author Jan Odvarko - East Desire
- * @version 2.3.3
- *
- * See usage examples at http://jscolor.com/examples/
- */
-
-
-"use strict";
-
-
-if (!window.jscolor) {
-
-window.jscolor = (function () { // BEGIN window.jscolor
-
-var jsc = {
-
-
- initialized : false,
-
- instances : [], // created instances of jscolor
-
- triggerQueue : [], // events waiting to be triggered after init
-
-
- register : function () {
- document.addEventListener('DOMContentLoaded', jsc.init, false);
- document.addEventListener('mousedown', jsc.onDocumentMouseDown, false);
- document.addEventListener('keyup', jsc.onDocumentKeyUp, false);
- window.addEventListener('resize', jsc.onWindowResize, false);
- },
-
-
- init : function () {
- if (jsc.initialized) {
- return;
- }
-
- jsc.pub.install();
- jsc.initialized = true;
-
- // trigger events waiting in the queue
- while (jsc.triggerQueue.length) {
- var ev = jsc.triggerQueue.shift();
- jsc.triggerGlobal(ev);
- }
- },
-
-
- installBySelector : function (selector, rootNode) {
- rootNode = rootNode ? jsc.node(rootNode) : document;
- if (!rootNode) {
- throw new Error('Missing root node');
- }
-
- var elms = rootNode.querySelectorAll(selector);
-
- // for backward compatibility with DEPRECATED installation/configuration using className
- var matchClass = new RegExp('(^|\\s)(' + jsc.pub.lookupClass + ')(\\s*(\\{[^}]*\\})|\\s|$)', 'i');
-
- for (var i = 0; i < elms.length; i += 1) {
-
- if (elms[i].jscolor && elms[i].jscolor instanceof jsc.pub) {
- continue; // jscolor already installed on this element
- }
-
- if (elms[i].type !== undefined && elms[i].type.toLowerCase() == 'color' && jsc.isColorAttrSupported) {
- continue; // skips inputs of type 'color' if supported by the browser
- }
-
- var dataOpts, m;
-
- if (
- (dataOpts = jsc.getDataAttr(elms[i], 'jscolor')) !== null ||
- (elms[i].className && (m = elms[i].className.match(matchClass))) // installation using className (DEPRECATED)
- ) {
- var targetElm = elms[i];
-
- var optsStr = '';
- if (dataOpts !== null) {
- optsStr = dataOpts;
-
- } else if (m) { // installation using className (DEPRECATED)
- console.warn('Installation using class name is DEPRECATED. Use data-jscolor="" attribute instead.' + jsc.docsRef);
- if (m[4]) {
- optsStr = m[4];
- }
- }
-
- var opts = null;
- if (optsStr.trim()) {
- try {
- opts = jsc.parseOptionsStr(optsStr);
- } catch (e) {
- console.warn(e + '\n' + optsStr);
- }
- }
-
- try {
- new jsc.pub(targetElm, opts);
- } catch (e) {
- console.warn(e);
- }
- }
- }
- },
-
-
- parseOptionsStr : function (str) {
- var opts = null;
-
- try {
- opts = JSON.parse(str);
-
- } catch (eParse) {
- if (!jsc.pub.looseJSON) {
- throw new Error('Could not parse jscolor options as JSON: ' + eParse);
- } else {
- // loose JSON syntax is enabled -> try to evaluate the options string as JavaScript object
- try {
- opts = (new Function ('var opts = (' + str + '); return typeof opts === "object" ? opts : {};'))();
- } catch (eEval) {
- throw new Error('Could not evaluate jscolor options: ' + eEval);
- }
- }
- }
- return opts;
- },
-
-
- getInstances : function () {
- var inst = [];
- for (var i = 0; i < jsc.instances.length; i += 1) {
- // if the targetElement still exists, the instance is considered "alive"
- if (jsc.instances[i] && jsc.instances[i].targetElement) {
- inst.push(jsc.instances[i]);
- }
- }
- return inst;
- },
-
-
- createEl : function (tagName) {
- var el = document.createElement(tagName);
- jsc.setData(el, 'gui', true)
- return el;
- },
-
-
- node : function (nodeOrSelector) {
- if (!nodeOrSelector) {
- return null;
- }
-
- if (typeof nodeOrSelector === 'string') {
- // query selector
- var sel = nodeOrSelector;
- var el = null;
- try {
- el = document.querySelector(sel);
- } catch (e) {
- console.warn(e);
- return null;
- }
- if (!el) {
- console.warn('No element matches the selector: %s', sel);
- }
- return el;
- }
-
- if (jsc.isNode(nodeOrSelector)) {
- // DOM node
- return nodeOrSelector;
- }
-
- console.warn('Invalid node of type %s: %s', typeof nodeOrSelector, nodeOrSelector);
- return null;
- },
-
-
- // See https://stackoverflow.com/questions/384286/
- isNode : function (val) {
- if (typeof Node === 'object') {
- return val instanceof Node;
- }
- return val && typeof val === 'object' && typeof val.nodeType === 'number' && typeof val.nodeName === 'string';
- },
-
-
- nodeName : function (node) {
- if (node && node.nodeName) {
- return node.nodeName.toLowerCase();
- }
- return false;
- },
-
-
- removeChildren : function (node) {
- while (node.firstChild) {
- node.removeChild(node.firstChild);
- }
- },
-
-
- isTextInput : function (el) {
- return el && jsc.nodeName(el) === 'input' && el.type.toLowerCase() === 'text';
- },
-
-
- isButton : function (el) {
- if (!el) {
- return false;
- }
- var n = jsc.nodeName(el);
- return (
- (n === 'button') ||
- (n === 'input' && ['button', 'submit', 'reset'].indexOf(el.type.toLowerCase()) > -1)
- );
- },
-
-
- isButtonEmpty : function (el) {
- switch (jsc.nodeName(el)) {
- case 'input': return (!el.value || el.value.trim() === '');
- case 'button': return (el.textContent.trim() === '');
- }
- return null; // could not determine element's text
- },
-
-
- // See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
- isPassiveEventSupported : (function () {
- var supported = false;
-
- try {
- var opts = Object.defineProperty({}, 'passive', {
- get: function () { supported = true; }
- });
- window.addEventListener('testPassive', null, opts);
- window.removeEventListener('testPassive', null, opts);
- } catch (e) {}
-
- return supported;
- })(),
-
-
- isColorAttrSupported : (function () {
- var elm = document.createElement('input');
- if (elm.setAttribute) {
- elm.setAttribute('type', 'color');
- if (elm.type.toLowerCase() == 'color') {
- return true;
- }
- }
- return false;
- })(),
-
-
- dataProp : '_data_jscolor',
-
-
- // usage:
- // setData(obj, prop, value)
- // setData(obj, {prop:value, ...})
- //
- setData : function () {
- var obj = arguments[0];
-
- if (arguments.length === 3) {
- // setting a single property
- var data = obj.hasOwnProperty(jsc.dataProp) ? obj[jsc.dataProp] : (obj[jsc.dataProp] = {});
- var prop = arguments[1];
- var value = arguments[2];
-
- data[prop] = value;
- return true;
-
- } else if (arguments.length === 2 && typeof arguments[1] === 'object') {
- // setting multiple properties
- var data = obj.hasOwnProperty(jsc.dataProp) ? obj[jsc.dataProp] : (obj[jsc.dataProp] = {});
- var map = arguments[1];
-
- for (var prop in map) {
- if (map.hasOwnProperty(prop)) {
- data[prop] = map[prop];
- }
- }
- return true;
- }
-
- throw new Error('Invalid arguments');
- },
-
-
- // usage:
- // removeData(obj, prop, [prop...])
- //
- removeData : function () {
- var obj = arguments[0];
- if (!obj.hasOwnProperty(jsc.dataProp)) {
- return true; // data object does not exist
- }
- for (var i = 1; i < arguments.length; i += 1) {
- var prop = arguments[i];
- delete obj[jsc.dataProp][prop];
- }
- return true;
- },
-
-
- getData : function (obj, prop, setDefault) {
- if (!obj.hasOwnProperty(jsc.dataProp)) {
- // data object does not exist
- if (setDefault !== undefined) {
- obj[jsc.dataProp] = {}; // create data object
- } else {
- return undefined; // no value to return
- }
- }
- var data = obj[jsc.dataProp];
-
- if (!data.hasOwnProperty(prop) && setDefault !== undefined) {
- data[prop] = setDefault;
- }
- return data[prop];
- },
-
-
- getDataAttr : function (el, name) {
- var attrName = 'data-' + name;
- var attrValue = el.getAttribute(attrName);
- return attrValue;
- },
-
-
- _attachedGroupEvents : {},
-
-
- attachGroupEvent : function (groupName, el, evnt, func) {
- if (!jsc._attachedGroupEvents.hasOwnProperty(groupName)) {
- jsc._attachedGroupEvents[groupName] = [];
- }
- jsc._attachedGroupEvents[groupName].push([el, evnt, func]);
- el.addEventListener(evnt, func, false);
- },
-
-
- detachGroupEvents : function (groupName) {
- if (jsc._attachedGroupEvents.hasOwnProperty(groupName)) {
- for (var i = 0; i < jsc._attachedGroupEvents[groupName].length; i += 1) {
- var evt = jsc._attachedGroupEvents[groupName][i];
- evt[0].removeEventListener(evt[1], evt[2], false);
- }
- delete jsc._attachedGroupEvents[groupName];
- }
- },
-
-
- preventDefault : function (e) {
- if (e.preventDefault) { e.preventDefault(); }
- e.returnValue = false;
- },
-
-
- captureTarget : function (target) {
- // IE
- if (target.setCapture) {
- jsc._capturedTarget = target;
- jsc._capturedTarget.setCapture();
- }
- },
-
-
- releaseTarget : function () {
- // IE
- if (jsc._capturedTarget) {
- jsc._capturedTarget.releaseCapture();
- jsc._capturedTarget = null;
- }
- },
-
-
- triggerEvent : function (el, eventName, bubbles, cancelable) {
- if (!el) {
- return;
- }
-
- var ev = null;
-
- if (typeof Event === 'function') {
- ev = new Event(eventName, {
- bubbles: bubbles,
- cancelable: cancelable
- });
- } else {
- // IE
- ev = document.createEvent('Event');
- ev.initEvent(eventName, bubbles, cancelable);
- }
-
- if (!ev) {
- return false;
- }
-
- // so that we know that the event was triggered internally
- jsc.setData(ev, 'internal', true);
-
- el.dispatchEvent(ev);
- return true;
- },
-
-
- triggerInputEvent : function (el, eventName, bubbles, cancelable) {
- if (!el) {
- return;
- }
- if (jsc.isTextInput(el)) {
- jsc.triggerEvent(el, eventName, bubbles, cancelable);
- }
- },
-
-
- eventKey : function (ev) {
- var keys = {
- 9: 'Tab',
- 13: 'Enter',
- 27: 'Escape',
- };
- if (typeof ev.code === 'string') {
- return ev.code;
- } else if (ev.keyCode !== undefined && keys.hasOwnProperty(ev.keyCode)) {
- return keys[ev.keyCode];
- }
- return null;
- },
-
-
- strList : function (str) {
- if (!str) {
- return [];
- }
- return str.replace(/^\s+|\s+$/g, '').split(/\s+/);
- },
-
-
- // The className parameter (str) can only contain a single class name
- hasClass : function (elm, className) {
- if (!className) {
- return false;
- }
- if (elm.classList !== undefined) {
- return elm.classList.contains(className);
- }
- // polyfill
- return -1 != (' ' + elm.className.replace(/\s+/g, ' ') + ' ').indexOf(' ' + className + ' ');
- },
-
-
- // The className parameter (str) can contain multiple class names separated by whitespace
- addClass : function (elm, className) {
- var classNames = jsc.strList(className);
-
- if (elm.classList !== undefined) {
- for (var i = 0; i < classNames.length; i += 1) {
- elm.classList.add(classNames[i]);
- }
- return;
- }
- // polyfill
- for (var i = 0; i < classNames.length; i += 1) {
- if (!jsc.hasClass(elm, classNames[i])) {
- elm.className += (elm.className ? ' ' : '') + classNames[i];
- }
- }
- },
-
-
- // The className parameter (str) can contain multiple class names separated by whitespace
- removeClass : function (elm, className) {
- var classNames = jsc.strList(className);
-
- if (elm.classList !== undefined) {
- for (var i = 0; i < classNames.length; i += 1) {
- elm.classList.remove(classNames[i]);
- }
- return;
- }
- // polyfill
- for (var i = 0; i < classNames.length; i += 1) {
- var repl = new RegExp(
- '^\\s*' + classNames[i] + '\\s*|' +
- '\\s*' + classNames[i] + '\\s*$|' +
- '\\s+' + classNames[i] + '(\\s+)',
- 'g'
- );
- elm.className = elm.className.replace(repl, '$1');
- }
- },
-
-
- getCompStyle : function (elm) {
- var compStyle = window.getComputedStyle ? window.getComputedStyle(elm) : elm.currentStyle;
-
- // Note: In Firefox, getComputedStyle returns null in a hidden iframe,
- // that's why we need to check if the returned value is non-empty
- if (!compStyle) {
- return {};
- }
- return compStyle;
- },
-
-
- // Note:
- // Setting a property to NULL reverts it to the state before it was first set
- // with the 'reversible' flag enabled
- //
- setStyle : function (elm, styles, important, reversible) {
- // using '' for standard priority (IE10 apparently doesn't like value undefined)
- var priority = important ? 'important' : '';
- var origStyle = null;
-
- for (var prop in styles) {
- if (styles.hasOwnProperty(prop)) {
- var setVal = null;
-
- if (styles[prop] === null) {
- // reverting a property value
-
- if (!origStyle) {
- // get the original style object, but dont't try to create it if it doesn't exist
- origStyle = jsc.getData(elm, 'origStyle');
- }
- if (origStyle && origStyle.hasOwnProperty(prop)) {
- // we have property's original value -> use it
- setVal = origStyle[prop];
- }
-
- } else {
- // setting a property value
-
- if (reversible) {
- if (!origStyle) {
- // get the original style object and if it doesn't exist, create it
- origStyle = jsc.getData(elm, 'origStyle', {});
- }
- if (!origStyle.hasOwnProperty(prop)) {
- // original property value not yet stored -> store it
- origStyle[prop] = elm.style[prop];
- }
- }
- setVal = styles[prop];
- }
-
- if (setVal !== null) {
- elm.style.setProperty(prop, setVal, priority);
- }
- }
- }
- },
-
-
- linearGradient : (function () {
-
- function getFuncName () {
- var stdName = 'linear-gradient';
- var prefixes = ['', '-webkit-', '-moz-', '-o-', '-ms-'];
- var helper = document.createElement('div');
-
- for (var i = 0; i < prefixes.length; i += 1) {
- var tryFunc = prefixes[i] + stdName;
- var tryVal = tryFunc + '(to right, rgba(0,0,0,0), rgba(0,0,0,0))';
-
- helper.style.background = tryVal;
- if (helper.style.background) { // CSS background successfully set -> function name is supported
- return tryFunc;
- }
- }
- return stdName; // fallback to standard 'linear-gradient' without vendor prefix
- }
-
- var funcName = getFuncName();
-
- return function () {
- return funcName + '(' + Array.prototype.join.call(arguments, ', ') + ')';
- };
-
- })(),
-
-
- setBorderRadius : function (elm, value) {
- jsc.setStyle(elm, {'border-radius' : value || '0'});
- },
-
-
- setBoxShadow : function (elm, value) {
- jsc.setStyle(elm, {'box-shadow': value || 'none'});
- },
-
-
- getElementPos : function (e, relativeToViewport) {
- var x=0, y=0;
- var rect = e.getBoundingClientRect();
- x = rect.left;
- y = rect.top;
- if (!relativeToViewport) {
- var viewPos = jsc.getViewPos();
- x += viewPos[0];
- y += viewPos[1];
- }
- return [x, y];
- },
-
-
- getElementSize : function (e) {
- return [e.offsetWidth, e.offsetHeight];
- },
-
-
- // get pointer's X/Y coordinates relative to viewport
- getAbsPointerPos : function (e) {
- var x = 0, y = 0;
- if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) {
- // touch devices
- x = e.changedTouches[0].clientX;
- y = e.changedTouches[0].clientY;
- } else if (typeof e.clientX === 'number') {
- x = e.clientX;
- y = e.clientY;
- }
- return { x: x, y: y };
- },
-
-
- // get pointer's X/Y coordinates relative to target element
- getRelPointerPos : function (e) {
- var target = e.target || e.srcElement;
- var targetRect = target.getBoundingClientRect();
-
- var x = 0, y = 0;
-
- var clientX = 0, clientY = 0;
- if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) {
- // touch devices
- clientX = e.changedTouches[0].clientX;
- clientY = e.changedTouches[0].clientY;
- } else if (typeof e.clientX === 'number') {
- clientX = e.clientX;
- clientY = e.clientY;
- }
-
- x = clientX - targetRect.left;
- y = clientY - targetRect.top;
- return { x: x, y: y };
- },
-
-
- getViewPos : function () {
- var doc = document.documentElement;
- return [
- (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0),
- (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0)
- ];
- },
-
-
- getViewSize : function () {
- var doc = document.documentElement;
- return [
- (window.innerWidth || doc.clientWidth),
- (window.innerHeight || doc.clientHeight),
- ];
- },
-
-
- // r: 0-255
- // g: 0-255
- // b: 0-255
- //
- // returns: [ 0-360, 0-100, 0-100 ]
- //
- RGB_HSV : function (r, g, b) {
- r /= 255;
- g /= 255;
- b /= 255;
- var n = Math.min(Math.min(r,g),b);
- var v = Math.max(Math.max(r,g),b);
- var m = v - n;
- if (m === 0) { return [ null, 0, 100 * v ]; }
- var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m);
- return [
- 60 * (h===6?0:h),
- 100 * (m/v),
- 100 * v
- ];
- },
-
-
- // h: 0-360
- // s: 0-100
- // v: 0-100
- //
- // returns: [ 0-255, 0-255, 0-255 ]
- //
- HSV_RGB : function (h, s, v) {
- var u = 255 * (v / 100);
-
- if (h === null) {
- return [ u, u, u ];
- }
-
- h /= 60;
- s /= 100;
-
- var i = Math.floor(h);
- var f = i%2 ? h-i : 1-(h-i);
- var m = u * (1 - s);
- var n = u * (1 - s * f);
- switch (i) {
- case 6:
- case 0: return [u,n,m];
- case 1: return [n,u,m];
- case 2: return [m,u,n];
- case 3: return [m,n,u];
- case 4: return [n,m,u];
- case 5: return [u,m,n];
- }
- },
-
-
- parseColorString : function (str) {
- var ret = {
- rgba: null,
- format: null // 'hex' | 'rgb' | 'rgba'
- };
-
- var m;
- if (m = str.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i)) {
- // HEX notation
-
- ret.format = 'hex';
-
- if (m[1].length === 6) {
- // 6-char notation
- ret.rgba = [
- parseInt(m[1].substr(0,2),16),
- parseInt(m[1].substr(2,2),16),
- parseInt(m[1].substr(4,2),16),
- null
- ];
- } else {
- // 3-char notation
- ret.rgba = [
- parseInt(m[1].charAt(0) + m[1].charAt(0),16),
- parseInt(m[1].charAt(1) + m[1].charAt(1),16),
- parseInt(m[1].charAt(2) + m[1].charAt(2),16),
- null
- ];
- }
- return ret;
-
- } else if (m = str.match(/^\W*rgba?\(([^)]*)\)\W*$/i)) {
- // rgb(...) or rgba(...) notation
-
- var params = m[1].split(',');
- var re = /^\s*(\d+|\d*\.\d+|\d+\.\d*)\s*$/;
- var mR, mG, mB, mA;
- if (
- params.length >= 3 &&
- (mR = params[0].match(re)) &&
- (mG = params[1].match(re)) &&
- (mB = params[2].match(re))
- ) {
- ret.format = 'rgb';
- ret.rgba = [
- parseFloat(mR[1]) || 0,
- parseFloat(mG[1]) || 0,
- parseFloat(mB[1]) || 0,
- null
- ];
-
- if (
- params.length >= 4 &&
- (mA = params[3].match(re))
- ) {
- ret.format = 'rgba';
- ret.rgba[3] = parseFloat(mA[1]) || 0;
- }
- return ret;
- }
- }
-
- return false;
- },
-
-
- // Canvas scaling for retina displays
- //
- // adapted from https://www.html5rocks.com/en/tutorials/canvas/hidpi/
- //
- scaleCanvasForHighDPR : function (canvas) {
- var dpr = window.devicePixelRatio || 1;
- canvas.width *= dpr;
- canvas.height *= dpr;
- var ctx = canvas.getContext('2d');
- ctx.scale(dpr, dpr);
- },
-
-
- genColorPreviewCanvas : function (color, separatorPos, specWidth, scaleForHighDPR) {
-
- var sepW = Math.round(jsc.pub.previewSeparator.length);
- var sqSize = jsc.pub.chessboardSize;
- var sqColor1 = jsc.pub.chessboardColor1;
- var sqColor2 = jsc.pub.chessboardColor2;
-
- var cWidth = specWidth ? specWidth : sqSize * 2;
- var cHeight = sqSize * 2;
-
- var canvas = jsc.createEl('canvas');
- var ctx = canvas.getContext('2d');
-
- canvas.width = cWidth;
- canvas.height = cHeight;
- if (scaleForHighDPR) {
- jsc.scaleCanvasForHighDPR(canvas);
- }
-
- // transparency chessboard - background
- ctx.fillStyle = sqColor1;
- ctx.fillRect(0, 0, cWidth, cHeight);
-
- // transparency chessboard - squares
- ctx.fillStyle = sqColor2;
- for (var x = 0; x < cWidth; x += sqSize * 2) {
- ctx.fillRect(x, 0, sqSize, sqSize);
- ctx.fillRect(x + sqSize, sqSize, sqSize, sqSize);
- }
-
- if (color) {
- // actual color in foreground
- ctx.fillStyle = color;
- ctx.fillRect(0, 0, cWidth, cHeight);
- }
-
- var start = null;
- switch (separatorPos) {
- case 'left':
- start = 0;
- ctx.clearRect(0, 0, sepW/2, cHeight);
- break;
- case 'right':
- start = cWidth - sepW;
- ctx.clearRect(cWidth - (sepW/2), 0, sepW/2, cHeight);
- break;
- }
- if (start !== null) {
- ctx.lineWidth = 1;
- for (var i = 0; i < jsc.pub.previewSeparator.length; i += 1) {
- ctx.beginPath();
- ctx.strokeStyle = jsc.pub.previewSeparator[i];
- ctx.moveTo(0.5 + start + i, 0);
- ctx.lineTo(0.5 + start + i, cHeight);
- ctx.stroke();
- }
- }
-
- return {
- canvas: canvas,
- width: cWidth,
- height: cHeight,
- };
- },
-
-
- // if position or width is not set => fill the entire element (0%-100%)
- genColorPreviewGradient : function (color, position, width) {
- var params = [];
-
- if (position && width) {
- params = [
- 'to ' + {'left':'right', 'right':'left'}[position],
- color + ' 0%',
- color + ' ' + width + 'px',
- 'rgba(0,0,0,0) ' + (width + 1) + 'px',
- 'rgba(0,0,0,0) 100%',
- ];
- } else {
- params = [
- 'to right',
- color + ' 0%',
- color + ' 100%',
- ];
- }
-
- return jsc.linearGradient.apply(this, params);
- },
-
-
- redrawPosition : function () {
-
- if (jsc.picker && jsc.picker.owner) {
- var thisObj = jsc.picker.owner;
-
- var tp, vp;
-
- if (thisObj.fixed) {
- // Fixed elements are positioned relative to viewport,
- // therefore we can ignore the scroll offset
- tp = jsc.getElementPos(thisObj.targetElement, true); // target pos
- vp = [0, 0]; // view pos
- } else {
- tp = jsc.getElementPos(thisObj.targetElement); // target pos
- vp = jsc.getViewPos(); // view pos
- }
-
- var ts = jsc.getElementSize(thisObj.targetElement); // target size
- var vs = jsc.getViewSize(); // view size
- var ps = jsc.getPickerOuterDims(thisObj); // picker size
- var a, b, c;
- switch (thisObj.position.toLowerCase()) {
- case 'left': a=1; b=0; c=-1; break;
- case 'right':a=1; b=0; c=1; break;
- case 'top': a=0; b=1; c=-1; break;
- default: a=0; b=1; c=1; break;
- }
- var l = (ts[b]+ps[b])/2;
-
- // compute picker position
- if (!thisObj.smartPosition) {
- var pp = [
- tp[a],
- tp[b]+ts[b]-l+l*c
- ];
- } else {
- var pp = [
- -vp[a]+tp[a]+ps[a] > vs[a] ?
- (-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) :
- tp[a],
- -vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ?
- (-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) :
- (tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c)
- ];
- }
-
- var x = pp[a];
- var y = pp[b];
- var positionValue = thisObj.fixed ? 'fixed' : 'absolute';
- var contractShadow =
- (pp[0] + ps[0] > tp[0] || pp[0] < tp[0] + ts[0]) &&
- (pp[1] + ps[1] < tp[1] + ts[1]);
-
- jsc._drawPosition(thisObj, x, y, positionValue, contractShadow);
- }
- },
-
-
- _drawPosition : function (thisObj, x, y, positionValue, contractShadow) {
- var vShadow = contractShadow ? 0 : thisObj.shadowBlur; // px
-
- jsc.picker.wrap.style.position = positionValue;
- jsc.picker.wrap.style.left = x + 'px';
- jsc.picker.wrap.style.top = y + 'px';
-
- jsc.setBoxShadow(
- jsc.picker.boxS,
- thisObj.shadow ?
- new jsc.BoxShadow(0, vShadow, thisObj.shadowBlur, 0, thisObj.shadowColor) :
- null);
- },
-
-
- getPickerDims : function (thisObj) {
- var dims = [
- 2 * thisObj.controlBorderWidth + 2 * thisObj.padding + thisObj.width,
- 2 * thisObj.controlBorderWidth + 2 * thisObj.padding + thisObj.height
- ];
- var sliderSpace = 2 * thisObj.controlBorderWidth + 2 * jsc.getControlPadding(thisObj) + thisObj.sliderSize;
- if (jsc.getSliderChannel(thisObj)) {
- dims[0] += sliderSpace;
- }
- if (thisObj.hasAlphaChannel()) {
- dims[0] += sliderSpace;
- }
- if (thisObj.closeButton) {
- dims[1] += 2 * thisObj.controlBorderWidth + thisObj.padding + thisObj.buttonHeight;
- }
- return dims;
- },
-
-
- getPickerOuterDims : function (thisObj) {
- var dims = jsc.getPickerDims(thisObj);
- return [
- dims[0] + 2 * thisObj.borderWidth,
- dims[1] + 2 * thisObj.borderWidth
- ];
- },
-
-
- getControlPadding : function (thisObj) {
- return Math.max(
- thisObj.padding / 2,
- (2 * thisObj.pointerBorderWidth + thisObj.pointerThickness) - thisObj.controlBorderWidth
- );
- },
-
-
- getPadYChannel : function (thisObj) {
- switch (thisObj.mode.charAt(1).toLowerCase()) {
- case 'v': return 'v'; break;
- }
- return 's';
- },
-
-
- getSliderChannel : function (thisObj) {
- if (thisObj.mode.length > 2) {
- switch (thisObj.mode.charAt(2).toLowerCase()) {
- case 's': return 's'; break;
- case 'v': return 'v'; break;
- }
- }
- return null;
- },
-
-
- onDocumentMouseDown : function (e) {
- var target = e.target || e.srcElement;
-
- if (target.jscolor && target.jscolor instanceof jsc.pub) { // clicked targetElement -> show picker
- if (target.jscolor.showOnClick && !target.disabled) {
- target.jscolor.show();
- }
- } else if (jsc.getData(target, 'gui')) { // clicked jscolor's GUI element
- var control = jsc.getData(target, 'control');
- if (control) {
- // jscolor's control
- jsc.onControlPointerStart(e, target, jsc.getData(target, 'control'), 'mouse');
- }
- } else {
- // mouse is outside the picker's controls -> hide the color picker!
- if (jsc.picker && jsc.picker.owner) {
- jsc.picker.owner.tryHide();
- }
- }
- },
-
-
- onDocumentKeyUp : function (e) {
- if (['Tab', 'Escape'].indexOf(jsc.eventKey(e)) !== -1) {
- if (jsc.picker && jsc.picker.owner) {
- jsc.picker.owner.tryHide();
- }
- }
- },
-
-
- onWindowResize : function (e) {
- jsc.redrawPosition();
- },
-
-
- onParentScroll : function (e) {
- // hide the picker when one of the parent elements is scrolled
- if (jsc.picker && jsc.picker.owner) {
- jsc.picker.owner.tryHide();
- }
- },
-
-
- onPickerTouchStart : function (e) {
- var target = e.target || e.srcElement;
-
- if (jsc.getData(target, 'control')) {
- jsc.onControlPointerStart(e, target, jsc.getData(target, 'control'), 'touch');
- }
- },
-
-
- // calls function specified in picker's property
- triggerCallback : function (thisObj, prop) {
- if (!thisObj[prop]) {
- return; // callback func not specified
- }
- var callback = null;
-
- if (typeof thisObj[prop] === 'string') {
- // string with code
- try {
- callback = new Function (thisObj[prop]);
- } catch (e) {
- console.error(e);
- }
- } else {
- // function
- callback = thisObj[prop];
- }
-
- if (callback) {
- callback.call(thisObj);
- }
- },
-
-
- // Triggers a color change related event(s) on all picker instances.
- // It is possible to specify multiple events separated with a space.
- triggerGlobal : function (eventNames) {
- var inst = jsc.getInstances();
- for (var i = 0; i < inst.length; i += 1) {
- inst[i].trigger(eventNames);
- }
- },
-
-
- _pointerMoveEvent : {
- mouse: 'mousemove',
- touch: 'touchmove'
- },
- _pointerEndEvent : {
- mouse: 'mouseup',
- touch: 'touchend'
- },
-
-
- _pointerOrigin : null,
- _capturedTarget : null,
-
-
- onControlPointerStart : function (e, target, controlName, pointerType) {
- var thisObj = jsc.getData(target, 'instance');
-
- jsc.preventDefault(e);
- jsc.captureTarget(target);
-
- var registerDragEvents = function (doc, offset) {
- jsc.attachGroupEvent('drag', doc, jsc._pointerMoveEvent[pointerType],
- jsc.onDocumentPointerMove(e, target, controlName, pointerType, offset));
- jsc.attachGroupEvent('drag', doc, jsc._pointerEndEvent[pointerType],
- jsc.onDocumentPointerEnd(e, target, controlName, pointerType));
- };
-
- registerDragEvents(document, [0, 0]);
-
- if (window.parent && window.frameElement) {
- var rect = window.frameElement.getBoundingClientRect();
- var ofs = [-rect.left, -rect.top];
- registerDragEvents(window.parent.window.document, ofs);
- }
-
- var abs = jsc.getAbsPointerPos(e);
- var rel = jsc.getRelPointerPos(e);
- jsc._pointerOrigin = {
- x: abs.x - rel.x,
- y: abs.y - rel.y
- };
-
- switch (controlName) {
- case 'pad':
- // if the value slider is at the bottom, move it up
- if (jsc.getSliderChannel(thisObj) === 'v' && thisObj.channels.v === 0) {
- thisObj.fromHSVA(null, null, 100, null);
- }
- jsc.setPad(thisObj, e, 0, 0);
- break;
-
- case 'sld':
- jsc.setSld(thisObj, e, 0);
- break;
-
- case 'asld':
- jsc.setASld(thisObj, e, 0);
- break;
- }
- thisObj.trigger('input');
- },
-
-
- onDocumentPointerMove : function (e, target, controlName, pointerType, offset) {
- return function (e) {
- var thisObj = jsc.getData(target, 'instance');
- switch (controlName) {
- case 'pad':
- jsc.setPad(thisObj, e, offset[0], offset[1]);
- break;
-
- case 'sld':
- jsc.setSld(thisObj, e, offset[1]);
- break;
-
- case 'asld':
- jsc.setASld(thisObj, e, offset[1]);
- break;
- }
- thisObj.trigger('input');
- }
- },
-
-
- onDocumentPointerEnd : function (e, target, controlName, pointerType) {
- return function (e) {
- var thisObj = jsc.getData(target, 'instance');
- jsc.detachGroupEvents('drag');
- jsc.releaseTarget();
-
- // Always trigger changes AFTER detaching outstanding mouse handlers,
- // in case some color change occured in user-defined onChange/onInput handler
- // would intrude into current mouse events
- thisObj.trigger('input');
- thisObj.trigger('change');
- };
- },
-
-
- setPad : function (thisObj, e, ofsX, ofsY) {
- var pointerAbs = jsc.getAbsPointerPos(e);
- var x = ofsX + pointerAbs.x - jsc._pointerOrigin.x - thisObj.padding - thisObj.controlBorderWidth;
- var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.controlBorderWidth;
-
- var xVal = x * (360 / (thisObj.width - 1));
- var yVal = 100 - (y * (100 / (thisObj.height - 1)));
-
- switch (jsc.getPadYChannel(thisObj)) {
- case 's': thisObj.fromHSVA(xVal, yVal, null, null); break;
- case 'v': thisObj.fromHSVA(xVal, null, yVal, null); break;
- }
- },
-
-
- setSld : function (thisObj, e, ofsY) {
- var pointerAbs = jsc.getAbsPointerPos(e);
- var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.controlBorderWidth;
- var yVal = 100 - (y * (100 / (thisObj.height - 1)));
-
- switch (jsc.getSliderChannel(thisObj)) {
- case 's': thisObj.fromHSVA(null, yVal, null, null); break;
- case 'v': thisObj.fromHSVA(null, null, yVal, null); break;
- }
- },
-
-
- setASld : function (thisObj, e, ofsY) {
- var pointerAbs = jsc.getAbsPointerPos(e);
- var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.controlBorderWidth;
- var yVal = 1.0 - (y * (1.0 / (thisObj.height - 1)));
-
- if (yVal < 1.0) {
- // if format is flexible and the current format doesn't support alpha, switch to a suitable one
- if (thisObj.format.toLowerCase() === 'any' && thisObj.getFormat() !== 'rgba') {
- thisObj._currentFormat = 'rgba';
- }
- }
-
- thisObj.fromHSVA(null, null, null, yVal);
- },
-
-
- createPalette : function () {
-
- var paletteObj = {
- elm: null,
- draw: null
- };
-
- var canvas = jsc.createEl('canvas');
- var ctx = canvas.getContext('2d');
-
- var drawFunc = function (width, height, type) {
- canvas.width = width;
- canvas.height = height;
-
- ctx.clearRect(0, 0, canvas.width, canvas.height);
-
- var hGrad = ctx.createLinearGradient(0, 0, canvas.width, 0);
- hGrad.addColorStop(0 / 6, '#F00');
- hGrad.addColorStop(1 / 6, '#FF0');
- hGrad.addColorStop(2 / 6, '#0F0');
- hGrad.addColorStop(3 / 6, '#0FF');
- hGrad.addColorStop(4 / 6, '#00F');
- hGrad.addColorStop(5 / 6, '#F0F');
- hGrad.addColorStop(6 / 6, '#F00');
-
- ctx.fillStyle = hGrad;
- ctx.fillRect(0, 0, canvas.width, canvas.height);
-
- var vGrad = ctx.createLinearGradient(0, 0, 0, canvas.height);
- switch (type.toLowerCase()) {
- case 's':
- vGrad.addColorStop(0, 'rgba(255,255,255,0)');
- vGrad.addColorStop(1, 'rgba(255,255,255,1)');
- break;
- case 'v':
- vGrad.addColorStop(0, 'rgba(0,0,0,0)');
- vGrad.addColorStop(1, 'rgba(0,0,0,1)');
- break;
- }
- ctx.fillStyle = vGrad;
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- };
-
- paletteObj.elm = canvas;
- paletteObj.draw = drawFunc;
-
- return paletteObj;
- },
-
-
- createSliderGradient : function () {
-
- var sliderObj = {
- elm: null,
- draw: null
- };
-
- var canvas = jsc.createEl('canvas');
- var ctx = canvas.getContext('2d');
-
- var drawFunc = function (width, height, color1, color2) {
- canvas.width = width;
- canvas.height = height;
-
- ctx.clearRect(0, 0, canvas.width, canvas.height);
-
- var grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
- grad.addColorStop(0, color1);
- grad.addColorStop(1, color2);
-
- ctx.fillStyle = grad;
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- };
-
- sliderObj.elm = canvas;
- sliderObj.draw = drawFunc;
-
- return sliderObj;
- },
-
-
- createASliderGradient : function () {
-
- var sliderObj = {
- elm: null,
- draw: null
- };
-
- var canvas = jsc.createEl('canvas');
- var ctx = canvas.getContext('2d');
-
- var drawFunc = function (width, height, color) {
- canvas.width = width;
- canvas.height = height;
-
- ctx.clearRect(0, 0, canvas.width, canvas.height);
-
- var sqSize = canvas.width / 2;
- var sqColor1 = jsc.pub.chessboardColor1;
- var sqColor2 = jsc.pub.chessboardColor2;
-
- // dark gray background
- ctx.fillStyle = sqColor1;
- ctx.fillRect(0, 0, canvas.width, canvas.height);
-
- for (var y = 0; y < canvas.height; y += sqSize * 2) {
- // light gray squares
- ctx.fillStyle = sqColor2;
- ctx.fillRect(0, y, sqSize, sqSize);
- ctx.fillRect(sqSize, y + sqSize, sqSize, sqSize);
- }
-
- var grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
- grad.addColorStop(0, color);
- grad.addColorStop(1, 'rgba(0,0,0,0)');
-
- ctx.fillStyle = grad;
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- };
-
- sliderObj.elm = canvas;
- sliderObj.draw = drawFunc;
-
- return sliderObj;
- },
-
-
- BoxShadow : (function () {
- var BoxShadow = function (hShadow, vShadow, blur, spread, color, inset) {
- this.hShadow = hShadow;
- this.vShadow = vShadow;
- this.blur = blur;
- this.spread = spread;
- this.color = color;
- this.inset = !!inset;
- };
-
- BoxShadow.prototype.toString = function () {
- var vals = [
- Math.round(this.hShadow) + 'px',
- Math.round(this.vShadow) + 'px',
- Math.round(this.blur) + 'px',
- Math.round(this.spread) + 'px',
- this.color
- ];
- if (this.inset) {
- vals.push('inset');
- }
- return vals.join(' ');
- };
-
- return BoxShadow;
- })(),
-
-
- flags : {
- leaveValue : 1 << 0,
- leaveAlpha : 1 << 1,
- leavePreview : 1 << 2,
- },
-
-
- enumOpts : {
- format: ['auto', 'any', 'hex', 'rgb', 'rgba'],
- previewPosition: ['left', 'right'],
- mode: ['hsv', 'hvs', 'hs', 'hv'],
- position: ['left', 'right', 'top', 'bottom'],
- alphaChannel: ['auto', true, false],
- },
-
-
- deprecatedOpts : {
- // : ( can be null)
- 'styleElement': 'previewElement',
- 'onFineChange': 'onInput',
- 'overwriteImportant': 'forceStyle',
- 'closable': 'closeButton',
- 'insetWidth': 'controlBorderWidth',
- 'insetColor': 'controlBorderColor',
- 'refine': null,
- },
-
-
- docsRef : ' ' + 'See https://jscolor.com/docs/',
-
-
- //
- // Usage:
- // var myPicker = new JSColor( [, ])
- //
- // (constructor is accessible via both 'jscolor' and 'JSColor' name)
- //
-
- pub : function (targetElement, opts) {
-
- var THIS = this;
-
- if (!opts) {
- opts = {};
- }
-
- this.channels = {
- r: 255, // red [0-255]
- g: 255, // green [0-255]
- b: 255, // blue [0-255]
- h: 0, // hue [0-360]
- s: 0, // saturation [0-100]
- v: 100, // value (brightness) [0-100]
- a: 1.0, // alpha (opacity) [0.0 - 1.0]
- };
-
- // General options
- //
- this.format = 'auto'; // 'auto' | 'any' | 'hex' | 'rgb' | 'rgba' - Format of the input/output value
- this.value = undefined; // INITIAL color value in any supported format. To change it later, use method fromString(), fromHSVA(), fromRGBA() or channel()
- this.alpha = undefined; // INITIAL alpha value. To change it later, call method channel('A', )
- this.onChange = undefined; // called when color changes. Value can be either a function or a string with JS code.
- this.onInput = undefined; // called repeatedly as the color is being changed, e.g. while dragging a slider. Value can be either a function or a string with JS code.
- this.valueElement = undefined; // element that will be used to display and input the color value
- this.alphaElement = undefined; // element that will be used to display and input the alpha (opacity) value
- this.previewElement = undefined; // element that will preview the picked color using CSS background
- this.previewPosition = 'left'; // 'left' | 'right' - position of the color preview in previewElement
- this.previewSize = 32; // (px) width of the color preview displayed in previewElement
- this.previewPadding = 8; // (px) space between color preview and content of the previewElement
- this.required = true; // whether the associated text input must always contain a color value. If false, the input can be left empty.
- this.hash = true; // whether to prefix the HEX color code with # symbol (only applicable for HEX format)
- this.uppercase = true; // whether to show the HEX color code in upper case (only applicable for HEX format)
- this.forceStyle = true; // whether to overwrite CSS style of the previewElement using !important flag
-
- // Color Picker options
- //
- this.width = 181; // width of color palette (in px)
- this.height = 101; // height of color palette (in px)
- this.mode = 'HSV'; // 'HSV' | 'HVS' | 'HS' | 'HV' - layout of the color picker controls
- this.alphaChannel = 'auto'; // 'auto' | true | false - if alpha channel is enabled, the alpha slider will be visible. If 'auto', it will be determined according to color format
- this.position = 'bottom'; // 'left' | 'right' | 'top' | 'bottom' - position relative to the target element
- this.smartPosition = true; // automatically change picker position when there is not enough space for it
- this.showOnClick = true; // whether to show the picker when user clicks its target element
- this.hideOnLeave = true; // whether to automatically hide the picker when user leaves its target element (e.g. upon clicking the document)
- this.sliderSize = 16; // px
- this.crossSize = 8; // px
- this.closeButton = false; // whether to display the Close button
- this.closeText = 'Close';
- this.buttonColor = 'rgba(0,0,0,1)'; // CSS color
- this.buttonHeight = 18; // px
- this.padding = 12; // px
- this.backgroundColor = 'rgba(255,255,255,1)'; // CSS color
- this.borderWidth = 1; // px
- this.borderColor = 'rgba(187,187,187,1)'; // CSS color
- this.borderRadius = 8; // px
- this.controlBorderWidth = 1; // px
- this.controlBorderColor = 'rgba(187,187,187,1)'; // CSS color
- this.shadow = true; // whether to display a shadow
- this.shadowBlur = 15; // px
- this.shadowColor = 'rgba(0,0,0,0.2)'; // CSS color
- this.pointerColor = 'rgba(76,76,76,1)'; // CSS color
- this.pointerBorderWidth = 1; // px
- this.pointerBorderColor = 'rgba(255,255,255,1)'; // CSS color
- this.pointerThickness = 2; // px
- this.zIndex = 5000;
- this.container = undefined; // where to append the color picker (BODY element by default)
-
- // Experimental
- //
- this.minS = 0; // min allowed saturation (0 - 100)
- this.maxS = 100; // max allowed saturation (0 - 100)
- this.minV = 0; // min allowed value (brightness) (0 - 100)
- this.maxV = 100; // max allowed value (brightness) (0 - 100)
- this.minA = 0.0; // min allowed alpha (opacity) (0.0 - 1.0)
- this.maxA = 1.0; // max allowed alpha (opacity) (0.0 - 1.0)
-
-
- // let's process the DEPRECATED 'options' property (this will be later removed)
- if (jsc.pub.options) {
- // let's set custom default options, if specified
- for (var opt in jsc.pub.options) {
- if (jsc.pub.options.hasOwnProperty(opt)) {
- try {
- setOption(opt, jsc.pub.options[opt]);
- } catch (e) {
- console.warn(e);
- }
- }
- }
- }
-
-
- // let's apply configuration presets
- //
- var presetsArr = [];
-
- if (opts.preset) {
- if (typeof opts.preset === 'string') {
- presetsArr = opts.preset.split(/\s+/);
- } else if (Array.isArray(opts.preset)) {
- presetsArr = opts.preset.slice(); // slice() to clone
- } else {
- console.warn('Unrecognized preset value');
- }
- }
-
- // always use the 'default' preset. If it's not listed, append it to the end.
- if (presetsArr.indexOf('default') === -1) {
- presetsArr.push('default');
- }
-
- // let's apply the presets in reverse order, so that should there be any overlapping options,
- // the formerly listed preset will override the latter
- for (var i = presetsArr.length - 1; i >= 0; i -= 1) {
- var pres = presetsArr[i];
- if (!pres) {
- continue; // preset is empty string
- }
- if (!jsc.pub.presets.hasOwnProperty(pres)) {
- console.warn('Unknown preset: %s', pres);
- continue;
- }
- for (var opt in jsc.pub.presets[pres]) {
- if (jsc.pub.presets[pres].hasOwnProperty(opt)) {
- try {
- setOption(opt, jsc.pub.presets[pres][opt]);
- } catch (e) {
- console.warn(e);
- }
- }
- }
- }
-
-
- // let's set specific options for this color picker
- var nonProperties = [
- // these options won't be set as instance properties
- 'preset',
- ];
- for (var opt in opts) {
- if (opts.hasOwnProperty(opt)) {
- if (nonProperties.indexOf(opt) === -1) {
- try {
- setOption(opt, opts[opt]);
- } catch (e) {
- console.warn(e);
- }
- }
- }
- }
-
-
- // Getter: option(name)
- // Setter: option(name, value)
- // option({name:value, ...})
- //
- this.option = function () {
- if (!arguments.length) {
- throw new Error('No option specified');
- }
-
- if (arguments.length === 1 && typeof arguments[0] === 'string') {
- // getting a single option
- try {
- return getOption(arguments[0]);
- } catch (e) {
- console.warn(e);
- }
- return false;
-
- } else if (arguments.length >= 2 && typeof arguments[0] === 'string') {
- // setting a single option
- try {
- if (!setOption(arguments[0], arguments[1])) {
- return false;
- }
- } catch (e) {
- console.warn(e);
- return false;
- }
- this.redraw(); // immediately redraws the picker, if it's displayed
- this.exposeColor(); // in case some preview-related or format-related option was changed
- return true;
-
- } else if (arguments.length === 1 && typeof arguments[0] === 'object') {
- // setting multiple options
- var opts = arguments[0];
- var success = true;
- for (var opt in opts) {
- if (opts.hasOwnProperty(opt)) {
- try {
- if (!setOption(opt, opts[opt])) {
- success = false;
- }
- } catch (e) {
- console.warn(e);
- success = false;
- }
- }
- }
- this.redraw(); // immediately redraws the picker, if it's displayed
- this.exposeColor(); // in case some preview-related or format-related option was changed
- return success;
- }
-
- throw new Error('Invalid arguments');
- }
-
-
- // Getter: channel(name)
- // Setter: channel(name, value)
- //
- this.channel = function (name, value) {
- if (typeof name !== 'string') {
- throw new Error('Invalid value for channel name: ' + name);
- }
-
- if (value === undefined) {
- // getting channel value
- if (!this.channels.hasOwnProperty(name.toLowerCase())) {
- console.warn('Getting unknown channel: ' + name);
- return false;
- }
- return this.channels[name.toLowerCase()];
-
- } else {
- // setting channel value
- var res = false;
- switch (name.toLowerCase()) {
- case 'r': res = this.fromRGBA(value, null, null, null); break;
- case 'g': res = this.fromRGBA(null, value, null, null); break;
- case 'b': res = this.fromRGBA(null, null, value, null); break;
- case 'h': res = this.fromHSVA(value, null, null, null); break;
- case 's': res = this.fromHSVA(null, value, null, null); break;
- case 'v': res = this.fromHSVA(null, null, value, null); break;
- case 'a': res = this.fromHSVA(null, null, null, value); break;
- default:
- console.warn('Setting unknown channel: ' + name);
- return false;
- }
- if (res) {
- this.redraw(); // immediately redraws the picker, if it's displayed
- return true;
- }
- }
-
- return false;
- }
-
-
- // Triggers given input event(s) by:
- // - executing on callback specified as picker's option
- // - triggering standard DOM event listeners attached to the value element
- //
- // It is possible to specify multiple events separated with a space.
- //
- this.trigger = function (eventNames) {
- var evs = jsc.strList(eventNames);
- for (var i = 0; i < evs.length; i += 1) {
- var ev = evs[i].toLowerCase();
-
- // trigger a callback
- var callbackProp = null;
- switch (ev) {
- case 'input': callbackProp = 'onInput'; break;
- case 'change': callbackProp = 'onChange'; break;
- }
- if (callbackProp) {
- jsc.triggerCallback(this, callbackProp);
- }
-
- // trigger standard DOM event listeners on the value element
- jsc.triggerInputEvent(this.valueElement, ev, true, true);
- }
- };
-
-
- // h: 0-360
- // s: 0-100
- // v: 0-100
- // a: 0.0-1.0
- //
- this.fromHSVA = function (h, s, v, a, flags) { // null = don't change
- if (h === undefined) { h = null; }
- if (s === undefined) { s = null; }
- if (v === undefined) { v = null; }
- if (a === undefined) { a = null; }
-
- if (h !== null) {
- if (isNaN(h)) { return false; }
- this.channels.h = Math.max(0, Math.min(360, h));
- }
- if (s !== null) {
- if (isNaN(s)) { return false; }
- this.channels.s = Math.max(0, Math.min(100, this.maxS, s), this.minS);
- }
- if (v !== null) {
- if (isNaN(v)) { return false; }
- this.channels.v = Math.max(0, Math.min(100, this.maxV, v), this.minV);
- }
- if (a !== null) {
- if (isNaN(a)) { return false; }
- this.channels.a = this.hasAlphaChannel() ?
- Math.max(0, Math.min(1, this.maxA, a), this.minA) :
- 1.0; // if alpha channel is disabled, the color should stay 100% opaque
- }
-
- var rgb = jsc.HSV_RGB(
- this.channels.h,
- this.channels.s,
- this.channels.v
- );
- this.channels.r = rgb[0];
- this.channels.g = rgb[1];
- this.channels.b = rgb[2];
-
- this.exposeColor(flags);
- return true;
- };
-
-
- // r: 0-255
- // g: 0-255
- // b: 0-255
- // a: 0.0-1.0
- //
- this.fromRGBA = function (r, g, b, a, flags) { // null = don't change
- if (r === undefined) { r = null; }
- if (g === undefined) { g = null; }
- if (b === undefined) { b = null; }
- if (a === undefined) { a = null; }
-
- if (r !== null) {
- if (isNaN(r)) { return false; }
- r = Math.max(0, Math.min(255, r));
- }
- if (g !== null) {
- if (isNaN(g)) { return false; }
- g = Math.max(0, Math.min(255, g));
- }
- if (b !== null) {
- if (isNaN(b)) { return false; }
- b = Math.max(0, Math.min(255, b));
- }
- if (a !== null) {
- if (isNaN(a)) { return false; }
- this.channels.a = this.hasAlphaChannel() ?
- Math.max(0, Math.min(1, this.maxA, a), this.minA) :
- 1.0; // if alpha channel is disabled, the color should stay 100% opaque
- }
-
- var hsv = jsc.RGB_HSV(
- r===null ? this.channels.r : r,
- g===null ? this.channels.g : g,
- b===null ? this.channels.b : b
- );
- if (hsv[0] !== null) {
- this.channels.h = Math.max(0, Math.min(360, hsv[0]));
- }
- if (hsv[2] !== 0) { // fully black color stays black through entire saturation range, so let's not change saturation
- this.channels.s = Math.max(0, this.minS, Math.min(100, this.maxS, hsv[1]));
- }
- this.channels.v = Math.max(0, this.minV, Math.min(100, this.maxV, hsv[2]));
-
- // update RGB according to final HSV, as some values might be trimmed
- var rgb = jsc.HSV_RGB(this.channels.h, this.channels.s, this.channels.v);
- this.channels.r = rgb[0];
- this.channels.g = rgb[1];
- this.channels.b = rgb[2];
-
- this.exposeColor(flags);
- return true;
- };
-
-
- // DEPRECATED. Use .fromHSVA() instead
- //
- this.fromHSV = function (h, s, v, flags) {
- console.warn('fromHSV() method is DEPRECATED. Using fromHSVA() instead.' + jsc.docsRef);
- return this.fromHSVA(h, s, v, null, flags);
- };
-
-
- // DEPRECATED. Use .fromRGBA() instead
- //
- this.fromRGB = function (r, g, b, flags) {
- console.warn('fromRGB() method is DEPRECATED. Using fromRGBA() instead.' + jsc.docsRef);
- return this.fromRGBA(r, g, b, null, flags);
- };
-
-
- this.fromString = function (str, flags) {
- if (!this.required && str.trim() === '') {
- // setting empty string to an optional color input
- this.setPreviewElementBg(null);
- this.setValueElementValue('');
- return true;
- }
-
- var color = jsc.parseColorString(str);
- if (!color) {
- return false; // could not parse
- }
- if (this.format.toLowerCase() === 'any') {
- this._currentFormat = color.format; // adapt format
- if (this.getFormat() !== 'rgba') {
- color.rgba[3] = 1.0; // when switching to a format that doesn't support alpha, set full opacity
- }
- this.redraw(); // to show/hide the alpha slider according to current format
- }
- this.fromRGBA(
- color.rgba[0],
- color.rgba[1],
- color.rgba[2],
- color.rgba[3],
- flags
- );
- return true;
- };
-
-
- this.toString = function (format) {
- if (format === undefined) {
- format = this.getFormat(); // format not specified -> use the current format
- }
- switch (format.toLowerCase()) {
- case 'hex': return this.toHEXString(); break;
- case 'rgb': return this.toRGBString(); break;
- case 'rgba': return this.toRGBAString(); break;
- }
- return false;
- };
-
-
- this.toHEXString = function () {
- return '#' + (
- ('0' + Math.round(this.channels.r).toString(16)).substr(-2) +
- ('0' + Math.round(this.channels.g).toString(16)).substr(-2) +
- ('0' + Math.round(this.channels.b).toString(16)).substr(-2)
- ).toUpperCase();
- };
-
-
- this.toRGBString = function () {
- return ('rgb(' +
- Math.round(this.channels.r) + ',' +
- Math.round(this.channels.g) + ',' +
- Math.round(this.channels.b) +
- ')');
- };
-
-
- this.toRGBAString = function () {
- return ('rgba(' +
- Math.round(this.channels.r) + ',' +
- Math.round(this.channels.g) + ',' +
- Math.round(this.channels.b) + ',' +
- (Math.round(this.channels.a * 100) / 100) +
- ')');
- };
-
-
- this.toGrayscale = function () {
- return (
- 0.213 * this.channels.r +
- 0.715 * this.channels.g +
- 0.072 * this.channels.b
- );
- };
-
-
- this.toCanvas = function () {
- return jsc.genColorPreviewCanvas(this.toRGBAString()).canvas;
- };
-
-
- this.toDataURL = function () {
- return this.toCanvas().toDataURL();
- };
-
-
- this.toBackground = function () {
- return jsc.pub.background(this.toRGBAString());
- };
-
-
- this.isLight = function () {
- return this.toGrayscale() > 255 / 2;
- };
-
-
- this.hide = function () {
- if (isPickerOwner()) {
- detachPicker();
- }
- };
-
-
- this.show = function () {
- drawPicker();
- };
-
-
- this.redraw = function () {
- if (isPickerOwner()) {
- drawPicker();
- }
- };
-
-
- this.getFormat = function () {
- return this._currentFormat;
- };
-
-
- this.hasAlphaChannel = function () {
- if (this.alphaChannel === 'auto') {
- return (
- this.format.toLowerCase() === 'any' || // format can change on the fly (e.g. from hex to rgba), so let's consider the alpha channel enabled
- this.getFormat() === 'rgba' || // the current format supports alpha channel
- this.alpha !== undefined || // initial alpha value is set, so we're working with alpha channel
- this.alphaElement !== undefined // the alpha value is redirected, so we're working with alpha channel
- );
- }
-
- return this.alphaChannel; // the alpha channel is explicitly set
- };
-
-
- this.processValueInput = function (str) {
- if (!this.fromString(str)) {
- // could not parse the color value - let's just expose the current color
- this.exposeColor();
- }
- };
-
-
- this.processAlphaInput = function (str) {
- if (!this.fromHSVA(null, null, null, parseFloat(str))) {
- // could not parse the alpha value - let's just expose the current color
- this.exposeColor();
- }
- };
-
-
- this.exposeColor = function (flags) {
-
- if (!(flags & jsc.flags.leaveValue) && this.valueElement) {
- var value = this.toString();
-
- if (this.getFormat() === 'hex') {
- if (!this.uppercase) { value = value.toLowerCase(); }
- if (!this.hash) { value = value.replace(/^#/, ''); }
- }
-
- this.setValueElementValue(value);
- }
-
- if (!(flags & jsc.flags.leaveAlpha) && this.alphaElement) {
- var value = Math.round(this.channels.a * 100) / 100;
- this.setAlphaElementValue(value);
- }
-
- if (!(flags & jsc.flags.leavePreview) && this.previewElement) {
- var previewPos = null; // 'left' | 'right' (null -> fill the entire element)
-
- if (
- jsc.isTextInput(this.previewElement) || // text input
- (jsc.isButton(this.previewElement) && !jsc.isButtonEmpty(this.previewElement)) // button with text
- ) {
- previewPos = this.previewPosition;
- }
-
- this.setPreviewElementBg(this.toRGBAString());
- }
-
- if (isPickerOwner()) {
- redrawPad();
- redrawSld();
- redrawASld();
- }
- };
-
-
- this.setPreviewElementBg = function (color) {
- if (!this.previewElement) {
- return;
- }
-
- var position = null; // color preview position: null | 'left' | 'right'
- var width = null; // color preview width: px | null = fill the entire element
- if (
- jsc.isTextInput(this.previewElement) || // text input
- (jsc.isButton(this.previewElement) && !jsc.isButtonEmpty(this.previewElement)) // button with text
- ) {
- position = this.previewPosition;
- width = this.previewSize;
- }
-
- var backgrounds = [];
-
- if (!color) {
- // there is no color preview to display -> let's remove any previous background image
- backgrounds.push({
- image: 'none',
- position: 'left top',
- size: 'auto',
- repeat: 'no-repeat',
- origin: 'padding-box',
- });
- } else {
- // CSS gradient for background color preview
- backgrounds.push({
- image: jsc.genColorPreviewGradient(
- color,
- position,
- width ? width - jsc.pub.previewSeparator.length : null
- ),
- position: 'left top',
- size: 'auto',
- repeat: position ? 'repeat-y' : 'repeat',
- origin: 'padding-box',
- });
-
- // data URL of generated PNG image with a gray transparency chessboard
- var preview = jsc.genColorPreviewCanvas(
- 'rgba(0,0,0,0)',
- position ? {'left':'right', 'right':'left'}[position] : null,
- width,
- true
- );
- backgrounds.push({
- image: 'url(\'' + preview.canvas.toDataURL() + '\')',
- position: (position || 'left') + ' top',
- size: preview.width + 'px ' + preview.height + 'px',
- repeat: position ? 'repeat-y' : 'repeat',
- origin: 'padding-box',
- });
- }
-
- var bg = {
- image: [],
- position: [],
- size: [],
- repeat: [],
- origin: [],
- };
- for (var i = 0; i < backgrounds.length; i += 1) {
- bg.image.push(backgrounds[i].image);
- bg.position.push(backgrounds[i].position);
- bg.size.push(backgrounds[i].size);
- bg.repeat.push(backgrounds[i].repeat);
- bg.origin.push(backgrounds[i].origin);
- }
-
- // set previewElement's background-images
- var sty = {
- 'background-image': bg.image.join(', '),
- 'background-position': bg.position.join(', '),
- 'background-size': bg.size.join(', '),
- 'background-repeat': bg.repeat.join(', '),
- 'background-origin': bg.origin.join(', '),
- };
- jsc.setStyle(this.previewElement, sty, this.forceStyle);
-
-
- // set/restore previewElement's padding
- var padding = {
- left: null,
- right: null,
- };
- if (position) {
- padding[position] = (this.previewSize + this.previewPadding) + 'px';
- }
-
- var sty = {
- 'padding-left': padding.left,
- 'padding-right': padding.right,
- };
- jsc.setStyle(this.previewElement, sty, this.forceStyle, true);
- };
-
-
- this.setValueElementValue = function (str) {
- if (this.valueElement) {
- if (jsc.nodeName(this.valueElement) === 'input') {
- this.valueElement.value = str;
- } else {
- this.valueElement.innerHTML = str;
- }
- }
- };
-
-
- this.setAlphaElementValue = function (str) {
- if (this.alphaElement) {
- if (jsc.nodeName(this.alphaElement) === 'input') {
- this.alphaElement.value = str;
- } else {
- this.alphaElement.innerHTML = str;
- }
- }
- };
-
-
- this._processParentElementsInDOM = function () {
- if (this._linkedElementsProcessed) { return; }
- this._linkedElementsProcessed = true;
-
- var elm = this.targetElement;
- do {
- // If the target element or one of its parent nodes has fixed position,
- // then use fixed positioning instead
- var compStyle = jsc.getCompStyle(elm);
- if (compStyle.position && compStyle.position.toLowerCase() === 'fixed') {
- this.fixed = true;
- }
-
- if (elm !== this.targetElement) {
- // Ensure to attach onParentScroll only once to each parent element
- // (multiple targetElements can share the same parent nodes)
- //
- // Note: It's not just offsetParents that can be scrollable,
- // that's why we loop through all parent nodes
- if (!jsc.getData(elm, 'hasScrollListener')) {
- elm.addEventListener('scroll', jsc.onParentScroll, false);
- jsc.setData(elm, 'hasScrollListener', true);
- }
- }
- } while ((elm = elm.parentNode) && jsc.nodeName(elm) !== 'body');
- };
-
-
- this.tryHide = function () {
- if (this.hideOnLeave) {
- this.hide();
- }
- };
-
-
- function setOption (option, value) {
- if (typeof option !== 'string') {
- throw new Error('Invalid value for option name: ' + option);
- }
-
- // enum option
- if (jsc.enumOpts.hasOwnProperty(option)) {
- if (typeof value === 'string') { // enum string values are case insensitive
- value = value.toLowerCase();
- }
- if (jsc.enumOpts[option].indexOf(value) === -1) {
- throw new Error('Option \'' + option + '\' has invalid value: ' + value);
- }
- }
-
- // deprecated option
- if (jsc.deprecatedOpts.hasOwnProperty(option)) {
- var oldOpt = option;
- var newOpt = jsc.deprecatedOpts[option];
- if (newOpt) {
- // if we have a new name for this option, let's log a warning and use the new name
- console.warn('Option \'%s\' is DEPRECATED, using \'%s\' instead.' + jsc.docsRef, oldOpt, newOpt);
- option = newOpt;
- } else {
- // new name not available for the option
- throw new Error('Option \'' + option + '\' is DEPRECATED');
- }
- }
-
- if (!(option in THIS)) {
- throw new Error('Unrecognized configuration option: ' + option);
- }
-
- THIS[option] = value;
- return true;
- }
-
-
- function getOption (option) {
- // deprecated option
- if (jsc.deprecatedOpts.hasOwnProperty(option)) {
- var oldOpt = option;
- var newOpt = jsc.deprecatedOpts[option];
- if (newOpt) {
- // if we have a new name for this option, let's log a warning and use the new name
- console.warn('Option \'%s\' is DEPRECATED, using \'%s\' instead.' + jsc.docsRef, oldOpt, newOpt);
- option = newOpt;
- } else {
- // new name not available for the option
- throw new Error('Option \'' + option + '\' is DEPRECATED');
- }
- }
-
- if (!(option in THIS)) {
- throw new Error('Unrecognized configuration option: ' + option);
- }
-
- return THIS[option];
- }
-
-
- function detachPicker () {
- jsc.removeClass(THIS.targetElement, jsc.pub.activeClassName);
- jsc.picker.wrap.parentNode.removeChild(jsc.picker.wrap);
- delete jsc.picker.owner;
- }
-
-
- function drawPicker () {
-
- // At this point, when drawing the picker, we know what the parent elements are
- // and we can do all related DOM operations, such as registering events on them
- // or checking their positioning
- THIS._processParentElementsInDOM();
-
- if (!jsc.picker) {
- jsc.picker = {
- owner: null, // owner picker instance
- wrap : jsc.createEl('div'),
- box : jsc.createEl('div'),
- boxS : jsc.createEl('div'), // shadow area
- boxB : jsc.createEl('div'), // border
- pad : jsc.createEl('div'),
- padB : jsc.createEl('div'), // border
- padM : jsc.createEl('div'), // mouse/touch area
- padPal : jsc.createPalette(),
- cross : jsc.createEl('div'),
- crossBY : jsc.createEl('div'), // border Y
- crossBX : jsc.createEl('div'), // border X
- crossLY : jsc.createEl('div'), // line Y
- crossLX : jsc.createEl('div'), // line X
- sld : jsc.createEl('div'), // slider
- sldB : jsc.createEl('div'), // border
- sldM : jsc.createEl('div'), // mouse/touch area
- sldGrad : jsc.createSliderGradient(),
- sldPtrS : jsc.createEl('div'), // slider pointer spacer
- sldPtrIB : jsc.createEl('div'), // slider pointer inner border
- sldPtrMB : jsc.createEl('div'), // slider pointer middle border
- sldPtrOB : jsc.createEl('div'), // slider pointer outer border
- asld : jsc.createEl('div'), // alpha slider
- asldB : jsc.createEl('div'), // border
- asldM : jsc.createEl('div'), // mouse/touch area
- asldGrad : jsc.createASliderGradient(),
- asldPtrS : jsc.createEl('div'), // slider pointer spacer
- asldPtrIB : jsc.createEl('div'), // slider pointer inner border
- asldPtrMB : jsc.createEl('div'), // slider pointer middle border
- asldPtrOB : jsc.createEl('div'), // slider pointer outer border
- btn : jsc.createEl('div'),
- btnT : jsc.createEl('span'), // text
- };
-
- jsc.picker.pad.appendChild(jsc.picker.padPal.elm);
- jsc.picker.padB.appendChild(jsc.picker.pad);
- jsc.picker.cross.appendChild(jsc.picker.crossBY);
- jsc.picker.cross.appendChild(jsc.picker.crossBX);
- jsc.picker.cross.appendChild(jsc.picker.crossLY);
- jsc.picker.cross.appendChild(jsc.picker.crossLX);
- jsc.picker.padB.appendChild(jsc.picker.cross);
- jsc.picker.box.appendChild(jsc.picker.padB);
- jsc.picker.box.appendChild(jsc.picker.padM);
-
- jsc.picker.sld.appendChild(jsc.picker.sldGrad.elm);
- jsc.picker.sldB.appendChild(jsc.picker.sld);
- jsc.picker.sldB.appendChild(jsc.picker.sldPtrOB);
- jsc.picker.sldPtrOB.appendChild(jsc.picker.sldPtrMB);
- jsc.picker.sldPtrMB.appendChild(jsc.picker.sldPtrIB);
- jsc.picker.sldPtrIB.appendChild(jsc.picker.sldPtrS);
- jsc.picker.box.appendChild(jsc.picker.sldB);
- jsc.picker.box.appendChild(jsc.picker.sldM);
-
- jsc.picker.asld.appendChild(jsc.picker.asldGrad.elm);
- jsc.picker.asldB.appendChild(jsc.picker.asld);
- jsc.picker.asldB.appendChild(jsc.picker.asldPtrOB);
- jsc.picker.asldPtrOB.appendChild(jsc.picker.asldPtrMB);
- jsc.picker.asldPtrMB.appendChild(jsc.picker.asldPtrIB);
- jsc.picker.asldPtrIB.appendChild(jsc.picker.asldPtrS);
- jsc.picker.box.appendChild(jsc.picker.asldB);
- jsc.picker.box.appendChild(jsc.picker.asldM);
-
- jsc.picker.btn.appendChild(jsc.picker.btnT);
- jsc.picker.box.appendChild(jsc.picker.btn);
-
- jsc.picker.boxB.appendChild(jsc.picker.box);
- jsc.picker.wrap.appendChild(jsc.picker.boxS);
- jsc.picker.wrap.appendChild(jsc.picker.boxB);
-
- jsc.picker.wrap.addEventListener('touchstart', jsc.onPickerTouchStart,
- jsc.isPassiveEventSupported ? {passive: false} : false);
- }
-
- var p = jsc.picker;
-
- var displaySlider = !!jsc.getSliderChannel(THIS);
- var displayAlphaSlider = THIS.hasAlphaChannel();
- var dims = jsc.getPickerDims(THIS);
- var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize);
- var controlPadding = jsc.getControlPadding(THIS);
- var borderRadius = Math.min(
- THIS.borderRadius,
- Math.round(THIS.padding * Math.PI)); // px
- var padCursor = 'crosshair';
-
- // wrap
- p.wrap.className = 'jscolor-picker-wrap';
- p.wrap.style.clear = 'both';
- p.wrap.style.width = (dims[0] + 2 * THIS.borderWidth) + 'px';
- p.wrap.style.height = (dims[1] + 2 * THIS.borderWidth) + 'px';
- p.wrap.style.zIndex = THIS.zIndex;
-
- // picker
- p.box.className = 'jscolor-picker';
- p.box.style.width = dims[0] + 'px';
- p.box.style.height = dims[1] + 'px';
- p.box.style.position = 'relative';
-
- // picker shadow
- p.boxS.className = 'jscolor-picker-shadow';
- p.boxS.style.position = 'absolute';
- p.boxS.style.left = '0';
- p.boxS.style.top = '0';
- p.boxS.style.width = '100%';
- p.boxS.style.height = '100%';
- jsc.setBorderRadius(p.boxS, borderRadius + 'px');
-
- // picker border
- p.boxB.className = 'jscolor-picker-border';
- p.boxB.style.position = 'relative';
- p.boxB.style.border = THIS.borderWidth + 'px solid';
- p.boxB.style.borderColor = THIS.borderColor;
- p.boxB.style.background = THIS.backgroundColor;
- jsc.setBorderRadius(p.boxB, borderRadius + 'px');
-
- // IE hack:
- // If the element is transparent, IE will trigger the event on the elements under it,
- // e.g. on Canvas or on elements with border
- p.padM.style.background = 'rgba(255,0,0,.2)';
- p.sldM.style.background = 'rgba(0,255,0,.2)';
- p.asldM.style.background = 'rgba(0,0,255,.2)';
-
- p.padM.style.opacity =
- p.sldM.style.opacity =
- p.asldM.style.opacity =
- '0';
-
- // pad
- p.pad.style.position = 'relative';
- p.pad.style.width = THIS.width + 'px';
- p.pad.style.height = THIS.height + 'px';
-
- // pad palettes (HSV and HVS)
- p.padPal.draw(THIS.width, THIS.height, jsc.getPadYChannel(THIS));
-
- // pad border
- p.padB.style.position = 'absolute';
- p.padB.style.left = THIS.padding + 'px';
- p.padB.style.top = THIS.padding + 'px';
- p.padB.style.border = THIS.controlBorderWidth + 'px solid';
- p.padB.style.borderColor = THIS.controlBorderColor;
-
- // pad mouse area
- p.padM.style.position = 'absolute';
- p.padM.style.left = 0 + 'px';
- p.padM.style.top = 0 + 'px';
- p.padM.style.width = (THIS.padding + 2 * THIS.controlBorderWidth + THIS.width + controlPadding) + 'px';
- p.padM.style.height = (2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height) + 'px';
- p.padM.style.cursor = padCursor;
- jsc.setData(p.padM, {
- instance: THIS,
- control: 'pad',
- })
-
- // pad cross
- p.cross.style.position = 'absolute';
- p.cross.style.left =
- p.cross.style.top =
- '0';
- p.cross.style.width =
- p.cross.style.height =
- crossOuterSize + 'px';
-
- // pad cross border Y and X
- p.crossBY.style.position =
- p.crossBX.style.position =
- 'absolute';
- p.crossBY.style.background =
- p.crossBX.style.background =
- THIS.pointerBorderColor;
- p.crossBY.style.width =
- p.crossBX.style.height =
- (2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px';
- p.crossBY.style.height =
- p.crossBX.style.width =
- crossOuterSize + 'px';
- p.crossBY.style.left =
- p.crossBX.style.top =
- (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2) - THIS.pointerBorderWidth) + 'px';
- p.crossBY.style.top =
- p.crossBX.style.left =
- '0';
-
- // pad cross line Y and X
- p.crossLY.style.position =
- p.crossLX.style.position =
- 'absolute';
- p.crossLY.style.background =
- p.crossLX.style.background =
- THIS.pointerColor;
- p.crossLY.style.height =
- p.crossLX.style.width =
- (crossOuterSize - 2 * THIS.pointerBorderWidth) + 'px';
- p.crossLY.style.width =
- p.crossLX.style.height =
- THIS.pointerThickness + 'px';
- p.crossLY.style.left =
- p.crossLX.style.top =
- (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2)) + 'px';
- p.crossLY.style.top =
- p.crossLX.style.left =
- THIS.pointerBorderWidth + 'px';
-
-
- // slider
- p.sld.style.overflow = 'hidden';
- p.sld.style.width = THIS.sliderSize + 'px';
- p.sld.style.height = THIS.height + 'px';
-
- // slider gradient
- p.sldGrad.draw(THIS.sliderSize, THIS.height, '#000', '#000');
-
- // slider border
- p.sldB.style.display = displaySlider ? 'block' : 'none';
- p.sldB.style.position = 'absolute';
- p.sldB.style.left = (THIS.padding + THIS.width + 2 * THIS.controlBorderWidth + 2 * controlPadding) + 'px';
- p.sldB.style.top = THIS.padding + 'px';
- p.sldB.style.border = THIS.controlBorderWidth + 'px solid';
- p.sldB.style.borderColor = THIS.controlBorderColor;
-
- // slider mouse area
- p.sldM.style.display = displaySlider ? 'block' : 'none';
- p.sldM.style.position = 'absolute';
- p.sldM.style.left = (THIS.padding + THIS.width + 2 * THIS.controlBorderWidth + controlPadding) + 'px';
- p.sldM.style.top = 0 + 'px';
- p.sldM.style.width = (
- (THIS.sliderSize + 2 * controlPadding + 2 * THIS.controlBorderWidth) +
- (displayAlphaSlider ? 0 : Math.max(0, THIS.padding - controlPadding)) // remaining padding to the right edge
- ) + 'px';
- p.sldM.style.height = (2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height) + 'px';
- p.sldM.style.cursor = 'default';
- jsc.setData(p.sldM, {
- instance: THIS,
- control: 'sld',
- })
-
- // slider pointer inner and outer border
- p.sldPtrIB.style.border =
- p.sldPtrOB.style.border =
- THIS.pointerBorderWidth + 'px solid ' + THIS.pointerBorderColor;
-
- // slider pointer outer border
- p.sldPtrOB.style.position = 'absolute';
- p.sldPtrOB.style.left = -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px';
- p.sldPtrOB.style.top = '0';
-
- // slider pointer middle border
- p.sldPtrMB.style.border = THIS.pointerThickness + 'px solid ' + THIS.pointerColor;
-
- // slider pointer spacer
- p.sldPtrS.style.width = THIS.sliderSize + 'px';
- p.sldPtrS.style.height = jsc.pub.sliderInnerSpace + 'px';
-
-
- // alpha slider
- p.asld.style.overflow = 'hidden';
- p.asld.style.width = THIS.sliderSize + 'px';
- p.asld.style.height = THIS.height + 'px';
-
- // alpha slider gradient
- p.asldGrad.draw(THIS.sliderSize, THIS.height, '#000');
-
- // alpha slider border
- p.asldB.style.display = displayAlphaSlider ? 'block' : 'none';
- p.asldB.style.position = 'absolute';
- p.asldB.style.left = (
- (THIS.padding + THIS.width + 2 * THIS.controlBorderWidth + controlPadding) +
- (displaySlider ? (THIS.sliderSize + 3 * controlPadding + 2 * THIS.controlBorderWidth) : 0)
- ) + 'px';
- p.asldB.style.top = THIS.padding + 'px';
- p.asldB.style.border = THIS.controlBorderWidth + 'px solid';
- p.asldB.style.borderColor = THIS.controlBorderColor;
-
- // alpha slider mouse area
- p.asldM.style.display = displayAlphaSlider ? 'block' : 'none';
- p.asldM.style.position = 'absolute';
- p.asldM.style.left = (
- (THIS.padding + THIS.width + 2 * THIS.controlBorderWidth + controlPadding) +
- (displaySlider ? (THIS.sliderSize + 2 * controlPadding + 2 * THIS.controlBorderWidth) : 0)
- ) + 'px';
- p.asldM.style.top = 0 + 'px';
- p.asldM.style.width = (
- (THIS.sliderSize + 2 * controlPadding + 2 * THIS.controlBorderWidth) +
- Math.max(0, THIS.padding - controlPadding) // remaining padding to the right edge
- ) + 'px';
- p.asldM.style.height = (2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height) + 'px';
- p.asldM.style.cursor = 'default';
- jsc.setData(p.asldM, {
- instance: THIS,
- control: 'asld',
- })
-
- // alpha slider pointer inner and outer border
- p.asldPtrIB.style.border =
- p.asldPtrOB.style.border =
- THIS.pointerBorderWidth + 'px solid ' + THIS.pointerBorderColor;
-
- // alpha slider pointer outer border
- p.asldPtrOB.style.position = 'absolute';
- p.asldPtrOB.style.left = -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px';
- p.asldPtrOB.style.top = '0';
-
- // alpha slider pointer middle border
- p.asldPtrMB.style.border = THIS.pointerThickness + 'px solid ' + THIS.pointerColor;
-
- // alpha slider pointer spacer
- p.asldPtrS.style.width = THIS.sliderSize + 'px';
- p.asldPtrS.style.height = jsc.pub.sliderInnerSpace + 'px';
-
-
- // the Close button
- function setBtnBorder () {
- var insetColors = THIS.controlBorderColor.split(/\s+/);
- var outsetColor = insetColors.length < 2 ? insetColors[0] : insetColors[1] + ' ' + insetColors[0] + ' ' + insetColors[0] + ' ' + insetColors[1];
- p.btn.style.borderColor = outsetColor;
- }
- var btnPadding = 15; // px
- p.btn.className = 'jscolor-btn-close';
- p.btn.style.display = THIS.closeButton ? 'block' : 'none';
- p.btn.style.position = 'absolute';
- p.btn.style.left = THIS.padding + 'px';
- p.btn.style.bottom = THIS.padding + 'px';
- p.btn.style.padding = '0 ' + btnPadding + 'px';
- p.btn.style.maxWidth = (dims[0] - 2 * THIS.padding - 2 * THIS.controlBorderWidth - 2 * btnPadding) + 'px';
- p.btn.style.overflow = 'hidden';
- p.btn.style.height = THIS.buttonHeight + 'px';
- p.btn.style.whiteSpace = 'nowrap';
- p.btn.style.border = THIS.controlBorderWidth + 'px solid';
- setBtnBorder();
- p.btn.style.color = THIS.buttonColor;
- p.btn.style.font = '12px sans-serif';
- p.btn.style.textAlign = 'center';
- p.btn.style.cursor = 'pointer';
- p.btn.onmousedown = function () {
- THIS.hide();
- };
- p.btnT.style.lineHeight = THIS.buttonHeight + 'px';
- p.btnT.innerHTML = '';
- p.btnT.appendChild(document.createTextNode(THIS.closeText));
-
- // reposition the pointers
- redrawPad();
- redrawSld();
- redrawASld();
-
- // If we are changing the owner without first closing the picker,
- // make sure to first deal with the old owner
- if (jsc.picker.owner && jsc.picker.owner !== THIS) {
- jsc.removeClass(jsc.picker.owner.targetElement, jsc.pub.activeClassName);
- }
-
- // Set a new picker owner
- jsc.picker.owner = THIS;
-
- // The redrawPosition() method needs picker.owner to be set, that's why we call it here,
- // after setting the owner
- if (THIS.container === document.body) {
- jsc.redrawPosition();
- } else {
- jsc._drawPosition(THIS, 0, 0, 'relative', false);
- }
-
- if (p.wrap.parentNode !== THIS.container) {
- THIS.container.appendChild(p.wrap);
- }
-
- jsc.addClass(THIS.targetElement, jsc.pub.activeClassName);
- }
-
-
- function redrawPad () {
- // redraw the pad pointer
- var yChannel = jsc.getPadYChannel(THIS);
- var x = Math.round((THIS.channels.h / 360) * (THIS.width - 1));
- var y = Math.round((1 - THIS.channels[yChannel] / 100) * (THIS.height - 1));
- var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize);
- var ofs = -Math.floor(crossOuterSize / 2);
- jsc.picker.cross.style.left = (x + ofs) + 'px';
- jsc.picker.cross.style.top = (y + ofs) + 'px';
-
- // redraw the slider
- switch (jsc.getSliderChannel(THIS)) {
- case 's':
- var rgb1 = jsc.HSV_RGB(THIS.channels.h, 100, THIS.channels.v);
- var rgb2 = jsc.HSV_RGB(THIS.channels.h, 0, THIS.channels.v);
- var color1 = 'rgb(' +
- Math.round(rgb1[0]) + ',' +
- Math.round(rgb1[1]) + ',' +
- Math.round(rgb1[2]) + ')';
- var color2 = 'rgb(' +
- Math.round(rgb2[0]) + ',' +
- Math.round(rgb2[1]) + ',' +
- Math.round(rgb2[2]) + ')';
- jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2);
- break;
- case 'v':
- var rgb = jsc.HSV_RGB(THIS.channels.h, THIS.channels.s, 100);
- var color1 = 'rgb(' +
- Math.round(rgb[0]) + ',' +
- Math.round(rgb[1]) + ',' +
- Math.round(rgb[2]) + ')';
- var color2 = '#000';
- jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2);
- break;
- }
-
- // redraw the alpha slider
- jsc.picker.asldGrad.draw(THIS.sliderSize, THIS.height, THIS.toHEXString());
- }
-
-
- function redrawSld () {
- var sldChannel = jsc.getSliderChannel(THIS);
- if (sldChannel) {
- // redraw the slider pointer
- var y = Math.round((1 - THIS.channels[sldChannel] / 100) * (THIS.height - 1));
- jsc.picker.sldPtrOB.style.top = (y - (2 * THIS.pointerBorderWidth + THIS.pointerThickness) - Math.floor(jsc.pub.sliderInnerSpace / 2)) + 'px';
- }
-
- // redraw the alpha slider
- jsc.picker.asldGrad.draw(THIS.sliderSize, THIS.height, THIS.toHEXString());
- }
-
-
- function redrawASld () {
- var y = Math.round((1 - THIS.channels.a) * (THIS.height - 1));
- jsc.picker.asldPtrOB.style.top = (y - (2 * THIS.pointerBorderWidth + THIS.pointerThickness) - Math.floor(jsc.pub.sliderInnerSpace / 2)) + 'px';
- }
-
-
- function isPickerOwner () {
- return jsc.picker && jsc.picker.owner === THIS;
- }
-
-
- function onValueKeyDown (ev) {
- if (jsc.eventKey(ev) === 'Enter') {
- if (THIS.valueElement) {
- THIS.processValueInput(THIS.valueElement.value);
- }
- THIS.tryHide();
- }
- }
-
-
- function onAlphaKeyDown (ev) {
- if (jsc.eventKey(ev) === 'Enter') {
- if (THIS.alphaElement) {
- THIS.processAlphaInput(THIS.alphaElement.value);
- }
- THIS.tryHide();
- }
- }
-
-
- function onValueChange (ev) {
- if (jsc.getData(ev, 'internal')) {
- return; // skip if the event was internally triggered by jscolor
- }
-
- var oldVal = THIS.valueElement.value;
-
- THIS.processValueInput(THIS.valueElement.value); // this might change the value
-
- jsc.triggerCallback(THIS, 'onChange');
-
- if (THIS.valueElement.value !== oldVal) {
- // value was additionally changed -> let's trigger the change event again, even though it was natively dispatched
- jsc.triggerInputEvent(THIS.valueElement, 'change', true, true);
- }
- }
-
-
- function onAlphaChange (ev) {
- if (jsc.getData(ev, 'internal')) {
- return; // skip if the event was internally triggered by jscolor
- }
-
- var oldVal = THIS.alphaElement.value;
-
- THIS.processAlphaInput(THIS.alphaElement.value); // this might change the value
-
- jsc.triggerCallback(THIS, 'onChange');
-
- // triggering valueElement's onChange (because changing alpha changes the entire color, e.g. with rgba format)
- jsc.triggerInputEvent(THIS.valueElement, 'change', true, true);
-
- if (THIS.alphaElement.value !== oldVal) {
- // value was additionally changed -> let's trigger the change event again, even though it was natively dispatched
- jsc.triggerInputEvent(THIS.alphaElement, 'change', true, true);
- }
- }
-
-
- function onValueInput (ev) {
- if (jsc.getData(ev, 'internal')) {
- return; // skip if the event was internally triggered by jscolor
- }
-
- if (THIS.valueElement) {
- THIS.fromString(THIS.valueElement.value, jsc.flags.leaveValue);
- }
-
- jsc.triggerCallback(THIS, 'onInput');
-
- // triggering valueElement's onInput
- // (not needed, it was dispatched normally by the browser)
- }
-
-
- function onAlphaInput (ev) {
- if (jsc.getData(ev, 'internal')) {
- return; // skip if the event was internally triggered by jscolor
- }
-
- if (THIS.alphaElement) {
- THIS.fromHSVA(null, null, null, parseFloat(THIS.alphaElement.value), jsc.flags.leaveAlpha);
- }
-
- jsc.triggerCallback(THIS, 'onInput');
-
- // triggering valueElement's onInput (because changing alpha changes the entire color, e.g. with rgba format)
- jsc.triggerInputEvent(THIS.valueElement, 'input', true, true);
- }
-
-
- //
- // Install the color picker on chosen element(s)
- //
-
-
- // Determine picker's container element
- if (this.container === undefined) {
- this.container = document.body; // default container is BODY element
-
- } else { // explicitly set to custom element
- this.container = jsc.node(this.container);
- }
-
- if (!this.container) {
- throw new Error('Cannot instantiate color picker without a container element');
- }
-
-
- // Fetch the target element
- this.targetElement = jsc.node(targetElement);
-
- if (!this.targetElement) {
- // temporarily customized error message to help with migrating from versions prior to 2.2
- if (typeof targetElement === 'string' && /^[a-zA-Z][\w:.-]*$/.test(targetElement)) {
- // targetElement looks like valid ID
- var possiblyId = targetElement;
- throw new Error('If \'' + possiblyId + '\' is supposed to be an ID, please use \'#' + possiblyId + '\' or any valid CSS selector.');
- }
-
- throw new Error('Cannot instantiate color picker without a target element');
- }
-
- if (this.targetElement.jscolor && this.targetElement.jscolor instanceof jsc.pub) {
- throw new Error('Color picker already installed on this element');
- }
-
-
- // link this instance with the target element
- this.targetElement.jscolor = this;
- jsc.addClass(this.targetElement, jsc.pub.className);
-
- // register this instance
- jsc.instances.push(this);
-
-
- // if target is BUTTON
- if (jsc.isButton(this.targetElement)) {
-
- if (this.targetElement.type.toLowerCase() !== 'button') {
- // on buttons, always force type to be 'button', e.g. in situations the target