Files @ 04ed5d134973
Branch filter:

Location: light9/light9/web/light9-color-picker.html

drewp@bigasterisk.com
WIP draw prom metrics on homepage
<link rel="import" href="/lib/polymer/polymer.html">
<link rel="import" href="/lib/paper-slider/paper-slider.html">

<dom-module id="light9-color-picker-float">
  <template>
    <style>
     :host {
         z-index: 10;
         position: fixed;
         width: 400px;
         height: 200px;
         border: 10px solid #000;
         box-shadow: 8px 11px 40px 0px rgba(0, 0, 0, 0.74);
         /* This display (and border-color) are replaced later. */
         display: none;
     }
     #largeCrosshair {
         position: absolute;
         left: -60px;
         top: -62px;
         pointer-events: none;
     }
     #largeCrosshair {
         background: url(/colorpick_crosshair_large.svg);
         width: 1000px; 
         height: 1000px;
     }
     #largeRainbowComp {
         display: inline-block;
         overflow: hidden;
         position: relative;
     }
     #largeRainbowComp {
         position: absolute;
         left: 0x;
         top: 0;
     }
     #largeRainbow {
         background: url(/colorpick_rainbow_large.png);
         width: 400px; 
         height: 200px;
         user-select: none;
     }
    </style>
    <div id="largeRainbowComp">
      <div id="largeRainbow"
           on-mousemove="onCanvasMove"
           on-mouseup="hideLarge"
      ></div>
      <div id="largeCrosshair"></div>
    </div>
  </template>
  <script>
   class Light9ColorPickerFloat extends Polymer.Element {
     static get is() { return "light9-color-picker-float"; }
     // more methods get added by Light9ColorPicker
   }
   customElements.define(Light9ColorPickerFloat.is, Light9ColorPickerFloat);
  </script>
</dom-module>

<dom-module id="light9-color-picker">
  <template>
    <style>
     :host {
         position: relative;
         display: flex;
         align-items: center;
         flex-wrap: wrap;
         user-select: none;
     }
     
     #swatch {
         display: inline-block;
         width: 50px; 
         height: 30px;
         margin-right: 3px;
         border: 1px solid #333;
     }
     
     paper-slider {
         width: 160px;
     }
     
     #vee {
         display: flex;
         align-items: center;
     }
     
     #outOfBounds {
         user-select: none;
         z-index: 1;
         background: #00000060;
         position: fixed;
         left: 0;
         top: 0;
         width: 100%;
         height: 100%;
         display: none; /* Toggledlater. */
     }
    </style>
    <div id="swatch" style="background-color: {{color}}"
         on-mousedown="onDownSmall"></div>
    <span id="vee">
      V:
      <paper-slider min="0"
                    max="255"
                    step="1"
                    value="{{sliderWriteValue}}"
                    immediate-value="{{value}}"></paper-slider>
    </span>
    <!-- Temporary scrim on the rest of the page. It looks like we're dimming
         the page to look pretty, but really this is so we can track the mouse
         when it's outside the large canvas. -->
    <div id="outOfBounds"
         on-mousemove="onOutOfBoundsMove"
         on-mouseup="hideLarge"></div>
    <!--  Large might span multiple columns, and chrome won't
         send events for those parts. Workaround: take it out of
         the columns. -->
    <light9-color-picker-float id="large"></light9-color-picker-float>
  </template>
  <script src="/lib/color/one-color.js"></script>
  <script>
   class RainbowCanvas {
     constructor(url, size) {
       this.size = size;
       var elem = document.createElement('canvas');
       elem.width = size[0];
       elem.height = size[1];
       this.ctx = elem.getContext('2d');

       this.colorPos = {} // color: pos
       this._loaded = false;
       this._loadWatchers = []; // callbacks
       
       var img = new Image();
       img.onload = function() {
         this.ctx.drawImage(img, 0, 0);
         this._readImage();
         this._loaded = true;
         this._loadWatchers.forEach(function(cb) { cb(); });
         this._loadWatchers = [];
       }.bind(this);
       img.src = url;
     }
     onLoad(cb) {
       // we'll call this when posFor is available
       if (this._loaded) {
         cb();
         return;
       }
       this._loadWatchers.push(cb);
     }
     _readImage() {
       var data = this.ctx.getImageData(
         0, 0, this.size[0], this.size[1]).data;
       for (var y = 0; y < this.size[1]; y+=1) {
         for (var x = 0; x < this.size[0]; x+=1) {
           var base = (y * this.size[0] + x) * 4;
           let px = [data[base + 0],
                     data[base + 1],
                     data[base + 2], 255];
           if (px[0] == 0 && px[1] == 0 && px[2] == 0) {
             // (there's no black on the rainbow images)
             throw new Error(`color picker canvas (${this.size[0]}) returns 0,0,0`);
           }
           var c = one.color(px).hex();
           this.colorPos[c] = [x, y];
         }
       }
     }        
     colorAt(pos) {
       var data = this.ctx.getImageData(pos[0], pos[1], 1, 1).data;
       return one.color([data[0], data[1], data[2], 255]).hex();
     }
     posFor(color) {
       if (color == '#000000') {
         throw new Error('no match');
       }
       
       let bright = one.color(color).value(1).hex();
       let r = parseInt(bright.substr(1, 2), 16),
           g = parseInt(bright.substr(3, 2), 16),
           b = parseInt(bright.substr(5, 2), 16);
       
       // We may not have a match for this color exactly (e.g. on
       // the small image), so we have to search for a near one.
       
       // 0, 1, -1, 2, -2, ...
       let walk = function(x) { return -x + (x > 0 ? 0 : 1); }
       
       var radius = 8;
       for (var dr = 0; dr < radius; dr = walk(dr)) {
         for (var dg = 0; dg < radius; dg = walk(dg)) {
           for (var db = 0; db < radius; db = walk(db)) {
             // Don't need bounds check- out of range
             // corrupt colors just won't match.
             color = one.color([r + dr, g + dg, b + db, 255]).hex();
             var pos = this.colorPos[color];
             if (pos !== undefined) {
               return pos;
             }
           }
         }
       }
       throw new Error('no match');
     }
   }

   
   class Light9ColorPicker extends Polymer.Element {
     static get is() { return "light9-color-picker"; }
     static get properties() { return {
       color: { type: String, notify: true },
       hueSatColor: { type: String, notify: true, value: null },
       value: { type: Number, notify: true }, // 0..255
       sliderWriteValue: { type: Number, notify: true },
     }; }
     static get observers() { return [
       'readColor(color)',
       'onValue(value)',
       'writeColor(hueSatColor, value)'
     ]; }
     ready() {
       super.ready();
       if (!window.pickerCanvases) {
         window.pickerCanvases = {
           large: new RainbowCanvas(
             '/colorpick_rainbow_large.png', [400, 200]),           
         };
       }
       this.large = window.pickerCanvases.large;
       this.$.large.onCanvasMove = this.onCanvasMove.bind(this);
       this.$.large.hideLarge = this.hideLarge.bind(this);
       document.body.append(this.$.large);
     }
     disconnectedCallback() {
       super.disconnectedCallback();
       document.body.removeChild(this.$.large);
     }
     onValue(value) {
       if (this.hueSatColor === null) {
         this.hueSatColor = '#ffffff';
       }
       let neverBlack = .1 + .9 * value / 255;
       this.$.swatch.style.filter = `brightness(${neverBlack})`;
     }
     writeColor(hueSatColor, value) {
       if (hueSatColor === null || this.pauseWrites) { return; }
       this.color = one.color(hueSatColor).value(value / 255).hex();
       this.$.large.style.borderColor = this.color;
     }
     readColor(color) {
       if (this.$.large.style.display == 'block') {
         // for performance, don't do color searches on covered widget
         return;
       }

       this.pauseWrites = true;
       var colorValue = one.color(color).value() * 255;
       // writing back to immediate-value doesn't work on paper-slider
       this.sliderWriteValue = colorValue;

       // don't update this if only the value changed, or we desaturate
       this.hueSatColor = one.color(color).value(1).hex();

       this.pauseWrites = false;
     }    
     showLarge(x, y) {
       this.$.large.style.display = 'block';
       this.$.outOfBounds.style.display = 'block';
       try {
         let pos;
         try {
           pos = this.large.posFor(this.color);
         } catch(e) {
           pos = [-999, -999];
         }
         this.moveLargeCrosshair(pos);
         this.$.large.style.left = (x - this.clamp(pos[0], 0, 400)) + 'px';
         this.$.large.style.top = (y - this.clamp(pos[1], 0, 200)) + 'px';
       } catch(e) {
         this.moveLargeCrosshair([-999, -999]);
         this.$.large.style.left = (400 / 2) + 'px';
         this.$.large.style.top = (200 / 2) + 'px';
         return;
       }
     }
     hideLarge() {
       this.$.large.style.display = 'none';
       this.$.outOfBounds.style.display = 'none';

       if (this.color !== undefined) {
         this.readColor(this.color);
       }
       this.closeTime = Date.now();
     }
     onDownSmall(ev) {
       this.showLarge(ev.pageX, ev.pageY);
     }
     moveLargeCrosshair(pos) {
       const ch = this.$.large.shadowRoot.querySelector("#largeCrosshair");
       ch.style.left = (pos[0] - ch.offsetWidth / 2) + 'px';
       ch.style.top = (pos[1] - ch.offsetHeight / 2) + 'px';
     }
     onCanvasMove(ev) {
       if (ev.buttons != 1) {
         this.hideLarge();
         return;
       }
       var canvas = this.$.large.shadowRoot.querySelector('#largeRainbow');
       var pos = [ev.offsetX - canvas.offsetLeft,
                  ev.offsetY - canvas.offsetTop];
       this.setLargePoint(pos);
     }
     setLargePoint(pos) {
       this.moveLargeCrosshair(pos);
       this.hueSatColor = this.large.colorAt(pos);

       // special case: it's useless to adjust the hue/sat of black
       if (this.value == 0) {
         this.value = 255;
       }
     }
     onOutOfBoundsMove(ev) {
       const largeX = ev.offsetX - this.$.large.offsetLeft;
       const largeY = ev.offsetY - this.$.large.offsetTop;
       this.setLargePoint([this.clamp(largeX, 0, 400-1),
                           this.clamp(largeY, 0, 200-1)]);
     }
     clamp(x, lo, hi) {
       return Math.max(lo, Math.min(hi, x));
     }
   }
   customElements.define(Light9ColorPicker.is, Light9ColorPicker);
  </script>
</dom-module>