From 089657a896c9d3c5ae9cb82cd36e2ee0470b5257 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Tue, 5 Aug 2025 04:10:16 +0200 Subject: [PATCH] Added a TemplateManager singleton for creating movable previewTemplates (#580) --- daggerheart.mjs | 3 + module/documents/_module.mjs | 1 + module/documents/templateManager.mjs | 99 +++++++++++++++++++++++++++ module/enrichers/TemplateEnricher.mjs | 20 +++--- 4 files changed, 113 insertions(+), 10 deletions(-) create mode 100644 module/documents/templateManager.mjs diff --git a/daggerheart.mjs b/daggerheart.mjs index 0fdca67c..4f9c4a44 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -19,6 +19,7 @@ import { import { placeables } from './module/canvas/_module.mjs'; import { registerRollDiceHooks } from './module/dice/dhRoll.mjs'; import './node_modules/@yaireo/tagify/dist/tagify.css'; +import TemplateManager from './module/documents/templateManager.mjs'; Hooks.once('init', () => { CONFIG.DH = SYSTEM; @@ -139,6 +140,8 @@ Hooks.once('init', () => { CONFIG.ux.ContextMenu = applications.ux.DHContextMenu; CONFIG.ux.TooltipManager = documents.DhTooltipManager; + CONFIG.ux.TemplateManager = new TemplateManager(); + game.socket.on(`system.${SYSTEM.id}`, socketRegistration.handleSocketEvent); // Make Compendium Dialog resizable diff --git a/module/documents/_module.mjs b/module/documents/_module.mjs index 540b06c1..ce33f982 100644 --- a/module/documents/_module.mjs +++ b/module/documents/_module.mjs @@ -5,3 +5,4 @@ export { default as DhActiveEffect } from './activeEffect.mjs'; export { default as DhChatMessage } from './chatMessage.mjs'; export { default as DhToken } from './token.mjs'; export { default as DhTooltipManager } from './tooltipManager.mjs'; +export { default as DhTemplateManager } from './templateManager.mjs'; diff --git a/module/documents/templateManager.mjs b/module/documents/templateManager.mjs new file mode 100644 index 00000000..c31b1baa --- /dev/null +++ b/module/documents/templateManager.mjs @@ -0,0 +1,99 @@ +/** + * A singleton class that handles preview templates. + */ + +export default class DhTemplateManager { + #activePreview; + + /** + * Create a template preview, deactivating any existing ones. + * @param {object} data + */ + async createPreview(data) { + const template = await canvas.templates._createPreview(data, { renderSheet: false }); + + this.#activePreview = { + document: template.document, + object: template, + origin: { x: template.document.x, y: template.document.y } + }; + + this.#activePreview.events = { + contextmenu: this.#cancelTemplate.bind(this), + mousedown: this.#confirmTemplate.bind(this), + mousemove: this.#onDragMouseMove.bind(this), + wheel: this.#onMouseWheel.bind(this) + }; + canvas.stage.on('mousemove', this.#activePreview.events.mousemove); + canvas.stage.on('mousedown', this.#activePreview.events.mousedown); + + canvas.app.view.addEventListener('wheel', this.#activePreview.events.wheel, true); + canvas.app.view.addEventListener('contextmenu', this.#activePreview.events.contextmenu); + } + + /** + * Handles the movement of the temlate preview on mousedrag. + * @param {mousemove Event} event + */ + #onDragMouseMove(event) { + event.stopPropagation(); + const { moveTime, object } = this.#activePreview; + const update = {}; + + const now = Date.now(); + if (now - (moveTime || 0) <= 16) return; + this.#activePreview.moveTime = now; + + let cursor = event.getLocalPosition(canvas.templates); + + Object.assign(update, canvas.grid.getCenterPoint(cursor)); + + object.document.updateSource(update); + object.renderFlags.set({ refresh: true }); + } + + /** + * Handles the rotation of the preview template on scrolling. + * @param {wheel Event} event + */ + #onMouseWheel(event) { + if (!event.shiftKey) return; + event.stopPropagation(); + event.preventDefault(); + const { moveTime, object } = this.#activePreview; + + const now = Date.now(); + if (now - (moveTime || 0) <= 16) return; + this.#activePreview.moveTime = now; + + object.document.updateSource({ + direction: object.document.direction + event.deltaY * 0.2 + }); + object.renderFlags.set({ refresh: true }); + } + + /** + * Cancels the preview template on right-click. + * @param {contextmenu Event} event + */ + #cancelTemplate(event) { + const { mousemove, mousedown, contextmenu } = this.#activePreview.events; + canvas.templates._onDragLeftCancel(event); + + canvas.stage.off('mousemove', mousemove); + canvas.stage.off('mousedown', mousedown); + canvas.app.view.removeEventListener('contextmenu', contextmenu); + } + + /** + * Creates a real MeasuredTemplate at the preview location and cancels the preview. + * @param {click Event} event + */ + #confirmTemplate(event) { + event.stopPropagation(); + + canvas.scene.createEmbeddedDocuments('MeasuredTemplate', [this.#activePreview.document.toObject()]); + + this.#cancelTemplate(event); + } +} diff --git a/module/enrichers/TemplateEnricher.mjs b/module/enrichers/TemplateEnricher.mjs index a09e217d..7f45b266 100644 --- a/module/enrichers/TemplateEnricher.mjs +++ b/module/enrichers/TemplateEnricher.mjs @@ -59,14 +59,14 @@ export const renderMeasuredTemplate = async event => { const distance = type === CONFIG.DH.GENERAL.templateTypes.EMANATION ? baseDistance + 2.5 : baseDistance; const { width, height } = game.canvas.scene.dimensions; - canvas.scene.createEmbeddedDocuments('MeasuredTemplate', [ - { - x: width / 2, - y: height / 2, - t: usedType, - distance: distance, - width: type === CONST.MEASURED_TEMPLATE_TYPES.RAY ? 5 : undefined, - angle: angle - } - ]); + const data = { + x: width / 2, + y: height / 2, + t: usedType, + distance: distance, + width: type === CONST.MEASURED_TEMPLATE_TYPES.RAY ? 5 : undefined, + angle: angle + }; + + CONFIG.ux.TemplateManager.createPreview(data); };