view web/fade/Light9Fader.ts @ 2405:69ca2b2fc133

overcomplicated attempt at persisting the pane layout in the rdf graph this was hard because we have to somehow wait for the graph to load before config'ing the panes
author drewp@bigasterisk.com
date Fri, 17 May 2024 16:58:26 -0700
parents 9a4bc2ea264e
children
line wrap: on
line source

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` <div id="handle"><hr /></div> `;
  }

  protected update(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): 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<any> | Map<PropertyKey, unknown>): void {
    super.updated(_changedProperties);
    const y = this.sliderTopY(this.value);
    this.handleEl.style.top = y + "px";
  }

  protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): 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);
  }
}