Changeset - 650375a47213
[Not reviewed]
default
1 0 1
drewp@bigasterisk.com - 20 months ago 2023-05-25 18:47:20
drewp@bigasterisk.com
start color-picker port from polymer to lit
1 file changed with 154 insertions and 155 deletions:
0 comments (0 inline, 0 general)
light9/web/light9-color-picker.ts
Show inline comments
 
file renamed from light9/web/light9-color-picker.html to light9/web/light9-color-picker.ts
 
<link rel="import" href="/lib/polymer/polymer.html">
 
<link rel="import" href="/lib/paper-slider/paper-slider.html">
 
import debug from "debug";
 
import { css, html, LitElement, PropertyValues } from "lit";
 
import { customElement, property } from "lit/decorators.js";
 
import { Literal, NamedNode } from "n3";
 
import { SyncedGraph } from "../web/SyncedGraph";
 
import { ControlValue } from "./Effect";
 
import { GraphToControls } from "./GraphToControls";
 
import { DeviceAttrRow } from "./Light9DeviceControl";
 
import { Choice } from "./Light9Listbox";
 
import { SubEvent } from "sub-events";
 

	
 
<dom-module id="light9-color-picker-float">
 
  <template>
 
    <style>
 
//<script src="/lib/color/one-color.js"></script>
 
const log = debug("control");
 

	
 
@customElement("light9-color-picker-float")
 
export class Light9ColorPickerFloat extends LitElement {
 
  static styles = [
 
    css`
 
     :host {
 
         z-index: 10;
 
         position: fixed;
 
         width: 400px;
 
         height: 200px;
 
         border: 10px solid #000;
 
@@ -38,33 +50,116 @@
 
     #largeRainbow {
 
         background: url(/colorpick_rainbow_large.png);
 
         width: 400px; 
 
         height: 200px;
 
         user-select: none;
 
     }
 
    </style>
 
    `,
 
  ];
 
  render() {
 
    return html`
 
    <div id="largeRainbowComp">
 
      <div id="largeRainbow"
 
           on-mousemove="onCanvasMove"
 
           on-mouseup="hideLarge"
 
      ></div>
 
        <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>
 

	
 
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
 

	
 
<dom-module id="light9-color-picker">
 
  <template>
 
    <style>
 
    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");
 
  }
 
}
 

	
 
@customElement("light9-color-picker")
 
export class Light9ColorPicker extends LitElement {
 
  static styles = [
 
    css`
 
     :host {
 
         position: relative;
 
         display: flex;
 
         align-items: center;
 
         flex-wrap: wrap;
 
         user-select: none;
 
@@ -95,142 +190,47 @@
 
         left: 0;
 
         top: 0;
 
         width: 100%;
 
         height: 100%;
 
         display: none; /* Toggledlater. */
 
     }
 
    </style>
 
    <div id="swatch" style="background-color: {{color}}"
 
         on-mousedown="onDownSmall"></div>
 
    `,
 
  ];
 
  render() {
 
    return html`
 
      <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>
 
        <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>
 
      <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 {
 
  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)'
 
     ]; }
 
    };
 
  }
 
  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]),           
 
        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);
 
@@ -238,24 +238,29 @@
 
     disconnectedCallback() {
 
       super.disconnectedCallback();
 
       document.body.removeChild(this.$.large);
 
     }
 
     onValue(value) {
 
       if (this.hueSatColor === null) {
 
         this.hueSatColor = '#ffffff';
 
      this.hueSatColor = "#ffffff";
 
       }
 
       let neverBlack = .1 + .9 * value / 255;
 
    let neverBlack = 0.1 + (0.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();
 
    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') {
 
    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;
 
@@ -265,56 +270,55 @@
 
       // 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';
 
    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';
 
      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';
 
      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';
 
    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';
 
    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];
 
    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);
 

	
 
@@ -323,17 +327,12 @@
 
         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)]);
 
    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>
 

	
0 comments (0 inline, 0 general)