lazy-widgets
Version:
Typescript retained mode GUI for the HTML canvas API
225 lines • 8.16 kB
JavaScript
import { DynMsg } from '../core/Strings.js';
/**
* A decorator for a public field which sets calls a callback if the property's
* value is changed.
*
* @typeParam V - The type of the field being watched
* @param callback - The callback to call if the value changes. `this` is bound.
* @category Decorator
*/
export function watchField(callback) {
// eslint-disable-next-line @typescript-eslint/ban-types
return function (target, propertyKey) {
const curValues = new WeakMap();
Object.defineProperty(target, propertyKey, {
set: function (value) {
const oldValue = curValues.get(this);
if (value !== oldValue) {
curValues.set(this, value);
callback.call(this, oldValue);
}
},
get: function () {
return curValues.get(this);
},
enumerable: true,
configurable: true,
});
};
}
/**
* A {@link watchField} which sets a given flag to true.
*
* @param flagKey - The key of the flag property to set to true
* @category Decorator
*/
export function flagField(flagKey) {
// eslint-disable-next-line @typescript-eslint/ban-types
return watchField(function (_oldValue) {
this[flagKey] = true;
});
}
/**
* A {@link watchField} which calls a method named `markWholeAsDirty` with no
* arguments. It's recommended to only use this on {@link Widget} and its
* subclasses, although technically you can use it in any class so long as it
* has a `markWholeAsDirty` method.
*
* @category Decorator
*/
// eslint-disable-next-line @typescript-eslint/ban-types
export const damageField = watchField(function (_oldValue) {
Object.getPrototypeOf(this).markWholeAsDirty.apply(this);
});
/**
* A {@link flagField} where the flag key is `_layoutDirty`.
*
* @category Decorator
*/
export const layoutField = flagField('_layoutDirty');
/**
* A {@link watchField} which sets a given array of flags to true.
*
* @param flagKeys - An array containing the keys of each flag property to set to true
* @category Decorator
*/
export function multiFlagField(flagKeys) {
// eslint-disable-next-line @typescript-eslint/ban-types
return watchField(function (_oldValue) {
for (const flagKey of flagKeys) {
this[flagKey] = true;
}
});
}
/**
* Similar to {@link watchField}, but for array fields, like tuples. Getting the
* property returns a shallow copy of the tuple, setting the value uses a
* shallow copy of the input value if the current value is not an array. If both
* the new value and the current value are arrays, then the current value's
* members are updated; no shallow copy is created.
*
* @param callback - The callback to call if the value changes. `this` is bound.
* @param allowNonArrays - Allow values which are not arrays to be used?
* @category Decorator
*/
export function watchArrayField(callback, allowNonArrays = false) {
// eslint-disable-next-line @typescript-eslint/ban-types
return function (target, propertyKey) {
// eslint-disable-next-line @typescript-eslint/ban-types
const curValues = new WeakMap();
Object.defineProperty(target, propertyKey, {
set: function (value) {
if (Array.isArray(value)) {
const curTuple = curValues.get(this);
if (Array.isArray(curTuple)) {
if (value.length !== curTuple.length) {
curTuple.length = value.length;
for (let i = 0; i < value.length; i++) {
curTuple[i] = value[i];
}
callback.call(this);
}
else {
for (let i = 0; i < value.length; i++) {
if (curTuple[i] !== value[i]) {
for (let j = 0; j < value.length; j++) {
curTuple[j] = value[j];
}
callback.call(this);
return;
}
}
}
}
else {
curValues.set(this, [...value]);
callback.call(this);
}
}
else {
if (allowNonArrays) {
curValues.set(this, value);
callback.call(this);
}
else {
throw new Error(DynMsg.NON_ARRAY_VALUE(propertyKey, value));
}
}
},
get: function () {
const curTuple = curValues.get(this);
if (!Array.isArray(curTuple)) {
return curTuple;
}
return [...curTuple];
},
enumerable: true,
configurable: true,
});
};
}
/**
* A {@link watchArrayField} which sets a given flag to true.
*
* @param flagKey - The key of the flag property to set to true
* @param allowNonArrays - Allow values which are not arrays to be used?
* @category Decorator
*/
export function flagArrayField(flagKey, allowNonArrays = false) {
// eslint-disable-next-line @typescript-eslint/ban-types
return watchArrayField(function () {
this[flagKey] = true;
}, allowNonArrays);
}
/**
* A mix between {@link damageField} and {@link watchArrayField}.
*
* @param allowNonArrays - Allow values which are not arrays to be used?
* @category Decorator
*/
export function damageArrayField(allowNonArrays = false) {
// eslint-disable-next-line @typescript-eslint/ban-types
return watchArrayField(function () {
Object.getPrototypeOf(this).markWholeAsDirty.apply(this);
}, allowNonArrays);
}
/**
* A {@link flagArrayField} where the flag key is `_layoutDirty`.
*
* @param allowNonArrays - Allow values which are not arrays to be used?
* @category Decorator
*/
export function layoutArrayField(allowNonArrays = false) {
return flagArrayField('_layoutDirty', allowNonArrays);
}
/**
* A {@link watchArrayField} which sets a given array of flags to true.
*
* @param flagKeys - An array containing the keys of each flag property to set to true
* @param allowNonArrays - Allow values which are not arrays to be used?
* @category Decorator
*/
export function multiFlagArrayField(flagKeys, allowNonArrays = false) {
// eslint-disable-next-line @typescript-eslint/ban-types
return watchArrayField(function () {
for (const flagKey of flagKeys) {
this[flagKey] = true;
}
}, allowNonArrays);
}
/**
* A {@link watchArrayField} where the '_layoutDirty' flag is set, and a damage
* method is called in the same way as {@link damageField}.
*
* @param allowNonArrays - Allow values which are not arrays to be used?
* @category Decorator
*/
export function damageLayoutArrayField(allowNonArrays = false) {
// eslint-disable-next-line @typescript-eslint/ban-types
return watchArrayField(function () {
this._layoutDirty = true;
Object.getPrototypeOf(this).markWholeAsDirty.apply(this);
}, allowNonArrays);
}
/**
* A decorator for a public field which is an alias for another field.
*
* @param fieldName - The name of the field to create an alias for.
* @category Decorator
*/
export function accessorAlias(fieldName) {
// eslint-disable-next-line @typescript-eslint/ban-types
return function (target, propertyKey) {
Object.defineProperty(target, propertyKey, {
set: function (value) {
this[fieldName] = value;
},
get: function () {
return this[fieldName];
},
enumerable: true,
configurable: true,
});
};
}
//# sourceMappingURL=FlagFields.js.map