Color picker improvements

This commit is contained in:
barsdeveloper
2022-10-16 13:56:38 +02:00
parent 0b19d89416
commit 192f2a4c11
23 changed files with 500 additions and 231 deletions

View File

@@ -14,7 +14,7 @@ import Utility from "./Utility"
* primaryInf: Number,
* primarySup: Number,
* secondaryInf: Number,
* secondarySup: Number
* secondarySup: Number,
* }} BoundariesInfo
*/

View File

@@ -244,10 +244,14 @@ export default class Utility {
}
/** @param {[Number, Number]} param0 */
static getPolarCoordinates([x, y]) {
static getPolarCoordinates([x, y], positiveTheta = false) {
let theta = Math.atan2(y, x)
if (positiveTheta && theta < 0) {
theta = 2 * Math.PI + theta
}
return [
Math.sqrt(x * x + y * y),
Math.atan2(y, x),
theta,
]
}

View File

@@ -1,6 +1,5 @@
import LinearColorEntity from "../entity/LinearColorEntity"
import ColorHandlerTemplate from "../template/ColorHandlerTemplate"
import IDraggableElement from "./IDraggableElement"
import IDraggableControlElement from "./IDraggableControlElement"
/** @typedef {import("../template/ColorPickerWindowTemplate").default} ColorPickerWindowTemplate */
/**
@@ -8,29 +7,12 @@ import IDraggableElement from "./IDraggableElement"
* @typedef {import("./WindowElement").default<T>} WindowElement
*/
/** @extends {IDraggableElement<Object, ColorHandlerTemplate>} */
export default class ColorHandlerElement extends IDraggableElement {
/** @type {WindowElement<ColorPickerWindowTemplate>} */
windowElement
/** @extends {IDraggableControlElement<Object, ColorHandlerTemplate>} */
export default class ColorHandlerElement extends IDraggableControlElement {
constructor() {
super({}, new ColorHandlerTemplate())
}
connectedCallback() {
super.connectedCallback()
this.windowElement = this.closest("ueb-window")
}
/** @param {Number[]} param0 */
setLocation([x, y]) {
super.setLocation(this.template.adjustLocation([x, y]))
}
computeColor() {
return new LinearColorEntity()
}
}
customElements.define("ueb-color-handler", ColorHandlerElement)

View File

@@ -0,0 +1,14 @@
import ColorSliderTemplate from "../template/ColorSliderTemplate"
import IDraggableControlElement from "./IDraggableControlElement"
/** @typedef {import("../template/IDraggableControlTemplate").default} IDraggableControlTemplate */
/** @extends {IDraggableControlElement<Object, ColorSliderTemplate>} */
export default class ColorSliderElement extends IDraggableControlElement {
constructor() {
super({}, new ColorSliderTemplate())
}
}
customElements.define("ueb-color-slider", ColorSliderElement)

View File

@@ -0,0 +1,37 @@
import IDraggableElement from "./IDraggableElement"
/**
* @typedef {import("../element/WindowElement").default} WindowElement
* @typedef {import("../entity/IEntity").default} IEntity
* @typedef {import("../template/IDraggableControlTemplate").default} IDraggableControlTemplate
*/
/**
* @template {IEntity} T
* @template {IDraggableControlTemplate} U
* @extends {IDraggableElement<T, U>}
*/
export default class IDraggableControlElement extends IDraggableElement {
/** @type {WindowElement} */
windowElement
/**
* @param {T} entity
* @param {U} template
*/
constructor(entity, template) {
super(entity, template)
}
connectedCallback() {
super.connectedCallback()
this.windowElement = this.closest("ueb-window")
}
/** @param {Number[]} param0 */
setLocation([x, y]) {
super.setLocation(this.template.adjustLocation([x, y]))
}
}

View File

@@ -28,9 +28,12 @@ export default class IDraggableElement extends IElement {
static dragEventName = Configuration.dragEventName
static dragGeneralEventName = Configuration.dragGeneralEventName
constructor(...args) {
// @ts-expect-error
super(...args)
/**
* @param {T} entity
* @param {U} template
*/
constructor(entity, template) {
super(entity, template)
this.locationX = 0
this.locationY = 0
}

View File

@@ -91,7 +91,7 @@ export default class IElement extends LitElement {
this.template.inputSetup()
}
/** @param {Map<String, String>} */
/** @param {Map<String, String>} changedProperties */
updated(changedProperties) {
super.updated(changedProperties)
this.template.updated(changedProperties)

View File

@@ -11,22 +11,27 @@ export default class LinearColorEntity extends IEntity {
A: new RealUnitEntity(1),
}
static fromWheelLocation([x, y], radius) {
static fromWheelLocation([x, y], radius, v, a) {
x -= radius
y -= radius
const [r, theta] = Utility.getPolarCoordinates([x, y])
return LinearColorEntity.fromHSV([-theta, r])
const [r, theta] = Utility.getPolarCoordinates([x, y], true)
return LinearColorEntity.fromHSVA([
1 - theta / (2 * Math.PI),
r / radius,
v,
a,
])
}
/** @param {Number[]} param0 */
static fromHSV([h, s, v, a = 1]) {
static fromHSVA([h, s, v, a = 1]) {
const i = Math.floor(h * 6)
const f = h * 6 - i
const p = v * (1 - s)
const q = v * (1 - f * s)
const t = v * (1 - (1 - f) * s)
const values = [v, q, p, p, t, v]
const [r, g, b] = [values[i % 6], values[(i + 4) % 6], values[(i + 2) % 6]]
const [r, g, b] = [values[i % 6], values[(i + 2) % 6], values[(i + 4) % 6]]
return new LinearColorEntity({
R: r,
G: g,
@@ -44,33 +49,38 @@ export default class LinearColorEntity extends IEntity {
}
toRGBA() {
return [this.R.value * 255, this.G.value * 255, this.B.value * 255, this.A.value * 255]
return [
Math.round(this.R.value * 255),
Math.round(this.G.value * 255),
Math.round(this.B.value * 255),
Math.round(this.A.value * 255),
]
}
toHSV() {
const [r, g, b, a] = this.toRGBA()
toHSVA() {
const [r, g, b, a] = [this.R.value, this.G.value, this.B.value, this.A.value]
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
const d = max - min
let h
const s = (max == 0 ? 0 : d / max)
const v = max / 255
const v = max
switch (max) {
case min:
h = 0
break
case r:
h = (g - b) + d * (g < b ? 6 : 0)
h = (g - b) / d + (g < b ? 6 : 0)
break
case g:
h = (b - r) + d * 2
h = (b - r) / d + 2
break
case b:
h = (r - g) + d * 4
h = (r - g) / d + 4
break
}
h /= 6 * d
return [h, s, v]
h /= 6
return [new RealUnitEntity(h), new RealUnitEntity(s), new RealUnitEntity(v), new RealUnitEntity(a)]
}
toNumber() {

View File

@@ -81,7 +81,7 @@ export default class BlueprintTemplate extends ITemplate {
<div class="ueb-viewport-overlay"></div>
<div class="ueb-viewport-body">
<div class="ueb-grid"
.style="--ueb-additional-x: ${this.element}; --ueb-additional-y: ${this.element.translateY}; --ueb-translate-x: ${this.element.translateX}; --ueb-translate-y: ${this.element.translateY};">
style="--ueb-additional-x: ${this.element}; --ueb-additional-y: ${this.element.translateY}; --ueb-translate-x: ${this.element.translateX}; --ueb-translate-y: ${this.element.translateY};">
<div class="ueb-grid-content">
<div data-links></div>
<div data-nodes></div>

View File

@@ -15,7 +15,7 @@ export default class BoolPinTemplate extends PinTemplate {
super.firstUpdated(changedProperties)
this.#input = this.element.querySelector(".ueb-pin-input")
let self = this
this.onChangeHandler = _ => this.element.setDefaultValue(self.#input.checked ? true : false)
this.onChangeHandler = _ => this.element.setDefaultValue(self.#input.checked)
this.#input.addEventListener("change", this.onChangeHandler)
}
@@ -31,22 +31,10 @@ export default class BoolPinTemplate extends PinTemplate {
]
}
getInputs() {
return [this.#input.checked ? "true" : "false"]
}
/**
* @param {Boolean[]} values
* @param {String[]} rawValues
*/
setDefaultValue(values = [], rawValues) {
this.element.setDefaultValue(values[0])
}
renderInput() {
if (this.element.isInput()) {
return html`
<input type="checkbox" class="ueb-pin-input" checked="${this.element.defaultValue ? "" : nothing}" />
<input type="checkbox" class="ueb-pin-input" ?checked="${this.element.defaultValue ? "" : nothing}" />
`
}
return super.renderInput()

View File

@@ -1,32 +1,10 @@
import IDraggableTemplate from "./IDraggableTemplate"
import MouseMoveDraggable from "../input/mouse/MouseMoveDraggable"
import IDraggableControlTemplate from "./IDraggableControlTemplate"
import Utility from "../Utility"
/** @typedef {import("../element/ColorHandlerElement").default} ColorHandlerElement */
/** @extends {IDraggableTemplate<ColorHandlerElement>} */
export default class ColorHandlerTemplate extends IDraggableTemplate {
#locationChangeCallback
connectedCallback() {
super.connectedCallback()
this.window = this.element.closest("ueb-window")
this.movementSpace = this.element.parentElement
const bounding = this.movementSpace.getBoundingClientRect()
this.movementSpaceSize = [bounding.width, bounding.height]
}
createDraggableObject() {
return new MouseMoveDraggable(this.element, this.element.blueprint, {
draggableElement: this.element.parentElement,
ignoreTranslateCompensate: true,
moveEverywhere: true,
movementSpace: this.element.parentElement,
repositionOnClick: true,
stepSize: 1,
})
}
/** @extends {IDraggableControlTemplate<ColorHandlerElement>} */
export default class ColorHandlerTemplate extends IDraggableControlTemplate {
/** @param {[Number, Number]} param0 */
adjustLocation([x, y]) {
@@ -37,11 +15,12 @@ export default class ColorHandlerTemplate extends IDraggableTemplate {
r = Math.min(r, radius), [x, y] = Utility.getCartesianCoordinates([r, theta])
x = Math.round(x + radius)
y = Math.round(-y + radius)
this.#locationChangeCallback?.([x, y])
const hsva = this.getColor().toHSVA()
this.locationChangeCallback?.([x, y], radius, hsva[2], hsva[3])
return [x, y]
}
setLocationChangeCallback(callback) {
this.#locationChangeCallback = callback
getColor() {
return this.element.windowElement.template.color
}
}

View File

@@ -1,16 +1,14 @@
import { html } from "lit"
import { styleMap } from "lit/directives/style-map.js"
import ColorHandlerElement from "../element/ColorHandlerElement"
import Configuration from "../Configuration"
import ColorSliderElement from "../element/ColorSliderElement"
import LinearColorEntity from "../entity/LinearColorEntity"
import Utility from "../Utility"
import WindowTemplate from "./WindowTemplate"
/** @typedef {import("../element/WindowElement").default} WindowElement */
export default class ColorPickerWindowTemplate extends WindowTemplate {
static windowName = html`Color Picker`
/** @type {LinearColorEntity} */
#color
get color() {
@@ -34,26 +32,38 @@ export default class ColorPickerWindowTemplate extends WindowTemplate {
firstUpdated(changedProperties) {
const wheelHandler = new ColorHandlerElement()
const spectrumHandler = new ColorHandlerElement()
wheelHandler.template.setLocationChangeCallback(([x, y]) => {
const [r, theta] = Utility.getPolarCoordinates([x, y])
this.color = LinearColorEntity.fromWheelLocation(x, y)
})
this.element.querySelector(".ueb-color-picker-wheel").appendChild(new ColorHandlerElement())
const saturationSlider = new ColorSliderElement()
wheelHandler.template.locationChangeCallback = ([x, y], radius, v, a) => {
this.color = LinearColorEntity.fromWheelLocation([x, y], radius, v, a)
}
this.element.querySelector(".ueb-color-picker-wheel").appendChild(wheelHandler)
}
renderContent() {
const [h, s, v] = this.color.toHSVA()
const style = {
"--ueb-color-r": this.color.R.toString(),
"--ueb-color-g": this.color.G.toString(),
"--ueb-color-b": this.color.B.toString(),
"--ueb-color-a": this.color.A.toString(),
"--ueb-color-h": h.toString(),
"--ueb-color-s": s.toString(),
"--ueb-color-v": v.toString(),
}
return html`
<div class="ueb-color-picker"
.style="--ueb-color-r: ${this.color.R}; --ueb-color-g: ${this.color.G}; --ueb-color-b: ${this.color.B}; --ueb-color-a: ${this.color.A};">
<div class="ueb-color-picker" style=${styleMap(style)}>
<div class="ueb-color-picker-toolbar">
<div class="ueb-color-picker-theme"></div>
<div class="ueb-color-picker-srgb"></div>
</div>
<div class="ueb-color-picker-main">
<div class="ueb-color-picker-wheel"></div>
<div class="ueb-color-picker-saturation"></div>
<div class="ueb-color-picker-value"></div>
<div class="ueb-color-picker-saturation">
<div class="ueb-color-picker-slider"></div>
</div>
<div class="ueb-color-picker-value">
<div class="ueb-color-picker-slider"></div>
</div>
<div class="ueb-color-picker-preview">
<div class="ueb-color-picker-preview-old"></div>
<div class="ueb-color-picker-preview-new"></div>
@@ -77,7 +87,7 @@ export default class ColorPickerWindowTemplate extends WindowTemplate {
`
}
cleanup() {
this.element.blueprint.removeEventListener(Configuration.colorWindowEventName, this.colorWindowHandler)
renderWindowName() {
return html`Color Picker`
}
}

View File

@@ -0,0 +1,21 @@
import IDraggableControlTemplate from "./IDraggableControlTemplate"
import Utility from "../Utility"
/** @typedef {import("../element/ColorHandlerElement").default} ColorHandlerElement */
/** @extends {IDraggableControlTemplate<ColorHandlerElement>} */
export default class ColorSliderTemplate extends IDraggableControlTemplate {
/** @param {[Number, Number]} param0 */
adjustLocation([x, y]) {
x = 0
y = Utility.clamp(y, 0, this.movementSpaceSize[1])
const hsva = this.getColor().toHSVA()
this.locationChangeCallback?.([x, y])
return [x, y]
}
getColor() {
return this.element.windowElement.template.color
}
}

View File

@@ -0,0 +1,48 @@
import IDraggableTemplate from "./IDraggableTemplate"
import MouseMoveDraggable from "../input/mouse/MouseMoveDraggable"
/**
* @typedef {import("../element/IDraggableElement").default} IDraggableElement
*/
/**
* @template {IDraggableElement} T
* @extends {IDraggableTemplate<T>}
*/
export default class IDraggableControlTemplate extends IDraggableTemplate {
#locationChangeCallback = ([x, y], ...args) => [x, y]
get locationChangeCallback() {
return this.#locationChangeCallback
}
set locationChangeCallback(callback) {
this.#locationChangeCallback = callback
}
movementSpace
movementSpaceSize = [0, 0]
connectedCallback() {
super.connectedCallback()
this.movementSpace = this.element.parentElement
const bounding = this.movementSpace.getBoundingClientRect()
this.movementSpaceSize = [bounding.width, bounding.height]
}
createDraggableObject() {
return new MouseMoveDraggable(this.element, this.element.blueprint, {
draggableElement: this.movementSpace,
ignoreTranslateCompensate: true,
moveEverywhere: true,
movementSpace: this.movementSpace,
repositionOnClick: true,
stepSize: 1,
})
}
/** @param {[Number, Number]} param0 */
adjustLocation([x, y]) {
this.#locationChangeCallback([x, y])
return [x, y]
}
}

View File

@@ -31,7 +31,7 @@ export default class LinearColorPinTemplate extends IInputPinTemplate {
/** @param {LinearColorEntity} color */
setPinColor: color => this.element.setDefaultValue(color),
},
})
}),
]
}
@@ -47,7 +47,7 @@ export default class LinearColorPinTemplate extends IInputPinTemplate {
if (this.element.isInput()) {
return html`
<span class="ueb-pin-input" data-linear-color="${this.element.defaultValue.toString()}"
.style="--ueb-linear-color:rgba(${this.element.defaultValue.toString()})">
style="--ueb-linear-color:rgba(${this.element.defaultValue.toString()})">
</span>
`
}

View File

@@ -7,8 +7,6 @@ import MouseMoveDraggable from "../input/mouse/MouseMoveDraggable"
/** @extends {IDraggableTemplate<WindowElement>} */
export default class WindowTemplate extends IDraggableTemplate {
static windowName = html`Window`
toggleAdvancedDisplayHandler
getDraggableElement() {
@@ -24,20 +22,11 @@ export default class WindowTemplate extends IDraggableTemplate {
})
}
createInputObjects() {
return [
...super.createInputObjects(),
this.createDraggableObject(),
]
}
render() {
return html`
<div class="ueb-window">
<div class="ueb-window-top">
<div class="ueb-window-name ueb-ellipsis-nowrap-text">${
// @ts-expect-error
this.constructor.windowName}</div>
<div class="ueb-window-name ueb-ellipsis-nowrap-text">${this.renderWindowName()}</div>
<div class="ueb-window-close">
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<line x1="2" y1="2" x2="30" y2="30" stroke="currentColor" stroke-width="4" />
@@ -52,6 +41,10 @@ export default class WindowTemplate extends IDraggableTemplate {
`
}
renderWindowName() {
return html`Window`
}
renderContent() {
return html``
}