From be0959dfd5683a8b69c0e477e46e34ab73117263 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Thu, 29 Jan 2026 21:18:35 -0500 Subject: [PATCH] Show token distance on hover --- lang/en.json | 8 +++ module/canvas/placeables/token.mjs | 55 +++++++++++++++++++ module/data/settings/Appearance.mjs | 19 +++++++ .../settings/appearance-settings/main.hbs | 4 ++ 4 files changed, 86 insertions(+) diff --git a/lang/en.json b/lang/en.json index e358977f..0186ae3e 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2405,6 +2405,14 @@ "hideAttribution": { "label": "Hide Attribution" }, + "showTokenDistance": { + "label": "Show Token Distance on Hover", + "choices": { + "always": "Always", + "encounters": "Encounters", + "never": "Never" + } + }, "expandedTitle": "Auto-expand Descriptions", "extendCharacterDescriptions": { "label": "Characters" diff --git a/module/canvas/placeables/token.mjs b/module/canvas/placeables/token.mjs index 2266d0da..a0c4f037 100644 --- a/module/canvas/placeables/token.mjs +++ b/module/canvas/placeables/token.mjs @@ -78,6 +78,61 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { return canvas.grid.measurePath([adjustedOriginPoint, adjustDestinationPoint]).distance; } + _onHoverIn(event, options) { + super._onHoverIn(event, options); + + // Check if the setting is enabled + const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).showTokenDistance; + if (setting === "never" || (setting === "encounters" && !game.combat?.started)) return; + + // Check if this token isn't invisible and is actually being hovered + const isTokenValid = + this.visible && + this.hover && + !this.isPreview && + !this.document.isSecret && + !this.controlled && + !this.animation; + if (!isTokenValid) return; + + // Ensure we have a single controlled token + const originToken = canvas.tokens.controlled[0]; + if (!originToken || canvas.tokens.controlled.length > 1) return; + + // Determine the actual range + const ranges = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement; + const distanceNum = originToken.distanceTo(this); + const distanceKey = ['melee', 'veryClose', 'close', 'far', 'very far'].find(r => ranges[r] >= distanceNum); + const distanceLabel = distanceKey ? game.i18n.localize(`DAGGERHEART.CONFIG.Range.${distanceKey}.name`) : null; + if (!distanceLabel) return; // todo: out of bounds instead + + // Create the element + const element = document.createElement('div'); + element.id = 'token-hover-distance'; + element.classList.add('waypoint-label', 'last'); + const ruler = document.createElement('i'); + ruler.classList.add('fa-solid', 'fa-ruler'); + element.appendChild(ruler); + const labelEl = document.createElement('span'); + labelEl.classList.add('total-measurement'); + labelEl.textContent = distanceLabel; + element.appendChild(labelEl); + + // Position the element and add to the DOM + const center = this.getCenterPoint(); + element.style.setProperty('--transformY', 'calc(-100% - 10px)'); + element.style.setProperty('--position-y', `${this.y}px`); + element.style.setProperty('--position-x', `${center.x}px`); + element.style.setProperty('--ui-scale', String(canvas.dimensions.uiScale)); + document.querySelector('#token-hover-distance')?.remove(); + document.querySelector('#measurement').appendChild(element); + } + + _onHoverOut(...args) { + super._onHoverOut(...args); + document.querySelector('#token-hover-distance')?.remove(); + } + /** Returns the point at which a line starting at origin and ending at destination intersects the edge of the bounds */ #getEdgeBoundary(bounds, originPoint, destinationPoint) { const points = [ diff --git a/module/data/settings/Appearance.mjs b/module/data/settings/Appearance.mjs index 2b8d3b27..d7a638d7 100644 --- a/module/data/settings/Appearance.mjs +++ b/module/data/settings/Appearance.mjs @@ -42,6 +42,25 @@ export default class DhAppearance extends foundry.abstract.DataModel { damage: new BooleanField(), target: new BooleanField() }), + showTokenDistance: new StringField({ + required: true, + choices: { + always: { + value: 'always', + label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.always' + }, + encounters: { + value: 'encounters', + label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.encounters' + }, + never: { + value: 'never', + label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.never' + } + }, + nullable: false, + initial: 'always' + }), hideAttribution: new BooleanField(), showGenericStatusEffects: new BooleanField({ initial: true }) }; diff --git a/templates/settings/appearance-settings/main.hbs b/templates/settings/appearance-settings/main.hbs index 75a7e634..32dd9e63 100644 --- a/templates/settings/appearance-settings/main.hbs +++ b/templates/settings/appearance-settings/main.hbs @@ -16,6 +16,10 @@ value=setting.showGenericStatusEffects localize=true}} {{formGroup + fields.showTokenDistance + value=setting.showTokenDistance + localize=true}} + {{formGroup fields.hideAttribution value=setting.hideAttribution localize=true}}