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

665
dist/ueblueprint.js vendored
View File

@@ -268,6 +268,143 @@ class CalculatedType {
}
}
class Observable {
/** @type {Map<String, Object[]>} */
#observers = new Map()
/**
* @param {String} property
* @param {(value: any) => {}} observer
*/
subscribe(property, observer) {
let observers = this.#observers;
if (observers.has(property)) {
let propertyObservers = observers.get(property);
if (propertyObservers.includes(observer)) {
return false
} else {
propertyObservers.push(observer);
}
} else {
let fromPrototype = false;
let propertyDescriptor = Object.getOwnPropertyDescriptor(this, property);
if (!propertyDescriptor) {
fromPrototype = true;
propertyDescriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this), property) ?? {};
if (!propertyDescriptor) {
return false
}
}
observers.set(property, [observer]);
const isValue = "value" in propertyDescriptor;
const hasSetter = "set" in propertyDescriptor;
if (!(isValue || hasSetter)) {
throw new Error(`Property ${property} is not a value or a setter`)
}
// A Symbol so it does not show up in Object.getOwnPropertyNames()
const storageKey = Symbol.for(property + "Storage");
const valInfoKey = Symbol.for(property + "ValInfo");
Object.defineProperties(
fromPrototype ? Object.getPrototypeOf(this) : this,
{
[storageKey]: {
configurable: true,
enumerable: false, // Non enumerable so it does not show up in for...in or Object.keys()
...(isValue
? {
value: this[property],
writable: true,
}
: {
get: propertyDescriptor.get,
set: propertyDescriptor.set,
}
)
},
[valInfoKey]: {
configurable: true,
enumerable: false,
value: [fromPrototype, isValue]
},
[property]: {
configurable: true,
...(isValue && {
get() {
return this[storageKey]
}
}),
set(v) {
this[storageKey] = v;
observers.get(property).forEach(observer => {
observer(this[property]);
});
},
}
}
);
}
return true
}
/**
* @param {String} property
* @param {Object} observer
*/
unsubscribe(property, observer) {
let observers = this.#observers.get(property);
if (!observers?.includes(observer)) {
return false
}
observers.splice(observers.indexOf(observer), 1);
if (observers.length == 0) {
const storageKey = Symbol.for(property + "Storage");
const valInfoKey = Symbol.for(property + "ValInfo");
const fromPrototype = this[valInfoKey][0];
this[valInfoKey][1];
Object.defineProperty(
fromPrototype ? Object.getPrototypeOf(this) : this,
property,
Object.getOwnPropertyDescriptor(fromPrototype ? Object.getPrototypeOf(this) : this, storageKey),
);
delete this[valInfoKey];
delete this[storageKey];
}
return true
}
}
/**
* @typedef {import("../entity/IEntity").default} IEntity
* @typedef {import("../entity/TypeInitialization").AnyValue} AnyValue
*/
/**
* @template T
* @typedef {import("../entity/TypeInitialization").AnyValueConstructor<T>} AnyValueConstructor
*/
/**
* @template {AnyValue} T
* @typedef {import("./ISerializer").default<T>} ISerializer
*/
class SerializerFactory {
/** @type {Map<AnyValueConstructor<AnyValue>, ISerializer<AnyValue>>} */
static #serializers = new Map()
static registerSerializer(entity, object) {
SerializerFactory.#serializers.set(entity, object);
}
/**
* @template {AnyValue} T
* @param {AnyValueConstructor<T>} entity
*/
static getSerializer(entity) {
return SerializerFactory.#serializers.get(entity)
}
}
/**
* @typedef {import("./IEntity").default} IEntity
* @typedef {IEntity | String | Number | Boolean | Array} AnyValue
@@ -597,144 +734,23 @@ class Utility {
/** @param {LinearColorEntity} value */
static printLinearColor(value) {
return `${Math.round(value.R * 255)}, ${Math.round(value.G * 255)}, ${Math.round(value.B * 255)}`
}
}
class Observable {
/** @type {Map<String, Object[]>} */
#observers = new Map()
/**
* @param {String} property
* @param {(value: any) => {}} observer
*/
subscribe(property, observer) {
let observers = this.#observers;
if (observers.has(property)) {
let propertyObservers = observers.get(property);
if (propertyObservers.includes(observer)) {
return false
} else {
propertyObservers.push(observer);
}
} else {
let fromPrototype = false;
let propertyDescriptor = Object.getOwnPropertyDescriptor(this, property);
if (!propertyDescriptor) {
fromPrototype = true;
propertyDescriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this), property) ?? {};
if (!propertyDescriptor) {
return false
}
}
observers.set(property, [observer]);
const isValue = "value" in propertyDescriptor;
const hasSetter = "set" in propertyDescriptor;
if (!(isValue || hasSetter)) {
throw new Error(`Property ${property} is not a value or a setter`)
}
// A Symbol so it does not show up in Object.getOwnPropertyNames()
const storageKey = Symbol.for(property + "Storage");
const valInfoKey = Symbol.for(property + "ValInfo");
Object.defineProperties(
fromPrototype ? Object.getPrototypeOf(this) : this,
{
[storageKey]: {
configurable: true,
enumerable: false, // Non enumerable so it does not show up in for...in or Object.keys()
...(isValue
? {
value: this[property],
writable: true,
}
: {
get: propertyDescriptor.get,
set: propertyDescriptor.set,
}
)
},
[valInfoKey]: {
configurable: true,
enumerable: false,
value: [fromPrototype, isValue]
},
[property]: {
configurable: true,
...(isValue && {
get() {
return this[storageKey]
}
}),
set(v) {
this[storageKey] = v;
observers.get(property).forEach(observer => {
observer(this[property]);
});
},
}
}
);
}
return true
}
/**
* @param {String} property
* @param {Object} observer
*/
unsubscribe(property, observer) {
let observers = this.#observers.get(property);
if (!observers?.includes(observer)) {
return false
}
observers.splice(observers.indexOf(observer), 1);
if (observers.length == 0) {
const storageKey = Symbol.for(property + "Storage");
const valInfoKey = Symbol.for(property + "ValInfo");
const fromPrototype = this[valInfoKey][0];
this[valInfoKey][1];
Object.defineProperty(
fromPrototype ? Object.getPrototypeOf(this) : this,
property,
Object.getOwnPropertyDescriptor(fromPrototype ? Object.getPrototypeOf(this) : this, storageKey),
);
delete this[valInfoKey];
delete this[storageKey];
}
return true
}
}
/**
* @typedef {import("../entity/IEntity").default} IEntity
* @typedef {import("../entity/TypeInitialization").AnyValue} AnyValue
*/
/**
* @template T
* @typedef {import("../entity/TypeInitialization").AnyValueConstructor<T>} AnyValueConstructor
*/
/**
* @template {AnyValue} T
* @typedef {import("./ISerializer").default<T>} ISerializer
*/
class SerializerFactory {
/** @type {Map<AnyValueConstructor<AnyValue>, ISerializer<AnyValue>>} */
static #serializers = new Map()
static registerSerializer(entity, object) {
SerializerFactory.#serializers.set(entity, object);
return `${Math.round(value.R.valueOf() * 255)}, ${Math.round(value.G.valueOf() * 255)}, ${Math.round(value.B.valueOf() * 255)}`
}
/**
* @template {AnyValue} T
* @param {AnyValueConstructor<T>} entity
*/
static getSerializer(entity) {
return SerializerFactory.#serializers.get(entity)
/** @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)
]
}
}
@@ -836,41 +852,6 @@ class IEntity extends Observable {
}
}
class IntegerEntity extends IEntity {
static attributes = {
value: Number,
}
/** @param {Object | Number | String} options */
constructor(options = 0) {
super(options);
/** @type {Number} */
this.value = Math.round(this.value);
}
valueOf() {
return this.value
}
toString() {
return this.value.toString()
}
}
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);
}
}
class ObjectReferenceEntity extends IEntity {
static attributes = {
@@ -956,6 +937,28 @@ class IdentifierEntity extends IEntity {
}
}
class IntegerEntity extends IEntity {
static attributes = {
value: Number,
}
/** @param {Object | Number | String} options */
constructor(options = 0) {
super(options);
/** @type {Number} */
this.value = Math.round(this.value);
}
valueOf() {
return this.value
}
toString() {
return this.value.toString()
}
}
class InvariantTextEntity extends IEntity {
static lookbehind = "INVTEXT"
@@ -996,42 +999,77 @@ class KeyBindingEntity extends IEntity {
}
}
class ColorChannelRealValueEntity extends IntegerEntity {
toString() {
return (this.value / 255).toFixed(6)
}
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)
}
}
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 [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);
@@ -1040,14 +1078,14 @@ 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;
@@ -1055,7 +1093,7 @@ 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() {
@@ -1442,42 +1480,6 @@ 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 LinearColorEntity:
return r.LinearColor
case FunctionReferenceEntity:
return r.FunctionReference
case PinEntity:
return r.Pin
case Array:
return P.seqMap(
P.string("("),
@@ -1494,6 +1496,42 @@ 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
}
@@ -1563,6 +1601,9 @@ 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")
@@ -1713,17 +1754,6 @@ 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)
@@ -2363,10 +2393,13 @@ class KeyboardSelectAll extends IKeyboardShortcut {
}
}
/** @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>}
*/
class IMouseClickDrag extends IPointing {
@@ -2390,6 +2423,7 @@ class IMouseClickDrag extends IPointing {
started = false
stepSize = 1
clickedPosition = [0, 0]
clickedOffset = [0, 0]
mouseLocation = [0, 0]
/**
@@ -2427,6 +2461,10 @@ 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
@@ -2708,6 +2746,7 @@ class IElement extends s {
this.template.inputSetup();
}
/** @param {Map<String, String>} */
updated(changedProperties) {
super.updated(changedProperties);
this.template.updated(changedProperties);
@@ -3420,7 +3459,6 @@ class MouseCreateLink extends IMouseClickDrag {
}
/**
* @typedef {import("../element/NodeElement").default} NodeElement
* @typedef {import("../input/IInput").default} IInput
*/
/**
@@ -3546,8 +3584,7 @@ class BoolPinTemplate extends PinTemplate {
class ExecPinTemplate extends PinTemplate {
/** @param {PinElement} pin */
renderIcon(pin) {
renderIcon() {
return $`
<svg viewBox="-2 0 16 16">
<path class="ueb-pin-tofill" stroke-width="1.25" stroke="white" fill="none"
@@ -3574,6 +3611,7 @@ class MouseMoveDraggable extends IMouseClickDrag {
? Utility.snapToGrid(location, this.stepSize)
: location
);
this.clickedOffset = [0, 0];
}
}
@@ -3598,7 +3636,10 @@ class MouseMoveDraggable extends IMouseClickDrag {
}
dragAction(location, offset) {
this.target.addLocation(offset);
this.target.setLocation([
location[0] - this.clickedOffset[0],
location[1] - this.clickedOffset[1]
]);
}
}
@@ -3643,6 +3684,84 @@ class IDraggableTemplate extends ITemplate {
}
}
/** @typedef {import("../element/ColorHandlerElement").default} ColorHandlerElement */
/** @extends {IDraggableTemplate<ColorHandlerElement>} */
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,
looseTarget: true,
moveEverywhere: true,
movementSpace: this.element.parentElement,
repositionClickOffset: true,
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;
}
}
/** @typedef {import("../template/ColorPickerWindowTemplate").default} ColorPickerWindowTemplate */
/**
* @template T
* @typedef {import("./WindowElement").default<T>} WindowElement
*/
/** @extends {IDraggableElement<Object, ColorHandlerTemplate>} */
class ColorHandlerElement extends IDraggableElement {
/** @type {WindowElement<ColorPickerWindowTemplate>} */
windowElement
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);
/** @typedef {import("../element/WindowElement").default} WindowElement */
/** @extends {IDraggableTemplate<WindowElement>} */
@@ -3659,9 +3778,10 @@ 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,
})
}
@@ -3711,10 +3831,11 @@ 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() {
@@ -3724,6 +3845,13 @@ class ColorPickerWindowTemplate extends WindowTemplate {
/** @param {Map} changedProperties */
firstUpdated(changedProperties) {
const wheelHandler = new ColorHandlerElement();
new ColorHandlerElement();
wheelHandler.template.setLocationChangeCallback(([x, y]) => {
this.color = LinearColorEntity.fromWheelLocation(x, y);
});
this.element.querySelector(".ueb-color-picker-wheel").appendChild(new ColorHandlerElement());
}
renderContent() {
@@ -3735,9 +3863,7 @@ 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">
@@ -4039,7 +4165,7 @@ class MouseOpenWindow extends IMouseClick {
/**
* @typedef {import("../element/PinElement").default} PinElement
* @typedef {import("../entity/LinearColorEntity").default} LinearColorEntity}
* @typedef {import("../entity/LinearColorEntity").default} LinearColorEntity
*/
class LinearColorPinTemplate extends IInputPinTemplate {
@@ -4053,7 +4179,6 @@ class LinearColorPinTemplate extends IInputPinTemplate {
this.#input = this.element.querySelector(".ueb-pin-input");
}
/** @returns {IInput[]} */
createInputObjects() {
return [
...super.createInputObjects(),
@@ -4071,8 +4196,7 @@ class LinearColorPinTemplate extends IInputPinTemplate {
]
}
/** @param {PinElement} pin */
getInputs(pin) {
getInputs() {
return [this.#input.dataset.linearColor]
}
@@ -4099,10 +4223,7 @@ class NamePinTemplate extends IInputPinTemplate {
/** @type {(e : InputEvent) => void} */
onInputHandler
/**
* @param {PinElement} pin
* @param {Map} changedProperties
*/
/** @param {Map} changedProperties */
firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
this.onInputHandler = e => {
@@ -4128,12 +4249,11 @@ class NamePinTemplate extends IInputPinTemplate {
});
}
/** @param {PinElement} pin */
getInputs(pin) {
getInputs() {
return this.inputContentElements.map(element => element.textContent) // textContent for performance reason
}
/** @param {String[]?} values */
/** @param {String[]} values */
setInputs(values = [], updateDefaultValue = true) {
values = values.map(value => value.replaceAll("\n", "")); // get rid of the new lines
super.setInputs(values, updateDefaultValue);
@@ -5931,16 +6051,6 @@ function initializeSerializerFactory() {
)
);
SerializerFactory.registerSerializer(
ColorChannelRealValueEntity,
new ToStringSerializer(ColorChannelValueEntity)
);
SerializerFactory.registerSerializer(
ColorChannelValueEntity,
new ToStringSerializer(ColorChannelValueEntity)
);
SerializerFactory.registerSerializer(
FunctionReferenceEntity,
new GeneralSerializer(bracketsWrapped, FunctionReferenceEntity)
@@ -6022,6 +6132,11 @@ function initializeSerializerFactory() {
new GeneralSerializer(v => v, PinReferenceEntity, "", " ", false, "", _ => "")
);
SerializerFactory.registerSerializer(
RealUnitEntity,
new ToStringSerializer(RealUnitEntity)
);
SerializerFactory.registerSerializer(
RotatorEntity,
new GeneralSerializer(bracketsWrapped, RotatorEntity)

File diff suppressed because one or more lines are too long

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,
})
}