From 8cf10535dc2451aac755e7688005f9a5805bc865 Mon Sep 17 00:00:00 2001 From: Eunomiac Date: Tue, 20 Feb 2024 07:01:38 -0500 Subject: [PATCH] Slight detour to get tooltips working --- css/style.min.css | 229 ++++++++-------- module/classes/BladesDirector.js | 117 ++++++++- module/classes/BladesRoll.js | 127 ++++----- module/core/constants.js | 1 + module/core/gsap.js | 115 +++----- module/core/utilities.js | 11 +- scss/sheets/_roll-collab-sheet.scss | 318 ++++++++++------------- templates/components/roll-collab-mod.hbs | 2 + templates/roll/roll-collab-action-gm.hbs | 82 ++---- ts/@types/blades-actor.d.ts | 11 +- ts/@types/blades-item.d.ts | 43 ++- ts/@types/index.d.ts | 1 + ts/classes/BladesDirector.ts | 148 +++++++++-- ts/classes/BladesRoll.ts | 130 ++++----- ts/core/constants.ts | 1 + ts/core/gsap.ts | 120 +++------ ts/core/utilities.ts | 11 +- 17 files changed, 775 insertions(+), 692 deletions(-) diff --git a/css/style.min.css b/css/style.min.css index 5714a1d1..0264061e 100644 --- a/css/style.min.css +++ b/css/style.min.css @@ -28509,6 +28509,10 @@ template { min-width: var(--full-roll-width, 550px); max-width: var(--full-roll-width, 550px); } +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance { + --root-height: 200px; + --icon-size: 150px; +} :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root { border-radius: 30px; } @@ -28523,9 +28527,21 @@ template { align-items: center; padding: 0 10px; border: none; - overflow: hidden; border-top-left-radius: 30px; border-top-right-radius: 30px; + position: relative; + overflow: visible; + justify-content: center; + z-index: 4; + gap: 10px; +} +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-header .source-name.shadowed { + flex-basis: unset; + flex-grow: 0; +} +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-header .shadowed.vs { + text-transform: none; + text-align: right; } :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-header .consequence-box { width: unset; @@ -28550,33 +28566,63 @@ template { border: none; padding-top: 5px; } -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .consequence-box { +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .split-root.flex-horizontal { + height: var(--root-height); +} +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .split-root.flex-horizontal .split-root-left { + flex-basis: 55%; +} +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .split-root.flex-horizontal .split-root-left .sheet-main .roll-sheet-float-block.rolling-dice-total-block { + position: absolute; + right: -25px; + top: calc(var(--root-height) * 0.2); +} +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .split-root.flex-horizontal .split-root-right { + flex-basis: 45%; + --header-height: 32px; + position: relative; + top: calc(-1 * (var(--header-height) + var(--roll-spacing))); + left: var(--roll-spacing); + height: calc(var(--root-height) + var(--header-height) + var(--roll-spacing)); + flex-shrink: 0; + border-top-right-radius: 30px; + overflow: hidden; +} +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .split-root.flex-horizontal .split-root-right .sheet-main { + height: 100%; display: flex; flex-direction: column; - width: 90%; - justify-content: center; - align-items: center; - margin: 3px 0; + justify-content: space-between; + padding-right: 5px; } -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .consequence-box.consequence-strong { - --consequence-bg-color: var(--blades-red-dark); - --consequence-border-color: var(--blades-red-bright); - --consequence-text-color: var(--blades-red-bright); +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .split-root.flex-horizontal .split-root-right .consequence-box { + display: flex; + flex-direction: column; + position: relative; + justify-content: flex-end; + align-items: flex-end; + left: 0; + width: 100%; } -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .consequence-box.consequence-resisted { - --consequence-bg-color: var(--blades-red-darkest); - --consequence-border-color: var(--blades-red); - --consequence-text-color: var(--blades-red); +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .split-root.flex-horizontal .split-root-right .consequence-box .consequence-icon-img { + position: absolute; + height: var(--icon-size); + width: var(--icon-size); + background: transparent; + border-radius: calc(0.5 * var(--icon-size)); + filter: blur(5px); + z-index: -1; } -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .consequence-box .consequence-name { +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .split-root.flex-horizontal .split-root-right .consequence-box .consequence-name { + font-size: 18px; + text-align: right; font-family: var(--font-emphasis); text-transform: uppercase; color: var(--consequence-text-color); text-shadow: var(--text-shadow-dark-strong); width: 100%; - text-align: center; } -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .consequence-box .consequence-label { +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .split-root.flex-horizontal .split-root-right .consequence-box .consequence-label { background: var(--consequence-bg-color); font-family: var(--font-decorative); font-style: italic; @@ -28589,6 +28635,50 @@ template { text-align: center; padding-top: 5px; } +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .split-root.flex-horizontal .split-root-right .consequence-box .consequence-type-label { + font-size: 24px; + color: var(--blades-red-bright); + font-family: var(--font-emphasis-narrow); + white-space: nowrap; + text-align: right; + text-transform: uppercase; + text-shadow: var(--text-shadow-dark-strong); +} +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .split-root.flex-horizontal .split-root-right .consequence-box.consequence-top { + top: 25px; +} +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .split-root.flex-horizontal .split-root-right .consequence-box.consequence-top .consequence-icon-img { + top: calc(-0.25 * var(--icon-size)); + right: calc(-0.25 * var(--icon-size)); +} +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .split-root.flex-horizontal .split-root-right .consequence-box.consequence-bottom { + top: unset; + bottom: 0; +} +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .split-root.flex-horizontal .split-root-right .consequence-box.consequence-bottom .consequence-icon-img { + top: unset; + bottom: calc(-0.25 * var(--icon-size)); + left: unset; + right: calc(-0.25 * var(--icon-size)); +} +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .split-root.flex-horizontal .split-root-right .consequence-box.consequence-bottom .consequence-type-label { + color: var(--blades-gold-bright); +} +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .split-root.flex-horizontal .split-root-right .consequence-triangle { + position: absolute; + top: 50%; + right: 10px; +} +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .consequence-box.consequence-strong { + --consequence-bg-color: var(--blades-red-dark); + --consequence-border-color: var(--blades-red-bright); + --consequence-text-color: var(--blades-red-bright); +} +:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .consequence-box.consequence-resisted { + --consequence-bg-color: var(--blades-red-darkest); + --consequence-border-color: var(--blades-red); + --consequence-text-color: var(--blades-red); +} :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root section.sheet-footer .roll-sheet-float-block.roll-button .roll-odds-strip .roll-odds-section-container { filter: blur(10px); } @@ -29431,110 +29521,7 @@ template { :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form .sheet-root .roll-num-container .roll-num-spread .origin-box[data-active=true] ~ * { background: transparent; } -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance { - --root-height: 200px; - --icon-size: 150px; -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .sheet-header.flex-horizontal { - position: relative; - overflow: visible; - justify-content: center; - z-index: 4; - gap: 10px; -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .sheet-header.flex-horizontal .source-name.shadowed { - flex-basis: unset; - flex-grow: 0; -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .sheet-header.flex-horizontal .shadowed.vs { - text-transform: none; - text-align: right; -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .split-root.flex-horizontal { - height: var(--root-height); -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .split-root.flex-horizontal .split-root-left { - flex-basis: 55%; -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .split-root.flex-horizontal .split-root-left .sheet-main .roll-sheet-float-block.rolling-dice-total-block { - position: absolute; - right: -25px; - top: calc(var(--root-height) * 0.2); -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .split-root.flex-horizontal .split-root-right { - flex-basis: 45%; - --header-height: 32px; - position: relative; - top: calc(-1 * (var(--header-height) + var(--roll-spacing))); - left: var(--roll-spacing); - height: calc(var(--root-height) + var(--header-height) + var(--roll-spacing)); - flex-shrink: 0; - border-top-right-radius: 30px; - overflow: hidden; -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .split-root.flex-horizontal .split-root-right .sheet-main { - height: 100%; - display: flex; - flex-direction: column; - justify-content: space-between; - padding-right: 5px; -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .split-root.flex-horizontal .split-root-right .consequence-box { - display: flex; - flex-direction: column; - position: relative; - justify-content: flex-end; - align-items: flex-end; - left: 0; - width: 100%; -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .split-root.flex-horizontal .split-root-right .consequence-box .consequence-icon-img { - position: absolute; - height: var(--icon-size); - width: var(--icon-size); - background: transparent; - border-radius: calc(0.5 * var(--icon-size)); - filter: blur(5px); - z-index: -1; -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .split-root.flex-horizontal .split-root-right .consequence-box .consequence-name { - font-size: 18px; - text-align: right; -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .split-root.flex-horizontal .split-root-right .consequence-box .consequence-type-label { - font-size: 24px; - color: var(--blades-red-bright); - font-family: var(--font-emphasis-narrow); - white-space: nowrap; - text-align: right; - text-transform: uppercase; - text-shadow: var(--text-shadow-dark-strong); -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .split-root.flex-horizontal .split-root-right .consequence-box.consequence-top { - top: 25px; -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .split-root.flex-horizontal .split-root-right .consequence-box.consequence-top .consequence-icon-img { - top: calc(-0.25 * var(--icon-size)); - right: calc(-0.25 * var(--icon-size)); -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .split-root.flex-horizontal .split-root-right .consequence-box.consequence-bottom { - top: unset; - bottom: 0; -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .split-root.flex-horizontal .split-root-right .consequence-box.consequence-bottom .consequence-icon-img { - top: unset; - bottom: calc(-0.25 * var(--icon-size)); - left: unset; - right: calc(-0.25 * var(--icon-size)); -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .split-root.flex-horizontal .split-root-right .consequence-box.consequence-bottom .consequence-type-label { - color: var(--blades-gold-bright); -} -:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form.roll-type-resistance .sheet-root .sheet-inner-root .split-root.flex-horizontal .split-root-right .consequence-triangle { - position: absolute; - top: 50%; - right: 10px; -} + .tox .tox-dialog-wrap .tox-dialog .tox-textarea-wrap { height: 100%; } diff --git a/module/classes/BladesDirector.js b/module/classes/BladesDirector.js index 040dd61d..b1b78e06 100644 --- a/module/classes/BladesDirector.js +++ b/module/classes/BladesDirector.js @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import U from "../core/utilities.js"; -import { ClockKey_SVGDATA, BladesPhase, ClockKeyDisplayMode } from "../core/constants.js"; +import C, { ClockKey_SVGDATA, BladesPhase, ClockKeyDisplayMode } from "../core/constants.js"; import BladesClockKey, { BladesClock } from "./BladesClockKey.js"; class BladesDirector { // #region INITIALIZATION ~ @@ -123,7 +123,7 @@ class BladesDirector { } // #endregion // #endregion - // #region CLOCKS & CLOCK KEYS + // #region CLOCKS & CLOCK KEYS ~ // #region >> INITIALIZATION ~ initClockKeySection(isResetting = false) { if (isResetting) { @@ -368,7 +368,7 @@ class BladesDirector { } // #endregion // #endregion - // #region SCORE PANEL + // #region SCORE PANEL ~ // #region >> INITIALIZATION ~ initScorePanelSockets() { // tbd... @@ -383,7 +383,7 @@ class BladesDirector { // tbd... } // #endregion - // #region LOCATIONS + // #region LOCATIONS ~ // #region >> INITIALIZATION ~ initLocationSockets() { // tbd... @@ -398,7 +398,7 @@ class BladesDirector { // tbd... } // #endregion - // #region NPCs + // #region NPCs ~ // #region >> INITIALIZATION ~ initNPCSockets() { // tbd... @@ -410,7 +410,7 @@ class BladesDirector { // tbd... } // #endregion - // #region PCs, COHORTs, CREW + // #region PCs, COHORTs, CREW ~ // #region >> INITIALIZATION ~ initPCSockets() { // tbd... @@ -439,7 +439,7 @@ class BladesDirector { // tbd... } // #endregion - // #region NOTIFICATIONS + // #region NOTIFICATIONS ~ // #region >> INITIALIZATION ~ initNotificationSockets() { socketlib.system.register("pushNotice_SocketCall", BladesDirector.pushNotice_SocketResponse.bind(BladesDirector)); @@ -520,7 +520,7 @@ class BladesDirector { }); } // #endregion - // #region TRANSITIONS + // #region TRANSITIONS ~ // #region >> INITIALIZATION ~ initTransitionSockets() { // tbd... @@ -534,19 +534,110 @@ class BladesDirector { // #endregion // #region TOOLTIPS ~ _tooltipObserver; + _tooltipElems = new Map(); + _displayedTooltipID; + displayTooltip(tooltip) { + if (!tooltip.id) { + throw new Error("Tooltip must have an ID to be cloned to the overlay."); + } + this._displayedTooltipID = tooltip.id; + const self = this; + // Clear out any other tooltips in the overlay. + game.eunoblades.Director.clearTooltips(); + if (!this._tooltipElems.has(tooltip.id)) { + // Create cloned tooltip and attach it to the tooltip overlay. + const ttClone$ = $(U.changeContainer(tooltip, game.eunoblades.Director.tooltipSection$[0], true)); + // Generate the reveal timeline and attach it to the cloned tooltip element. + const revealTimeline = U.gsap.effects.blurRevealTooltip(ttClone$[0], { + onReverseComplete() { + if (ttClone$.attr("id") === self._displayedTooltipID) { + delete self._displayedTooltipID; + } + game.eunoblades.Director._tooltipElems.delete(ttClone$.attr("id")); + game.eunoblades.Director.tooltipSection$.find(`#${ttClone$.attr("id")}`).remove(); + game.eunoblades.Director.tooltipSection$.children("[style*='opacity: 0'], [style*='opacity:0']").each(function () { + const id = this.id; // Get the ID of the current element + if (id === self._displayedTooltipID) { + return; + } + if (id) { + game.eunoblades.Director._tooltipElems.delete(id); // Remove from the map if the ID exists + } + $(this).remove(); // Remove the element from the DOM + }); + } + }); + ttClone$.data("revealTimeline", revealTimeline); + // Register the cloned tooltip element to the master map + this._tooltipElems.set(tooltip.id, ttClone$); + } + // Play the timeline. + this._tooltipElems.get(tooltip.id)?.data("revealTimeline")?.play(); + } + clearTooltip(tooltipID, isClearingIfTweening = true) { + if (tooltipID === this._displayedTooltipID) { + delete this._displayedTooltipID; + } + const ttElem = game.eunoblades.Director._tooltipElems.get(tooltipID); + if (!ttElem) { + return; + } + const ttTimeline = ttElem.data("revealTimeline"); + if (ttTimeline.isActive() && !isClearingIfTweening) { + return; + } + ttTimeline.reverse(); + } clearTooltips() { - // Look for tooltip elements in the overlay container, and reverse their timelines. - game.eunoblades.Director.tooltipSection$.find(".tooltip").each((i, el) => { - U.gsap.effects.blurRemoveTooltip(el); + eLog.checkLog3("Observer", "Observer Triggered!"); + // Look for tooltip elements in the overlay container, and remove them. + game.eunoblades.Director._tooltipElems.forEach((ttElem) => { + if (ttElem.attr("id") === this._displayedTooltipID) { + return; + } + game.eunoblades.Director.clearTooltip(ttElem.attr("id"), true); }); } initTooltipSection() { - const { clearTooltips } = this; + const self = this; + this.clearTooltips(); // Reset tooltip observer this._tooltipObserver?.kill(); + // Simplified throttle function that takes a function with Observer parameter + const throttle = (func, limit) => { + let lastFunc; + let lastRan; + return function (obs) { + const now = Date.now(); + if (!lastRan || now - lastRan >= limit) { + func(obs); + lastRan = now; + } + else { + clearTimeout(lastFunc); + lastFunc = window.setTimeout(() => { + if (now - lastRan >= limit) { + func(obs); + lastRan = now; + } + }, limit - (now - lastRan)); + } + }; + }; + // Throttled onMove callback + const throttledOnMove = throttle((obs) => { + // Calculate the absolute magnitude of velocity independent of direction + const magnitudeOfVelocity = Math.sqrt((obs.velocityX ** 2) + (obs.velocityY ** 2)); + if (magnitudeOfVelocity >= C.MIN_MOUSE_MOVEMENT_THRESHOLD) { + self.clearTooltips(); + } + }, 200); // Adjust 200ms to your preferred throttling limit this._tooltipObserver = Observer.create({ type: "touch,pointer", - onClick: clearTooltips + // onMove: throttledOnMove, + onClick() { + self.clearTooltips(); + } }); } } diff --git a/module/classes/BladesRoll.js b/module/classes/BladesRoll.js index 0e9dbd84..4b563eca 100644 --- a/module/classes/BladesRoll.js +++ b/module/classes/BladesRoll.js @@ -198,6 +198,7 @@ class BladesRollMod extends BladesTargetLink { return this.getSchemaFromStrings(modString.split(/@/)); }); } + isRerendering = false; get status() { // USER STATUS of "ForcedOn", "ForcedOff", or "Hidden" trumps all other status values. if (this.userStatus && BladesRollMod.GMOnlyModStatuses.includes(this.userStatus)) { @@ -543,10 +544,11 @@ class BladesRollMod extends BladesTargetLink { if (val === this.userStatus) { return; } + const { isRerendering } = this; if (!val || val === this.baseStatus) { this.updateTarget("user_status", null) .then(() => { - if (this.rollInstance.isRendered) { + if (isRerendering) { this.rollInstance.renderRollCollab_SocketCall(); } }); @@ -559,7 +561,7 @@ class BladesRollMod extends BladesTargetLink { } this.updateTarget("user_status", val) .then(() => { - if (this.rollInstance.isRendered) { + if (isRerendering) { this.rollInstance.renderRollCollab_SocketCall(); } }); @@ -571,10 +573,11 @@ class BladesRollMod extends BladesTargetLink { if (val === this.heldStatus) { return; } + const { isRerendering } = this; if (!val) { this.updateTarget("held_status", null) .then(() => { - if (this.rollInstance.isRendered) { + if (isRerendering) { this.rollInstance.renderRollCollab_SocketCall(); } }); @@ -582,7 +585,7 @@ class BladesRollMod extends BladesTargetLink { else { this.updateTarget("held_status", val) .then(() => { - if (this.rollInstance.isRendered) { + if (isRerendering) { this.rollInstance.renderRollCollab_SocketCall(); } }); @@ -1633,55 +1636,6 @@ class BladesRoll extends BladesTargetLink { eLog.checkLog3("rollCollab", "constructRollCollab_SocketResponse()", { params: { linkData }, rollInst }); this.renderRollCollab_SocketResponse(rollInst.id); } - _elem$; - _overlayPosition = { x: 200, y: 200 }; - get overlayPosition() { return this._overlayPosition; } - set overlayPosition(val) { this._overlayPosition = val; } - _positionDragger; - get positionDragger() { - if (this._positionDragger) { - return this._positionDragger; - } - return this.spawnPositionDragger(); - } - spawnPositionDragger() { - const self = this; - if (!this._elem$) { - throw new Error(`[BladesRoll.spawnPositionDragger] No elem$ found for roll ${this.id}.`); - } - return (this._positionDragger = new Dragger(this._elem$, { - type: "top,left", - trigger: ".window-header.draggable", - onDragStart() { - U.gsap.to(this.target, { opacity: 0.25, duration: 0.25, ease: "power2" }); - }, - onDragEnd() { - U.gsap.to(this.target, { opacity: 1, duration: 0.25, ease: "power2" }); - self.overlayPosition = { x: this.endX, y: this.endY }; - } - })); - } - get elem$() { - if (this._elem$) { - return this._elem$; - } - this._positionDragger = undefined; - const elem$ = $(`#${this.id}`); - if (elem$.length) { - this._elem$ = elem$; - } - else { - this._elem$ = $(`
`).appendTo("body"); - } - this.spawnPositionDragger(); - return this._elem$; - } - async renderRollCollab() { - this.prepareRollParticipantData(); - const html = await renderTemplate(this.collabTemplate, this.context); - this.elem$.html(html); - this.activateListeners(); - } renderRollCollab_SocketCall() { socketlib.system.executeForEveryone("renderRollCollab_SocketCall", this.id); } @@ -2493,7 +2447,7 @@ class BladesRoll extends BladesTargetLink { return []; } // #endregion - // #region *** ROLL COLLAB CONTEXT *** ~ + // #region *** ROLL COLLAB HTML INTERACTION *** ~ /** * Retrieve the data for rendering the base RollCollab sheet. * @returns {Promise} The data which can be used to render the HTML of the sheet. @@ -2925,6 +2879,33 @@ class BladesRoll extends BladesTargetLink { } // #endregion // #region *** ROLL COLLAB HTML ELEMENT *** + _elem$; + _overlayPosition = { x: 200, y: 200 }; + get overlayPosition() { return this._overlayPosition; } + set overlayPosition(val) { this._overlayPosition = val; } + get elem$() { + if (this._elem$) { + return this._elem$; + } + const elem$ = $(`#${this.id}`); + if (elem$.length) { + this._elem$ = elem$; + } + else { + this._elem$ = $(`
`).appendTo("body"); + this._elem$.css({ + left: `${this.overlayPosition.x}px`, + top: `${this.overlayPosition.y}px` + }); + } + return this._elem$; + } + async renderRollCollab() { + this.prepareRollParticipantData(); + const html = await renderTemplate(this.collabTemplate, this.context); + this.elem$.html(html); + this.activateListeners(); + } get isRendered() { return Boolean(this._elem$?.length); } @@ -2965,26 +2946,28 @@ class BladesRoll extends BladesTargetLink { if (!rollMod) { throw new Error(`Unable to find roll mod with id '${id}'`); } + rollMod.isRerendering = true; switch (rollMod.status) { case RollModStatus.Hidden: rollMod.userStatus = RollModStatus.ForcedOff; - return; + break; case RollModStatus.ForcedOff: rollMod.userStatus = RollModStatus.ToggledOff; - return; + break; case RollModStatus.ToggledOff: rollMod.userStatus = RollModStatus.ToggledOn; - return; + break; case RollModStatus.ToggledOn: rollMod.userStatus = game.user.isGM ? RollModStatus.ForcedOn : RollModStatus.ToggledOff; - return; + break; case RollModStatus.ForcedOn: rollMod.userStatus = RollModStatus.Hidden; - return; + break; default: throw new Error(`Unrecognized RollModStatus: ${rollMod.status}`); } + rollMod.isRerendering = false; } /** * Handles setting of rollMod status via GM pop-out controls @@ -3191,9 +3174,35 @@ class BladesRoll extends BladesTargetLink { // } // #endregion // #region ACTIVATE LISTENERS ~ + _positionDragger; + get positionDragger() { + if (this._positionDragger) { + return this._positionDragger; + } + return this.spawnPositionDragger(); + } + spawnPositionDragger() { + const self = this; + if (!this._elem$) { + throw new Error(`[BladesRoll.spawnPositionDragger] No elem$ found for roll ${this.id}.`); + } + this._positionDragger?.kill(); + return (this._positionDragger = new Dragger(this._elem$, { + type: "top,left", + trigger: ".window-header.dragger", + onDragStart() { + U.gsap.to(this.target, { opacity: 0.25, duration: 0.25, ease: "power2" }); + }, + onDragEnd() { + U.gsap.to(this.target, { opacity: 1, duration: 0.25, ease: "power2" }); + self.overlayPosition = { x: this.endX, y: this.endY }; + } + })); + } activateListeners() { ApplyTooltipAnimations(this.elem$); ApplyConsequenceAnimations(this.elem$); + this.spawnPositionDragger(); // If a rollClockKey exists, initialize its elements if (this.rollClockKey) { this.elem$.find(".roll-clock").removeClass("hidden"); diff --git a/module/core/constants.js b/module/core/constants.js index b422216b..924c02c5 100644 --- a/module/core/constants.js +++ b/module/core/constants.js @@ -453,6 +453,7 @@ const C = { "gpt-4-32k" ] }, + MIN_MOUSE_MOVEMENT_THRESHOLD: 2000, AI_FILE_IDS: { BladesPDF: "file-n72HTTNwt051piPbswQ8isUa" }, diff --git a/module/core/gsap.js b/module/core/gsap.js index 4c3d2b14..567fc653 100644 --- a/module/core/gsap.js +++ b/module/core/gsap.js @@ -556,7 +556,7 @@ export const gsapEffects = { // #endregion // #region GENERAL: 'blurRemove', 'hoverTooltip', 'textJitter' blurRemove: { - effect: (targets, config) => U.gsap.timeline() + effect: (targets, config) => U.gsap.timeline({ stagger: config.stagger }) .to(targets, { skewX: config.skewX, duration: config.duration / 2, @@ -589,7 +589,8 @@ export const gsapEffects = { duration: 0.5, x: "+=300", scale: 1.5, - blur: 10 + blur: 10, + stagger: 0 }, extendTimeline: true }, @@ -682,85 +683,45 @@ export const gsapEffects = { }, blurRemoveTooltip: { effect: (tooltip, config) => { - const tooltip$ = $(tooltip); - const container$ = $(config.container) ?? tooltip$.data("tooltipContainer"); - const tl = U.gsap.timeline({ - onComplete() { - if (container$.length) { - U.changeContainer(tooltip$[0], container$[0]); - } - else { - tooltip$.remove(); - } - } - }) - .blurRemove(tooltip$[0], { ignoreMargin: true, blur: 15 }); + const tl = U.gsap.timeline({}) + .blurRemove(tooltip, { ignoreMargin: true, blur: 15, stagger: config.stagger }); return tl; }, - defaults: {}, - extendTimeline: true - }, - blurRevealTooltip: { - effect: (tooltip, config) => { - const tooltip$ = $(tooltip); - return U.gsap.timeline({ - onStart() { - // First check if there is already a valid, existant container element attached to the tooltip - if (!tooltip$.data("tooltipContainer")) { - tooltip$.data("tooltipContainer", tooltip$.parent()[0]); - } - U.changeContainer(tooltip$[0], game.eunoblades.Director.tooltipSection$[0]); - } - }).from(tooltip$[0], { - filter: "blur(15px)", - autoAlpha: 0, - xPercent: 50, - yPercent: -100, - scale: 1.5, - ease: "back.out" - }); - }, defaults: { - tooltipScale: 0.75 + stagger: 0 }, extendTimeline: true }, - hoverTooltip: { - effect: (tooltip, _config) => { - const tooltipElem = $(tooltip)[0]; - const tooltipContainer$ = $(tooltipElem).parent(); - const tooltipContainer = tooltipContainer$[0]; - const overlayContainer = game.eunoblades.Director.tooltipSection$[0]; + blurRevealTooltip: { + effect: (tooltip, config) => { return U.gsap.timeline({ paused: true, - onStart() { - U.changeContainer(tooltipElem, overlayContainer); - }, - onComplete() { - U.gsap.set(tooltipElem, { filter: "none" }); - }, - onReverseComplete() { - U.changeContainer(tooltipElem, tooltipContainer); - } - }).fromTo(tooltipElem, { - filter: "blur(15px)", + onReverseComplete: config.onReverseComplete + // onInterrupt() { this.reverse(); } + }).fromTo(tooltip, { + filter: `blur(${config.blurStrength}px)`, autoAlpha: 0, xPercent: 50, - yPercent: -100, - scale: 1.5 + yPercent: -200, + scale: config.scale }, { - filter: "blur(0px)", + filter: "none", autoAlpha: 1, - scale: 1, xPercent: -50, yPercent: -100, - duration: 0.25, - ease: "back.out" - }, 0); + scale: 1, + ease: config.ease, + duration: config.duration + }); }, defaults: { - tooltipScale: 0.75 - } + scale: 1.5, + blurStrength: 15, + ease: "back.out", + duration: 0.25, + onReverseComplete: undefined + }, + extendTimeline: true }, textJitter: { effect: (target, config) => { @@ -850,27 +811,21 @@ export function ApplyTooltipAnimations(html) { } // Find the tooltip's parent container. If its position isn't relative or absolute, set it to relative. const tooltipContainer = $(tooltipElem).parent()[0]; - if ($(tooltipContainer).css("position") !== "relative" && $(tooltipContainer).css("position") !== "absolute") { + if ($(tooltipContainer).css("position") !== "relative" + && $(tooltipContainer).css("position") !== "absolute") { $(tooltipContainer).css("position", "relative"); } - // // Register the tooltip timeline in the global map, so it can be reversed even if containing document is closed or re-rendered. - // game.eunoblades.Tooltips.set(tooltipElem, U.gsap.effects.hoverTooltip( - // tooltipElem, - // { - // scalingElems: [...$(el).find(".tooltip-scaling-elem")].filter((elem) => Boolean(elem)), - // xMotion: $(tooltipElem).hasClass("tooltip-left") ? "-=250" : "+=200", - // tooltipScale: $(tooltipElem).hasClass("tooltip-small") ? 1 : 1.2 - // } - // )); - // $(el).data("hoverTimeline", () => game.eunoblades.Tooltips.get(tooltipElem) as gsap.core.Timeline); + // Set the tooltip itself to absolute positioning + $(tooltipElem).css("position", "absolute"); + // Assign a unique ID to the tooltip element + const tooltipID = `tooltip-${randomID()}`; + $(tooltipElem).attr("id", tooltipID); $(el).on({ mouseenter: function () { - U.gsap.effects.blurRevealTooltip(tooltipElem); - // $(el).data("hoverTimeline")().play(); + game.eunoblades.Director.displayTooltip(tooltipElem); }, mouseleave: function () { - U.gsap.effects.blurRemoveTooltip(tooltipElem); - // $(el).data("hoverTimeline")().reverse(); + game.eunoblades.Director.clearTooltip(tooltipID); } }); }); diff --git a/module/core/utilities.js b/module/core/utilities.js index 378e2443..6471b694 100644 --- a/module/core/utilities.js +++ b/module/core/utilities.js @@ -1390,7 +1390,9 @@ const withLog = (fn) => { }; // #endregion ▄▄▄▄▄ FUNCTIONS ▄▄▄▄▄ // #region ████████ HTML: Parsing HTML Code, Manipulating DOM Objects ████████ ~ -const changeContainer = (elem, container) => { +const changeContainer = (elem, container, isCloning = false) => { + elem = $(elem)[0]; + container = $(container)[0]; // Get the element's current container, which defines its current coordinate space. const curContainer = $(elem).parent()[0]; // Get the element's current position in its current coordinate space. @@ -1400,10 +1402,15 @@ const changeContainer = (elem, container) => { }; // Convert the element's position in its current space, to the equivalent position in the target space. const relPos = MotionPathPlugin.convertCoordinates(curContainer, container, curPosition); - // eLog.checkLog3("changeContainer", "Target Element", {elem, container, curContainer, curPosition, relPos}); + eLog.checkLog3("changeContainer", "Target Element", { elem, container, curContainer, curPosition, relPos }); + // Clone the element, if indicated + if (isCloning) { + elem = $(elem).clone()[0]; + } // Append the element to the new container, and set its new position $(elem).appendTo($(container)); gsap.set(elem, relPos); + return elem; }; const adjustTextContainerAspectRatio = (textContainer, targetRatio, maxHeight, maxWidth, minFontSize = 8) => { textContainer = $(textContainer)[0]; diff --git a/scss/sheets/_roll-collab-sheet.scss b/scss/sheets/_roll-collab-sheet.scss index 75f8d348..0a10fca5 100644 --- a/scss/sheets/_roll-collab-sheet.scss +++ b/scss/sheets/_roll-collab-sheet.scss @@ -937,8 +937,8 @@ } &.roll-type-resistance { - // min-width: 500px; - // max-width: 500px; + --root-height: 200px; + --icon-size: 150px; .sheet-root { border-radius: 30px; @@ -957,10 +957,25 @@ align-items: center; padding: 0 10px; border: none; - overflow: hidden; + // overflow: hidden; border-top-left-radius: 30px; border-top-right-radius: 30px; + position: relative; + overflow: visible; + justify-content: center; + z-index: 4; + gap: 10px; + + .source-name.shadowed { + flex-basis: unset; + flex-grow: 0; + } + .shadowed.vs { + text-transform: none; + text-align: right; + } + .consequence-box { width: unset; flex-basis: 40%; @@ -975,10 +990,6 @@ text-align: right; } .consequence-label { - // font-size: toRem(14px); - // line-height: toRem(12px); - // padding-top: 3px; - // margin: 0px; min-width: 150px; text-transform: none; } @@ -992,13 +1003,130 @@ padding-top: 5px; } + .split-root.flex-horizontal { + height: var(--root-height); + + .split-root-left { + flex-basis: 55%; + + .sheet-main { + + .roll-sheet-float-block.rolling-dice-total-block { + position: absolute; + right: -25px; + top: calc(var(--root-height) * 0.2); + } + } + } + + .split-root-right { + flex-basis: 45%; + --header-height: 32px; + position: relative; + top: calc(-1 * (var(--header-height) + var(--roll-spacing))); + left: var(--roll-spacing); + height: calc(var(--root-height) + var(--header-height) + var(--roll-spacing)); + flex-shrink: 0; + border-top-right-radius: 30px; + overflow: hidden; + + .sheet-main { + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + padding-right: 5px; + + } + + .consequence-box { + display: flex; + flex-direction: column; + position: relative; + justify-content: flex-end; + align-items: flex-end; + left: 0; + width: 100%; + + .consequence-icon-img { + position: absolute; + height: var(--icon-size); + width: var(--icon-size); + background: transparent; + border-radius: calc(0.5 * var(--icon-size)); + filter: blur(5px); + z-index: -1; + } + + .consequence-name { + font-size: 18px; + text-align: right; + font-family: var(--font-emphasis); + text-transform: uppercase; + color: var(--consequence-text-color); + text-shadow: var(--text-shadow-dark-strong); + width: 100%; + } + .consequence-label { + background: var(--consequence-bg-color); + font-family: var(--font-decorative); + font-style: italic; + font-size: 1.25rem; + line-height: 1.25rem; + color: var(--blades-white-bright); + text-shadow: var(--text-shadow-dark-strong); + border: 2px outset var(--consequence-border-color); + width: 100%; + text-align: center; + padding-top: 5px; + } + .consequence-type-label { + font-size: 24px; + color: var(--blades-red-bright); + font-family: var(--font-emphasis-narrow); + white-space: nowrap; + text-align: right; + text-transform: uppercase; + text-shadow: var(--text-shadow-dark-strong); + } + + &.consequence-top { + top: 25px; + + .consequence-icon-img { + top: calc(-0.25 * var(--icon-size)); + // left: 0; // calc(1 * var(--icon-size)); + right: calc(-0.25 * var(--icon-size)); + } + } + + &.consequence-bottom { + top: unset; + bottom: 0; + + .consequence-icon-img { + top: unset; + bottom: calc(-0.25 * var(--icon-size)); + left: unset; + right: calc(-0.25 * var(--icon-size)); + } + + .consequence-type-label { + color: var(--blades-gold-bright); + } + } + } + + .consequence-triangle { + position: absolute; + top: 50%; + right: 10px; + } + } + + } + .consequence-box { - display: flex; - flex-direction: column; - width: 90%; - justify-content: center; - align-items: center; - margin: 3px 0; &.consequence-strong { --consequence-bg-color: var(--blades-red-dark); @@ -1013,27 +1141,9 @@ } .consequence-name { - font-family: var(--font-emphasis); - text-transform: uppercase; - color: var(--consequence-text-color); - text-shadow: var(--text-shadow-dark-strong); - width: 100%; - text-align: center; } - .consequence-label { - background: var(--consequence-bg-color); - font-family: var(--font-decorative); - font-style: italic; - font-size: 1.25rem; - line-height: 1.25rem; - color: var(--blades-white-bright); - text-shadow: var(--text-shadow-dark-strong); - border: 2px outset var(--consequence-border-color); - width: 100%; - text-align: center; - padding-top: 5px; - } + } section.sheet-footer { @@ -1916,149 +2026,5 @@ } } } - - form.roll-type-resistance { - --root-height: 200px; - --icon-size: 150px; - - .sheet-root { - - .sheet-inner-root { - // border-radius: 30px; - // overflow: hidden; - // position: relative; - - .sheet-header.flex-horizontal { - position: relative; - overflow: visible; - justify-content: center; - // padding-right: 0; - z-index: 4; - gap: 10px; - - .source-name.shadowed { - flex-basis: unset; - flex-grow: 0; - } - .shadowed.vs { - text-transform: none; - text-align: right; - } - - } - - .split-root.flex-horizontal { - height: var(--root-height); - - .split-root-left { - flex-basis: 55%; - - .sheet-main { - - .roll-sheet-float-block.rolling-dice-total-block { - position: absolute; - right: -25px; - top: calc(var(--root-height) * 0.2); - } - } - } - - .split-root-right { - flex-basis: 45%; - --header-height: 32px; - position: relative; - top: calc(-1 * (var(--header-height) + var(--roll-spacing))); - left: var(--roll-spacing); - height: calc(var(--root-height) + var(--header-height) + var(--roll-spacing)); - flex-shrink: 0; - border-top-right-radius: 30px; - overflow: hidden; - - .sheet-main { - height: 100%; - display: flex; - flex-direction: column; - justify-content: space-between; - padding-right: 5px; - - } - - .consequence-box { - display: flex; - flex-direction: column; - position: relative; - justify-content: flex-end; - align-items: flex-end; - left: 0; - width: 100%; - - .consequence-icon-img { - position: absolute; - height: var(--icon-size); - width: var(--icon-size); - background: transparent; - border-radius: calc(0.5 * var(--icon-size)); - filter: blur(5px); - z-index: -1; - } - - .consequence-name { - // font-family: var(--font-emphasis-narrow); - font-size: 18px; - // margin-top: 10px; - text-align: right; - } - .consequence-type-label { - font-size: 24px; - color: var(--blades-red-bright); - font-family: var(--font-emphasis-narrow); - white-space: nowrap; - text-align: right; - text-transform: uppercase; - text-shadow: var(--text-shadow-dark-strong); - } - - &.consequence-top { - top: 25px; - - .consequence-icon-img { - top: calc(-0.25 * var(--icon-size)); - // left: 0; // calc(1 * var(--icon-size)); - right: calc(-0.25 * var(--icon-size)); - } - } - - &.consequence-bottom { - top: unset; - bottom: 0; - - .consequence-icon-img { - top: unset; - bottom: calc(-0.25 * var(--icon-size)); - left: unset; - right: calc(-0.25 * var(--icon-size)); - } - - .consequence-type-label { - color: var(--blades-gold-bright); - } - } - } - - .consequence-triangle { - position: absolute; - top: 50%; - right: 10px; - } - } - - } - } - - .sheet-footer { - - } - } - } } } \ No newline at end of file diff --git a/templates/components/roll-collab-mod.hbs b/templates/components/roll-collab-mod.hbs index b78a099d..1fa04825 100644 --- a/templates/components/roll-collab-mod.hbs +++ b/templates/components/roll-collab-mod.hbs @@ -1,3 +1,5 @@ + + diff --git a/templates/roll/roll-collab-action-gm.hbs b/templates/roll/roll-collab-action-gm.hbs index 2a057b05..0afa2585 100644 --- a/templates/roll/roll-collab-action-gm.hbs +++ b/templates/roll/roll-collab-action-gm.hbs @@ -1,4 +1,4 @@ -
+

Action Roll

Close @@ -95,25 +95,13 @@
{{#each posRollMods.roll }} {{#unless isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" - status=status - sideString=sideString - tooltip=tooltip - selectOptions=selectOptions - isGM=@root.isGM - }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/unless}} {{/each}} {{#each negRollMods.roll }} {{#unless isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" - status=status - sideString=sideString - tooltip=tooltip - selectOptions=selectOptions - isGM=@root.isGM - }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/unless}} {{/each}}
@@ -121,24 +109,12 @@
{{#each posRollMods.roll }} {{#if isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" - status=status - sideString=sideString - tooltip=tooltip - selectOptions=selectOptions - isGM=@root.isGM - }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/if}} {{/each}} {{#each negRollMods.roll }} {{#if isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" - status=status - sideString=sideString - tooltip=tooltip - selectOptions=selectOptions - isGM=@root.isGM - }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/if}} {{/each}}
@@ -165,15 +141,13 @@
{{#each posRollMods.position }} {{#unless isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" status=status sideString=sideString - tooltip=tooltip selectOptions=selectOptions isGM=@root.isGM }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/unless}} {{/each}} {{#each negRollMods.position }} {{#unless isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" status=status sideString=sideString - tooltip=tooltip selectOptions=selectOptions isGM=@root.isGM }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/unless}} {{/each}}
@@ -181,14 +155,12 @@
{{#each posRollMods.position }} {{#if isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" status=status sideString=sideString - tooltip=tooltip selectOptions=selectOptions isGM=@root.isGM }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/if}} {{/each}} {{#each negRollMods.position }} {{#if isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" status=status sideString=sideString - tooltip=tooltip selectOptions=selectOptions isGM=@root.isGM }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/if}} {{/each}}
@@ -217,15 +189,13 @@
{{#each posRollMods.effect }} {{#unless isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" status=status sideString=sideString - tooltip=tooltip selectOptions=selectOptions isGM=@root.isGM }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/unless}} {{/each}} {{#each negRollMods.effect }} {{#unless isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" status=status sideString=sideString - tooltip=tooltip selectOptions=selectOptions isGM=@root.isGM }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/unless}} {{/each}}
@@ -233,14 +203,12 @@
{{#each posRollMods.effect }} {{#if isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" status=status sideString=sideString - tooltip=tooltip selectOptions=selectOptions isGM=@root.isGM }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/if}} {{/each}} {{#each negRollMods.effect }} {{#if isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" status=status sideString=sideString - tooltip=tooltip selectOptions=selectOptions isGM=@root.isGM }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/if}} {{/each}}
@@ -256,15 +224,13 @@
{{#each posRollMods.result }} {{#unless isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" status=status sideString=sideString - tooltip=tooltip selectOptions=selectOptions isGM=@root.isGM }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/unless}} {{/each}} {{#each negRollMods.result }} {{#unless isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" status=status sideString=sideString - tooltip=tooltip selectOptions=selectOptions isGM=@root.isGM }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/unless}} {{/each}}
@@ -272,14 +238,12 @@
{{#each posRollMods.result }} {{#if isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" status=status sideString=sideString - tooltip=tooltip selectOptions=selectOptions isGM=@root.isGM }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/if}} {{/each}} {{#each negRollMods.result }} {{#if isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" status=status sideString=sideString - tooltip=tooltip selectOptions=selectOptions isGM=@root.isGM }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/if}} {{/each}}
@@ -362,15 +326,13 @@
{{#each posRollMods.after }} {{#unless isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" status=status sideString=sideString - tooltip=tooltip selectOptions=selectOptions isGM=@root.isGM }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/unless}} {{/each}} {{#each negRollMods.after }} {{#unless isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" status=status sideString=sideString - tooltip=tooltip selectOptions=selectOptions isGM=@root.isGM}} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/unless}} {{/each}}
@@ -378,14 +340,12 @@
{{#each posRollMods.after }} {{#if isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" status=status sideString=sideString - tooltip=tooltip selectOptions=selectOptions isGM=@root.isGM }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/if}} {{/each}} {{#each negRollMods.after }} {{#if isInInactiveBlock}} - {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" status=status sideString=sideString - tooltip=tooltip selectOptions=selectOptions isGM=@root.isGM }} + {{> "systems/eunos-blades/templates/components/roll-collab-mod.hbs" }} {{/if}} {{/each}}
diff --git a/ts/@types/blades-actor.d.ts b/ts/@types/blades-actor.d.ts index 63f74565..0d4bfb26 100644 --- a/ts/@types/blades-actor.d.ts +++ b/ts/@types/blades-actor.d.ts @@ -177,12 +177,13 @@ declare global { DeepPartial { } // Distinguishing schema types for BladesActor subtypes + type BladesActorOfType = - T extends BladesActorType.pc ? BladesPC & { system: ExtractBladesActorSystem } : - T extends BladesActorType.npc ? BladesNPC & { system: ExtractBladesActorSystem } : - T extends BladesActorType.crew ? BladesCrew & { system: ExtractBladesActorSystem } : - T extends BladesActorType.faction ? BladesFaction & { system: ExtractBladesActorSystem } : - never & { + T extends BladesActorType.pc ? BladesPC & { system: ExtractBladesActorSystem } : + T extends BladesActorType.npc ? BladesNPC & { system: ExtractBladesActorSystem } : + T extends BladesActorType.crew ? BladesCrew & { system: ExtractBladesActorSystem } : + T extends BladesActorType.faction ? BladesFaction & { system: ExtractBladesActorSystem } : + BladesActor & { system: ExtractBladesActorSystem }; diff --git a/ts/@types/blades-item.d.ts b/ts/@types/blades-item.d.ts index 6ec8e1ea..a8971387 100644 --- a/ts/@types/blades-item.d.ts +++ b/ts/@types/blades-item.d.ts @@ -1,7 +1,14 @@ import { BladesItemType, ClockColor, District, BladesPhase, Randomizers } from "../core/constants"; import BladesItem from "../BladesItem"; +import BladesPC from "../documents/actors/BladesPC.js"; +import BladesCrew from "../documents/actors/BladesCrew.js"; +import BladesNPC from "../documents/actors/BladesNPC.js"; +import BladesFaction from "../documents/actors/BladesFaction.js"; import BladesClockKeeper from '../documents/items/BladesClockKeeper.js'; import BladesGMTracker from '../documents/items/BladesGMTracker.js'; +import BladesLocation from "../documents/items/BladesLocation.js"; +import BladesScore from "../documents/items/BladesScore.js"; +import BladesProject from "../documents/items/BladesProject.js"; declare global { @@ -11,6 +18,15 @@ declare global { // Random categories for BladesScore type RandomCat = keyof typeof Randomizers["GM"]; + // Embeddable BladesItem types + // type EmbeddableItemType = + // ParentType extends BladesActorType.pc ? BladesItemType.ability|BladesItemType.background|BladesItemType.cohort_expert|BladesItemType.cohort_gang|BladesItemType.feature|BladesItemType.heritage|BladesItemType.gear|BladesItemType.playbook|BladesItemType.stricture|BladesItemType.vice|BladesItemType.project|BladesItemType.ritual|BladesItemType.design : + // ParentType extends BladesActorType.crew ? BladesItemType.cohort_expert|BladesItemType.cohort_gang|BladesItemType.crew_ability|BladesItemType.crew_playbook|BladesItemType.crew_reputation|BladesItemType.crew_upgrade|BladesItemType.preferred_op : + // ParentType extends BladesActorType.npc ? BladesItemType.ability|BladesItemType.background|BladesItemType.cohort_expert|BladesItemType.cohort_gang|BladesItemType.feature|BladesItemType.heritage|BladesItemType.gear|BladesItemType.playbook|BladesItemType.stricture|BladesItemType.vice|BladesItemType.project|BladesItemType.ritual|BladesItemType.design : + // ParentType extends BladesActorType.faction ? BladesItemType.cohort_expert|BladesItemType.cohort_gang|BladesItemType.crew_ability|BladesItemType.crew_playbook|BladesItemType.crew_reputation|BladesItemType.crew_upgrade|BladesItemType.gear|BladesItemType.preferred_op|BladesItemType.project|BladesItemType.ritual|BladesItemType.design : + // ParentType extends BladesItemType.location ? BladesItemType.location : + // ParentType extends BladesItemType.score ? BladesItemType.location : never; + // #region SCHEMA DATA: TEMPLATE.JSON & SYSTEM // template.json "template" definitions for BladesItems @@ -217,9 +233,30 @@ declare global { Partial, Partial { } - type BladesItemOfType = BladesItem & { - system: ExtractBladesItemSystem - }; + type BladesItemOfType = + // T extends BladesItemType.ability ? BladesItem & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.background ? BladesItem & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.clock_keeper ? BladesClockKeeper & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.cohort_gang ? BladesItem & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.cohort_expert ? BladesItem & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.crew_ability ? BladesItem & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.crew_reputation ? BladesItem & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.crew_playbook ? BladesItem & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.crew_upgrade ? BladesItem & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.feature ? BladesItem & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.gm_tracker ? BladesGMTracker & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.heritage ? BladesItem & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.gear ? BladesItem & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.playbook ? BladesItem & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.preferred_op ? BladesItem & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.stricture ? BladesItem & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.vice ? BladesItem & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.project ? BladesProject & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.ritual ? BladesItem & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.design ? BladesItem & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.location ? BladesLocation & { system: ExtractBladesItemSystem } : + // T extends BladesItemType.score ? BladesScore & { system: ExtractBladesItemSystem } : + BladesItem & { system: ExtractBladesItemSystem } type ExtractBladesItemSystem = { [BladesItemType.ability]: BladesItemSchema.Ability, diff --git a/ts/@types/index.d.ts b/ts/@types/index.d.ts index 0408a498..711c49e3 100644 --- a/ts/@types/index.d.ts +++ b/ts/@types/index.d.ts @@ -8,6 +8,7 @@ import BladesConsequence from "../classes/BladesConsequence"; import BladesClockKey from "../classes/BladesClockKey"; import BladesPushAlert from "../classes/BladesPushAlert"; import BladesChat from "../classes/BladesChat"; +import BladesDirector from "../classes/BladesDirector"; import C from "../core/constants"; import type gsap from "gsap/all"; diff --git a/ts/classes/BladesDirector.ts b/ts/classes/BladesDirector.ts index 8d577c9f..3c78154c 100644 --- a/ts/classes/BladesDirector.ts +++ b/ts/classes/BladesDirector.ts @@ -85,9 +85,9 @@ class BladesDirector { // #region OVERLAY ~ // #region >> Overlay Elements$ ~ - private _overlayContainer?: HTMLElement; - private _overlayContainer$?: JQuery; - private get overlayContainer(): HTMLElement { + public _overlayContainer?: HTMLElement; + public _overlayContainer$?: JQuery; + public get overlayContainer(): HTMLElement { if (!this._overlayContainer) { [this._overlayContainer] = $("#blades-overlay"); } @@ -97,42 +97,42 @@ class BladesDirector { } return this._overlayContainer; } - private get overlayContainer$(): JQuery { + public get overlayContainer$(): JQuery { if (!this._overlayContainer$) { this._overlayContainer$ = $(this.overlayContainer); } return this._overlayContainer$; } - private get clockKeySection$(): JQuery { + public get clockKeySection$(): JQuery { return this.overlayContainer$.find(".overlay-section-clock-keys"); } - private get locationSection$(): JQuery { + public get locationSection$(): JQuery { return this.overlayContainer$.find(".overlay-section-location"); } - private get scorePanelSection$(): JQuery { + public get scorePanelSection$(): JQuery { return this.overlayContainer$.find(".overlay-section-score-panel"); } - private get npcSection$(): JQuery { + public get npcSection$(): JQuery { return this.overlayContainer$.find(".overlay-section-npcs"); } - private get playerSection$(): JQuery { + public get playerSection$(): JQuery { return this.overlayContainer$.find(".overlay-section-players"); } - private get crewSection$(): JQuery { + public get crewSection$(): JQuery { return this.overlayContainer$.find(".overlay-section-crew"); } - private get notificationSection$(): JQuery { + public get notificationSection$(): JQuery { return this.overlayContainer$.find(".overlay-section-notifications"); } - private get transitionSection$(): JQuery { + public get transitionSection$(): JQuery { return this.overlayContainer$.find(".overlay-section-transitions"); } @@ -170,7 +170,7 @@ class BladesDirector { // #endregion - // #region CLOCKS & CLOCK KEYS + // #region CLOCKS & CLOCK KEYS ~ // #region >> INITIALIZATION ~ private initClockKeySection(isResetting = false) { @@ -455,7 +455,7 @@ class BladesDirector { // #endregion - // #region SCORE PANEL + // #region SCORE PANEL ~ // #region >> INITIALIZATION ~ private initScorePanelSockets() { @@ -474,7 +474,7 @@ class BladesDirector { } // #endregion - // #region LOCATIONS + // #region LOCATIONS ~ // #region >> INITIALIZATION ~ private initLocationSockets() { @@ -493,7 +493,7 @@ class BladesDirector { } // #endregion - // #region NPCs + // #region NPCs ~ // #region >> INITIALIZATION ~ private initNPCSockets() { @@ -508,7 +508,7 @@ class BladesDirector { } // #endregion - // #region PCs, COHORTs, CREW + // #region PCs, COHORTs, CREW ~ // #region >> INITIALIZATION ~ private initPCSockets() { // tbd... @@ -543,7 +543,7 @@ class BladesDirector { } // #endregion - // #region NOTIFICATIONS + // #region NOTIFICATIONS ~ // #region >> INITIALIZATION ~ private initNotificationSockets() { @@ -638,7 +638,7 @@ class BladesDirector { } // #endregion - // #region TRANSITIONS + // #region TRANSITIONS ~ // #region >> INITIALIZATION ~ private initTransitionSockets() { @@ -659,20 +659,118 @@ class BladesDirector { // #region TOOLTIPS ~ _tooltipObserver?: Observer; - private clearTooltips() { - // Look for tooltip elements in the overlay container, and reverse their timelines. - game.eunoblades.Director.tooltipSection$.find(".tooltip").each((i: number, el: HTMLElement) => { - U.gsap.effects.blurRemoveTooltip(el); + _tooltipElems: Map> = new Map>(); + _displayedTooltipID?: string; + + displayTooltip(tooltip: HTMLElement) { + if (!tooltip.id) { + throw new Error("Tooltip must have an ID to be cloned to the overlay."); + } + this._displayedTooltipID = tooltip.id; + const self = this; + // Clear out any other tooltips in the overlay. + game.eunoblades.Director.clearTooltips(); + if (!this._tooltipElems.has(tooltip.id)) { + // Create cloned tooltip and attach it to the tooltip overlay. + const ttClone$ = $(U.changeContainer( + tooltip, + game.eunoblades.Director.tooltipSection$[0], + true + )); + // Generate the reveal timeline and attach it to the cloned tooltip element. + const revealTimeline = U.gsap.effects.blurRevealTooltip( + ttClone$[0], + { + onReverseComplete() { + if (ttClone$.attr("id") === self._displayedTooltipID) { + delete self._displayedTooltipID; + } + game.eunoblades.Director._tooltipElems.delete(ttClone$.attr("id") as string); + game.eunoblades.Director.tooltipSection$.find(`#${ttClone$.attr("id")}`).remove(); + game.eunoblades.Director.tooltipSection$.children("[style*='opacity: 0'], [style*='opacity:0']").each(function() { + const id = this.id; // Get the ID of the current element + if (id === self._displayedTooltipID) { return; } + if (id) { + game.eunoblades.Director._tooltipElems.delete(id); // Remove from the map if the ID exists + } + $(this).remove(); // Remove the element from the DOM + }); + } + } + ); + ttClone$.data("revealTimeline", revealTimeline); + // Register the cloned tooltip element to the master map + this._tooltipElems.set(tooltip.id, ttClone$); + } + // Play the timeline. + this._tooltipElems.get(tooltip.id)?.data("revealTimeline")?.play(); + } + + clearTooltip(tooltipID: string, isClearingIfTweening = true) { + if (tooltipID === this._displayedTooltipID) { + delete this._displayedTooltipID; + } + const ttElem = game.eunoblades.Director._tooltipElems.get(tooltipID); + if (!ttElem) {return;} + const ttTimeline = ttElem.data("revealTimeline") as gsap.core.Timeline; + if (ttTimeline.isActive() && !isClearingIfTweening) { return; } + ttTimeline.reverse(); + } + + clearTooltips() { + eLog.checkLog3("Observer", "Observer Triggered!"); + // Look for tooltip elements in the overlay container, and remove them. + game.eunoblades.Director._tooltipElems.forEach((ttElem) => { + if (ttElem.attr("id") === this._displayedTooltipID) { + return; + } + game.eunoblades.Director.clearTooltip(ttElem.attr("id") as string, true); }); } + private initTooltipSection() { - const {clearTooltips} = this; + const self = this; + this.clearTooltips(); // Reset tooltip observer this._tooltipObserver?.kill(); + + // Simplified throttle function that takes a function with Observer parameter + const throttle = (func: (obs: Observer) => void, limit: number) => { + let lastFunc: number; + let lastRan: number; + return function(this: void, obs: Observer) { + const now = Date.now(); + if (!lastRan || now - lastRan >= limit) { + func(obs); + lastRan = now; + } else { + clearTimeout(lastFunc); + lastFunc = window.setTimeout(() => { + if (now - lastRan >= limit) { + func(obs); + lastRan = now; + } + }, limit - (now - lastRan)); + } + }; + }; + + // Throttled onMove callback + const throttledOnMove = throttle((obs: Observer) => { + // Calculate the absolute magnitude of velocity independent of direction + const magnitudeOfVelocity = Math.sqrt((obs.velocityX ** 2) + (obs.velocityY ** 2)); + if (magnitudeOfVelocity >= C.MIN_MOUSE_MOVEMENT_THRESHOLD) { + self.clearTooltips(); + } + }, 200); // Adjust 200ms to your preferred throttling limit + this._tooltipObserver = Observer.create({ type: "touch,pointer", - onClick: clearTooltips + // onMove: throttledOnMove, + onClick() { + self.clearTooltips(); + } }); } // #endregion diff --git a/ts/classes/BladesRoll.ts b/ts/classes/BladesRoll.ts index 9a4e8825..6ccdf017 100644 --- a/ts/classes/BladesRoll.ts +++ b/ts/classes/BladesRoll.ts @@ -210,6 +210,8 @@ class BladesRollMod extends BladesTargetLink { }); } + public isRerendering = false; + get status() { // USER STATUS of "ForcedOn", "ForcedOff", or "Hidden" trumps all other status values. if (this.userStatus && BladesRollMod.GMOnlyModStatuses.includes(this.userStatus)) { @@ -572,10 +574,11 @@ class BladesRollMod extends BladesTargetLink { get userStatus(): RollModStatus | undefined {return this.data.user_status;} set userStatus(val: RollModStatus | undefined) { if (val === this.userStatus) {return;} + const {isRerendering} = this; if (!val || val === this.baseStatus) { this.updateTarget("user_status", null) .then(() => { - if (this.rollInstance.isRendered) { + if (isRerendering) { this.rollInstance.renderRollCollab_SocketCall(); } }); @@ -588,7 +591,7 @@ class BladesRollMod extends BladesTargetLink { } this.updateTarget("user_status", val) .then(() => { - if (this.rollInstance.isRendered) { + if (isRerendering) { this.rollInstance.renderRollCollab_SocketCall(); } }); @@ -599,17 +602,18 @@ class BladesRollMod extends BladesTargetLink { get heldStatus(): RollModStatus | undefined {return this.data.held_status;} set heldStatus(val: RollModStatus | undefined) { if (val === this.heldStatus) {return;} + const {isRerendering} = this; if (!val) { this.updateTarget("held_status", null) .then(() => { - if (this.rollInstance.isRendered) { + if (isRerendering) { this.rollInstance.renderRollCollab_SocketCall(); } }); } else { this.updateTarget("held_status", val) .then(() => { - if (this.rollInstance.isRendered) { + if (isRerendering) { this.rollInstance.renderRollCollab_SocketCall(); } }); @@ -1856,55 +1860,6 @@ class BladesRoll extends BladesTargetLink { this.renderRollCollab_SocketResponse(rollInst.id); } - _elem$?: JQuery; - _overlayPosition: gsap.Point2D = {x: 200, y: 200}; - get overlayPosition(): gsap.Point2D { return this._overlayPosition; } - set overlayPosition(val: gsap.Point2D) { this._overlayPosition = val; } - - _positionDragger?: Dragger; - get positionDragger(): Dragger { - if (this._positionDragger) { return this._positionDragger; } - return this.spawnPositionDragger(); - } - - spawnPositionDragger() { - const self = this; - if (!this._elem$) { - throw new Error(`[BladesRoll.spawnPositionDragger] No elem$ found for roll ${this.id}.`); - } - return (this._positionDragger = new Dragger(this._elem$, { - type: "top,left", - trigger: ".window-header.draggable", - onDragStart(this: Dragger) { - U.gsap.to(this.target, {opacity: 0.25, duration: 0.25, ease: "power2"}); - }, - onDragEnd(this: Dragger) { - U.gsap.to(this.target, {opacity: 1, duration: 0.25, ease: "power2"}); - self.overlayPosition = {x: this.endX, y: this.endY}; - } - })); - } - - get elem$(): JQuery { - if (this._elem$) { return this._elem$; } - this._positionDragger = undefined; - const elem$ = $(`#${this.id}`); - if (elem$.length) { - this._elem$ = elem$; - } else { - this._elem$ = $(`
`).appendTo("body"); - } - this.spawnPositionDragger(); - return this._elem$; - } - - async renderRollCollab() { - this.prepareRollParticipantData(); - const html = await renderTemplate(this.collabTemplate, this.context); - this.elem$.html(html); - this.activateListeners(); - } - renderRollCollab_SocketCall() { socketlib.system.executeForEveryone("renderRollCollab_SocketCall", this.id); } @@ -2863,8 +2818,7 @@ class BladesRoll extends BladesTargetLink { // #endregion - // #region *** ROLL COLLAB CONTEXT *** ~ - + // #region *** ROLL COLLAB HTML INTERACTION *** ~ /** * Retrieve the data for rendering the base RollCollab sheet. @@ -3390,6 +3344,33 @@ class BladesRoll extends BladesTargetLink { // #region *** ROLL COLLAB HTML ELEMENT *** + _elem$?: JQuery; + _overlayPosition: gsap.Point2D = {x: 200, y: 200}; + get overlayPosition(): gsap.Point2D { return this._overlayPosition; } + set overlayPosition(val: gsap.Point2D) { this._overlayPosition = val; } + + get elem$(): JQuery { + if (this._elem$) { return this._elem$; } + const elem$ = $(`#${this.id}`); + if (elem$.length) { + this._elem$ = elem$; + } else { + this._elem$ = $(`
`).appendTo("body"); + this._elem$.css({ + left: `${this.overlayPosition.x}px`, + top: `${this.overlayPosition.y}px` + }); + } + return this._elem$; + } + + async renderRollCollab() { + this.prepareRollParticipantData(); + const html = await renderTemplate(this.collabTemplate, this.context); + this.elem$.html(html); + this.activateListeners(); + } + get isRendered(): boolean { return Boolean(this._elem$?.length); } @@ -3433,17 +3414,19 @@ class BladesRoll extends BladesTargetLink { const rollMod = this.getRollModByID(id); if (!rollMod) {throw new Error(`Unable to find roll mod with id '${id}'`);} + rollMod.isRerendering = true; switch (rollMod.status) { - case RollModStatus.Hidden: rollMod.userStatus = RollModStatus.ForcedOff; return; - case RollModStatus.ForcedOff: rollMod.userStatus = RollModStatus.ToggledOff; return; - case RollModStatus.ToggledOff: rollMod.userStatus = RollModStatus.ToggledOn; return; + case RollModStatus.Hidden: rollMod.userStatus = RollModStatus.ForcedOff; break; + case RollModStatus.ForcedOff: rollMod.userStatus = RollModStatus.ToggledOff; break; + case RollModStatus.ToggledOff: rollMod.userStatus = RollModStatus.ToggledOn; break; case RollModStatus.ToggledOn: rollMod.userStatus = game.user.isGM ? RollModStatus.ForcedOn : RollModStatus.ToggledOff; - return; - case RollModStatus.ForcedOn: rollMod.userStatus = RollModStatus.Hidden; return; + break; + case RollModStatus.ForcedOn: rollMod.userStatus = RollModStatus.Hidden; break; default: throw new Error(`Unrecognized RollModStatus: ${rollMod.status}`); } + rollMod.isRerendering = false; } /** @@ -3662,10 +3645,37 @@ class BladesRoll extends BladesTargetLink { // #endregion // #region ACTIVATE LISTENERS ~ + + _positionDragger?: Dragger; + get positionDragger(): Dragger { + if (this._positionDragger) { return this._positionDragger; } + return this.spawnPositionDragger(); + } + spawnPositionDragger() { + const self = this; + if (!this._elem$) { + throw new Error(`[BladesRoll.spawnPositionDragger] No elem$ found for roll ${this.id}.`); + } + this._positionDragger?.kill(); + return (this._positionDragger = new Dragger(this._elem$, { + type: "top,left", + trigger: ".window-header.dragger", + onDragStart(this: Dragger) { + U.gsap.to(this.target, {opacity: 0.25, duration: 0.25, ease: "power2"}); + }, + onDragEnd(this: Dragger) { + U.gsap.to(this.target, {opacity: 1, duration: 0.25, ease: "power2"}); + self.overlayPosition = {x: this.endX, y: this.endY}; + } + })); + } activateListeners() { + ApplyTooltipAnimations(this.elem$); ApplyConsequenceAnimations(this.elem$); + this.spawnPositionDragger(); + // If a rollClockKey exists, initialize its elements if (this.rollClockKey) { this.elem$.find(".roll-clock").removeClass("hidden"); diff --git a/ts/core/constants.ts b/ts/core/constants.ts index 6a5a780f..15e53955 100644 --- a/ts/core/constants.ts +++ b/ts/core/constants.ts @@ -438,6 +438,7 @@ const C = { "gpt-4-32k" ] }, + MIN_MOUSE_MOVEMENT_THRESHOLD: 2000, AI_FILE_IDS: { BladesPDF: "file-n72HTTNwt051piPbswQ8isUa" }, diff --git a/ts/core/gsap.ts b/ts/core/gsap.ts index 1937b1c0..8fd0bd2e 100644 --- a/ts/core/gsap.ts +++ b/ts/core/gsap.ts @@ -633,7 +633,7 @@ export const gsapEffects: Record = { // #region GENERAL: 'blurRemove', 'hoverTooltip', 'textJitter' blurRemove: { - effect: (targets, config) => U.gsap.timeline() + effect: (targets, config) => U.gsap.timeline({stagger: config.stagger}) .to( targets, { @@ -677,7 +677,8 @@ export const gsapEffects: Record = { duration: 0.5, x: "+=300", scale: 1.5, - blur: 10 + blur: 10, + stagger: 0 }, extendTimeline: true }, @@ -785,97 +786,51 @@ export const gsapEffects: Record = { }, blurRemoveTooltip: { effect: (tooltip, config) => { - const tooltip$ = $(tooltip as HTMLElement); - const container$ = $(config.container) ?? tooltip$.data("tooltipContainer"); - const tl = U.gsap.timeline({ - onComplete() { - if (container$.length) { - U.changeContainer(tooltip$[0], container$[0]); - } else { - tooltip$.remove(); - } - } - }) - .blurRemove(tooltip$[0], {ignoreMargin: true, blur: 15}); + const tl = U.gsap.timeline({}) + .blurRemove(tooltip, {ignoreMargin: true, blur: 15, stagger: config.stagger}); return tl; }, defaults: { - + stagger: 0 }, extendTimeline: true }, blurRevealTooltip: { effect: (tooltip, config) => { - const tooltip$ = $(tooltip as HTMLElement); - return U.gsap.timeline({ - onStart() { - // First check if there is already a valid, existant container element attached to the tooltip - if (!tooltip$.data("tooltipContainer")) { - tooltip$.data("tooltipContainer", tooltip$.parent()[0]); - } - U.changeContainer(tooltip$[0], game.eunoblades.Director.tooltipSection$[0]); - } - }).from( - tooltip$[0], - { - filter: "blur(15px)", - autoAlpha: 0, - xPercent: 50, - yPercent: -100, - scale: 1.5, - ease: "back.out" - } - ); - }, - defaults: { - tooltipScale: 0.75 - }, - extendTimeline: true - }, - hoverTooltip: { - effect: (tooltip, _config) => { - const tooltipElem = $(tooltip as HTMLElement)[0]; - const tooltipContainer$ = $(tooltipElem).parent(); - const tooltipContainer = tooltipContainer$[0]; - const overlayContainer = game.eunoblades.Director.tooltipSection$[0]; - return U.gsap.timeline({ paused: true, - onStart() { - U.changeContainer(tooltipElem, overlayContainer); - }, - onComplete() { - U.gsap.set(tooltipElem, {filter: "none"}); - }, - onReverseComplete() { - U.changeContainer(tooltipElem, tooltipContainer); - } + onReverseComplete: config.onReverseComplete + // onInterrupt() { this.reverse(); } }).fromTo( - tooltipElem, + tooltip, { - filter: "blur(15px)", + filter: `blur(${config.blurStrength}px)`, autoAlpha: 0, xPercent: 50, - yPercent: -100, - scale: 1.5 + yPercent: -200, + scale: config.scale }, { - filter: "blur(0px)", + filter: "none", autoAlpha: 1, - scale: 1, xPercent: -50, yPercent: -100, - duration: 0.25, - ease: "back.out" - }, - 0 + scale: 1, + ease: config.ease, + duration: config.duration + } ); }, defaults: { - tooltipScale: 0.75 - } + scale: 1.5, + blurStrength: 15, + ease: "back.out", + duration: 0.25, + onReverseComplete: undefined + }, + extendTimeline: true }, textJitter: { effect: (target, config) => { @@ -970,29 +925,24 @@ export function ApplyTooltipAnimations(html: JQuery) { // Find the tooltip's parent container. If its position isn't relative or absolute, set it to relative. const tooltipContainer = $(tooltipElem).parent()[0]; - if ($(tooltipContainer).css("position") !== "relative" && $(tooltipContainer).css("position") !== "absolute") { + if ($(tooltipContainer).css("position") !== "relative" + && $(tooltipContainer).css("position") !== "absolute") { $(tooltipContainer).css("position", "relative"); } - // // Register the tooltip timeline in the global map, so it can be reversed even if containing document is closed or re-rendered. - // game.eunoblades.Tooltips.set(tooltipElem, U.gsap.effects.hoverTooltip( - // tooltipElem, - // { - // scalingElems: [...$(el).find(".tooltip-scaling-elem")].filter((elem) => Boolean(elem)), - // xMotion: $(tooltipElem).hasClass("tooltip-left") ? "-=250" : "+=200", - // tooltipScale: $(tooltipElem).hasClass("tooltip-small") ? 1 : 1.2 - // } - // )); - - // $(el).data("hoverTimeline", () => game.eunoblades.Tooltips.get(tooltipElem) as gsap.core.Timeline); + // Set the tooltip itself to absolute positioning + $(tooltipElem).css("position", "absolute"); + + // Assign a unique ID to the tooltip element + const tooltipID = `tooltip-${randomID()}`; + $(tooltipElem).attr("id", tooltipID); + $(el).on({ mouseenter: function() { - U.gsap.effects.blurRevealTooltip(tooltipElem); - // $(el).data("hoverTimeline")().play(); + game.eunoblades.Director.displayTooltip(tooltipElem); }, mouseleave: function() { - U.gsap.effects.blurRemoveTooltip(tooltipElem); - // $(el).data("hoverTimeline")().reverse(); + game.eunoblades.Director.clearTooltip(tooltipID); } }); }); diff --git a/ts/core/utilities.ts b/ts/core/utilities.ts index e12f0623..cd81d4ee 100644 --- a/ts/core/utilities.ts +++ b/ts/core/utilities.ts @@ -1468,7 +1468,9 @@ const withLog = (fn: (...args: unknown[]) => unknown) => { // #region ████████ HTML: Parsing HTML Code, Manipulating DOM Objects ████████ ~ -const changeContainer = (elem: HTMLElement, container: HTMLElement) => { +const changeContainer = (elem: HTMLElement, container: HTMLElement, isCloning = false): HTMLElement => { + elem = $(elem)[0]; + container = $(container)[0]; // Get the element's current container, which defines its current coordinate space. const curContainer = $(elem).parent()[0]; // Get the element's current position in its current coordinate space. @@ -1482,10 +1484,15 @@ const changeContainer = (elem: HTMLElement, container: HTMLElement) => { container, curPosition ); - // eLog.checkLog3("changeContainer", "Target Element", {elem, container, curContainer, curPosition, relPos}); + eLog.checkLog3("changeContainer", "Target Element", {elem, container, curContainer, curPosition, relPos}); + // Clone the element, if indicated + if (isCloning) { + elem = $(elem).clone()[0]; + } // Append the element to the new container, and set its new position $(elem).appendTo($(container)); gsap.set(elem, relPos); + return elem; }; const adjustTextContainerAspectRatio = (