Commits

Christian Krebs  committed e3e7b04

Improved contrast tool.

  • Participants
  • Parent commits 7f49b1f

Comments (0)

Files changed (9)

File resources/scripts/dom.js

 })();
 */
 
-/* testing in Chrome or FF
+/* testing in Chrome or FF *
 if (document.createElementNS &&
     document.createElement('div').namespaceURI != 'http://www.w3.org/1999/xhtml')
 {
     return this.createElementNS('http://www.w3.org/1999/xhtml', name);
   };
 }
-*/
+/* */
 
 if (!Element.prototype.contains)
 {
   }
 }
 
+if (!Object.getOwnPropertyNames)
+{
+  Object.getOwnPropertyNames = function(obj)
+  {
+    var ret = [];
+    for (var p in obj)
+    {
+      if (Object.prototype.hasOwnProperty.call(obj, p))
+        ret.push(p);
+    }
+    return ret;
+  }
+}
+
 /**
  * Check if a string appears to be a number, that is, all letters in the
  * string are numbers. Does not take in to account decimals. Clones the
 
 window.CustomElements.AutoScrollHeightFeature = function()
 {
-  this.adjust_height = function()
+  this._adjust_height = function(delta, event)
   {
-    if (this.scrollHeight != this.offsetHeight)
+    if (!this.value)
     {
-      // TODO values should not be hardcoded
-      this.style.height = (4 + (this.scrollHeight > 16 ? this.scrollHeight : 16)) + 'px';
+      this.style.height = "auto";
+      this.rows = 1;
+    }
+    else
+    {
+      this.rows = 0;
+      this.style.height = "0";
+      this.style.height = this.scrollHeight + delta + "px";
     }
   };
 
-  this._get_adjust_height = function(count_lines, line_height, border_padding)
+  this._get_delta = function(ele)
   {
-    var lines = -1;
-    return function()
-    {
-      var new_count = count_lines(this.value);
-      if (new_count != lines)
-      {
-        lines = new_count;
-        this.style.height = (border_padding + (lines) * line_height) + 'px';
-      }
-    }
-  }
+    var style = window.getComputedStyle(ele, null);
+    var is_border_box = style.getPropertyValue("box-sizing") == "border-box";
+    var prop = is_border_box ? "border" : "padding";
+    var sign = is_border_box ? 1 : -1;
 
-  this._count_lines = (function(re)
-  {
-    return function(str)
-    {
-      for (var count = 1; re.exec(str); count++);
-      return count;
-    };
-  })(/\r\n/g);
-
-  this._get_line_height = function(textarea)
-  {
-    // computed style returns by default just "normal"
-    var
-    CRLF = "\r\n",
-    offset_height = textarea.offsetHeight,
-    textarea_value = textarea._get_value(),
-    line_height = 0,
-    test_value = "\r\n\r\n\r\n\r\n\r\n\r\n";
-
-    textarea.value = test_value;
-    while (textarea.scrollHeight < offset_height)
-    {
-      textarea.value = (test_value += CRLF);
-    }
-    line_height = textarea.scrollHeight;
-    textarea.value = (test_value += CRLF);
-    line_height = textarea.scrollHeight - line_height;
-    textarea.value = textarea_value;
-    return line_height;
-  };
-
-  this._get_border_padding = function(ele)
-  {
-    var
-    border_padding = 0,
-    style_dec = window.getComputedStyle(ele, null);
-
-    if (style_dec.getPropertyValue('box-sizing') == 'border-box')
-    {
-      ['padding-top', 'padding-bottom', 'border-top', 'border-bottom'].forEach(function(prop)
-      {
-        border_padding += parseInt(style_dec.getPropertyValue(prop)) || 0;
-      })
-    };
-    return border_padding;
+    return (sign * parseInt(style.getPropertyValue(prop + "-bottom")) || 0) +
+           (sign * parseInt(style.getPropertyValue(prop + "-top")) || 0);
   };
 
   (this._inits || (this._inits = [])).push(function(ele)
   {
-    var adjust_height = this._get_adjust_height(this._count_lines,
-        this._get_line_height(ele), this._get_border_padding(ele));
-    adjust_height.call(ele);
+    var delta = this._get_delta(ele);
+    var adjust_height = this._adjust_height.bind(ele, delta);
+    adjust_height();
     ele.addEventListener('input', adjust_height, false);
+    // Custom event to force adjust of height
+    ele.addEventListener('heightadjust', adjust_height, false);
   });
+
 };
 
 CustomElements.add(function()
   this.type = '_auto_height_textarea';
   this.html_name = 'textarea';
 },
-'PlaceholderFeature',
 'AutoScrollHeightFeature');
 
+CanvasRenderingContext2D.prototype.draw_2d_gradient = function(top_color_list,
+                                                               bottom_color_list,
+                                                               flip)
+{
+  if (typeof bottom_color_list == "boolean")
+  {
+    flip = bottom_color_list;
+    bottom_color_list = null;
+  }
 
+  if (!this._src_canvas)
+  {
+    this._src_canvas = document.createElement("canvas");
+    this._src_ctx = this._src_canvas.getContext("2d");
+  }
+  var width = this._src_canvas.width = this.canvas.width;
+  var height = this._src_canvas.height = this.canvas.height;
+  var set_stop = function(color, index, list)
+  {
+    this.addColorStop((1 / (list.length - 1 || 1)) * index, color);  
+  };
+  var lg = flip
+         ? this._src_ctx.createLinearGradient(0, 0, 0, height)
+         : this._src_ctx.createLinearGradient(0, 0, width, 0);
+  this._src_ctx.clearRect(0, 0, width, height);
+  top_color_list.forEach(set_stop, lg);
+  this._src_ctx.fillStyle = lg;
+  this._src_ctx.globalCompositeOperation = "source-over";
+  this._src_ctx.fillRect(0, 0, width, height);
+  this.drawImage(this._src_canvas, 0, 0);
+  if (bottom_color_list)
+  {
+    this._src_ctx.clearRect(0, 0, width, height);
+    lg = flip
+         ? this._src_ctx.createLinearGradient(0, 0, width, 0)
+         : this._src_ctx.createLinearGradient(0, 0, 0, height);
+    lg.addColorStop(0, "hsla(0, 0%, 0%, 0)");  
+    lg.addColorStop(1, "hsla(0, 0%, 0%, 1)");
+    this._src_ctx.fillStyle = lg;
+    this._src_ctx.fillRect(0, 0, width, height);
+    this._src_ctx.globalCompositeOperation = "source-in";
+    lg = flip
+         ? this._src_ctx.createLinearGradient(0, 0, 0, height)
+         : this._src_ctx.createLinearGradient(0, 0, width, 0);  
+    bottom_color_list.forEach(set_stop, lg);
+    this._src_ctx.fillStyle = lg;
+    this._src_ctx.fillRect(0, 0, width, height);
+    this.drawImage(this._src_canvas, 0, 0);
+  }
+};
+
+

File resources/ui-scripts/colorpicker.js

 /**
   * To create a color picker.
   * The class has a single method render which returns a template
-  * to be render with Element.render. The call of that method will also add 
-  * an event listener for DOMNodeInserted to setup the color picker (checked 
-  * with the class name of the container element). The setup call will add 
-  * an according listener for DOMNodeRemoved which will clean up any state 
-  * and listeners, that means removing the color picker from the DOM will 
+  * to be render with Element.render. The call of that method will also add
+  * an event listener for DOMNodeInserted to setup the color picker (checked
+  * with the class name of the container element). The setup call will add
+  * an according listener for DOMNodeRemoved which will clean up any state
+  * and listeners, that means removing the color picker from the DOM will
   * clean up automatically.
   * @param {Function} cb. A callback called for each new color selection
   * with an instance of Color as argument.
   CP_1D_CLASS = "color-picker-1-d-graphic",
   CP_OLD_CLASS = "color-picker-color-old",
   CP_NEW_CLASS = "color-picker-color",
-  SLIDER_BASE_CLASS = 'color-picker-slider-base', 
+  SLIDER_BASE_CLASS = 'color-picker-slider-base',
   SLIDER_CLASS = 'color-picker-slider',
-  POINTER_CLASS = 'color-picker-pointer',
-  CP_ALPHA_CLASS = "color-picker-alpha",
-  CP_ALPHA_BG = "color-picker-number alpha";
+  POINTER_CLASS = 'color-picker-pointer';
 
   /* private */
   this._verify_inputs =
     hex: {min:0, max: 0xFFFFFF, last: 0, base: 16},
     alpha: {min:0, max: 1, last: 0, base: 10},
   }
-  
+
   this._verify = function(input, check)
   {
     var ret = (check.max == 1 ? parseFloat : parseInt)(input, check.base);
-    if (typeof ret == 'number' && !isNaN(ret) && 
+    if (typeof ret == 'number' && !isNaN(ret) &&
         ret >= check.min && ret <= check.max)
     {
       if (check.base == 16)
     }
     return check.last;
   }
- 
+
   // generic DOM input event handler on the container element of the color picker
   this._oninput = function(event)
   {
     if (event.target.name in this._verify_inputs && event.target.value)
     {
       var target = event.target;
-      var verifier = this._verify_inputs[target.name];
-      if (target.value)
+      var target_value = target.value;
+      if (target_value)
       {
-        var value = this._verify(target.value, verifier);
+        var verifier = this._verify_inputs[target.name];
+        var value = "";
+
+        // Special handle hex since we're inserting a "#" in _update_inputs()
+        // and onblur()
+        var cursor_pos;
+        var do_verification = true;
+        if (target.name === "hex")
+        {
+          cursor_pos = target.selectionStart + 1;
+          target_value = target_value.replace(/[^0-9a-f]/ig, function() {
+            cursor_pos--;
+            return "";
+          }).slice(0, 6).toUpperCase();
+
+          if (target_value === "")
+          {
+            target_value = verifier.min;
+            value = "";
+            do_verification = false;
+          }
+        }
+
+        if (do_verification)
+          value = this._verify(target_value, verifier);
+
         this._cs[target.name] = value;
         if (verifier.max == 1)
         {
-          if (value.toString().length > 5 || value != target.value)
-            target.value = value.toFixed(3)
+          if (value.toString().length > 5 || value != target_value)
+            target.value = value.toFixed(3);
         }
         else
           target.value = value;
         this._set_coordinates();
-        this._set_cs_coordinates();
         this._cb_color.clone(this._cs);
         this._update_inputs(event.target.name);
+        if (target.name === "hex" && cursor_pos !== undefined)
+          target.selectionStart = target.selectionEnd = cursor_pos;
         this._update_xy_graphic();
         this._update_xy_slider();
-        this._update_xy_slider_color();
         this._update_z_graphic();
         this._update_z_slider();
-        this._update_alpha_graphic();
-        this._update_alpha_slider();
         this._update_sample_color();
         this._cb(this._cb_color);
       }
     }
   }
-  
+
   // generic DOM click event handler on the container element of the color picker
   this._onclick = function(event)
   {
     if (color_value)
     {
       if (color_value == 'cancel')
+      {
         color = this._initial_color;
-      this._cs.clone(color);
-      this._cb_color.clone(color);
-      if (color.type == color.KEYWORD)
-        this._cs.type = typeof color.alpha == 'number' ? color.RGBA : color.HEX;
+        this._cs.clone(color);
+        this._cb_color.clone(color);
+        if (color.type == color.KEYWORD)
+          this._cs.type = color.RGBA;
+      }
+      else
+      {
+        this._cs.setHex(color_value);
+        this._cb_color.clone(this._cs);
+      }
       this._update();
       this._cb(this._cb_color);
     }
   {
     if (event.target.name in this._verify_inputs)
     {
+      var prefix = (event.target.name === "hex") ? "#" : "";
       var target = event.target;
       var verifier = this._verify_inputs[target.name];
       var value = this._verify(target.value, verifier);
       if (value != target.value || !target.value)
-        target.value = verifier.max == 1 ? value.toFixed(3) : value;
+        target.value = prefix + (verifier.max == 1 ? value.toFixed(3) : value);
     }
   }
-  
+
   // generic DOM change event handler on the container element of the color picker
   this._onchange = function(event)
   {
     if (event.target.name == 'color-space')
       this._set_color_space(event.target.value);
   }
-  
+
   // callback for the x-y axes slider.
   this._onxy = function(x, y)
   {
     this._set_cs_coordinates();
     this._cb_color.clone(this._cs);
     this._update_inputs();
-    this._update_xy_slider_color();
     this._update_z_graphic();
-    this._update_alpha_graphic();
     this._update_sample_color();
     this._cb(this._cb_color);
   }
-  
+
   // callback for the z axis slider.
   this._onz = function(z)
   {
     this._cb_color.clone(this._cs);
     this._update_inputs();
     this._update_xy_graphic();
-    this._update_xy_slider_color();
-    this._update_alpha_graphic();
     this._update_sample_color();
     this._cb(this._cb_color);
   }
 
-  // callback for the alpha slider
-  this._onalpha = function(alpha)
-  {
-    this._cs.alpha = alpha;
-    this._cb_color.clone(this._cs);
-    this._update_sample_color();
-    this._update_inputs(null, ['alpha']);
-    this._cb(this._cb_color); 
-  };
-  
   // DOMNodeRemoved event handler
   this._onremove = function(event)
   {
     if (event.target.nodeType == 1 && event.target.contains(this._ele))
     {
-      document.removeEventListener('DOMNodeRemoved', this._onremove_bound, false); 
+      document.removeEventListener('DOMNodeRemoved', this._onremove_bound, false);
       this._ele.removeEventListener('input', this._oninput_bound, false);
       this._ele.removeEventListener('click', this._onclick_bound, false);
       this._ele.removeEventListener('change', this._onchange_bound, false);
       this._ele.removeEventListener('blur', this._onblur_bound, true);
-      this._ele = null; 
+      this._ele = null;
       this._ele_inputs = null;
       this._ele_sample_color = null;
       this._ele_sample_color_solid = null;
-      this._ele_xy_graphic = null;
       this._ele_z_graphic = null;
-      this._ele_alpha_graphic = null;
-      this._ele_xy_slider = null;
       this._xy_slider = null;
       this._z_slider = null;
-      this._alpha_slider = null;
+      this._2d_ctx = null;
+      this._1d_ctx = null;
     }
   }
-  
+
   // methods to upadte parts of the view
 
   // update all the inputs
   {
     for (var input = null, i = 0; input = this._ele_inputs[i]; i++)
     {
-      if (input.name != setter && 
+      if (input.name != setter &&
           (!inputs_to_update || inputs_to_update.indexOf(input.name) > -1))
       {
-        input.value = this._verify_inputs[input.name].max == 1 ? 
-                      this._cs[input.name].toFixed(3) : 
-                      this._cs[input.name]; 
+        input.value = this._verify_inputs[input.name].max == 1 ?
+                      this._cs[input.name].toFixed(3) :
+                      this._cs[input.name];
       }
+
+      if (input.name === "hex")
+        input.value = "#" + input.value;
     }
   }
-  
+
   // update the position of the slider for the z-axis
   this._update_z_slider = function()
   {
     this._z_slider.y = this._cur_z;
   }
-  
+
   // update the position of the slider foe the x-y axes
   this._update_xy_slider = function()
   {
     this._xy_slider.x = this._cur_x;
     this._xy_slider.y = this._cur_y;
   }
-  
-  // update the color of the slider for the x-y axes
-  this._update_xy_slider_color = function()
-  { 
-    var gray_value = this._cs.xyz(this._cur_x, 
-                                  this._cur_y, 
-                                  this._cur_z).getGrayValue() / 255;
-    this._ele_xy_slider.setAttribute('stroke', 
-                                      gray_value > .3 ? 
-                                     'hsl(0, 0%, 20%)' : 
-                                     'hsl(0, 0%, 80%)'); 
-  }
-  
+
   // update the sample color
   this._update_sample_color = function()
   {
     var color = this._cs.xyz(this._cur_x, this._cur_y, this._cur_z);
-    this._ele_sample_color.style.backgroundColor = 
-      this._has_alpha ? color.rgba : color.hhex;
+    this._ele_sample_color.style.backgroundColor = color.rgba;
   }
 
-  // update the position of the slider foe the lpha value
-  this._update_alpha_slider = function()
-  {
-    if (this._has_alpha)
-      this._alpha_slider.y = this._cs.alpha;
-  }
-  
   // update everything
   this._update = function()
   {
     this._update_inputs();
     this._update_xy_graphic();
     this._update_xy_slider();
-    this._update_xy_slider_color();
     this._update_z_graphic();
     this._update_z_slider();
-    this._update_alpha_graphic();
-    this._update_alpha_slider();
-    this._update_sample_color(); 
+    this._update_sample_color();
   }
-  
+
   /**
     * Update methods for the graphics of a given color cube.
     * The color picker supports one of the following color spaces:
-    * 's-v-h', 'h-v-s', 'h-s-v', 'b-g-r', 'b-r-g' or 'r-g-b'. 
-    * Each token has its update method, e.g. 's-v-h' will match 
+    * 's-v-h', 'h-v-s', 'h-s-v', 'b-g-r', 'b-r-g' or 'r-g-b'.
+    * Each token has its update method, e.g. 's-v-h' will match
     * an '_update_sv' and an '_update_h' method.
   */
 
   this._update_sv = function()
   {
-    this._ele_xy_graphic.clearAndRender(window.templates.gradient_2d
-    (
-      ['#fff', this._cs.xyz(1, 1, this._cur_z).hhex], 
-      ['#000']
-    ));
+    this._2d_ctx.draw_2d_gradient(['#fff', this._cs.xyz(1, 1, this._cur_z).hhex],
+                                  ['#000']);
   }
-  
-  this._update_hs = 
+
+  this._update_hs =
   this._update_hv = function()
   {
-    this._ele_xy_graphic.clearAndRender(window.templates.gradient_2d
-    (
-      [
-        this._cs.xyz(0/6, 1, this._cur_z).hhex,
-        this._cs.xyz(1/6, 1, this._cur_z).hhex,
-        this._cs.xyz(2/6, 1, this._cur_z).hhex,
-        this._cs.xyz(3/6, 1, this._cur_z).hhex,
-        this._cs.xyz(4/6, 1, this._cur_z).hhex,
-        this._cs.xyz(5/6, 1, this._cur_z).hhex,
-        this._cs.xyz(6/6, 1, this._cur_z).hhex,
-      ], 
-      [this._cs.xyz(0, 0, this._cur_z).hhex]
-    ));
+    this._2d_ctx.draw_2d_gradient([this._cs.xyz(0/6, 1, this._cur_z).hhex,
+                                   this._cs.xyz(1/6, 1, this._cur_z).hhex,
+                                   this._cs.xyz(2/6, 1, this._cur_z).hhex,
+                                   this._cs.xyz(3/6, 1, this._cur_z).hhex,
+                                   this._cs.xyz(4/6, 1, this._cur_z).hhex,
+                                   this._cs.xyz(5/6, 1, this._cur_z).hhex,
+                                   this._cs.xyz(6/6, 1, this._cur_z).hhex],
+                                  [this._cs.xyz(0, 0, this._cur_z).hhex]);
   }
-  
+
   this._update_rg =
   this._update_br =
   this._update_bg = function()
   {
-    this._ele_xy_graphic.clearAndRender(window.templates.gradient_2d
-    (
-      [
-        this._cs.xyz(0, 1, this._cur_z).hhex, 
-        this._cs.xyz(1, 1, this._cur_z).hhex
-      ], 
-      [
-        this._cs.xyz(0, 0, this._cur_z).hhex, 
-        this._cs.xyz(1, 0, this._cur_z).hhex
-      ]
-    ));
+    this._2d_ctx.draw_2d_gradient([this._cs.xyz(0, 1, this._cur_z).hhex,
+                                   this._cs.xyz(1, 1, this._cur_z).hhex],
+                                  [this._cs.xyz(0, 0, this._cur_z).hhex,
+                                   this._cs.xyz(1, 0, this._cur_z).hhex]);
   }
-    
-  this._update_r = 
+
+  this._update_r =
   this._update_g =
   this._update_b =
   this._update_s =
   this._update_v = function()
   {
-    this._ele_z_graphic.clearAndRender(window.templates.gradient
-    (
-      [
-        this._cs.xyz(this._cur_x, this._cur_y, 0).hhex, 
-        this._cs.xyz(this._cur_x, this._cur_y, 1).hhex
-      ], true
-    ));
+    this._1d_ctx.draw_2d_gradient([this._cs.xyz(this._cur_x, this._cur_y, 1).hhex,
+                                   this._cs.xyz(this._cur_x, this._cur_y, 0).hhex],
+                                  true);
   }
-    
+
   this._update_h = function()
   {
-    this._ele_z_graphic.innerHTML = '';
-    this._ele_z_graphic.render(window.templates.gradient
-    (
-      ['#f00', '#ff0', '#0f0', '#0ff', '#00f', '#f0f', '#f00'], 
-      true
-    ));
+    this._1d_ctx.draw_2d_gradient(['#f00', '#f0f', '#00f', 
+                                   '#0ff', '#0f0', '#ff0', '#f00'], true);
   }
-  
-  this._update_alpha_graphic = function()
-  {
-    if (this._has_alpha)
-    {
-      var rgb = this._cs.xyz(this._cur_x, 
-                             this._cur_y, 
-                             this._cur_z).getRGB().join(',');
-      this._ele_alpha_graphic.clearAndRender(window.templates.gradient
-      (
-        ['rgba(' + rgb + ', 0)', 'rgba(' + rgb + ', 1)'], 
-        true
-      ));
-    }
-  }
-    
+
   this._color_properties =
   {
     'h': [360, 'setHue', 'getHue'],
     'g': [255, 'setGreen', 'getGreen'],
     'b': [255, 'setBlue', 'getBlue']
   }
-    
+
   // To set a color space.
   // color_space is one of 's-v-h', 'h-v-s', 'h-s-v', 'b-g-r', 'b-r-g' or 'r-g-b'.
   this._set_color_space = function(color_space)
     this._cs.hex = color;
     this._update();
   }
-  
+
   this._set_cs_coordinates = function()
   {
-    this._cs.x = this._cur_x
+    this._cs.x = this._cur_x;
     this._cs.y = this._cur_y;
     this._cs.z = this._cur_z;
   }
-  
+
   this._set_coordinates = function()
   {
     this._cur_x = this._cs.x;
     this._cur_y = this._cs.y;
     this._cur_z = this._cs.z;
   }
-  
+
   // DOMNodeInserted event handler.
   this._setup = function(event)
   {
     if (this._ele)
     {
       document.removeEventListener('DOMNodeInserted', this._setup_bound, false);
-      this._ele_inputs = Array.prototype.slice.call
-      (this._ele.getElementsByTagName('input')).filter(function(input)
-      {
-        return ['h', 's', 'v', 
-                'r', 'g', 'b', 
-                'hex', 
-                'alpha'].indexOf(input.name) != -1;
-      });
-      this._ele_sample_color = 
+      this._ele_inputs = Array.prototype.slice.call(
+        this._ele.getElementsByTagName('input')).filter(function(input)
+        {
+          return ['h', 's', 'v',
+                  'r', 'g', 'b',
+                  'hex',
+                  'alpha'].indexOf(input.name) != -1;
+        }
+      );
+      this._ele_sample_color =
         this._ele.getElementsByClassName(CP_NEW_CLASS)[0];
       var ele_xy = this._ele.getElementsByClassName(CP_2D_CLASS)[0];
       var ele_z = this._ele.getElementsByClassName(CP_1D_CLASS)[0];
-      this._ele_xy_graphic = ele_xy.getElementsByTagName('div')[0];
-      this._ele_z_graphic = ele_z.getElementsByTagName('div')[0];
+      var canvas = ele_xy.querySelector("canvas");
+      this._2d_ctx = canvas && canvas.getContext("2d");
+      canvas = ele_z.querySelector("canvas");
+      this._1d_ctx = canvas && canvas.getContext("2d");
       this._xy_slider = new Slider(
       {
         container: ele_xy,
         slider_base_class: SLIDER_BASE_CLASS,
         slider_class: POINTER_CLASS,
-        slider_template: window.templates.svg_slider_circle(), 
         onxy: this._onxy.bind(this),
         min_x: 0,
         max_x: 1,
         min_y: 1,
         max_y: 0
       });
-      this._ele_xy_slider = ele_xy.getElementsByTagName('circle')[0];
       this._z_slider = new Slider(
       {
         container: ele_z,
         slider_base_class: SLIDER_BASE_CLASS,
         slider_class: SLIDER_CLASS,
-        slider_template: window.templates.svg_slider_z(), 
         ony: this._onz.bind(this),
         min_y: 1,
         max_y: 0
       });
-      if (this._has_alpha = (typeof this._initial_color.alpha == 'number'))
-      {
-        this._cs.alpha = this._initial_color.alpha;
-        ele_z = this._ele.getElementsByClassName(CP_ALPHA_CLASS)[0];
-        this._ele_alpha_graphic = ele_z.getElementsByTagName('div')[0];
-        this._ele_sample_color_solid = 
-          this._ele_sample_color.getElementsByTagName('div')[0];
-        this._alpha_slider = new Slider(
-        {
-          container: ele_z,
-          slider_base_class: SLIDER_BASE_CLASS,
-          slider_class: SLIDER_CLASS,
-          slider_template: window.templates.svg_slider_z(), 
-          ony: this._onalpha.bind(this),
-          min_y: 1,
-          max_y: 0
-        });
-      }
+
       this._set_color_space('s-v-h');
       this._ele.addEventListener('input', this._oninput_bound, false);
       this._ele.addEventListener('click', this._onclick_bound, false);
   }
 
   /* implementation */
-  
-  this.render = function()
+
+  this.render = function(alpha_disabled, palette_disabled)
   {
     document.addEventListener('DOMNodeInserted', this._setup_bound, false);
-    return window.templates.color_picker_popup(this._initial_color, 
-                                               CP_CLASS, CP_2D_CLASS, 
-                                               CP_1D_CLASS, CP_OLD_CLASS, 
-                                               CP_NEW_CLASS, 'h', CP_ALPHA_CLASS,
-                                               CP_ALPHA_BG)
+    return window.templates.color_picker_popup(this._initial_color,
+                                               CP_CLASS, CP_2D_CLASS,
+                                               CP_1D_CLASS, CP_OLD_CLASS,
+                                               CP_NEW_CLASS, 'h',
+                                               alpha_disabled, palette_disabled);
   }
-  
+
+  this.update = function(color_value)
+  {
+    if (color_value)
+    {
+      this._cs.setHex(color_value);
+      this._cb_color.clone(this._cs);
+      this._update();
+      this._cb(this._cb_color);
+    }
+  };
+
   /* instatiation */
   this._init = function(cb, color)
   {
     this._cs.clone(color);
     this._cb_color = new Color();
     if (color.type == color.KEYWORD)
-      this._cs.type = typeof color.alpha == 'number' ? color.RGBA : color.HEX;
+      this._cs.type = color.RGBA;
     this._cur_x = 0;
     this._cur_y = 0;
     this._cur_z = 0;
     this._onblur_bound = this._onblur.bind(this);
     this._onremove_bound = this._onremove.bind(this);
   }
-  
+
 };

File resources/ui-scripts/colorpickertemplateold.js

+(function()
+{
+
+  /**
+    * Templates with svg_ prefix are meant to be used as part 
+    * of an other svg template, e.g. the svg root element must already exist.
+    */
+  var get_id = (function()
+  {
+    var id_count = 0;
+    return function()
+    {
+      return "svg-uid-" + (++id_count);
+    };
+  })();
+  
+  this.svg_stop = function(offset, stop_color, stop_opacity)
+  {
+    return (
+    ['stop', 
+      'offset', offset, 
+      'stop-color', stop_color
+    ].concat( stop_opacity ? ['stop-opacity', stop_opacity] : [] ));
+  };
+
+  this.svg_liner_gradient = function(id, colors, rotate)
+  {
+    var ret = ['linearGradient'];
+    var count = colors.length - 1;
+    ret.push(colors.map(function(color, index, colors)
+    {
+      return this.svg_stop((index / count * 100).toFixed(2) + '%', color);
+    }, this));
+    ret.push('id', id, 'gradientUnits', 'objectBoundingBox');
+    if (rotate) ret.push('x1', '50%', 'y1', '100%', 'x2', '50%', 'y2', '0%');
+    return ret;
+  }
+  /*
+  this.svg_rect = function(x, y, width, height, rx, ry, fill, mask, id)
+  {
+    var ret = 
+    ['rect', 
+      'x', x.toString(), 
+      'y', y.toString(), 
+      'width', width.toString(), 
+      'height', height.toString()
+    ];
+    if (rx) 
+      ret.push('rx', rx.toString());
+    if (ry) 
+      ret.push('ry', ry.toString());
+    if (fill) 
+      ret.push('fill', fill);
+    if (mask) 
+      ret.push('mask', mask);
+    if (id) 
+      ret.push('id', id);
+    return ret;
+  };
+  */
+  this.svg_liner_mask = function(id, rotate)
+  {
+    var grad_id = get_id();
+    return ( 
+    [
+      this.svg_liner_gradient(grad_id, ['#fff', '#000'], rotate),
+      ['mask',
+        this.svg_rect(null, null, 0, 0, '100%', '100%', 0, 0, 'url(#' + grad_id + ')'),
+        'maskUnits', 'objectBoundingBox',
+        'x', '0', 
+        'y', '0',
+        'width', '100%',  
+        'height', '100%',
+        'id', id
+      ]
+    ]);
+  };
+  
+  this.svg_gradient = function(x, y, width, height, colors, rotate, mask)
+  {
+    var svg_defs = ['defs'];
+    if (colors.length > 1)
+    {
+      var grad_id = get_id();
+      svg_defs.push(this.svg_liner_gradient(grad_id, colors, rotate));
+    }
+    if (mask)
+    {
+      var mask_id = get_id();
+      svg_defs.push(this.svg_liner_mask(mask_id, true));
+    }
+    return (
+    [
+      svg_defs,
+      this.svg_rect(null, null,
+                    x, y, width, height, 0, 0, 
+                    colors.length > 1 ? 'url(#' + grad_id + ')' : colors[0], 
+                    null, null, null,
+                    mask ? 'url(#' + mask_id + ')' : false),
+    ]); 
+  };
+  
+  /**
+    * To create an svg gradient.
+    * @param {Array} colors. An array of css color values.
+    * @param {Boolean} rotate. If turned on, the gradient will be 
+    * turned by 90 degrees.
+    * @param  {Boolean} mask. If turned on the gradient will be masked with 
+    * an alpha gradient 0 - 1, turned by 90 degrees.
+    * @param {Number} x. The x position. Defaults to 0.
+    * @param {Number} y. The y position. Defaults to 0.
+    * @param {Number} width. The width. Defaults to 100%.
+    * @param {Number} height. The height. Defaults to 100%.
+    */
+  this.gradient = function(colors, rotate, mask, x, y, width, height)
+  {
+    x || (x = 0);
+    y || (y = 0);
+    width || (width = '100%');
+    height || (height = '100%');
+    
+    return (
+    ['svg:svg',
+      this.svg_gradient(x, y, width, height, colors, rotate, mask),
+      'width', '100%',
+      'height', '100%',
+      'version', '1.1'
+    ]);
+  };
+  
+  /**
+    * To create an 2d svg gradient, e.g a layer in a rgb color space.
+    * @param {Array} top_colors. An array of css color values 
+    * for top-left to top-right.
+    * @param {Array} bottom_colors. An array of css color values 
+    * for bottom-left to bottom-right.
+    * @param {Number} x. The x position. Defaults to 0.
+    * @param {Number} y. The y position. Defaults to 0.
+    * @param {Number} width. The width. Defaults to 100%.
+    * @param {Number} height. The height. Defaults to 100%.
+    */
+  this.gradient_2d = function(top_colors, bottom_colors, x, y, width, height)
+  {
+    x || (x = 0);
+    y || (y = 0);
+    width || (width = '100%');
+    height || (height = '100%');
+    
+    return (
+    ['svg:svg',
+      this.svg_gradient(x, y, width, height, top_colors),
+      this.svg_gradient(x, y, width, height, bottom_colors, false, true),
+      'width', '100%',
+      'height', '100%',
+      'version', '1.1'
+    ]);
+  };
+  
+  this.color_picker_inputs = function(z_axis)
+  { 
+
+    const COLORSPACE = 0, Z = 4;
+
+    var 
+    set_checked = function(colorspace)
+    {
+      if (colorspace[COLORSPACE][Z] == z_axis)
+        colorspace.push('checked')
+      return colorspace;
+    },
+    shv_inputs = 
+    [
+      ['s-v-h', 'h', 'H:', 'number', '°', '0', '360'],
+      ['h-v-s', 's', 'S:', 'number', '%', '0', '100'],
+      ['h-s-v', 'v', 'V:', 'number', '%', '0', '100'],
+    ].map(set_checked),
+    rgb_inputs = 
+    [
+      ['b-g-r', 'r', 'R:', 'number', null, '0', '255'],
+      ['b-r-g', 'g', 'G:', 'number', null, '0', '255'],
+      ['r-g-b', 'b', 'B:', 'number', null, '0', '255'],
+    ].map(set_checked),
+    hex_inputs =
+    [
+      [null, 'hex', '#', 'text'],
+    ];
+    
+    return (
+    ['form', 
+      ['table', 
+        shv_inputs.map(this.color_picker_inputs_row, this),
+        ['tr', ['td', 'class', 'color-picker-spacer', 'colspan', '4']],
+        rgb_inputs.map(this.color_picker_inputs_row, this),
+        ['tr', ['td', 'class', 'color-picker-spacer', 'colspan', '4']],
+        hex_inputs.map(this.color_picker_inputs_row, this),
+        'class', 'color-picker-inputs'
+      ]
+    ]);
+  };
+  
+  this.color_picker_inputs_row = function(input, index)
+  {
+    const 
+    COLOR_SPACE = 0, 
+    METHOD = 1, 
+    LABEL = 2, 
+    TYPE = 3, 
+    UNITS = 4, 
+    MIN = 5, 
+    MAX = 6,
+    CHECKED = 7;
+    
+    return (
+    ['tr', 
+      ['td',
+        input[COLOR_SPACE] ? 
+        ['input', 
+          'type', 'radio', 
+          'name', 'color-space', 
+          'value', input[COLOR_SPACE]
+        ].concat(input[CHECKED] ? ['checked', 'checked'] : []) :
+        []
+      ],
+      ['td', input[LABEL]], 
+      ['td',
+        ['input', 
+          'name', input[METHOD], 
+          'type', input[TYPE],
+          'class', 'color-picker-' + input[TYPE], 
+        ].concat(input[MIN] ? ['min', input[MIN], 'max', input[MAX]] : []),
+      ].concat(input[UNITS] ? [] : ['colspan', '2']),
+      input[UNITS] ? ['td', input[UNITS]] : []
+    ])
+  }
+
+  this.slider_focus_catcher = function()
+  {
+    return (
+    ['input', 'style', 'display: block; ' +
+                       'position:absolute; ' +
+                       'left: -1000px; ' +
+                       'top:0; ' +
+                       'width: 10px;'
+    ]);
+  }
+  
+  this.color_picker_popup = function(existing_color, cp_class, cp_2d_class, 
+                                     cp_1d_class, cp_old_class, cp_new_class,
+                                     z_axis, cp_alpha_class, cp_alpha_bg)
+  {
+    var has_alpha = typeof existing_color.alpha == "number";
+    return (
+    ['div',
+      ['div',
+        ['div', 'class', 'height-100'],
+        'data-handler', 'onxy',
+        'class', cp_2d_class
+      ],
+      ['div',
+        ['div', 'class', 'height-100'],
+        'data-handler', 'onz',
+        'class', cp_1d_class
+      ],
+      window.templates.color_picker_inputs(z_axis), 
+      has_alpha ? 
+      ['svg:svg',
+        this.svg_rect(null, null, 0, 0, 100, 36, 0, 0, "#000"), 
+        ['path', 
+          'd', 'M 50 0 l -50 0 l 0 36 l 50 -36 l 50 0 l -50 36 z',
+          'fill', '#fff', 
+        ],
+        'viewBox', '0 0 100px 36px',
+        'version', '1.1',
+        'class', 'color-sample-alpha-bg'
+      ] : [],
+      ['div', 
+        'class', cp_old_class, 
+        'data-color', 'cancel',
+        'style', 'background-color:' + existing_color.rgba
+      ],
+      ['div', 'class', cp_new_class],
+      has_alpha ?
+      ['div',
+        ['div', 'class', 'height-100'],
+        'data-handler', 'onalpha',
+        'class', cp_alpha_class
+      ] : [],
+      has_alpha ?
+      ['div',
+        ['label',
+          'alpha: ',
+          ['input', 
+            'name', 'alpha', 
+            'type', 'number', 
+            'min', '0', 
+            'max', '1',
+            'step', '0.01',
+            'class', cp_alpha_bg, 
+          ],
+        ],
+        'class', 'color-picker-input-alpha'
+      ]: [],
+      'class', cp_class + (has_alpha ? ' alpha' : '')
+    ]);
+  }
+
+  this.svg_slider_z = function(rotate)
+  {
+    return (
+    ['svg:svg',
+        ['path', 
+          'd', rotate ? 
+               'M 0.5 0.5 l 18 0 l 0 6 l -9 12 l -9 -12 z' :
+               'M 0.5 0.5 l 0 18 l 6 0 l 12 -9 l -12 -9 z',
+          'fill', 'hsl(0, 0%, 70%)', 
+          'stroke', '#000', 
+          'stroke-width', '1'
+        ],
+        ['path', 
+          'd', rotate ?
+               'M 0.5 79.5 l 18 0 l 0 -6 l -9 -12 l -9 12 z' : 
+               'M 79.5 0.5 l 0 18 l -6 0 l -12 -9 l 12 -9 z', 
+          'fill', 'hsl(0, 0%, 70%)', 
+          'stroke', '#000', 
+          'stroke-width', '1'
+        ],
+      'viewBox', rotate ? '0 0 20 80' : '0 0 80 20',
+      'version', '1.1'
+    ]);
+  }
+  
+  this.slider = function(slider_base_class, slider_class, slider_template)
+  {
+    return (
+    ['div',
+      ['div',
+        slider_template || this.svg_slider_z(),
+        'class', slider_class
+      ],
+      'class', slider_base_class
+    ]);
+  }
+
+  this.cubic_bezier = function(cubic_bezier_base_class)
+  {
+    const 
+    BORDER = 10, 
+    DELTA = 100,
+    ITER = 10, 
+    WIDTH = 2 * DELTA + 100;
+
+    return (
+    ['div',
+      ['svg:svg',
+        this.svg_rect(null, null, 10, 10, 300, 300, 0, 0, 'hsl(0, 0%, 98%)', 'hsl(0, 0%, 50%)', 1),
+        this._svg_line(null, null, 
+                       BORDER, BORDER + WIDTH / 2, BORDER + WIDTH, BORDER + WIDTH / 2, 
+                       'none', 'hsl(0, 0%, 80%)', WIDTH, '1 9', .2),
+        this._svg_line(null, null, 
+                       BORDER + WIDTH / 2, BORDER, BORDER + WIDTH / 2, BORDER + WIDTH, 
+                       'none', 'hsl(0, 0%, 80%)', WIDTH, '1 9', .2),
+        'viewBox', '0 0 320 320',
+        'version', '1.1'
+      ],
+      'class', cubic_bezier_base_class
+    ]);
+  }
+
+  this._reduce_path = function(str, point)
+  { 
+    const X = 0, Y = 1;
+    return str + point[X] + ', ' + point[Y] + ' ';
+  }
+
+  this.svg_cubic_bezier = function(p1x, p1y, p2x, p2y, CLASS_P1, CLASS_P2)
+  {
+    const BORDER = 10, DELTA = 100;
+    var 
+    x0 = BORDER + DELTA,
+    y0 = BORDER + DELTA + 100,
+    x1 = BORDER + DELTA + 100,
+    y1 = BORDER + DELTA,
+    path = '';
+
+    p1x += x0;
+    p1y = y0 - p1y;
+    p2x += x0;
+    p2y = y0 - p2y;
+    path = ['M', [[x0, y0]].reduce(this._reduce_path, ' '), 
+            'C', [[p1x, p1y], 
+                  [p2x, p2y], 
+                  [x1, y1]].reduce(this._reduce_path, ' ')
+           ].join('');
+
+    return (
+    [
+      this._svg_line(null, null, x0, y0, p1x, p1y, 'none', 'black', .5, .5),
+      this._svg_line(null, null, x1, y1, p2x, p2y, 'none', 'black', .5, .5),
+      this._svg_circle(null, CLASS_P1, p1x, p1y, 5, 'hsl(0, 0%, 70%)', 'black', .5),
+      this._svg_circle(null, CLASS_P2, p2x, p2y, 5, 'hsl(0, 0%, 70%)', 'black', .5),
+      this._svg_path(null, null, path, 'none', 'red', .5),
+    ]);
+  }
+
+  this._svg_pattern = function(id)
+  {
+    return (
+    ['pattern',
+      this._svg_line(null, null, 0, 0, 10, 0, 'none', 'hsl(0, 0%, 75%)', 1, .5),
+      this._svg_line(null, null, 0, 0, 0, 10, 'none', 'hsl(0, 0%, 75%)', 1, .5),
+      'id', id,
+      'patternUnits', 'userSpaceOnUse',
+      'x', '-.5',
+      'y', '-.5',
+      'width', '10',
+      'height', '10',
+      'viewBox', '0 0 10 10',
+    ]);
+  }
+
+  this.svg_rect = function(id, _class, x, y, width, height, rx, ry, fill, stroke, stroke_width, opacity, mask)
+  {
+    return (
+    [
+      ['id', id],
+      ['class', _class],
+      ['x', x],
+      ['y', y],
+      ['width', width],
+      ['height', height],
+      ['rx', rx],
+      ['ry', ry],
+      ['fill', fill],
+      ['stroke', stroke],
+      ['stroke-width', stroke_width],
+      ['opacity', opacity],
+      ['mask', mask],
+    ].reduce(this._svg_reduce_attrs, ['rect']));
+  };
+
+  this._svg_line = function(id, _class, x1, y1, x2, y2, fill, stroke, stroke_width, stroke_dasharray, stroke_opacity, opacity)
+  {
+    return (
+    [
+      ['id', id],
+      ['class', _class],
+      ['x1', x1],
+      ['y1', y1],
+      ['x2', x2],
+      ['y2', y2],
+      ['fill', fill],
+      ['stroke', stroke],
+      ['stroke-width', stroke_width],
+      ['stroke-dasharray', stroke_dasharray],
+      ['stroke-opacity', stroke_opacity],
+      ['opacity', opacity],
+    ].reduce(this._svg_reduce_attrs, ['line']));
+  }
+
+  this._svg_path = function(id, _class, d, fill, stroke, stroke_width, opacity)
+  {
+    return (
+    [
+      ['id', id],
+      ['class', _class],
+      ['d', d],
+      ['fill', fill],
+      ['stroke', stroke],
+      ['stroke-width', stroke_width],
+      ['opacity', opacity],
+    ].reduce(this._svg_reduce_attrs, ['path']));
+  }
+
+  this._svg_circle = function(id, _class, cx, cy, r, fill, stroke, stroke_width, opacity)
+  {
+    return (
+    [
+      ['id', id],
+      ['class', _class],
+      ['cx', cx],
+      ['cy', cy],
+      ['r', r],
+      ['fill', fill],
+      ['stroke', stroke],
+      ['stroke-width', stroke_width],
+      ['opacity', opacity],
+    ].reduce(this._svg_reduce_attrs, ['circle']));
+  }
+
+  this._svg_reduce_attrs = function(list, item)
+  {
+    const KEY = 0, VALUE = 1;
+    if (!(item[VALUE] === null || item[VALUE] === undefined))
+      list.push(item[KEY], String(item[VALUE]));
+    return list;
+  }
+
+  this.svg_slider_circle = function()
+  {
+    return (
+    ['svg:svg',
+      ['circle', 
+        'cx', '10', 
+        'cy', '10', 
+        'r', '9.5', 
+        'fill', 'none', 
+        'stroke', 
+        'hsl(0, 0%, 20%)', 
+        'stroke-width', '1'
+      ],
+      'viewBox', '0 0 20 20',
+      'version', '1.1'
+    ]);
+  }
+  
+  this.pointer = function(pointer_class)
+  {
+    return (
+    ['div',
+      this.svg_slider_circle(),
+      'class', pointer_class
+    ]);
+  }
+  
+}).apply(window.templates || (window.templates = {}));

File resources/ui-scripts/colorpickertemplates.js

 {
 
   /**
-    * Templates with svg_ prefix are meant to be used as part 
+    * Templates with svg_ prefix are meant to be used as part
     * of an other svg template, e.g. the svg root element must already exist.
     */
   var get_id = (function()
       return "svg-uid-" + (++id_count);
     };
   })();
-  
-  this.svg_stop = function(offset, stop_color, stop_opacity)
+
+  this.color_picker_inputs = function(z_axis, alpha_disabled)
   {
-    return (
-    ['stop', 
-      'offset', offset, 
-      'stop-color', stop_color
-    ].concat( stop_opacity ? ['stop-opacity', stop_opacity] : [] ));
-  };
-
-  this.svg_liner_gradient = function(id, colors, rotate)
-  {
-    var ret = ['linearGradient'];
-    var count = colors.length - 1;
-    ret.push(colors.map(function(color, index, colors)
-    {
-      return this.svg_stop((index / count * 100).toFixed(2) + '%', color);
-    }, this));
-    ret.push('id', id, 'gradientUnits', 'objectBoundingBox');
-    if (rotate) ret.push('x1', '50%', 'y1', '100%', 'x2', '50%', 'y2', '0%');
-    return ret;
-  }
-  /*
-  this.svg_rect = function(x, y, width, height, rx, ry, fill, mask, id)
-  {
-    var ret = 
-    ['rect', 
-      'x', x.toString(), 
-      'y', y.toString(), 
-      'width', width.toString(), 
-      'height', height.toString()
-    ];
-    if (rx) 
-      ret.push('rx', rx.toString());
-    if (ry) 
-      ret.push('ry', ry.toString());
-    if (fill) 
-      ret.push('fill', fill);
-    if (mask) 
-      ret.push('mask', mask);
-    if (id) 
-      ret.push('id', id);
-    return ret;
-  };
-  */
-  this.svg_liner_mask = function(id, rotate)
-  {
-    var grad_id = get_id();
-    return ( 
-    [
-      this.svg_liner_gradient(grad_id, ['#fff', '#000'], rotate),
-      ['mask',
-        this.svg_rect(null, null, 0, 0, '100%', '100%', 0, 0, 'url(#' + grad_id + ')'),
-        'maskUnits', 'objectBoundingBox',
-        'x', '0', 
-        'y', '0',
-        'width', '100%',  
-        'height', '100%',
-        'id', id
-      ]
-    ]);
-  };
-  
-  this.svg_gradient = function(x, y, width, height, colors, rotate, mask)
-  {
-    var svg_defs = ['defs'];
-    if (colors.length > 1)
-    {
-      var grad_id = get_id();
-      svg_defs.push(this.svg_liner_gradient(grad_id, colors, rotate));
-    }
-    if (mask)
-    {
-      var mask_id = get_id();
-      svg_defs.push(this.svg_liner_mask(mask_id, true));
-    }
-    return (
-    [
-      svg_defs,
-      this.svg_rect(null, null,
-                    x, y, width, height, 0, 0, 
-                    colors.length > 1 ? 'url(#' + grad_id + ')' : colors[0], 
-                    null, null, null,
-                    mask ? 'url(#' + mask_id + ')' : false),
-    ]); 
-  };
-  
-  /**
-    * To create an svg gradient.
-    * @param {Array} colors. An array of css color values.
-    * @param {Boolean} rotate. If turned on, the gradient will be 
-    * turned by 90 degrees.
-    * @param  {Boolean} mask. If turned on the gradient will be masked with 
-    * an alpha gradient 0 - 1, turned by 90 degrees.
-    * @param {Number} x. The x position. Defaults to 0.
-    * @param {Number} y. The y position. Defaults to 0.
-    * @param {Number} width. The width. Defaults to 100%.
-    * @param {Number} height. The height. Defaults to 100%.
-    */
-  this.gradient = function(colors, rotate, mask, x, y, width, height)
-  {
-    x || (x = 0);
-    y || (y = 0);
-    width || (width = '100%');
-    height || (height = '100%');
-    
-    return (
-    ['svg:svg',
-      this.svg_gradient(x, y, width, height, colors, rotate, mask),
-      'width', '100%',
-      'height', '100%',
-      'version', '1.1'
-    ]);
-  };
-  
-  /**
-    * To create an 2d svg gradient, e.g a layer in a rgb color space.
-    * @param {Array} top_colors. An array of css color values 
-    * for top-left to top-right.
-    * @param {Array} bottom_colors. An array of css color values 
-    * for bottom-left to bottom-right.
-    * @param {Number} x. The x position. Defaults to 0.
-    * @param {Number} y. The y position. Defaults to 0.
-    * @param {Number} width. The width. Defaults to 100%.
-    * @param {Number} height. The height. Defaults to 100%.
-    */
-  this.gradient_2d = function(top_colors, bottom_colors, x, y, width, height)
-  {
-    x || (x = 0);
-    y || (y = 0);
-    width || (width = '100%');
-    height || (height = '100%');
-    
-    return (
-    ['svg:svg',
-      this.svg_gradient(x, y, width, height, top_colors),
-      this.svg_gradient(x, y, width, height, bottom_colors, false, true),
-      'width', '100%',
-      'height', '100%',
-      'version', '1.1'
-    ]);
-  };
-  
-  this.color_picker_inputs = function(z_axis)
-  { 
 
     const COLORSPACE = 0, Z = 4;
 
-    var 
+    var
     set_checked = function(colorspace)
     {
       if (colorspace[COLORSPACE][Z] == z_axis)
         colorspace.push('checked')
       return colorspace;
     },
-    shv_inputs = 
+    shv_inputs =
     [
-      ['s-v-h', 'h', 'H:', 'number', '°', '0', '360'],
-      ['h-v-s', 's', 'S:', 'number', '%', '0', '100'],
-      ['h-s-v', 'v', 'V:', 'number', '%', '0', '100'],
+      ['s-v-h', 'h', 'h:', 'number', '°', '0', '360'],
+      ['h-v-s', 's', 's:', 'number', '%', '0', '100'],
+      ['h-s-v', 'v', 'v:', 'number', '%', '0', '100'],
     ].map(set_checked),
-    rgb_inputs = 
+    rgb_inputs =
     [
-      ['b-g-r', 'r', 'R:', 'number', null, '0', '255'],
-      ['b-r-g', 'g', 'G:', 'number', null, '0', '255'],
-      ['r-g-b', 'b', 'B:', 'number', null, '0', '255'],
+      ['b-g-r', 'r', 'r:', 'number', null, '0', '255'],
+      ['b-r-g', 'g', 'g:', 'number', null, '0', '255'],
+      ['r-g-b', 'b', 'b:', 'number', null, '0', '255'],
     ].map(set_checked),
-    hex_inputs =
-    [
-      [null, 'hex', '#', 'text'],
+    alpha_input =
+    ["tr",
+       ["td", ""],
+       ["td", "α: "],
+       ["td",
+         ["input",
+          'name', 'alpha',
+          'type', 'number',
+          'min', '0',
+          'max', '1',
+          'step', '0.05',
+          'class', 'color-picker-number',
+          'disabled', alpha_disabled
+         ]
+       ]
     ];
-    
+
     return (
-    ['form', 
-      ['table', 
+    ['form',
+      ['table',
         shv_inputs.map(this.color_picker_inputs_row, this),
-        ['tr', ['td', 'class', 'color-picker-spacer', 'colspan', '4']],
+        ['tr', ['td', 'class', 'color-picker-spacer', 'colspan', '3']],
         rgb_inputs.map(this.color_picker_inputs_row, this),
-        ['tr', ['td', 'class', 'color-picker-spacer', 'colspan', '4']],
-        hex_inputs.map(this.color_picker_inputs_row, this),
+        ['tr', ['td', 'class', 'color-picker-spacer', 'colspan', '3']],
+        alpha_input,
         'class', 'color-picker-inputs'
       ]
     ]);
   };
-  
+
   this.color_picker_inputs_row = function(input, index)
   {
-    const 
-    COLOR_SPACE = 0, 
-    METHOD = 1, 
-    LABEL = 2, 
-    TYPE = 3, 
-    UNITS = 4, 
-    MIN = 5, 
+    const
+    COLOR_SPACE = 0,
+    METHOD = 1,
+    LABEL = 2,
+    TYPE = 3,
+    UNITS = 4,
+    MIN = 5,
     MAX = 6,
     CHECKED = 7;
-    
+
+    var id = 'color-picker-input-' + input[METHOD];
+
     return (
-    ['tr', 
+    ['tr',
       ['td',
-        input[COLOR_SPACE] ? 
-        ['input', 
-          'type', 'radio', 
-          'name', 'color-space', 
+        input[COLOR_SPACE] ?
+        ['input',
+          'type', 'radio',
+          'name', 'color-space',
           'value', input[COLOR_SPACE]
         ].concat(input[CHECKED] ? ['checked', 'checked'] : []) :
         []
       ],
-      ['td', input[LABEL]], 
       ['td',
-        ['input', 
-          'name', input[METHOD], 
+        ['label',
+            input[LABEL],
+         'for', id
+        ]
+      ],
+      ['td',
+        ['input',
+          'name', input[METHOD],
           'type', input[TYPE],
-          'class', 'color-picker-' + input[TYPE], 
+          'class', 'color-picker-' + input[TYPE],
+          'id', id,
         ].concat(input[MIN] ? ['min', input[MIN], 'max', input[MAX]] : []),
-      ].concat(input[UNITS] ? [] : ['colspan', '2']),
-      input[UNITS] ? ['td', input[UNITS]] : []
+        input[UNITS] ? ["span", " " + input[UNITS], "class", "color-picker-input-unit"]
+                     : ""
+      ]
     ])
   }
 
                        'width: 10px;'
     ]);
   }
-  
-  this.color_picker_popup = function(existing_color, cp_class, cp_2d_class, 
+
+  this.color_picker_popup = function(existing_color, cp_class, cp_2d_class,
                                      cp_1d_class, cp_old_class, cp_new_class,
-                                     z_axis, cp_alpha_class, cp_alpha_bg)
+                                     z_axis, alpha_disabled, palette_disabled)
   {
-    var has_alpha = typeof existing_color.alpha == "number";
     return (
-    ['div',
       ['div',
-        ['div', 'class', 'height-100'],
-        'data-handler', 'onxy',
-        'class', cp_2d_class
-      ],
+        ['div', 'class', 'color-picker-arrow'],
+        ['div',
+          ['canvas', 'class', 'cp-canvas'],
+          'data-handler', 'onxy',
+          'class', cp_2d_class
+        ],
+        ['div',
+          ['canvas', 'class', 'cp-canvas'],
+          'data-handler', 'onz',
+          'class', cp_1d_class
+        ],
+        window.templates.color_picker_inputs(z_axis, alpha_disabled),
+        ["input", "name", "hex", "class", "color-picker-text"],
+        ["div",
+          ['div',
+            'class', cp_old_class,
+            'data-color', 'cancel',
+            'style', 'background-color:' + existing_color.rgba
+          ],
+          ['div', 'class', cp_new_class],
+         "class", "color-picker-color-alpha-bg"
+        ],
+        window.templates.color_picker_palette_dropdown(palette_disabled),
+        ["div",
+          ["span",
+             ui_strings.S_BUTTON_CANCEL,
+           "class", "ui-button",
+           "handler", "color-picker-cancel",
+           "tabindex", "1"
+          ],
+          ["span",
+             ui_strings.S_BUTTON_OK,
+           "class", "ui-button",
+           "handler", "color-picker-ok",
+           "tabindex", "1"
+          ],
+         "class", "color-picker-controls"
+        ],
+       'class', cp_class
+      ]
+    );
+  };
+
+  this.color_picker_palette_dropdown = function(palette_disabled)
+  {
+    if (window.cls && cls.ColorPalette)
+    {
+      var MAX_PALETTE_ITEMS = 9;
+      var FALLBACK_COLOR = "999";
+      var palette = cls.ColorPalette.get_instance().get_color_palette().slice(0, MAX_PALETTE_ITEMS);
+      var colors = palette.map(function(item) { return item.color; });
+      while (colors.length < MAX_PALETTE_ITEMS)
+      {
+        colors.push(FALLBACK_COLOR);
+      }
+
+      return ([
+        "div",
+          colors.map(this._color_picker_palette_square),
+        "class", "color-picker-palette-dropdown" + (palette_disabled ? " disabled" : ""),
+        "data-tooltip", !palette_disabled && "color-palette"
+      ]);
+    }
+    return [];
+  };
+
+  this._color_picker_palette_square = function(color)
+  {
+    return [
+      "div",
+      "class", "color-picker-palette-square",
+      "style", "background-color: #" + color
+    ];
+  };
+
+  this.color_picker_palette = function()
+  {
+    if (window.cls && cls.ColorPalette)
+    {
+      var palette = cls.ColorPalette.get_instance().get_color_palette();
+      return (
       ['div',
-        ['div', 'class', 'height-100'],
-        'data-handler', 'onz',
-        'class', cp_1d_class
-      ],
-      window.templates.color_picker_inputs(z_axis), 
-      has_alpha ? 
-      ['svg:svg',
-        this.svg_rect(null, null, 0, 0, 100, 36, 0, 0, "#000"), 
-        ['path', 
-          'd', 'M 50 0 l -50 0 l 0 36 l 50 -36 l 50 0 l -50 36 z',
-          'fill', '#fff', 
-        ],
-        'viewBox', '0 0 100px 36px',
-        'version', '1.1',
-        'class', 'color-sample-alpha-bg'
-      ] : [],
-      ['div', 
-        'class', cp_old_class, 
-        'data-color', 'cancel',
-        'style', 'background-color:' + existing_color.rgba
-      ],
-      ['div', 'class', cp_new_class],
-      has_alpha ?
-      ['div',
-        ['div', 'class', 'height-100'],
-        'data-handler', 'onalpha',
-        'class', cp_alpha_class
-      ] : [],
-      has_alpha ?
-      ['div',
-        ['label',
-          'alpha: ',
-          ['input', 
-            'name', 'alpha', 
-            'type', 'number', 
-            'min', '0', 
-            'max', '1',
-            'step', '0.01',
-            'class', cp_alpha_bg, 
-          ],
-        ],
-        'class', 'color-picker-input-alpha'
-      ]: [],
-      'class', cp_class + (has_alpha ? ' alpha' : '')
+        this.color_picker_palette_item_add(),
+        palette.map(this.color_picker_palette_item, this),
+       'class', 'color-picker-palette',
+       'data-menu', 'color-picker-palette'
+      ]);
+    }
+    return [];
+  };
+
+  this.color_picker_palette_item_add = function()
+  {
+    return ([
+      "span",
+      "handler", "add-color-picker-color",
+      "class", "color-picker-palette-item-add ui-control ui-button"
     ]);
   }
 
-  this.svg_slider_z = function(rotate)
+  this.color_picker_palette_item = function(item)
   {
     return (
-    ['svg:svg',
-        ['path', 
-          'd', rotate ? 
-               'M 0.5 0.5 l 18 0 l 0 6 l -9 12 l -9 -12 z' :
-               'M 0.5 0.5 l 0 18 l 6 0 l 12 -9 l -12 -9 z',
-          'fill', 'hsl(0, 0%, 70%)', 
-          'stroke', '#000', 
-          'stroke-width', '1'
-        ],
-        ['path', 
-          'd', rotate ?
-               'M 0.5 79.5 l 18 0 l 0 -6 l -9 -12 l -9 12 z' : 
-               'M 79.5 0.5 l 0 18 l -6 0 l -12 -9 l 12 -9 z', 
-          'fill', 'hsl(0, 0%, 70%)', 
-          'stroke', '#000', 
-          'stroke-width', '1'
-        ],
-      'viewBox', rotate ? '0 0 20 80' : '0 0 80 20',
-      'version', '1.1'
-    ]);
+    ['span',
+      'data-color', item.color,
+      'data-color-id', String(item.id),
+      'handler', 'set-color-picker-color',
+      'style', 'background-color: #' + item.color,
+      'class', 'color-picker-palette-item']);
   }
-  
+
   this.slider = function(slider_base_class, slider_class, slider_template)
   {
     return (
     ['div',
       ['div',
-        slider_template || this.svg_slider_z(),
+        slider_template || [],
         'class', slider_class
       ],
       'class', slider_base_class
     ]);
   }
 
-  this.cubic_bezier = function(cubic_bezier_base_class)
-  {
-    const 
-    BORDER = 10, 
-    DELTA = 100,
-    ITER = 10, 
-    WIDTH = 2 * DELTA + 100;
-
-    return (
-    ['div',
-      ['svg:svg',
-        this.svg_rect(null, null, 10, 10, 300, 300, 0, 0, 'hsl(0, 0%, 98%)', 'hsl(0, 0%, 50%)', 1),
-        this._svg_line(null, null, 
-                       BORDER, BORDER + WIDTH / 2, BORDER + WIDTH, BORDER + WIDTH / 2, 
-                       'none', 'hsl(0, 0%, 80%)', WIDTH, '1 9', .2),
-        this._svg_line(null, null, 
-                       BORDER + WIDTH / 2, BORDER, BORDER + WIDTH / 2, BORDER + WIDTH, 
-                       'none', 'hsl(0, 0%, 80%)', WIDTH, '1 9', .2),
-        'viewBox', '0 0 320 320',
-        'version', '1.1'
-      ],
-      'class', cubic_bezier_base_class
-    ]);
-  }
-
-  this._reduce_path = function(str, point)
-  { 
-    const X = 0, Y = 1;
-    return str + point[X] + ', ' + point[Y] + ' ';
-  }
-
-  this.svg_cubic_bezier = function(p1x, p1y, p2x, p2y, CLASS_P1, CLASS_P2)
-  {
-    const BORDER = 10, DELTA = 100;
-    var 
-    x0 = BORDER + DELTA,
-    y0 = BORDER + DELTA + 100,
-    x1 = BORDER + DELTA + 100,
-    y1 = BORDER + DELTA,
-    path = '';
-
-    p1x += x0;
-    p1y = y0 - p1y;
-    p2x += x0;
-    p2y = y0 - p2y;
-    path = ['M', [[x0, y0]].reduce(this._reduce_path, ' '), 
-            'C', [[p1x, p1y], 
-                  [p2x, p2y], 
-                  [x1, y1]].reduce(this._reduce_path, ' ')
-           ].join('');
-
-    return (
-    [
-      this._svg_line(null, null, x0, y0, p1x, p1y, 'none', 'black', .5, .5),
-      this._svg_line(null, null, x1, y1, p2x, p2y, 'none', 'black', .5, .5),
-      this._svg_circle(null, CLASS_P1, p1x, p1y, 5, 'hsl(0, 0%, 70%)', 'black', .5),
-      this._svg_circle(null, CLASS_P2, p2x, p2y, 5, 'hsl(0, 0%, 70%)', 'black', .5),
-      this._svg_path(null, null, path, 'none', 'red', .5),
-    ]);
-  }
-
-  this._svg_pattern = function(id)
-  {
-    return (
-    ['pattern',
-      this._svg_line(null, null, 0, 0, 10, 0, 'none', 'hsl(0, 0%, 75%)', 1, .5),
-      this._svg_line(null, null, 0, 0, 0, 10, 'none', 'hsl(0, 0%, 75%)', 1, .5),
-      'id', id,
-      'patternUnits', 'userSpaceOnUse',
-      'x', '-.5',
-      'y', '-.5',
-      'width', '10',
-      'height', '10',
-      'viewBox', '0 0 10 10',
-    ]);
-  }
-
-  this.svg_rect = function(id, _class, x, y, width, height, rx, ry, fill, stroke, stroke_width, opacity, mask)
-  {
-    return (
-    [
-      ['id', id],
-      ['class', _class],
-      ['x', x],
-      ['y', y],
-      ['width', width],
-      ['height', height],
-      ['rx', rx],
-      ['ry', ry],
-      ['fill', fill],
-      ['stroke', stroke],
-      ['stroke-width', stroke_width],
-      ['opacity', opacity],
-      ['mask', mask],
-    ].reduce(this._svg_reduce_attrs, ['rect']));
-  };
-
-  this._svg_line = function(id, _class, x1, y1, x2, y2, fill, stroke, stroke_width, stroke_dasharray, stroke_opacity, opacity)
-  {
-    return (
-    [
-      ['id', id],
-      ['class', _class],
-      ['x1', x1],
-      ['y1', y1],
-      ['x2', x2],
-      ['y2', y2],
-      ['fill', fill],
-      ['stroke', stroke],
-      ['stroke-width', stroke_width],
-      ['stroke-dasharray', stroke_dasharray],
-      ['stroke-opacity', stroke_opacity],
-      ['opacity', opacity],
-    ].reduce(this._svg_reduce_attrs, ['line']));
-  }
-
-  this._svg_path = function(id, _class, d, fill, stroke, stroke_width, opacity)
-  {
-    return (
-    [
-      ['id', id],
-      ['class', _class],
-      ['d', d],
-      ['fill', fill],
-      ['stroke', stroke],
-      ['stroke-width', stroke_width],
-      ['opacity', opacity],
-    ].reduce(this._svg_reduce_attrs, ['path']));
-  }
-
-  this._svg_circle = function(id, _class, cx, cy, r, fill, stroke, stroke_width, opacity)
-  {
-    return (
-    [
-      ['i