From ff981cd5b7ab9274630a1417593af96e36b99e7a Mon Sep 17 00:00:00 2001 From: Kyle Gunger Date: Wed, 13 Nov 2024 02:23:34 -0500 Subject: Color Wheel Widget --- index.html | 6 -- scripts/gui-common/color.js | 53 +++++++++ scripts/gui-common/widgets.js | 244 ++++++++++++++++++++++++++++++------------ scripts/main.js | 2 + styles/widgets.css | 37 ++++++- 5 files changed, 264 insertions(+), 78 deletions(-) diff --git a/index.html b/index.html index 23578ff..f5fd475 100644 --- a/index.html +++ b/index.html @@ -33,12 +33,6 @@ -
Lamp 1
-
10%
-
-
-
-
68 diff --git a/scripts/gui-common/color.js b/scripts/gui-common/color.js index 90eac14..0b25f68 100644 --- a/scripts/gui-common/color.js +++ b/scripts/gui-common/color.js @@ -52,8 +52,61 @@ class Color { return new Color(r / 255, g / 255, b / 255, a); } + + static d(h) + { + let d = Math.floor((h - (h % PI_THIRDS)) / PI_THIRDS); + return (DIVS.length - 1 + d) % (DIVS.length - 1); + } + + static from_hsv(h, s, v) + { + h = (((h % TWO_PI)) + TWO_PI) % TWO_PI; + + let d = Color.d(h); + + h = (h % PI_THIRDS) / PI_THIRDS; + + let out = DIVS[d].interpolate(DIVS[d + 1], h); + out = WHITE.interpolate(out, s); + + return BLACK.interpolate(out, v); + } + + static from_hsl(h, s, l) + { + h = (((h % TWO_PI)) + TWO_PI) % TWO_PI; + + let d = Color.d(h); + + h = (h % PI_THIRDS) / PI_THIRDS; + + let out = DIVS[d].interpolate(DIVS[d + 1], h); + let L = BLACK.interpolate(WHITE, l); + + if (l < 0.5) + out = BLACK.interpolate(out, l / 0.5); + else + out = out.interpolate(WHITE, (l - 0.5) / 0.5); + + return L.interpolate(out, s); + } } +const RED = new Color(1, 0, 0, 1); +const YELLOW = new Color(1, 1, 0, 1); +const GREEN = new Color(0, 1, 0, 1); +const CYAN = new Color(0, 1, 1, 1); +const BLUE = new Color(0, 0, 1, 1); +const MAGENTA = new Color(1, 0, 1, 1); +const WHITE = new Color(1, 1, 1, 1); +const BLACK = new Color(0, 0, 0, 1); + +const PI_THIRDS = Math.PI / 3; +const TWO_PI = Math.PI * 2; + +const DIVS = [RED, YELLOW, GREEN, CYAN, BLUE, MAGENTA, RED]; + /** * Interpolate between two colors * @param {Color} a diff --git a/scripts/gui-common/widgets.js b/scripts/gui-common/widgets.js index 88f1db8..9ec1712 100644 --- a/scripts/gui-common/widgets.js +++ b/scripts/gui-common/widgets.js @@ -28,6 +28,8 @@ class Widget extends EventTarget{ this.element.addEventListener("mouseleave", this.#emitMouseEvent.bind(this)); this.element.addEventListener("mouseenter", this.#emitMouseEvent.bind(this)); + this.element.addEventListener("click", this.#emitMouseEvent.bind(this)); + this.element.addEventListener("touchstart", this.#emitTouchEvent.bind(this)); this.element.addEventListener("touchend", this.#emitTouchEvent.bind(this)); this.element.addEventListener("touchmove", this.#emitTouchEvent.bind(this)); @@ -236,7 +238,7 @@ class WidgetCheckbox extends WidgetToggle { } } -class WidgetSlider extends Widget +class WidgetDragable extends Widget { /** @type {number} */ #primed = 0; @@ -244,13 +246,92 @@ class WidgetSlider extends Widget /** @type {number} */ #gone = 0; - #boundUp = null; - #boundMove = null; + /** @type {(e: MouseEvent) => void} */ + #m_up = null; + /** @type {(e: MouseEvent, b: number) => void} */ + #m_move = null; + + /** @type {(e: TouchEvent) => void} */ + #t_up = null; + /** @type {(e: TouchEvent) => void} */ + #t_move = null; + + + /** + * Constructor + */ + constructor () + { + super(); + + this.#m_up = this.#unpress.bind(this); + this.#m_move = this.#move.bind(this); + + this.addEventListener("mousedown", this.#press); + this.addEventListener("mousemove", this.#move); + this.addEventListener("mouseup", this.#unpress); + this.addEventListener("mouseleave", this.#leave); + this.addEventListener("mouseenter", this.#enter); + } + + /** @param {MouseEvent} event */ + #press(event) + { + this.#primed |= (1 << event.button); + this.#move(event); + } + + /** @param {MouseEvent} event */ + #unpress(event) + { + if (((1 << event.button) & this.#primed) == 0) + return; + this.#primed -= (1 << event.button); + this.#move(event); + if (this.#primed == 0) + { + if (this.#gone) + { + this.#gone = 0; + window.removeEventListener("mouseup", this.#m_up); + window.removeEventListener("mousemove", this.#m_move); + } + } + } + + /** @param {MouseEvent} event */ + #move(event) + { + this.move(event, this.#primed); + } + + /** @param {MouseEvent} event */ + #leave(event) + { + if (this.#primed == 0) + return; + this.#gone = 1; + window.addEventListener("mouseup", this.#m_up); + window.addEventListener("mousemove", this.#m_move); + } + + /** @param {MouseEvent} event */ + #enter(event) + { + if (this.#primed == 0) + return; + this.#gone = 0; + window.removeEventListener("mouseup", this.#m_up); + window.removeEventListener("mousemove", this.#m_move); + } +} +class WidgetSlider extends WidgetDragable +{ /** @type {HTMLElement} */ #detail = null; /** @type {(e: HTMLElement, v: number, p: number) => void} */ - #detailUpdater = null; + #u_detail = null; /** @type {number} */ #tmpNum = 0; @@ -286,37 +367,29 @@ class WidgetSlider extends Widget this.#detail = document.createElement("div"); this.#detail.classList.add("detail"); this.element.appendChild(this.#detail); - - this.addEventListener("mousedown", this.#press); - this.addEventListener("mousemove", this.#move); - this.addEventListener("mouseup", this.#unpress); - this.addEventListener("mouseleave", this.#leave); - this.addEventListener("mouseenter", this.#enter); + this.addEventListener("change", this.#change); - - this.#boundUp = this.#unpress.bind(this); - this.#boundMove = this.#move.bind(this); + this.#max = max; this.#min = min; this.#step = step; this.#precision = trunc; this.#percent = percent; - this.#detailUpdater = this.update_detail.bind(this); - } - - /** @param {MouseEvent} event */ - #press(event) - { - this.#primed |= (1 << event.button); - this.#move(event); + this.#u_detail = this.update_detail.bind(this); } - /** @param {MouseEvent} event */ - #move(event) + /** + * @param {MouseEvent} event + * @param {number} btns + */ + move(event, btns) { - if (this.#primed == 0) + if (btns == 0) + { + this.set(this.#tmpNum); return; + } let rect = this.element.getBoundingClientRect(); let top = 0, bot = 0, point = 0; @@ -361,48 +434,10 @@ class WidgetSlider extends Widget this.#tmpNum = Math.min(Math.max(this.#tmpNum, this.#min), this.#max); let percent = (this.#tmpNum - this.#min) / (this.#max - this.#min); - this.#detailUpdater(this.#detail, this.#tmpNum, percent, this.#percent); + this.#u_detail(this.#detail, this.#tmpNum, percent, this.#percent); this.element.style.setProperty("--percent", percent); } - /** @param {MouseEvent} event */ - #unpress(event) - { - if (((1 << event.button) & this.#primed) == 0) - return; - this.#primed -= (1 << event.button); - if (this.#primed == 0) - { - this.set(this.#tmpNum); - if (this.#gone) - { - this.#gone = 0; - window.removeEventListener("mouseup", this.#boundUp); - window.removeEventListener("mousemove", this.#boundMove); - } - } - } - - /** @param {MouseEvent} event */ - #leave(event) - { - if (this.#primed == 0) - return; - this.#gone = 1; - window.addEventListener("mouseup", this.#boundUp); - window.addEventListener("mousemove", this.#boundMove); - } - - /** @param {MouseEvent} event */ - #enter(event) - { - if (this.#primed == 0) - return; - this.#gone = 0; - window.removeEventListener("mouseup", this.#boundUp); - window.removeEventListener("mousemove", this.#boundMove); - } - update_detail(el, val, percent) { if (this.#percent) @@ -413,7 +448,7 @@ class WidgetSlider extends Widget setDetailUpdater(updater) { - this.#detailUpdater = updater; + this.#u_detail = updater; } /** @param {number} m */ @@ -514,10 +549,83 @@ class WidgetColorLight extends WidgetSlider } } -/** @typedef {Widget} WidgetSlider */ -/** @typedef {Widget} WidgetColorWheel */ -/** @typedef {Widget} WidgetColorTemp */ -/** @typedef {Widget} WidgetColorLight */ +class WidgetColorWheel extends WidgetDragable +{ + /** @type {Color} */ + #tmpColor = null; + /** @type {HTMLElement} */ + #detail = null; + + /** + * Constructor + */ + constructor () + { + super(); + this.element.classList.add("color-wheel"); + + this.#detail = document.createElement("div"); + this.#detail.classList.add("detail"); + this.element.appendChild(this.#detail); + } + + /** + * @param {MouseEvent} event + * @param {number} btns + */ + move(event, btns) + { + if (btns == 0) + { + this.set(this.#tmpColor); + return; + } + + let rect = this.element.getBoundingClientRect(); + + // Points + let tmpX = event.clientX; + let tmpY = rect.bottom - event.clientY + rect.top; + + // Percents + tmpX = (tmpX - rect.left) / (rect.right - rect.left); + tmpY = (tmpY - rect.top) / (rect.bottom - rect.top); + + // Vecs + tmpX = (Math.min(Math.max(0.0, tmpX), 1.0) - 0.5) * 2; + tmpY = (Math.min(Math.max(0.0, tmpY), 1.0) - 0.5) * 2; + + // Normalized + let mag = Math.sqrt(tmpX * tmpX + tmpY * tmpY); + if (mag > 1) + { + tmpX /= mag; + tmpY /= mag; + } + + this.element.style.setProperty("--pos-x", tmpX); + this.element.style.setProperty("--pos-y", tmpY); + this.update_detail(tmpX, tmpY, mag); + } + + update_detail(x, y, mag) + { + if (x == 0) + { + if (y > 0) + this.#tmpColor = Color.from_hsv(Math.PI / 2, mag, 1); + else + this.#tmpColor = Color.from_hsv(-Math.PI / 2, mag, 1); + } + else if (x < 0) + this.#tmpColor = Color.from_hsv(Math.atan(y / x) + Math.PI, mag, 1); + else + this.#tmpColor = Color.from_hsv(Math.atan(y / x), mag, 1); + + this.element.style.setProperty("--detail", this.#tmpColor.rgb()); + } +} + /** @typedef {Widget & {getGague: () => number, setGague: (value: number) => void}} WidgetThermostat */ /** @typedef {Widget & {addSelection: (name: string, value: any) => void, setSelection: (name: string) => boolean, removeSelection: (name: string) => void, getSelection: () => string}} WidgetSelectButton */ diff --git a/scripts/main.js b/scripts/main.js index ac6dbe9..3cdbe12 100644 --- a/scripts/main.js +++ b/scripts/main.js @@ -16,6 +16,8 @@ class Client { content.appendChild(this.temp.element); this.light = new WidgetColorLight(); content.appendChild(this.light.element); + this.wheel = new WidgetColorWheel(); + content.appendChild(this.wheel.element); // content.appendChild(Widget("button").el); // content.appendChild(Widget("checkbox").el); // content.appendChild(Widget("slider").el); diff --git a/styles/widgets.css b/styles/widgets.css index 5a29507..5a7728e 100644 --- a/styles/widgets.css +++ b/styles/widgets.css @@ -147,7 +147,7 @@ input { height: calc(100% * var(--percent)); width: 100%; background-color: var(--w-sl-fill); - + transition-duration: 0.15s; content: ''; } @@ -162,6 +162,7 @@ input { .slider:active > .fill::before { background-color: var(--w-sl-fill-active); + transition-duration: 0s; } .slider::after { @@ -185,7 +186,7 @@ input { border-bottom: var(--w-sl-dots); } -.slider > .detail, .color-light > .detail, .color-temp > .detail { +.slider > .detail, .color-light > .detail, .color-temp > .detail, .color-wheel > .detail { display: block; position: absolute; @@ -203,10 +204,13 @@ input { width: calc(1.2 * var(--base-unit)); height: var(--base-unit); pointer-events: none; + user-select: none; align-content: center; text-align: center; padding: 2px; + + transition-duration: 0.15s; } .slider.h > .detail, .color-light.h > .detail, .color-temp.h > .detail { @@ -217,10 +221,12 @@ input { .slider:active > .detail { background-color: rgba(0, 132, 255, 1); color: rgba(255, 255, 255, 1); + transition-duration: 0s; } -.color-light:active > .detail, .color-temp:active > .detail { +.color-light:active > .detail, .color-temp:active > .detail, .color-wheel:active > .detail { background-color: var(--detail); + transition-duration: 0s; } .inactive > .detail { @@ -334,7 +340,7 @@ input { */ .color-wheel { - background: radial-gradient(white, transparent calc(1.5 * var(--base-unit))), conic-gradient(#f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00); + background: radial-gradient(white, transparent calc(1.5 * var(--base-unit))), conic-gradient(from 90deg, #f00, #f0f, #00f, #0ff, #0f0, #ff0, #f00); border-radius: 50%; width: calc(3 * var(--base-unit)); height: calc(3 * var(--base-unit)); @@ -356,6 +362,10 @@ input { box-sizing: border-box; } +.color-wheel:active::after { + transition-duration: 0s; +} + .color-wheel:hover::after { border-color: #888; } @@ -368,6 +378,19 @@ input { border-color: rgb(68, 68, 68, 0); } +.color-wheel > .detail { + width: calc(var(--base-unit) * 0.5); + height: calc(var(--base-unit) * 0.5); + border-radius: 5px; + box-shadow: none; + top: 0; + left: calc(100% + 7px); +} + +.color-wheel:active > .detail { + box-shadow: 1px 1px var(--w-shadow), 3px 3px var(--w-shadow), 5px 5px var(--w-shadow); +} + .color-temp { width: var(--base-unit); height: calc(3 * var(--base-unit)); @@ -396,6 +419,12 @@ input { box-sizing: border-box; border-radius: 7px; + + transition-duration: 0.15s; +} + +.color-temp:active::after, .color-light:active::after { + transition-duration: 0s; } .color-temp:hover::after, .color-light:hover::after { -- cgit v1.2.3