/** * FF Typescript Foundation Library * Copyright 2019 Ralph Wiedemeier, Frame Factory GmbH * * License: MIT */ import math from "@ff/core/math"; import Color, { Vector3 } from "@ff/core/Color"; import "./LineEdit"; import { ILineEditChangeEvent } from "./LineEdit"; import LinearSlider, { ILinearSliderChangeEvent} from "./LinearSlider"; import VectorSlider, { IVectorSliderChangeEvent } from "./VectorSlider"; import CustomElement, { customElement, html, property, PropertyValues } from "./CustomElement"; //////////////////////////////////////////////////////////////////////////////// const _hueColor = new Color(); export { Color }; export interface IColorEditChangeEvent extends CustomEvent { type: "change"; target: ColorEdit; detail: { color: Color; isDragging: boolean; } } @customElement("ff-color-edit") export default class ColorEdit extends CustomElement { @property({ attribute: false }) color = new Color(); @property({ type: Boolean }) alpha = false; @property({ type: Boolean }) numeric = false; private _hsv: Vector3; private _lumSatSlider: VectorSlider; private _hueSlider: LinearSlider; private _alphaSlider: LinearSlider; constructor() { super(); this.onLumSatChange = this.onLumSatChange.bind(this); this.onHueChange = this.onHueChange.bind(this); this.onAlphaChange = this.onAlphaChange.bind(this); this.addEventListener("click", e => e.stopPropagation()); this._hsv = new Vector3(); this._lumSatSlider = new VectorSlider().on("change", this.onLumSatChange); this._hueSlider = new LinearSlider().addClass("ff-hue-slider").on("change", this.onHueChange); this._hueSlider.direction = "vertical"; } protected firstConnected() { this.classList.add("ff-flex-column", "ff-control", "ff-color-edit"); } protected update(changedProperties: PropertyValues): void { if (changedProperties.has("color")) { this.color.toHSV(this._hsv); } if (changedProperties.has("alpha")) { if (this.alpha && !this._alphaSlider) { this._alphaSlider = new LinearSlider().addClass("ff-alpha-slider").on("change", this.onAlphaChange); this._alphaSlider.direction = "vertical"; } else if (!this.alpha && this._alphaSlider) { this._alphaSlider.remove(); } } super.update(changedProperties); } protected render() { let numericControls = null; if (this.numeric) { const color = this.color; const hex = color.toString(false).substr(1); const alphaControl = this.alpha ? html`
A
` : null; numericControls = html`
R
G
B
${alphaControl}
#
`; } return html`
${this._lumSatSlider}${this._hueSlider}${this._alphaSlider}
${numericControls}`; } protected updated() { this._hueSlider.value = 1 - this._hsv.x / 360; const hue = _hueColor.setHSV(this._hsv.x).toString(false); const slGrad = `linear-gradient(to bottom, transparent, black), linear-gradient(to right, white, ${hue})`; this._lumSatSlider.style.backgroundImage = slGrad; this._lumSatSlider.setXY(this._hsv.y, this._hsv.z); if (this.alpha) { const color = this.color.toString(/* includeAlpha */ false); const fg = getComputedStyle(this._alphaSlider).color; const alphaGrad = `linear-gradient(to top, transparent, ${color}), repeating-linear-gradient(-45deg, transparent, transparent 8px, ${fg} 8px, ${fg} 16px)`; this._alphaSlider.style.backgroundImage = alphaGrad; this._alphaSlider.value = this.color.alpha; } } protected onLumSatChange(event: IVectorSliderChangeEvent) { event.stopPropagation(); const value = event.detail.value; this._hsv.y = value.x; this._hsv.z = value.y; this.color.setHSV(this._hsv); this.requestUpdate(); this.emitChangeEvent(event.detail.isDragging); } protected onHueChange(event: ILinearSliderChangeEvent) { event.stopPropagation(); this._hsv.x = (1 - event.target.value) * 360; this.color.setHSV(this._hsv); this.requestUpdate(); this.emitChangeEvent(event.detail.isDragging); } protected onAlphaChange(event: ILinearSliderChangeEvent) { event.stopPropagation(); this.color.alpha = event.target.value; this.requestUpdate(); this.emitChangeEvent(event.detail.isDragging); } protected onNumericEdit(event: ILineEditChangeEvent) { event.stopPropagation(); const name = event.target.name; if (name === "string") { this.color.setString(event.detail.text, 1, false); } else { let value = parseInt(event.detail.text); if (!isFinite(value)) { return; } this.color[name] = math.limit(value, 0, 255); } this.color.toHSV(this._hsv); this.requestUpdate(); this.emitChangeEvent(event.detail.isEditing); } protected onKeyDown(event: KeyboardEvent) { const isSmallStepUp = event.code === "ArrowRight" || event.code === "ArrowUp"; const isLargeStepUp = event.code === "PageUp"; const isSmallStepDown = event.code === "ArrowLeft" || event.code === "ArrowDown"; const isLargeStepDown = event.code === "PageDown"; const shouldStep = isSmallStepUp || isLargeStepUp || isSmallStepDown || isLargeStepDown; if(shouldStep) { if(event.target === this._hueSlider || event.target === this._alphaSlider) { const dir = (isSmallStepUp || isLargeStepUp) ? -1 : 1; const step = (isSmallStepUp || isSmallStepDown) ? 0.01 : 0.1; if(event.target === this._hueSlider) { this._hsv.x = math.limit(this._hsv.x + step*360*dir, 0, 360); this.color.setHSV(this._hsv); } else { this.color.alpha = math.limit(this.color.alpha + step*dir, 0, 1); } this.requestUpdate(); this.emitChangeEvent(false); } else if(event.target === this._lumSatSlider) { if(isSmallStepUp || isSmallStepDown) { const dir = isSmallStepUp ? 1 : -1; const step = event.shiftKey ? 0.1 : 0.01; if(event.code === "ArrowRight" || event.code === "ArrowLeft") { this._hsv.y = math.limit(this._hsv.y + step*dir, 0, 1); } else { this._hsv.z = math.limit(this._hsv.z + step*dir, 0, 1); } this.color.setHSV(this._hsv); this.requestUpdate(); this.emitChangeEvent(false); } } } } protected emitChangeEvent(isDragging: boolean) { this.dispatchEvent(new CustomEvent("change", { detail: { color: this.color, isDragging }, bubbles: true })); } }