Color picker refactoring

This commit is contained in:
barsdeveloper
2022-10-14 18:18:54 +02:00
parent a55a475f70
commit b13bc68ab3
16 changed files with 577 additions and 398 deletions

View File

@@ -240,6 +240,22 @@ export default class Utility {
/** @param {LinearColorEntity} value */
static printLinearColor(value) {
return `${Math.round(value.R * 255)}, ${Math.round(value.G * 255)}, ${Math.round(value.B * 255)}`
return `${Math.round(value.R.valueOf() * 255)}, ${Math.round(value.G.valueOf() * 255)}, ${Math.round(value.B.valueOf() * 255)}`
}
/** @param {[Number, Number]} param0 */
static getPolarCoordinates([x, y]) {
return [
Math.sqrt(x * x + y * y),
Math.atan2(y, x),
]
}
/** @param {[Number, Number]} param0 */
static getCartesianCoordinates([r, theta]) {
return [
r * Math.cos(theta),
r * Math.sin(theta)
]
}
}

View File

@@ -8,6 +8,7 @@ import IDraggableElement from "./IDraggableElement"
* @typedef {import("./WindowElement").default<T>} WindowElement
*/
/** @extends {IDraggableElement<Object, ColorHandlerTemplate>} */
export default class ColorHandlerElement extends IDraggableElement {
/** @type {WindowElement<ColorPickerWindowTemplate>} */
@@ -23,10 +24,8 @@ export default class ColorHandlerElement extends IDraggableElement {
}
/** @param {Number[]} param0 */
addLocation([x, y]) {
super.addLocation([x, y])
this.windowElement.windowOptions
this.windowElement.template.color = this.computeColor()
setLocation([x, y]) {
super.setLocation(this.template.adjustLocation([x, y]))
}
computeColor() {

View File

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

View File

@@ -1,9 +0,0 @@
import Utility from "../Utility"
import IntegerEntity from "./IntegerEntity"
export default class ColorChannelRealValueEntity extends IntegerEntity {
toString() {
return (this.value / 255).toFixed(6)
}
}

View File

@@ -1,15 +0,0 @@
import Utility from "../Utility"
import IntegerEntity from "./IntegerEntity"
export default class ColorChannelValueEntity extends IntegerEntity {
static attributes = {
value: Number,
}
/** @param {Object | Number | String} options */
constructor(options = 0) {
super(options)
this.value = Utility.clamp(this.value, 0, 255)
}
}

View File

@@ -1,37 +1,56 @@
import ColorChannelRealValueEntity from "./ColorChannelRealValueEntity"
import IEntity from "./IEntity"
import Utility from "../Utility"
import RealUnitEntity from "./UnitRealEntity"
export default class LinearColorEntity extends IEntity {
static attributes = {
R: ColorChannelRealValueEntity,
G: ColorChannelRealValueEntity,
B: ColorChannelRealValueEntity,
A: ColorChannelRealValueEntity,
R: RealUnitEntity,
G: RealUnitEntity,
B: RealUnitEntity,
A: new RealUnitEntity(1),
}
static fromWheelLocation([x, y], radius) {
x -= radius
y -= radius
const mod = Math.sqrt(x * x + y * y)
const [r, theta] = Utility.getPolarCoordinates([x, y])
return LinearColorEntity.fromHSV([-theta, r])
}
/** @param {Number[]} param0 */
static fromHSV([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]]
return new LinearColorEntity({
R: r,
G: g,
B: b,
A: a,
})
}
constructor(options = {}) {
super(options)
/** @type {ColorChannelRealValueEntity} */ this.R
/** @type {ColorChannelRealValueEntity} */ this.G
/** @type {ColorChannelRealValueEntity} */ this.B
/** @type {ColorChannelRealValueEntity} */ this.A
/** @type {RealUnitEntity} */ this.R
/** @type {RealUnitEntity} */ this.G
/** @type {RealUnitEntity} */ this.B
/** @type {RealUnitEntity} */ this.A
}
toRGBA() {
return [this.R, this.G, this.B, this.A]
return [this.R.value * 255, this.G.value * 255, this.B.value * 255, this.A.value * 255]
}
toHSV() {
const max = Math.max(this.R.value, this.G.value, this.B.value)
const min = Math.min(this.R.value, this.G.value, this.B.value)
const [r, g, b, a] = this.toRGBA()
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)
@@ -40,14 +59,14 @@ export default class LinearColorEntity extends IEntity {
case min:
h = 0
break
case this.R.value:
h = (this.G.value - this.B.value) + d * (this.G.value < this.B.value ? 6 : 0)
case r:
h = (g - b) + d * (g < b ? 6 : 0)
break
case this.G.value:
h = (this.B.value - this.R.value) + d * 2
case g:
h = (b - r) + d * 2
break
case this.B.value:
h = (this.R.value - this.G.value) + d * 4
case b:
h = (r - g) + d * 4
break
}
h /= 6 * d
@@ -55,7 +74,7 @@ export default class LinearColorEntity extends IEntity {
}
toNumber() {
return (this.R.value << 24) + (this.G.value << 16) + (this.B.value << 8) + this.A.value
return (this.R.value * 0xff << 3 * 0x8) + (this.G.value * 0xff << 2 * 0x8) + (this.B.value * 0xff << 0x8) + this.A.value
}
toString() {

View File

@@ -0,0 +1,24 @@
import IEntity from "./IEntity"
import Utility from "../Utility"
export default class RealUnitEntity extends IEntity {
static attributes = {
value: Number,
}
/** @param {Object | Number | String} options */
constructor(options = 0) {
super(options)
/** @type {Number} */
this.value = Utility.clamp(this.value, 0, 1)
}
valueOf() {
return this.value
}
toString() {
return this.value.toFixed(6)
}
}

View File

@@ -2,10 +2,13 @@ import Configuration from "../../Configuration"
import IPointing from "./IPointing"
import Utility from "../../Utility"
/** @typedef {import("../../Blueprint").default} Blueprint */
/**
* @typedef {import("../../Blueprint").default} Blueprint
* @typedef {import("../../element/IDraggableElement").default} IDraggableElement
*/
/**
* @template {HTMLElement} T
* @template {IDraggableElement} T
* @extends {IPointing<T>}
*/
export default class IMouseClickDrag extends IPointing {
@@ -29,6 +32,7 @@ export default class IMouseClickDrag extends IPointing {
started = false
stepSize = 1
clickedPosition = [0, 0]
clickedOffset = [0, 0]
mouseLocation = [0, 0]
/**
@@ -66,6 +70,10 @@ export default class IMouseClickDrag extends IPointing {
self.#movementListenedElement.addEventListener("mousemove", self.#mouseStartedMovingHandler)
document.addEventListener("mouseup", self.#mouseUpHandler)
self.clickedPosition = self.locationFromEvent(e)
self.clickedOffset = [
self.clickedPosition[0] - self.target.locationX,
self.clickedPosition[1] - self.target.locationY,
]
self.clicked(self.clickedPosition)
}
break

View File

@@ -18,6 +18,7 @@ export default class MouseMoveDraggable extends IMouseClickDrag {
? Utility.snapToGrid(location, this.stepSize)
: location
)
this.clickedOffset = [0, 0]
}
}
@@ -42,6 +43,9 @@ export default class MouseMoveDraggable extends IMouseClickDrag {
}
dragAction(location, offset) {
this.target.addLocation(offset)
this.target.setLocation([
location[0] - this.clickedOffset[0],
location[1] - this.clickedOffset[1]
])
}
}

View File

@@ -1,5 +1,4 @@
// @ts-nocheck
import ColorChannelValueEntity from "../entity/ColorChannelValueEntity"
import FunctionReferenceEntity from "../entity/FunctionReferenceEntity"
import GuidEntity from "../entity/GuidEntity"
import IdentifierEntity from "../entity/IdentifierEntity"
@@ -14,6 +13,7 @@ import Parsimmon from "parsimmon"
import PathSymbolEntity from "../entity/PathSymbolEntity"
import PinEntity from "../entity/PinEntity"
import PinReferenceEntity from "../entity/PinReferenceEntity"
import RealUnitEntity from "../entity/UnitRealEntity"
import RotatorEntity from "../entity/RotatorEntity"
import SimpleSerializationRotatorEntity from "../entity/SimpleSerializationRotatorEntity"
import SimpleSerializationVectorEntity from "../entity/SimpleSerializationVectorEntity"
@@ -37,44 +37,6 @@ export default class Grammar {
return result
}
switch (Utility.getType(attributeType)) {
case Boolean:
return r.Boolean
case Number:
return r.Number
case IntegerEntity:
return r.Integer
case String:
return r.String
case GuidEntity:
return r.Guid
case IdentifierEntity:
return r.Identifier
case ObjectReferenceEntity:
return r.Reference
case LocalizedTextEntity:
return r.LocalizedText
case InvariantTextEntity:
return r.InvariantText
case PinReferenceEntity:
return r.PinReference
case VectorEntity:
return r.Vector
case RotatorEntity:
return r.Rotator
case SimpleSerializationRotatorEntity:
return r.SimpleSerializationRotator
case SimpleSerializationVectorEntity:
return r.SimpleSerializationVector
case ColorChannelValueEntity:
return r.ColorChannelValue
case ColorChannelRealValue:
return r.ColorChannelRealValue
case LinearColorEntity:
return r.LinearColor
case FunctionReferenceEntity:
return r.FunctionReference
case PinEntity:
return r.Pin
case Array:
return P.seqMap(
P.string("("),
@@ -91,6 +53,42 @@ export default class Grammar {
P.string(")"),
(_, grammar, __) => grammar
)
case Boolean:
return r.Boolean
case FunctionReferenceEntity:
return r.FunctionReference
case GuidEntity:
return r.Guid
case IdentifierEntity:
return r.Identifier
case IntegerEntity:
return r.Integer
case InvariantTextEntity:
return r.InvariantText
case LinearColorEntity:
return r.LinearColor
case LocalizedTextEntity:
return r.LocalizedText
case Number:
return r.Number
case ObjectReferenceEntity:
return r.Reference
case PinEntity:
return r.Pin
case PinReferenceEntity:
return r.PinReference
case RealUnitEntity:
return r.RealUnit
case RotatorEntity:
return r.Rotator
case SimpleSerializationRotatorEntity:
return r.SimpleSerializationRotator
case SimpleSerializationVectorEntity:
return r.SimpleSerializationVector
case String:
return r.String
case VectorEntity:
return r.Vector
default:
return defaultGrammar
}
@@ -160,6 +158,9 @@ export default class Grammar {
/** @param {Grammar} r */
RealNumber = r => P.regex(/[-\+]?[0-9]+\.[0-9]+/).map(Number).desc("a number written as real")
/** @param {Grammar} r */
RealUnit = r => P.regex(/\+?[0-9]+(?:\.[0-9]+)?/).map(Number).assert(v => v >= 0 && v <= 1).desc("a number between 0 and 1")
/** @param {Grammar} r */
NaturalNumber = r => P.regex(/0|[1-9]\d*/).map(Number).desc("a natural number")
@@ -310,17 +311,6 @@ export default class Grammar {
})
)
/** @param {Grammar} r */
ColorChannelValue = r => P.alt(
r.RealNumber.map(v => new ColorChannelValueEntity(v * 255)),
r.ColorNumber.map(v => new ColorChannelValueEntity(v)),
)
/** @param {Grammar} r */
ColorChannelRealValue = r => P.alt(
r.RealNumber.map(v => new ColorChannelValueEntity(v * 255))
)
/** @param {Grammar} r */
LinearColor = r => Grammar.createEntityGrammar(r, LinearColorEntity)

View File

@@ -1,5 +1,3 @@
import ColorChannelRealValueEntity from "../entity/ColorChannelRealValueEntity"
import ColorChannelValueEntity from "../entity/ColorChannelValueEntity"
import CustomSerializer from "./CustomSerializer"
import FunctionReferenceEntity from "../entity/FunctionReferenceEntity"
import GeneralSerializer from "./GeneralSerializer"
@@ -16,6 +14,7 @@ import ObjectSerializer from "./ObjectSerializer"
import PathSymbolEntity from "../entity/PathSymbolEntity"
import PinEntity from "../entity/PinEntity"
import PinReferenceEntity from "../entity/PinReferenceEntity"
import RealUnitEntity from "../entity/UnitRealEntity"
import RotatorEntity from "../entity/RotatorEntity"
import SerializerFactory from "./SerializerFactory"
import SimpleSerializationRotatorEntity from "../entity/SimpleSerializationRotatorEntity"
@@ -66,16 +65,6 @@ export default function initializeSerializerFactory() {
)
)
SerializerFactory.registerSerializer(
ColorChannelRealValueEntity,
new ToStringSerializer(ColorChannelValueEntity)
)
SerializerFactory.registerSerializer(
ColorChannelValueEntity,
new ToStringSerializer(ColorChannelValueEntity)
)
SerializerFactory.registerSerializer(
FunctionReferenceEntity,
new GeneralSerializer(bracketsWrapped, FunctionReferenceEntity)
@@ -157,6 +146,11 @@ export default function initializeSerializerFactory() {
new GeneralSerializer(v => v, PinReferenceEntity, "", " ", false, "", _ => "")
)
SerializerFactory.registerSerializer(
RealUnitEntity,
new ToStringSerializer(RealUnitEntity)
)
SerializerFactory.registerSerializer(
RotatorEntity,
new GeneralSerializer(bracketsWrapped, RotatorEntity)

View File

@@ -1,14 +1,20 @@
import IDraggableTemplate from "./IDraggableTemplate"
import MouseMoveDraggable from "../input/mouse/MouseMoveDraggable"
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() {
@@ -22,4 +28,21 @@ export default class ColorHandlerTemplate extends IDraggableTemplate {
stepSize: 1,
})
}
/** @param {[Number, Number]} param0 */
adjustLocation([x, y]) {
const radius = Math.round(this.movementSpaceSize[0] / 2)
x = x - radius
y = -(y - radius)
let [r, theta] = Utility.getPolarCoordinates([x, y])
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])
return [x, y]
}
setLocationChangeCallback(callback) {
this.#locationChangeCallback = callback
}
}

View File

@@ -1,6 +1,8 @@
import { html } from "lit"
import ColorHandlerElement from "../element/ColorHandlerElement"
import Configuration from "../Configuration"
import LinearColorEntity from "../entity/LinearColorEntity"
import Utility from "../Utility"
import WindowTemplate from "./WindowTemplate"
/** @typedef {import("../element/WindowElement").default} WindowElement */
@@ -16,10 +18,11 @@ export default class ColorPickerWindowTemplate extends WindowTemplate {
}
/** @param {LinearColorEntity} value */
set color(value) {
if (value.toNumber() == this.color.toNumber()) {
this.element.requestUpdate("color", this.#color)
this.#color = value
if (value.toNumber() == this.color?.toNumber()) {
return
}
this.element.requestUpdate("color", this.#color)
this.#color = value
}
connectedCallback() {
@@ -29,6 +32,14 @@ export default class ColorPickerWindowTemplate extends WindowTemplate {
/** @param {Map} changedProperties */
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())
}
renderContent() {
@@ -40,9 +51,7 @@ export default class ColorPickerWindowTemplate extends WindowTemplate {
<div class="ueb-color-picker-srgb"></div>
</div>
<div class="ueb-color-picker-main">
<div class="ueb-color-picker-wheel">
<ueb-color-handler></ueb-color-handler>
</div>
<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-preview">

View File

@@ -18,9 +18,10 @@ export default class WindowTemplate extends IDraggableTemplate {
createDraggableObject() {
return new MouseMoveDraggable(this.element, this.element.blueprint, {
draggableElement: this.getDraggableElement(),
ignoreTranslateCompensate: true,
looseTarget: true,
stepSize: 1,
movementSpace: this.element.blueprint,
stepSize: 1,
})
}