Initial
This commit is contained in:
349
resources/app/client/pixi/placeables/note.js
Normal file
349
resources/app/client/pixi/placeables/note.js
Normal file
@@ -0,0 +1,349 @@
|
||||
/**
|
||||
* A Note is an implementation of PlaceableObject which represents an annotated location within the Scene.
|
||||
* Each Note links to a JournalEntry document and represents its location on the map.
|
||||
* @category - Canvas
|
||||
* @see {@link NoteDocument}
|
||||
* @see {@link NotesLayer}
|
||||
*/
|
||||
class Note extends PlaceableObject {
|
||||
|
||||
/** @inheritdoc */
|
||||
static embeddedName = "Note";
|
||||
|
||||
/** @override */
|
||||
static RENDER_FLAGS = {
|
||||
redraw: {propagate: ["refresh"]},
|
||||
refresh: {propagate: ["refreshState", "refreshPosition", "refreshTooltip", "refreshElevation"], alias: true},
|
||||
refreshState: {propagate: ["refreshVisibility"]},
|
||||
refreshVisibility: {},
|
||||
refreshPosition: {},
|
||||
refreshTooltip: {},
|
||||
refreshElevation: {propagate: ["refreshVisibility"]},
|
||||
/** @deprecated since v12 */
|
||||
refreshText: {propagate: ["refreshTooltip"], deprecated: {since: 12, until: 14}, alias: true}
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* The control icon.
|
||||
* @type {ControlIcon}
|
||||
*/
|
||||
controlIcon;
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* The tooltip.
|
||||
* @type {PreciseText}
|
||||
*/
|
||||
tooltip;
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
get bounds() {
|
||||
const {x, y, iconSize} = this.document;
|
||||
const r = iconSize / 2;
|
||||
return new PIXI.Rectangle(x - r, y - r, 2*r, 2*r);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* The associated JournalEntry which is referenced by this Note
|
||||
* @type {JournalEntry}
|
||||
*/
|
||||
get entry() {
|
||||
return this.document.entry;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* The specific JournalEntryPage within the associated JournalEntry referenced by this Note.
|
||||
*/
|
||||
get page() {
|
||||
return this.document.page;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Determine whether the Note is visible to the current user based on their perspective of the Scene.
|
||||
* Visibility depends on permission to the underlying journal entry, as well as the perspective of controlled Tokens.
|
||||
* If Token Vision is required, the user must have a token with vision over the note to see it.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isVisible() {
|
||||
const accessTest = this.document.page ?? this.document.entry;
|
||||
const access = accessTest?.testUserPermission(game.user, "LIMITED") ?? true;
|
||||
if ( (access === false) || !canvas.visibility.tokenVision || this.document.global ) return access;
|
||||
const point = {x: this.document.x, y: this.document.y};
|
||||
const tolerance = this.document.iconSize / 4;
|
||||
return canvas.visibility.testVisibility(point, {tolerance, object: this});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Rendering
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
async _draw(options) {
|
||||
this.controlIcon = this.addChild(this._drawControlIcon());
|
||||
this.tooltip = this.addChild(this._drawTooltip());
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Draw the control icon.
|
||||
* @returns {ControlIcon}
|
||||
* @protected
|
||||
*/
|
||||
_drawControlIcon() {
|
||||
const {texture, iconSize} = this.document;
|
||||
const icon = new ControlIcon({texture: texture.src, size: iconSize, tint: texture.tint});
|
||||
icon.x -= (iconSize / 2);
|
||||
icon.y -= (iconSize / 2);
|
||||
return icon;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Draw the tooltip.
|
||||
* @returns {PreciseText}
|
||||
* @protected
|
||||
*/
|
||||
_drawTooltip() {
|
||||
const tooltip = new PreciseText(this.document.label, this._getTextStyle());
|
||||
tooltip.eventMode = "none";
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Refresh the tooltip.
|
||||
* @protected
|
||||
*/
|
||||
_refreshTooltip() {
|
||||
this.tooltip.text = this.document.label;
|
||||
this.tooltip.style = this._getTextStyle();
|
||||
const halfPad = (0.5 * this.document.iconSize) + 12;
|
||||
switch ( this.document.textAnchor ) {
|
||||
case CONST.TEXT_ANCHOR_POINTS.CENTER:
|
||||
this.tooltip.anchor.set(0.5, 0.5);
|
||||
this.tooltip.position.set(0, 0);
|
||||
break;
|
||||
case CONST.TEXT_ANCHOR_POINTS.BOTTOM:
|
||||
this.tooltip.anchor.set(0.5, 0);
|
||||
this.tooltip.position.set(0, halfPad);
|
||||
break;
|
||||
case CONST.TEXT_ANCHOR_POINTS.TOP:
|
||||
this.tooltip.anchor.set(0.5, 1);
|
||||
this.tooltip.position.set(0, -halfPad);
|
||||
break;
|
||||
case CONST.TEXT_ANCHOR_POINTS.LEFT:
|
||||
this.tooltip.anchor.set(1, 0.5);
|
||||
this.tooltip.position.set(-halfPad, 0);
|
||||
break;
|
||||
case CONST.TEXT_ANCHOR_POINTS.RIGHT:
|
||||
this.tooltip.anchor.set(0, 0.5);
|
||||
this.tooltip.position.set(halfPad, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Define a PIXI TextStyle object which is used for the tooltip displayed for this Note
|
||||
* @returns {PIXI.TextStyle}
|
||||
* @protected
|
||||
*/
|
||||
_getTextStyle() {
|
||||
const style = CONFIG.canvasTextStyle.clone();
|
||||
|
||||
// Positioning
|
||||
if ( this.document.textAnchor === CONST.TEXT_ANCHOR_POINTS.LEFT ) style.align = "right";
|
||||
else if ( this.document.textAnchor === CONST.TEXT_ANCHOR_POINTS.RIGHT ) style.align = "left";
|
||||
|
||||
// Font preferences
|
||||
style.fontFamily = this.document.fontFamily || CONFIG.defaultFontFamily;
|
||||
style.fontSize = this.document.fontSize;
|
||||
|
||||
// Toggle stroke style depending on whether the text color is dark or light
|
||||
const color = this.document.textColor;
|
||||
style.fill = color;
|
||||
style.stroke = color.hsv[2] > 0.6 ? 0x000000 : 0xFFFFFF;
|
||||
style.strokeThickness = 4;
|
||||
return style;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Incremental Refresh */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_applyRenderFlags(flags) {
|
||||
if ( flags.refreshState ) this._refreshState();
|
||||
if ( flags.refreshVisibility ) this._refreshVisibility();
|
||||
if ( flags.refreshPosition ) this._refreshPosition();
|
||||
if ( flags.refreshTooltip ) this._refreshTooltip();
|
||||
if ( flags.refreshElevation ) this._refreshElevation();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Refresh the visibility.
|
||||
* @protected
|
||||
*/
|
||||
_refreshVisibility() {
|
||||
const wasVisible = this.visible;
|
||||
this.visible = this.isVisible;
|
||||
if ( this.controlIcon ) this.controlIcon.refresh({
|
||||
visible: this.visible,
|
||||
borderVisible: this.hover || this.layer.highlightObjects
|
||||
});
|
||||
if ( wasVisible !== this.visible ) {
|
||||
this.layer.hintMapNotes();
|
||||
MouseInteractionManager.emulateMoveEvent();
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Refresh the state of the Note. Called the Note enters a different interaction state.
|
||||
* @protected
|
||||
*/
|
||||
_refreshState() {
|
||||
this.alpha = this._getTargetAlpha();
|
||||
this.tooltip.visible = this.hover || this.layer.highlightObjects;
|
||||
this.zIndex = this.hover ? 1 : 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Refresh the position of the Note. Called with the coordinates change.
|
||||
* @protected
|
||||
*/
|
||||
_refreshPosition() {
|
||||
const {x, y} = this.document;
|
||||
if ( (this.position.x !== x) || (this.position.y !== y) ) MouseInteractionManager.emulateMoveEvent();
|
||||
this.position.set(this.document.x, this.document.y);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Refresh the elevation of the control icon.
|
||||
* @protected
|
||||
*/
|
||||
_refreshElevation() {
|
||||
this.controlIcon.elevation = this.document.elevation;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Document Event Handlers */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritDoc */
|
||||
_onUpdate(changed, options, userId) {
|
||||
super._onUpdate(changed, options, userId);
|
||||
|
||||
// Incremental Refresh
|
||||
const positionChanged = ("x" in changed) || ("y" in changed);
|
||||
this.renderFlags.set({
|
||||
redraw: ("texture" in changed) || ("iconSize" in changed),
|
||||
refreshVisibility: positionChanged || ["entryId", "pageId", "global"].some(k => k in changed),
|
||||
refreshPosition: positionChanged,
|
||||
refreshTooltip: ["text", "fontFamily", "fontSize", "textAnchor", "textColor", "iconSize"].some(k => k in changed),
|
||||
refreshElevation: "elevation" in changed
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Interactivity */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_canHover(user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_canView(user) {
|
||||
const {entry, page} = this.document;
|
||||
if ( !entry ) return false;
|
||||
if ( game.user.isGM ) return true;
|
||||
if ( page?.testUserPermission(game.user, "LIMITED", {exact: true}) ) {
|
||||
// Special-case handling for image pages.
|
||||
return page.type === "image";
|
||||
}
|
||||
const accessTest = page ?? entry;
|
||||
return accessTest.testUserPermission(game.user, "OBSERVER");
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_canConfigure(user) {
|
||||
return canvas.notes.active && this.document.canUserModify(game.user, "update");
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritdoc */
|
||||
_onClickLeft2(event) {
|
||||
const {entry, page} = this.document;
|
||||
if ( !entry ) return;
|
||||
const options = {};
|
||||
if ( page ) {
|
||||
options.mode = JournalSheet.VIEW_MODES.SINGLE;
|
||||
options.pageId = page.id;
|
||||
}
|
||||
const allowed = Hooks.call("activateNote", this, options);
|
||||
if ( allowed === false ) return;
|
||||
if ( page?.type === "image" ) {
|
||||
return new ImagePopout(page.src, {
|
||||
uuid: page.uuid,
|
||||
title: page.name,
|
||||
caption: page.image.caption
|
||||
}).render(true);
|
||||
}
|
||||
entry.sheet.render(true, options);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Deprecations and Compatibility */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @deprecated since v12
|
||||
* @ignore
|
||||
*/
|
||||
get text() {
|
||||
const msg = "Note#text has been deprecated. Use Note#document#label instead.";
|
||||
foundry.utils.logCompatibilityWarning(msg, {since: 12, until: 14, once: true});
|
||||
return this.document.label;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @deprecated since v12
|
||||
* @ignore
|
||||
*/
|
||||
get size() {
|
||||
const msg = "Note#size has been deprecated. Use Note#document#iconSize instead.";
|
||||
foundry.utils.logCompatibilityWarning(msg, {since: 12, until: 14, once: true});
|
||||
return this.document.iconSize;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user