import debug from "debug"; import { css, html, LitElement, PropertyValueMap } from "lit"; import { customElement, property, query } from "lit/decorators.js"; import { clamp } from "../floating_color_picker"; const log = debug("fade"); class Drag { constructor(public startDragPxY: number, public startDragValue: number) {} } @customElement("light9-fader") export class Light9Fader extends LitElement { static styles = [ css` :host { display: inline-block; border: 2px gray inset; background: #000; height: 80px; } #handle { background: gray; border: 3px outset #838499; position: relative; left: 0px; right: -25px; border-radius: 4px; margin: 0 1px; } `, ]; @property() value: number = 0; @query("#handle") handleEl!: HTMLElement; troughHeight = 80 - 2 - 2 - 5 - 5; handleHeight = 16; drag?: Drag; unmutedValue: number = 1; render() { return html`

`; } protected update(changedProperties: PropertyValueMap | Map): void { super.update(changedProperties); if (changedProperties.has("value")) { } } valueChangedFromUi() { this.value = clamp(this.value, 0, 1); this.dispatchEvent(new CustomEvent("change", { detail: { value: this.value } })); } protected updated(_changedProperties: PropertyValueMap | Map): void { super.updated(_changedProperties); const y = this.sliderTopY(this.value); this.handleEl.style.top = y + "px"; } protected firstUpdated(_changedProperties: PropertyValueMap | Map): void { super.firstUpdated(_changedProperties); this.handleEl.style.height = this.handleHeight + "px"; this.events(); } events() { const hand = this.handleEl; hand.addEventListener("mousedown", (ev: MouseEvent) => { ev.stopPropagation(); if (ev.buttons == 1) { this.drag = new Drag(ev.clientY, this.value); } else if (ev.buttons == 2) { this.onRmb(); } }); this.addEventListener("mousedown", (ev: MouseEvent) => { ev.stopPropagation(); if (ev.buttons == 1) { this.value = this.sliderValue(ev.offsetY); this.valueChangedFromUi(); this.drag = new Drag(ev.clientY, this.value); } else if (ev.buttons == 2) { // RMB in trough this.onRmb(); } }); this.addEventListener("contextmenu", (event) => { event.preventDefault(); }); this.addEventListener("wheel", (ev: WheelEvent) => { ev.preventDefault(); this.value += (ev.deltaY / this.troughHeight) * -0.03; this.valueChangedFromUi(); }); const maybeDrag = (ev: MouseEvent) => { if (ev.buttons != 1) return; if (this.drag === undefined) return; ev.stopPropagation(); this.onMouseDrag(ev.clientY - this.drag.startDragPxY!); }; hand.addEventListener("mousemove", maybeDrag); this.addEventListener("mousemove", maybeDrag); window.addEventListener("mousemove", maybeDrag); hand.addEventListener("mouseup", this.onMouseUpAnywhere.bind(this)); this.addEventListener("mouseup", this.onMouseUpAnywhere.bind(this)); window.addEventListener("mouseup", this.onMouseUpAnywhere.bind(this)); } onRmb() { if (this.value > 0.1) { // mute this.unmutedValue = this.value; this.value = 0; } else { // unmute this.value = this.unmutedValue; } this.valueChangedFromUi(); } onMouseDrag(dy: number) { if (this.drag === undefined) throw "unexpected"; this.value = this.drag.startDragValue - dy / this.troughHeight; this.valueChangedFromUi(); } onMouseUpAnywhere() { this.drag = undefined; } sliderTopY(value: number): number { const usableY = this.troughHeight - this.handleHeight / 2 - 1; const yAdj = this.handleHeight / 2 - 5 - 2; return (1 - value) * usableY + yAdj; } sliderValue(offsetY: number): number { const usableY = this.troughHeight - this.handleHeight; const yAdj = this.handleHeight / 2 - 5 - 2; return clamp(1 - (offsetY - yAdj) / usableY, 0, 1); } }