some layer upgrades

Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>
This commit is contained in:
Victor Seiji Hariki 2022-11-27 13:06:13 -03:00
parent c1b17c1b0e
commit 4e27770284
4 changed files with 230 additions and 20 deletions

View file

@ -299,6 +299,8 @@
<!-- Base Libs -->
<script src="js/util.js" type="text/javascript"></script>
<script src="js/input.js" type="text/javascript"></script>
<script src="js/layers.js" type="text/javascript"></script>
<script src="js/commands.js" type="text/javascript"></script>
<script src="js/ui/history.js" type="text/javascript"></script>
<script src="js/settingsbar.js" type="text/javascript"></script>

View file

@ -112,6 +112,22 @@ const imgCtx = imgCanvas.getContext("2d");
const bgCanvas = document.getElementById("backgroundCanvas"); // gray bg grid
const bgCtx = bgCanvas.getContext("2d");
// Layering
const imageCollection = layers.registerCollection("image", {
name: "Image Layers",
scope: {
always: {
key: "default",
options: {
name: "Default Image Layer",
},
},
},
});
layers.registerCollection("mask", {name: "Mask Layers", requiresActive: true});
//
function startup() {
testHostConfiguration();
testHostConnection();

View file

@ -4,29 +4,169 @@
* It manages canvases and their locations and sizes according to current viewport views
*/
// Errors
class LayerNestedScopesError extends Error {
// For when a scope is created in another scope
}
class LayerNoScopeError extends Error {
// For when an action that requires a scope is attempted
// in a collection with no scope.
}
const layers = {
collections: makeWriteOnce({}, "layers.collections"),
// Registers a new collection
registerCollection: (key, options = {}) => {
defaultOpt(options, {
// If collection is visible on the Layer View Toolbar
visible: true,
// Display name for the collection
name: key,
/**
* If layer creates a layer scope
*
* A layer scope is a context where one, and only one layer inside it or its
* subscopes can be active at a time. Nested scopes are not supported.
* It receives an object of type:
*
* {
* // If there must be a selected layer, pass information to create the first
* always: {
* key,
* options
* }
* }
*/
scope: null,
// Parent collection
parent: null,
});
// Finds the closest parent with a defined scope
const findScope = (collection = options.parent) => {
if (!collection) return null;
if (collection.scope) return collection;
return findScope(collection._parent);
};
// Path used for logging purposes
const _logpath = options.parent
? options.parent + "." + key
: "layers.collections." + key;
// If we have a scope already, we can't add a new scope
if (options.scope && findScope())
throw new LayerNestedScopesError(`Layer scopes must not be nested`);
const collection = makeWriteOnce(
{
_parent: options.parent,
_logpath,
_layers: [],
layers: {},
name: options.name,
scope: options.scope,
// Registers a new layer
registerLayer: (name) => {
const layer = {
registerLayer: (key, options = {}) => {
defaultOpt(options, {
// Display name for the layer
name: key,
});
// Path used for logging purposes
const _layerlogpath = _logpath + ".layers." + key;
const layer = makeWriteOnce(
{
_logpath: _layerlogpath,
id: guid(),
name: layer,
// This is where black magic starts
name: options.name,
state: new Proxy(
{visible: true},
{
set(obj, opt, val) {
switch (opt) {
case "visible":
layer.canvas.style.display = val ? "block" : "none";
break;
}
obj[opt] = val;
},
}
),
// This is where black magic will take place in the future
// A proxy for the canvas object
canvas: new Proxy(document.createElement("canvas"), {}),
};
// Activates this layer in the scope
activate: () => {
const scope = findScope(collection);
if (scope) {
scope.active = layer;
console.debug(
`[layers] Layer ${layer._logpath} now active in scope ${scope._logpath}`
);
}
},
// Deactivates this layer in the scope
deactivate: () => {
const scope = findScope(collection);
if (scope && scope.active === layer) scope.active = null;
console.debug();
},
},
_layerlogpath
);
// Add to indexers
collection._layers.push(layer);
collection.layers[key] = layer;
console.info(
`[layers] Layer '${layer.name}' at ${layer._logpath} registered`
);
return layer;
},
// Deletes a layer
deleteLayer: (layer) => {
collection._layers.splice(
collection._layers.findIndex(
(l) => l.id === layer || l.id === layer.id
),
1
);
if (typeof layer === "object") {
layers._layers = layers._layers.filter((l) => l.id === layer.id);
delete layers[layer.id];
delete collection.layers[layer.id];
} else if (typeof layer === "string") {
layers._layers = layers._layers.filter((l) => l.id === layer);
delete layers[layer];
delete collection.layers[layer];
}
console.info(`[layers] Layer '${layer}' deleted`);
},
},
_logpath
);
if (parent) parent[key] = collection;
else layers.collections[key] = collection;
console.info(
`[layers] Collection '${options.name}' at ${_logpath} registered`
);
// If always, we must create a layer to select
if (options.scope && options.scope.always)
collection
.registerLayer(options.scope.always.key, options.scope.always.options)
.activate();
return collection;
},
};

52
js/util.d.ts vendored Normal file
View file

@ -0,0 +1,52 @@
/**
* Generates a random string in the following format:
*
* xxxx-xxxx-xxxx-...-xxxx
*
* @param size number of character quartets to generate
* @return Generated ID
*/
declare function guid(size: number): string;
/**
* Sets default values for options parameters
*
* @param options An object received as a parameter
* @param defaults An object with default values for each expected key
* @return The original options parameter
*/
declare function defaultOpt(
options: {[key: string]: any},
defaults: {[key: string]: any}
): {[key: string]: any};
/**
* Sets default values for options parameters
*
* @param options An object received as a parameter
* @param defaults An object with default values for each expected key
* @return The original options parameter
*/
declare function makeReadOnly(
options: {[key: string]: any},
defaults: {[key: string]: any}
): {[key: string]: any};
/**
* Makes an object read-only, throwing an exception when attempting to set
*
* @param obj Object to be proxied
* @param name Name of the object, for logging purposes
* @return The proxied object
*/
declare function makeReadOnly(obj: object, name?: string): object;
/**
* Makes an object have each key be writeable only once, throwing an exception when
* attempting to set an existing parameter
*
* @param obj Object to be proxied
* @param name Name of the object, for logging purposes
* @return The proxied object
*/
declare function makeWriteOnce(obj: object, name?: string): object;