changeset 2320:280be980aaa0

new fader widget
author drewp@bigasterisk.com
date Thu, 01 Jun 2023 14:19:42 -0700
parents 5deeefdc6ee3
children ed0db55f604c
files light9/fade/Light9FadeUi.ts light9/fade/Light9Fader.ts
diffstat 2 files changed, 150 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- a/light9/fade/Light9FadeUi.ts	Thu Jun 01 14:18:14 2023 -0700
+++ b/light9/fade/Light9FadeUi.ts	Thu Jun 01 14:19:42 2023 -0700
@@ -1,13 +1,12 @@
-import { fastSlider, fastSliderLabel, provideFASTDesignSystem } from "@microsoft/fast-components";
 import debug from "debug";
 import { css, html, LitElement } from "lit";
 import { customElement, property, state } from "lit/decorators.js";
 import { NamedNode } from "n3";
 import { getTopGraph } from "../web/RdfdbSyncedGraph";
+import { shortShow, showRoot } from "../web/show_specific";
 import { SyncedGraph } from "../web/SyncedGraph";
-import { shortShow, showRoot } from "../web/show_specific";
 export { EditChoice } from "../web/EditChoice";
-provideFASTDesignSystem().register(fastSlider(), fastSliderLabel());
+export { Light9Fader } from "./Light9Fader";
 
 debug.enable("*");
 const log = debug("fade");
@@ -19,6 +18,7 @@
     css`
       :host {
         display: block;
+        user-select: none; /* really this is only desirable during slider drag events */
       }
     `,
   ];
@@ -30,7 +30,7 @@
 
       <div id="fps"></div>
 
-      ${this.faders.map((fd) => html` <light9-fader .uri=${fd}></light9-fader> `)}
+      ${this.faders.map((fd) => html` <light9-effect-fader .uri=${fd}></light9-effect-fader> `)}
     `;
   }
 
@@ -62,8 +62,8 @@
   }
 }
 
-@customElement("light9-fader")
-export class Light9Fader extends LitElement {
+@customElement("light9-effect-fader")
+export class Light9EffectFader extends LitElement {
   static styles = [
     css`
       :host {
@@ -71,24 +71,15 @@
         border: 2px gray outset;
         background: #272727;
       }
-      fast-slider {
-        height: 256px;
-      }
-      fast-slider > .track {
-        background: #e3bbc0;
-        box-shadow: 0 0 8px;
-      }
-      fast-slider {
-        --accent-foreground-rest: #0a0a0c;
+      light9-fader {
+        margin: 4px;
+        width: 100%;
       }
     `,
   ];
   render() {
     return html`
-      <fast-slider orientation="vertical" .value=${this.value} step=${1 / 255} min="1" max="0" @change=${this.onSliderInput}>
-        <fast-slider-label label="0"></fast-slider-label>
-        <fast-slider-label label="1.0"></fast-slider-label>
-      </fast-slider>
+      <light9-fader .value=${this.value} @change=${this.onSliderInput}></light9-fader>
       <div>${this.value.toPrecision(3)}</div>
       <div>eff: <edit-choice .uri=${this.effect} @edited=${this.onEffectChange}></edit-choice></div>
       <div>attr: <edit-choice .uri=${this.effectAttr} @edited=${this.onEffectAttrChange}></edit-choice></div>
@@ -153,7 +144,7 @@
     }
     const U = this.graph.U();
     const prev = this.value;
-    const v: number = (ev.target as any).valueAsNumber;
+    const v: number = ev.detail.value;
     this.value = parseFloat(v.toPrecision(3)); // rewrite pls
     if (this.value == prev) {
       return;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/light9/fade/Light9Fader.ts	Thu Jun 01 14:19:42 2023 -0700
@@ -0,0 +1,139 @@
+import debug from "debug";
+import { css, html, LitElement, PropertyValueMap } from "lit";
+import { customElement, property, query } from "lit/decorators.js";
+
+import { clamp } from "../web/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: 250px;
+      }
+      #handle {
+        background: gray;
+        border: 5px gray outset;
+        position: relative;
+        left: 0;
+        right: -25px;
+      }
+    `,
+  ];
+
+  @property() value: number = 0;
+
+  @query("#handle") handleEl!: HTMLElement;
+
+  troughHeight = 250 - 2 - 2 - 5 - 5;
+  handleHeight = 20;
+
+  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")) {
+      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.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 / 120 * -.05;
+    });
+
+    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;
+    }
+  }
+  onMouseDrag(dy: number) {
+    if (this.drag === undefined) throw "unexpected";
+    this.value = this.drag.startDragValue - dy / this.troughHeight;
+  }
+
+  onMouseUpAnywhere() {
+    this.drag = undefined;
+  }
+
+  sliderTopY(value: number): number {
+    const usableY = this.troughHeight - this.handleHeight;
+    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);
+  }
+}