diff --git a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs index 78448a1..6921b54 100644 --- a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs @@ -193,8 +193,9 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin * @param {PointerEvent} event */ async #ammoInfoPointerEnter(event) { - console.log(event.x, event.y); - const { x, y } = event; + const pos = event.target.getBoundingClientRect(); + const x = pos.x + Math.floor(pos.width / 2); + const y = pos.y; this.#ammoTrackerHoverTimeout = setTimeout( () => { @@ -214,7 +215,7 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin async #ammoInfoPointerOut() { if (this.#ammoTracker) { - // this.#ammoTracker.close(); + this.#ammoTracker.close(); }; if (this.#ammoTrackerHoverTimeout !== null) { diff --git a/module/Apps/popovers/AmmoTracker.mjs b/module/Apps/popovers/AmmoTracker.mjs index b6d59db..aeb96a9 100644 --- a/module/Apps/popovers/AmmoTracker.mjs +++ b/module/Apps/popovers/AmmoTracker.mjs @@ -7,14 +7,9 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin( // #region Options static DEFAULT_OPTIONS = { classes: [ + `ripcrypt`, `ripcrypt--AmmoTracker`, ], - window: { - frame: false, - positioned: true, - resizable: false, - minimizable: false, - }, position: { width: 100, height: 30, @@ -30,164 +25,9 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin( // #endregion // #region Instance Data - popover = {}; - constructor({ popover, ...options}) { - - // For when the caller doesn't provide anything, we want this to behave - // like a normal Application. - popover.framed ??= true; - popover.locked ??= true; - - if (popover.framed) { - options.window.frame = true; - options.window.minimizable = true; - } - - super(options); - - this.popover = popover; - }; // #endregion // #region Lifecycle - _insertElement(element) { - // console.log(this.popover); - const existing = document.getElementById(element.id); - if (existing) { - existing.replaceWith(element); - return; - }; - // const pos = element.getBoundingClientRect(); - - // const horizontalOffset = Math.floor(pos.width / 2); - // console.log({ x: this.popover.x, y: this.popover.y, height: pos.height, xOffset: horizontalOffset }); - - element.style.position = `absolute`; - element.style.color = `black`; - element.style.background = `greenyellow`; - element.style[`z-index`] = 10000; - // element.style.left = `${this.popover.x - horizontalOffset}px`; - // element.style.top = `${this.popover.y - pos.height}px`; - // this.position = { - // left: this.popover.x - horizontalOffset, - // top: this.popover.y - pos.height, - // }; - - // standard addition - document.body.append(element); - }; - - // _updatePosition(position) { - // const pos = super._updatePosition(position); - // if (this.popover.framed) { return pos }; - - // delete pos.left; - // delete pos.top; - - // const el = this.element; - // let bounds; - // let width, height; - - // // Implicit height - // if ( true ) { - // Object.assign(el.style, {width: `${width}px`, height: ""}); - // bounds = el.getBoundingClientRect(); - // height = bounds.height; - // } - - // // Implicit width - // if ( true ) { - // Object.assign(el.style, {height: `${height}px`, width: ""}); - // bounds = el.getBoundingClientRect(); - // width = bounds.width; - // } - - // // const { width, height } = this.element.getBoundingClientRect(); - // const horizontalOffset = Math.floor(width / 2); - // pos.left = this.popover.x - horizontalOffset; - // pos.top = this.popover.y - height; - - // console.log({ x: this.popover.x, y: this.popover.y, height, xOffset: horizontalOffset, width }); - - // return pos; - // } - - /** - * @override - * Custom implementation in order to make it show up approximately where I - * want it to when being created. - */ - _updatePosition(position) { - if ( !this.element ) { return position }; - const el = this.element; - let {width, height, left, top, scale} = position; - scale ??= 1.0; - const computedStyle = getComputedStyle(el); - let minWidth = ApplicationV2.parseCSSDimension(computedStyle.minWidth, el.parentElement.offsetWidth) || 0; - let maxWidth = ApplicationV2.parseCSSDimension(computedStyle.maxWidth, el.parentElement.offsetWidth) || Infinity; - let minHeight = ApplicationV2.parseCSSDimension(computedStyle.minHeight, el.parentElement.offsetHeight) || 0; - let maxHeight = ApplicationV2.parseCSSDimension(computedStyle.maxHeight, el.parentElement.offsetHeight) || Infinity; - let bounds = el.getBoundingClientRect(); - const {clientWidth, clientHeight} = document.documentElement; - - // Explicit width - const autoWidth = width === `auto`; - if ( !autoWidth ) { - const targetWidth = Number(width || bounds.width); - minWidth = parseInt(minWidth) || 0; - maxWidth = parseInt(maxWidth) || (clientWidth / scale); - width = Math.clamp(targetWidth, minWidth, maxWidth); - } - - // Explicit height - const autoHeight = height === `auto`; - if ( !autoHeight ) { - const targetHeight = Number(height || bounds.height); - minHeight = parseInt(minHeight) || 0; - maxHeight = parseInt(maxHeight) || (clientHeight / scale); - height = Math.clamp(targetHeight, minHeight, maxHeight); - } - - // Implicit height - if ( autoHeight ) { - Object.assign(el.style, {width: `${width}px`, height: ``}); - bounds = el.getBoundingClientRect(); - height = bounds.height; - } - - // Implicit width - if ( autoWidth ) { - Object.assign(el.style, {height: `${height}px`, width: ``}); - bounds = el.getBoundingClientRect(); - width = bounds.width; - } - - // Left Offset - const scaledWidth = width * scale; - const targetLeft = left ?? (this.popover.x - Math.floor( scaledWidth / 2 )); - const maxLeft = Math.max(clientWidth - scaledWidth, 0); - left = Math.clamp(targetLeft, 0, maxLeft); - - // Top Offset - const scaledHeight = height * scale; - const targetTop = top ?? (this.popover.y - scaledHeight); - const maxTop = Math.max(clientHeight - scaledHeight, 0); - top = Math.clamp(targetTop, 0, maxTop); - - // Scale - scale ??= 1.0; - return { - width: autoWidth ? `auto` : width, - height: autoHeight ? `auto` : height, - left, - top, - scale, - }; - } - - async _onFirstRender(context, options) { - await super._onFirstRender(context, options); - }; // #endregion // #region Actions diff --git a/module/Apps/popovers/GenericPopoverMixin.mjs b/module/Apps/popovers/GenericPopoverMixin.mjs index 89b7bfd..dfe2518 100644 --- a/module/Apps/popovers/GenericPopoverMixin.mjs +++ b/module/Apps/popovers/GenericPopoverMixin.mjs @@ -1,4 +1,123 @@ +const { ApplicationV2 } = foundry.applications.api; + export function GenericPopoverMixin(HandlebarsApp) { - class GenericRipCryptPopover extends HandlebarsApp {}; + class GenericRipCryptPopover extends HandlebarsApp { + static DEFAULT_OPTIONS = { + classes: [ + `popover`, + ], + window: { + frame: false, + positioned: true, + resizable: false, + minimizable: false, + }, + actions: {}, + }; + + popover = {}; + constructor({ popover, ...options}) { + + // For when the caller doesn't provide anything, we want this to behave + // like a normal Application instance. + popover.framed ??= true; + popover.locked ??= true; + + if (popover.framed) { + options.window.frame = true; + options.window.minimizable = true; + }; + + super(options); + + this.popover = popover; + }; + + async close(options = {}) { + if (!this.popover.framed) { + options.animate ??= false; + }; + return super.close(options); + }; + + /** + * @override + * Custom implementation in order to make it show up approximately where I + * want it to when being created. + * + * Most of this implementation is identical to the ApplicationV2 + * implementation, the biggest difference is how targetLeft and targetRight + * are calculated. + */ + _updatePosition(position) { + if (!this.element) { return position }; + if (this.popover.framed) { return position } + + const el = this.element; + let {width, height, left, top, scale} = position; + scale ??= 1.0; + const computedStyle = getComputedStyle(el); + let minWidth = ApplicationV2.parseCSSDimension(computedStyle.minWidth, el.parentElement.offsetWidth) || 0; + let maxWidth = ApplicationV2.parseCSSDimension(computedStyle.maxWidth, el.parentElement.offsetWidth) || Infinity; + let minHeight = ApplicationV2.parseCSSDimension(computedStyle.minHeight, el.parentElement.offsetHeight) || 0; + let maxHeight = ApplicationV2.parseCSSDimension(computedStyle.maxHeight, el.parentElement.offsetHeight) || Infinity; + let bounds = el.getBoundingClientRect(); + const {clientWidth, clientHeight} = document.documentElement; + + // Explicit width + const autoWidth = width === `auto`; + if ( !autoWidth ) { + const targetWidth = Number(width || bounds.width); + minWidth = parseInt(minWidth) || 0; + maxWidth = parseInt(maxWidth) || (clientWidth / scale); + width = Math.clamp(targetWidth, minWidth, maxWidth); + } + + // Explicit height + const autoHeight = height === `auto`; + if ( !autoHeight ) { + const targetHeight = Number(height || bounds.height); + minHeight = parseInt(minHeight) || 0; + maxHeight = parseInt(maxHeight) || (clientHeight / scale); + height = Math.clamp(targetHeight, minHeight, maxHeight); + } + + // Implicit height + if ( autoHeight ) { + Object.assign(el.style, {width: `${width}px`, height: ``}); + bounds = el.getBoundingClientRect(); + height = bounds.height; + } + + // Implicit width + if ( autoWidth ) { + Object.assign(el.style, {height: `${height}px`, width: ``}); + bounds = el.getBoundingClientRect(); + width = bounds.width; + } + + // Left Offset + const scaledWidth = width * scale; + const targetLeft = left ?? (this.popover.x - Math.floor( scaledWidth / 2 )); + const maxLeft = Math.max(clientWidth - scaledWidth, 0); + left = Math.clamp(targetLeft, 0, maxLeft); + + // Top Offset + const scaledHeight = height * scale; + const targetTop = top ?? (this.popover.y - scaledHeight); + const maxTop = Math.max(clientHeight - scaledHeight, 0); + top = Math.clamp(targetTop, 0, maxTop); + + // Scale + scale ??= 1.0; + return { + width: autoWidth ? `auto` : width, + height: autoHeight ? `auto` : height, + left, + top, + scale, + }; + }; + }; return GenericRipCryptPopover; }; diff --git a/templates/css/common.css b/templates/css/common.css index cb8c9f0..8d025bb 100644 --- a/templates/css/common.css +++ b/templates/css/common.css @@ -2,6 +2,8 @@ @import url("./vars.css"); +@import url("./popover.css"); + @import url("./elements/button.css"); @import url("./elements/input.css"); @import url("./elements/lists.css"); diff --git a/templates/css/popover.css b/templates/css/popover.css new file mode 100644 index 0000000..1fbb7c9 --- /dev/null +++ b/templates/css/popover.css @@ -0,0 +1,5 @@ +.ripcrypt.popover { + position: absolute; + z-index: var(--z-index-tooltip); + transform-origin: top left; +}