This commit is contained in:
2025-01-04 00:34:03 +01:00
parent 41829408dc
commit 0ca14bbc19
18111 changed files with 1871397 additions and 0 deletions

View File

@@ -0,0 +1,152 @@
/**
* An abstract pattern for primary layers of the game canvas to implement.
* @category - Canvas
* @abstract
* @interface
*/
class CanvasLayer extends PIXI.Container {
/**
* Options for this layer instance.
* @type {{name: string}}
*/
options = this.constructor.layerOptions;
// Default interactivity
interactiveChildren = false;
/* -------------------------------------------- */
/* Layer Attributes */
/* -------------------------------------------- */
/**
* Customize behaviors of this CanvasLayer by modifying some behaviors at a class level.
* @type {{name: string}}
*/
static get layerOptions() {
return {
name: "",
baseClass: CanvasLayer
};
}
/* -------------------------------------------- */
/**
* Return a reference to the active instance of this canvas layer
* @type {CanvasLayer}
*/
static get instance() {
return canvas[this.layerOptions.name];
}
/* -------------------------------------------- */
/**
* The canonical name of the CanvasLayer is the name of the constructor that is the immediate child of the
* defined baseClass for the layer type.
* @type {string}
*
* @example
* canvas.lighting.name -> "LightingLayer"
*/
get name() {
const baseCls = this.constructor.layerOptions.baseClass;
let cls = Object.getPrototypeOf(this.constructor);
let name = this.constructor.name;
while ( cls ) {
if ( cls !== baseCls ) {
name = cls.name;
cls = Object.getPrototypeOf(cls);
}
else break;
}
return name;
}
/* -------------------------------------------- */
/**
* The name used by hooks to construct their hook string.
* Note: You should override this getter if hookName should not return the class constructor name.
* @type {string}
*/
get hookName() {
return this.name;
}
/* -------------------------------------------- */
/**
* An internal reference to a Promise in-progress to draw the CanvasLayer.
* @type {Promise<CanvasLayer>}
*/
#drawing = Promise.resolve(this);
/* -------------------------------------------- */
/**
* Is the layer drawn?
* @type {boolean}
*/
#drawn = false;
/* -------------------------------------------- */
/* Rendering
/* -------------------------------------------- */
/**
* Draw the canvas layer, rendering its internal components and returning a Promise.
* The Promise resolves to the drawn layer once its contents are successfully rendered.
* @param {object} [options] Options which configure how the layer is drawn
* @returns {Promise<CanvasLayer>}
*/
async draw(options={}) {
return this.#drawing = this.#drawing.finally(async () => {
console.log(`${vtt} | Drawing the ${this.constructor.name} canvas layer`);
await this.tearDown();
await this._draw(options);
Hooks.callAll(`draw${this.hookName}`, this);
this.#drawn = true;
});
}
/**
* The inner _draw method which must be defined by each CanvasLayer subclass.
* @param {object} options Options which configure how the layer is drawn
* @abstract
* @protected
*/
async _draw(options) {
throw new Error(`The ${this.constructor.name} subclass of CanvasLayer must define the _draw method`);
}
/* -------------------------------------------- */
/**
* Deconstruct data used in the current layer in preparation to re-draw the canvas
* @param {object} [options] Options which configure how the layer is deconstructed
* @returns {Promise<CanvasLayer>}
*/
async tearDown(options={}) {
if ( !this.#drawn ) return this;
MouseInteractionManager.emulateMoveEvent();
this.#drawn = false;
this.renderable = false;
await this._tearDown(options);
Hooks.callAll(`tearDown${this.hookName}`, this);
this.renderable = true;
MouseInteractionManager.emulateMoveEvent();
return this;
}
/**
* The inner _tearDown method which may be customized by each CanvasLayer subclass.
* @param {object} options Options which configure how the layer is deconstructed
* @protected
*/
async _tearDown(options) {
this.removeChildren().forEach(c => c.destroy({children: true}));
}
}

View File

@@ -0,0 +1,229 @@
/**
* A subclass of CanvasLayer which provides support for user interaction with its contained objects.
* @category - Canvas
*/
class InteractionLayer extends CanvasLayer {
/**
* Is this layer currently active
* @type {boolean}
*/
get active() {
return this.#active;
}
/** @ignore */
#active = false;
/** @override */
eventMode = "passive";
/**
* Customize behaviors of this CanvasLayer by modifying some behaviors at a class level.
* @type {{name: string, zIndex: number}}
*/
static get layerOptions() {
return Object.assign(super.layerOptions, {
baseClass: InteractionLayer,
zIndex: 0
});
}
/* -------------------------------------------- */
/* Methods */
/* -------------------------------------------- */
/**
* Activate the InteractionLayer, deactivating other layers and marking this layer's children as interactive.
* @param {object} [options] Options which configure layer activation
* @param {string} [options.tool] A specific tool in the control palette to set as active
* @returns {InteractionLayer} The layer instance, now activated
*/
activate({tool}={}) {
// Set this layer as active
const wasActive = this.#active;
this.#active = true;
// Deactivate other layers
for ( const name of Object.keys(Canvas.layers) ) {
const layer = canvas[name];
if ( (layer !== this) && (layer instanceof InteractionLayer) ) layer.deactivate();
}
// Re-render Scene controls
ui.controls?.initialize({layer: this.constructor.layerOptions.name, tool});
if ( wasActive ) return this;
// Reset the interaction manager
canvas.mouseInteractionManager?.reset({state: false});
// Assign interactivity for the active layer
this.zIndex = this.getZIndex();
this.eventMode = "static";
this.interactiveChildren = true;
// Call layer-specific activation procedures
this._activate();
Hooks.callAll(`activate${this.hookName}`, this);
Hooks.callAll("activateCanvasLayer", this);
return this;
}
/**
* The inner _activate method which may be defined by each InteractionLayer subclass.
* @protected
*/
_activate() {}
/* -------------------------------------------- */
/**
* Deactivate the InteractionLayer, removing interactivity from its children.
* @returns {InteractionLayer} The layer instance, now inactive
*/
deactivate() {
if ( !this.#active ) return this;
canvas.highlightObjects(false);
this.#active = false;
this.eventMode = "passive";
this.interactiveChildren = false;
this.zIndex = this.getZIndex();
this._deactivate();
Hooks.callAll(`deactivate${this.hookName}`, this);
return this;
}
/**
* The inner _deactivate method which may be defined by each InteractionLayer subclass.
* @protected
*/
_deactivate() {}
/* -------------------------------------------- */
/** @override */
async _draw(options) {
this.hitArea = canvas.dimensions.rect;
this.zIndex = this.getZIndex();
}
/* -------------------------------------------- */
/**
* Get the zIndex that should be used for ordering this layer vertically relative to others in the same Container.
* @returns {number}
*/
getZIndex() {
return this.options.zIndex;
}
/* -------------------------------------------- */
/* Event Listeners and Handlers */
/* -------------------------------------------- */
/**
* Handle left mouse-click events which originate from the Canvas stage.
* @see {@link Canvas._onClickLeft}
* @param {PIXI.FederatedEvent} event The PIXI InteractionEvent which wraps a PointerEvent
* @protected
*/
_onClickLeft(event) {}
/* -------------------------------------------- */
/**
* Handle double left-click events which originate from the Canvas stage.
* @see {@link Canvas.#onClickLeft2}
* @param {PIXI.FederatedEvent} event The PIXI InteractionEvent which wraps a PointerEvent
* @protected
*/
_onClickLeft2(event) {}
/* -------------------------------------------- */
/**
* Does the User have permission to left-click drag on the Canvas?
* @param {User} user The User performing the action.
* @param {PIXI.FederatedEvent} event The event object.
* @returns {boolean}
* @protected
*/
_canDragLeftStart(user, event) {
return true;
}
/* -------------------------------------------- */
/**
* Start a left-click drag workflow originating from the Canvas stage.
* @see {@link Canvas.#onDragLeftStart}
* @param {PIXI.FederatedEvent} event The PIXI InteractionEvent which wraps a PointerEvent
* @protected
*/
_onDragLeftStart(event) {}
/* -------------------------------------------- */
/**
* Continue a left-click drag workflow originating from the Canvas stage.
* @see {@link Canvas.#onDragLeftMove}
* @param {PIXI.FederatedEvent} event The PIXI InteractionEvent which wraps a PointerEvent
* @protected
*/
_onDragLeftMove(event) {}
/* -------------------------------------------- */
/**
* Conclude a left-click drag workflow originating from the Canvas stage.
* @see {@link Canvas.#onDragLeftDrop}
* @param {PIXI.FederatedEvent} event The PIXI InteractionEvent which wraps a PointerEvent
* @protected
*/
_onDragLeftDrop(event) {}
/* -------------------------------------------- */
/**
* Cancel a left-click drag workflow originating from the Canvas stage.
* @see {@link Canvas.#onDragLeftDrop}
* @param {PointerEvent} event A right-click pointer event on the document.
* @protected
*/
_onDragLeftCancel(event) {}
/* -------------------------------------------- */
/**
* Handle right mouse-click events which originate from the Canvas stage.
* @see {@link Canvas._onClickRight}
* @param {PIXI.FederatedEvent} event The PIXI InteractionEvent which wraps a PointerEvent
* @protected
*/
_onClickRight(event) {}
/* -------------------------------------------- */
/**
* Handle mouse-wheel events which occur for this active layer.
* @see {@link MouseManager._onWheel}
* @param {WheelEvent} event The WheelEvent initiated on the document
* @protected
*/
_onMouseWheel(event) {}
/* -------------------------------------------- */
/**
* Handle a DELETE keypress while this layer is active.
* @see {@link ClientKeybindings._onDelete}
* @param {KeyboardEvent} event The delete key press event
* @protected
*/
async _onDeleteKey(event) {}
}
/* -------------------------------------------- */

File diff suppressed because it is too large Load Diff