# HG changeset patch
# User drewp@bigasterisk.com
# Date 2023-05-26 22:46:16
# Node ID 9645581bff2402536cc788190678d64e24fedf35
# Parent 8d6792a6ffdb425b7e61641c673d9f79f44e8231
redo color picker code in lit
diff --git a/light9/live/Light9LiveControl.ts b/light9/live/Light9LiveControl.ts
--- a/light9/live/Light9LiveControl.ts
+++ b/light9/live/Light9LiveControl.ts
@@ -2,15 +2,20 @@ import debug from "debug";
import { css, html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators.js";
import { Literal, NamedNode } from "n3";
+import { SubEvent } from "sub-events";
import { SyncedGraph } from "../web/SyncedGraph";
import { ControlValue } from "./Effect";
import { GraphToControls } from "./GraphToControls";
import { DeviceAttrRow } from "./Light9DeviceControl";
import { Choice } from "./Light9Listbox";
+export { Slider } from "@material/mwc-slider";
+export { Light9ColorPicker } from "../web/light9-color-picker";
const log = debug("control");
-export { Slider } from "@material/mwc-slider";
+
+const makeType = (d: "scalar" | "color" | "choice") => new NamedNode(`http://light9.bigasterisk.com/${d}`);
+
// UI for one device attr (of any type).
@customElement("light9-live-control")
export class Light9LiveControl extends LitElement {
@@ -25,45 +30,12 @@ export class Light9LiveControl extends L
#colorControls > * {
margin: 0 3px;
}
- #colorControls paper-slider {
- }
- paper-slider {
- width: 100%;
- height: 25px;
- }
-
- paper-slider {
- --paper-slider-knob-color: var(--paper-red-500);
- --paper-slider-active-color: var(--paper-red-500);
-
- --paper-slider-font-color: white;
- --paper-slider-input: {
- width: 75px;
-
- background: black;
- display: inline-block;
- }
- }
+ :host {
+ border: 2px solid white;
+ }
`,
];
- render() {
- if (this.dataType.value === "http://light9.bigasterisk.com/scalar") {
- return html` `;
- } else if (this.dataType.value === "http://light9.bigasterisk.com/color") {
- return html`
-
-
-
-
- `;
- } else if (this.dataType.value === "http://light9.bigasterisk.com/choice") {
- return html` `;
- } else {
- throw new Error(`${this.dataType} unknown`);
- }
- }
-
// passed from parent
@property() device!: NamedNode;
@property() dataType: NamedNode;
@@ -83,15 +55,34 @@ export class Light9LiveControl extends L
@property() pickedChoice: Choice | null = null;
@property() choiceValue: Choice | null = null;
+ valueChanged: SubEvent = new SubEvent();
+
constructor() {
super();
- this.dataType = new NamedNode("http://light9.bigasterisk.com/scalar");
+ this.dataType = makeType("color");
// getTopGraph().then((g) => {
// this.graph = g;
// // this.graph.runHandler(this.graphReads.bind(this), `${this.device} ${this.deviceAttrRow.uri} reads`);
// });
}
+ render() {
+ const dbg=html`
+ ]`
+ if (this.dataType.equals(makeType("scalar"))) {
+ return html`${dbg} `;
+ } else if (this.dataType.equals(makeType("color"))) {
+ return html` ${dbg}
+
+
+
+
+ `;
+ } else if (this.dataType.equals(makeType("choice"))) {
+ return html`${dbg} `;
+ }
+ }
+
// graphReads() {
// const U = this.graph.U();
// }
@@ -115,7 +106,7 @@ export class Light9LiveControl extends L
onGraphValueChanged(v: ControlValue | null) {
// log("change: control must display", v);
// this.enableChange = false;
- if (this.dataType.value == "http://light9.bigasterisk.com/scalar") {
+ if (this.dataType.equals(makeType("scalar"))) {
if (v !== null) {
setTimeout(() => {
// only needed once per page layout
diff --git a/light9/web/floating_color_picker.ts b/light9/web/floating_color_picker.ts
new file mode 100644
--- /dev/null
+++ b/light9/web/floating_color_picker.ts
@@ -0,0 +1,311 @@
+// Note that this file deals only with hue+sat. See Light9ColorPicker for the value component.
+
+import debug from "debug";
+import { css, html, LitElement } from "lit";
+import { customElement, query } from "lit/decorators.js";
+import color from "onecolor";
+import { SubEvent } from "sub-events";
+
+const log = debug("control");
+
+function clamp(x: number, lo: number, hi: number) {
+ return Math.max(lo, Math.min(hi, x));
+}
+
+class RainbowCoord {
+ // origin is rainbow top-lefft
+ constructor(public x: number, public y: number) {}
+}
+
+export class ClientCoord {
+ // origin is top-left of client viewport (regardless of page scroll)
+ constructor(public x: number, public y: number) {}
+}
+
+// Load the rainbow, and map between colors and pixels.
+class RainbowCanvas {
+ ctx: CanvasRenderingContext2D;
+ colorPos: { [color: string]: RainbowCoord } = {};
+ _loaded = false;
+ _loadWatchers: (() => void)[] = [];
+ constructor(url: string, public size: RainbowCoord) {
+ var elem = document.createElement("canvas");
+ elem.width = size.x;
+ elem.height = size.y;
+ this.ctx = elem.getContext("2d")!;
+
+ var img = new Image();
+ img.onload = () => {
+ this.ctx.drawImage(img, 0, 0);
+ this._readImage();
+ this._loaded = true;
+ this._loadWatchers.forEach(function (cb) {
+ cb();
+ });
+ this._loadWatchers = [];
+ };
+ img.src = url;
+ }
+
+ onLoad(cb: () => void) {
+ // 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.x, this.size.y).data;
+ for (var y = 0; y < this.size.y; y += 1) {
+ for (var x = 0; x < this.size.x; x += 1) {
+ var base = (y * this.size.x + 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.x}) returns 0,0,0`);
+ }
+ var c = color(px).hex();
+ this.colorPos[c] = new RainbowCoord(x, y);
+ }
+ }
+ }
+
+ colorAt(pos: RainbowCoord) {
+ var data = this.ctx.getImageData(pos.x, pos.y, 1, 1).data;
+ return color([data[0], data[1], data[2], 255]).hex();
+ }
+
+ posFor(col: string): RainbowCoord {
+ if (col == "#000000") {
+ throw new Error("no match");
+ }
+
+ let bright = color(col).value(1).hex();
+ let r = parseInt(bright.slice(1, 3), 16),
+ g = parseInt(bright.slice(3, 5), 16),
+ b = parseInt(bright.slice(5, 7), 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: number): number {
+ 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.
+ const color2 = color([r + dr, g + dg, b + db, 255]);
+ const pos = this.colorPos[color2.hex()];
+ if (pos !== undefined) {
+ return pos;
+ }
+ }
+ }
+ }
+ throw new Error("no match");
+ }
+}
+
+// One-per-page element that floats above everything. Plus the scrim element, which is also per-page.
+@customElement("light9-color-picker-float")
+class Light9ColorPickerFloat extends LitElement {
+ static styles = [
+ css`
+ :host {
+ z-index: 10;
+ position: fixed; /* host coords are the same as client coords /*
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+
+ /* Updated 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 {
+ z-index: 2;
+ position: relative;
+ width: 400px;
+ height: 200px;
+ border: 10px solid #000;
+ box-shadow: 8px 11px 40px 0px rgba(0, 0, 0, 0.74);
+ overflow: hidden;
+ }
+ #largeRainbow {
+ background: url(/colorpick_rainbow_large.png);
+ width: 400px;
+ height: 200px;
+ user-select: none;
+ }
+ #outOfBounds {
+ user-select: none;
+ z-index: 1;
+ background: #00000060;
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ }
+ `,
+ ];
+
+ @query("#largeCrosshair") largeCrosshairEl!: HTMLElement;
+ @query("#largeRainbow") largeRainbowEl!: HTMLElement;
+
+ canvasMove: SubEvent = new SubEvent();
+ outsideMove: SubEvent = new SubEvent();
+ mouseUp: SubEvent = new SubEvent();
+
+ render() {
+ return html`
+
+
+
+ `;
+ }
+
+ // make top-left of rainbow image be at this pos
+ placeRainbow(pos: ClientCoord) {
+ const el = this.shadowRoot?.querySelector("#largeRainbowComp")! as HTMLElement;
+ const cssBorder = 10;
+ el.style.left = pos.x - cssBorder + "px";
+ el.style.top = pos.y - cssBorder + "px";
+ }
+
+ moveLargeCrosshair(pos: RainbowCoord) {
+ const ch = this.largeCrosshairEl;
+ ch.style.left = pos.x - ch.offsetWidth / 2 + "px";
+ ch.style.top = pos.y - ch.offsetHeight / 2 + "px";
+ }
+
+ private onCanvasMove(ev: MouseEvent) {
+ this.canvasMove.emit(new RainbowCoord(ev.offsetX, ev.offsetY));
+ }
+
+ private onMouseUp(ev: MouseEvent) {
+ this.mouseUp.emit();
+ }
+
+ private onOutOfBoundsMove(ev: MouseEvent) {
+ this.outsideMove.emit(new ClientCoord(ev.clientX, ev.clientY));
+ }
+}
+
+class PickerFloat {
+ private rainbow?: RainbowCanvas;
+ private currentListener?: (hsc: string) => void;
+ private rainbowOrigin: ClientCoord = new ClientCoord(0, 0);
+ private floatEl?: Light9ColorPickerFloat;
+
+ pageInit() {
+ this.getFloatEl();
+ this.getRainbow();
+ }
+
+ private getFloatEl(): Light9ColorPickerFloat {
+ if (!this.floatEl) {
+ this.floatEl = document.createElement("light9-color-picker-float") as Light9ColorPickerFloat;
+ this.subscribeToFloatElement(this.floatEl);
+ document.body.appendChild(this.floatEl);
+ }
+ return this.floatEl;
+ }
+
+ private subscribeToFloatElement(el: Light9ColorPickerFloat) {
+ el.canvasMove.subscribe(this.onCanvasMove.bind(this));
+ el.outsideMove.subscribe(this.onOutsideMove.bind(this));
+ el.mouseUp.subscribe(() => {
+ this.hide();
+ });
+ }
+
+ private onCanvasMove(pos: RainbowCoord) {
+ pos = new RainbowCoord( //
+ clamp(pos.x, 0, 400 - 1), //
+ clamp(pos.y, 0, 200 - 1)
+ );
+ this.getFloatEl().moveLargeCrosshair(pos);
+ if (this.currentListener) {
+ this.currentListener(this.getRainbow().colorAt(pos));
+ }
+ }
+
+ private onOutsideMove(pos: ClientCoord) {
+ const rp = this.toRainbow(pos);
+ log("rp", rp);
+ this.onCanvasMove(rp);
+ }
+
+ private getRainbow(): RainbowCanvas {
+ if (!this.rainbow) {
+ this.rainbow = new RainbowCanvas("/colorpick_rainbow_large.png", new RainbowCoord(400, 200));
+ }
+ return this.rainbow;
+ }
+
+ startPick(clickPoint: ClientCoord, startColor: string, onNewHueSatColor: (hsc: string) => void) {
+ log("start pick", clickPoint);
+ const el = this.getFloatEl();
+
+ let pos: RainbowCoord;
+ try {
+ pos = this.getRainbow().posFor(startColor);
+ } catch (e) {
+ pos = new RainbowCoord(-999, -999);
+ }
+
+ this.rainbowOrigin = new ClientCoord( //
+ clickPoint.x - clamp(pos.x, 0, 400), //
+ clickPoint.y - clamp(pos.y, 0, 200)
+ );
+ log("rainbow goes to", this.rainbowOrigin);
+
+ el.placeRainbow(this.rainbowOrigin);
+ setTimeout(() => {
+ this.getFloatEl().moveLargeCrosshair(pos);
+ }, 1);
+
+ el.style.display = "block";
+ log("set listener");
+ this.currentListener = onNewHueSatColor;
+ }
+
+ private hide() {
+ const el = this.getFloatEl();
+ el.style.display = "none";
+ this.currentListener = undefined;
+ }
+
+ private toRainbow(pos: ClientCoord): RainbowCoord {
+ return new RainbowCoord( //
+ pos.x - this.rainbowOrigin.x, //
+ pos.y - this.rainbowOrigin.y
+ );
+ }
+}
+
+export const pickerFloat = new PickerFloat();
diff --git a/light9/web/light9-color-picker.ts b/light9/web/light9-color-picker.ts
--- a/light9/web/light9-color-picker.ts
+++ b/light9/web/light9-color-picker.ts
@@ -1,157 +1,13 @@
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 { css, html, LitElement, PropertyValueMap } from "lit";
+import { customElement, property, query } from "lit/decorators.js";
+import color from "onecolor";
import { SubEvent } from "sub-events";
-import color from "onecolor";
+import { ClientCoord, pickerFloat } from "./floating_color_picker";
+import { Slider } from "@material/mwc-slider";
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`
-
- `;
- }
- // 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");
- }
-}
+type int8 = number;
@customElement("light9-color-picker")
export class Light9ColorPicker extends LitElement {
@@ -173,7 +29,7 @@ export class Light9ColorPicker extends L
border: 1px solid #333;
}
- paper-slider {
+ mwc-slider {
width: 160px;
}
@@ -181,158 +37,65 @@ export class Light9ColorPicker extends L
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`
-
-
- V:
-
-
-
-
-
-
+
+ V:
`;
}
- 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);
+ @property() color: string = "#000"; // actual output color, computed by value*hueSatColor
+ @property() hueSatColor: string = "#fff"; // always V=1, to be scaled down
+ @property() value: int8 = 0;
+
+ @query("#swatch") swatchEl!: HTMLElement;
+ @query("#outOfBounds") outOfBoundsEl!: HTMLElement;
+
+ connectedCallback(): void {
+ super.connectedCallback();
+ pickerFloat.pageInit();
}
- onValue(value) {
- if (this.hueSatColor === null) {
- this.hueSatColor = "#ffffff";
+ update(changedProperties: PropertyValueMap) {
+ if (changedProperties.has("color")) {
+ this.setColor(this.color);
}
- let neverBlack = 0.1 + (0.9 * value) / 255;
- this.$.swatch.style.filter = `brightness(${neverBlack})`;
- }
- writeColor(hueSatColor, value) {
- if (hueSatColor === null || this.pauseWrites) {
- return;
+ if (changedProperties.has("value") || changedProperties.has("hueSatColor")) {
+ this.color = color(this.hueSatColor)
+ .value(this.value / 255)
+ .hex();
+ const sl = this.shadowRoot?.querySelector("#value") as Slider;
+ if (sl) {
+ sl.value = this.value;
+ }
}
- this.color = one
- .color(hueSatColor)
- .value(value / 255)
- .hex();
- this.$.large.style.borderColor = this.color;
+ super.update(changedProperties);
+ }
+
+ private onVSliderChange(ev: CustomEvent) {
+ this.value = ev.detail.value;
+ this.swatchEl.style.borderColor = this.hueSatColor;
}
- readColor(color) {
- if (this.$.large.style.display == "block") {
- // for performance, don't do color searches on covered widget
- return;
+
+ // for outside users of the component
+ setColor(col: string) {
+ log(`set color pick to color ${col}`);
+ if (col == "") {
+ col = "#000";
}
-
- this.pauseWrites = true;
- var colorValue = one.color(color).value() * 255;
- // writing back to immediate-value doesn't work on paper-slider
- this.sliderWriteValue = colorValue;
+ this.value = color(col).value() * 255;
// 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();
+ this.hueSatColor = color(col).value(1).hex();
}
- 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;
+
+ private startFloatingPick(ev: MouseEvent) {
+ if (this.value < (20 as int8)) {
+ log("boost");
+ this.value = 255 as int8;
}
- 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));
+ pickerFloat.startPick(new ClientCoord(ev.clientX, ev.clientY), this.color, (hsc: string) => {
+ this.hueSatColor = hsc;
+ });
}
}