Mercurial > code > home > repos > light9
changeset 2240:650375a47213
start color-picker port from polymer to lit
author | drewp@bigasterisk.com |
---|---|
date | Thu, 25 May 2023 11:47:20 -0700 |
parents | b136c450ebee |
children | 8d6792a6ffdb |
files | light9/web/light9-color-picker.html light9/web/light9-color-picker.ts |
diffstat | 2 files changed, 338 insertions(+), 339 deletions(-) [+] |
line wrap: on
line diff
--- a/light9/web/light9-color-picker.html Thu May 25 11:46:22 2023 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,339 +0,0 @@ -<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> -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/light9/web/light9-color-picker.ts Thu May 25 11:47:20 2023 -0700 @@ -0,0 +1,338 @@ +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"; + +//<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; + 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; + } + `, + ]; + render() { + return html` + <div id="largeRainbowComp"> + <div id="largeRainbow" on-mousemove="onCanvasMove" on-mouseup="hideLarge"></div> + <div id="largeCrosshair"></div> + </div> + `; + } + // more methods get added by Light9ColorPicker +} + +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"); + } +} + +@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; + } + + #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. */ + } + `, + ]; + 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> + </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> + `; + } + 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 = 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(); + 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)); + } +}