1052 lines
34 KiB
JavaScript
1052 lines
34 KiB
JavaScript
/**
|
|
* @typedef {Object} SceneControlTool
|
|
* @property {string} name
|
|
* @property {string} title
|
|
* @property {string} icon
|
|
* @property {boolean} visible
|
|
* @property {boolean} toggle
|
|
* @property {boolean} active
|
|
* @property {boolean} button
|
|
* @property {Function} onClick
|
|
* @property {ToolclipConfiguration} toolclip Configuration for rendering the tool's toolclip.
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} SceneControl
|
|
* @property {string} name
|
|
* @property {string} title
|
|
* @property {string} layer
|
|
* @property {string} icon
|
|
* @property {boolean} visible
|
|
* @property {SceneControlTool[]} tools
|
|
* @property {string} activeTool
|
|
*/
|
|
|
|
/**
|
|
* @typedef {object} ToolclipConfiguration
|
|
* @property {string} src The filename of the toolclip video.
|
|
* @property {string} heading The heading string.
|
|
* @property {ToolclipConfigurationItem[]} items The items in the toolclip body.
|
|
*/
|
|
|
|
/**
|
|
* @typedef {object} ToolclipConfigurationItem
|
|
* @property {string} [paragraph] A plain paragraph of content for this item.
|
|
* @property {string} [heading] A heading for the item.
|
|
* @property {string} [content] Content for the item.
|
|
* @property {string} [reference] If the item is a single key reference, use this instead of content.
|
|
*/
|
|
|
|
/**
|
|
* Scene controls navigation menu
|
|
* @extends {Application}
|
|
*/
|
|
class SceneControls extends Application {
|
|
|
|
/** @inheritdoc */
|
|
static get defaultOptions() {
|
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
width: 100,
|
|
id: "controls",
|
|
template: "templates/hud/controls.html",
|
|
popOut: false
|
|
});
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* The Array of Scene Control buttons which are currently rendered
|
|
* @type {SceneControl[]}
|
|
*/
|
|
controls = this._getControlButtons();
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* The currently active control set
|
|
* @type {string}
|
|
*/
|
|
get activeControl() {
|
|
return this.#control;
|
|
}
|
|
|
|
#control = "token";
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* The currently active tool in the control palette
|
|
* @type {string}
|
|
*/
|
|
get activeTool() {
|
|
return this.#tools[this.#control];
|
|
}
|
|
|
|
/**
|
|
* Track which tool is active within each control set
|
|
* @type {Record<string, string>}
|
|
*/
|
|
#tools = {};
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Return the active control set
|
|
* @type {SceneControl|null}
|
|
*/
|
|
get control() {
|
|
if ( !this.controls ) return null;
|
|
return this.controls.find(c => c.name === this.#control) || null;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Return the actively controlled tool
|
|
* @type {SceneControlTool|null}
|
|
*/
|
|
get tool() {
|
|
const control = this.control;
|
|
if ( !control ) return null;
|
|
return this.#tools[control.name] || null;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* Methods */
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Initialize the Scene Controls by obtaining the set of control buttons and rendering the HTML
|
|
* @param {object} options Options which modify how the controls UI is initialized
|
|
* @param {string} [options.control] An optional control set to set as active
|
|
* @param {string} [options.layer] An optional layer name to target as the active control
|
|
* @param {string} [options.tool] A specific named tool to set as active for the palette
|
|
*/
|
|
initialize({control, layer, tool} = {}) {
|
|
|
|
// Determine the control set to activate
|
|
let controlSet = control ? this.controls.find(c => c.name === control) : null;
|
|
if ( !controlSet && layer ) controlSet = this.controls.find(c => c.layer === layer);
|
|
if ( !controlSet ) controlSet = this.control;
|
|
|
|
// Determine the tool to activate
|
|
tool ||= this.#tools[controlSet.name] || controlSet.activeTool || null;
|
|
|
|
// Activate the new control scheme
|
|
this.#control = controlSet?.name || null;
|
|
if ( controlSet && (this.#tools[this.#control] !== tool) ) {
|
|
this.#tools[this.#control] = tool;
|
|
|
|
// Refresh placeable states if the active tool changed
|
|
if ( canvas.ready ) {
|
|
// TODO: Perhaps replace this with a CanvasLayer#_onToolChanged callback
|
|
const layer = canvas[controlSet.layer];
|
|
if ( layer instanceof PlaceablesLayer ) {
|
|
for ( const placeable of layer.placeables ) placeable.renderFlags.set({refreshState: true});
|
|
}
|
|
}
|
|
}
|
|
this.controls = this._getControlButtons();
|
|
|
|
// Render the UI
|
|
this.render(true);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/** @inheritdoc */
|
|
async getData(options={}) {
|
|
const showToolclips = game.settings.get("core", "showToolclips");
|
|
const canvasActive = !!canvas.scene;
|
|
const controls = [];
|
|
const isMac = navigator.appVersion.includes("Mac");
|
|
const mod = isMac ? "⌘" : game.i18n.localize("CONTROLS.CtrlAbbr");
|
|
const alt = isMac ? "⌥" : game.i18n.localize("CONTROLS.Alt");
|
|
|
|
for ( const c of this.controls ) {
|
|
if ( c.visible === false ) continue;
|
|
const control = foundry.utils.deepClone(c);
|
|
control.isActive = canvasActive && (this.#control === control.name);
|
|
control.css = control.isActive ? "active" : "";
|
|
control.tools = [];
|
|
|
|
for ( const t of c.tools ) {
|
|
if ( t.visible === false ) continue;
|
|
const tool = foundry.utils.deepClone(t);
|
|
tool.isActive = canvasActive && ((this.#tools[control.name] === tool.name) || (tool.toggle && tool.active));
|
|
tool.css = [
|
|
tool.toggle ? "toggle" : null,
|
|
tool.isActive ? "active" : null
|
|
].filterJoin(" ");
|
|
tool.tooltip = showToolclips && tool.toolclip
|
|
? await renderTemplate("templates/hud/toolclip.html", { ...tool.toolclip, alt, mod })
|
|
: tool.title;
|
|
control.tools.push(tool);
|
|
}
|
|
|
|
if ( control.tools.length ) controls.push(control);
|
|
}
|
|
|
|
// Return data for rendering
|
|
return {
|
|
controls,
|
|
active: canvasActive,
|
|
cssClass: canvasActive ? "" : "disabled"
|
|
};
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* Event Listeners and Handlers */
|
|
/* -------------------------------------------- */
|
|
|
|
/** @inheritdoc */
|
|
activateListeners(html) {
|
|
html.find(".scene-control").click(this._onClickLayer.bind(this));
|
|
html.find(".control-tool").click(this._onClickTool.bind(this));
|
|
canvas.notes?.hintMapNotes();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Handle click events on a Control set
|
|
* @param {Event} event A click event on a tool control
|
|
* @private
|
|
*/
|
|
_onClickLayer(event) {
|
|
event.preventDefault();
|
|
if ( !canvas.ready ) return;
|
|
const li = event.currentTarget;
|
|
const controlName = li.dataset.control;
|
|
if ( this.#control === controlName ) return;
|
|
this.#control = controlName;
|
|
const control = this.controls.find(c => c.name === controlName);
|
|
if ( control ) canvas[control.layer].activate();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Handle click events on Tool controls
|
|
* @param {Event} event A click event on a tool control
|
|
* @private
|
|
*/
|
|
_onClickTool(event) {
|
|
event.preventDefault();
|
|
if ( !canvas.ready ) return;
|
|
const li = event.currentTarget;
|
|
const control = this.control;
|
|
const toolName = li.dataset.tool;
|
|
const tool = control.tools.find(t => t.name === toolName);
|
|
|
|
// Handle Toggles
|
|
if ( tool.toggle ) {
|
|
tool.active = !tool.active;
|
|
if ( tool.onClick instanceof Function ) tool.onClick(tool.active);
|
|
}
|
|
|
|
// Handle Buttons
|
|
else if ( tool.button ) {
|
|
if ( tool.onClick instanceof Function ) tool.onClick();
|
|
}
|
|
|
|
// Handle Tools
|
|
else if ( this.#tools[control.name] !== toolName ) {
|
|
this.#tools[control.name] = toolName;
|
|
const layer = canvas[control.layer];
|
|
if ( layer instanceof PlaceablesLayer ) {
|
|
for ( const placeable of layer.placeables ) placeable.renderFlags.set({refreshState: true});
|
|
}
|
|
if ( tool.onClick instanceof Function ) tool.onClick();
|
|
}
|
|
|
|
// Render the controls
|
|
this.render();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Get the set of Control sets and tools that are rendered as the Scene Controls.
|
|
* These controls may be extended using the "getSceneControlButtons" Hook.
|
|
* @returns {SceneControl[]}
|
|
* @private
|
|
*/
|
|
_getControlButtons() {
|
|
const controls = [];
|
|
const isGM = game.user.isGM;
|
|
const clip = game.settings.get("core", "showToolclips") ? "Clip" : "";
|
|
const commonControls = {
|
|
create: { heading: "CONTROLS.CommonCreate", reference: "CONTROLS.ClickDrag" },
|
|
move: { heading: "CONTROLS.CommonMove", reference: "CONTROLS.Drag" },
|
|
edit: { heading: "CONTROLS.CommonEdit", reference: "CONTROLS.DoubleClick" },
|
|
editAlt: { heading: "CONTROLS.CommonEdit", reference: "CONTROLS.DoubleRightClick" },
|
|
sheet: { heading: "CONTROLS.CommonOpenSheet", reference: "CONTROLS.DoubleClick" },
|
|
hide: { heading: "CONTROLS.CommonHide", reference: "CONTROLS.RightClick" },
|
|
delete: { heading: "CONTROLS.CommonDelete", reference: "CONTROLS.Delete" },
|
|
rotate: { heading: "CONTROLS.CommonRotate", content: "CONTROLS.ShiftOrCtrlScroll" },
|
|
select: { heading: "CONTROLS.CommonSelect", reference: "CONTROLS.Click" },
|
|
selectAlt: { heading: "CONTROLS.CommonSelect", content: "CONTROLS.ClickOrClickDrag" },
|
|
selectMultiple: { heading: "CONTROLS.CommonSelectMultiple", reference: "CONTROLS.ShiftClick" },
|
|
hud: { heading: "CONTROLS.CommonToggleHUD", reference: "CONTROLS.RightClick" },
|
|
draw: { heading: "CONTROLS.CommonDraw", reference: "CONTROLS.ClickDrag" },
|
|
drawProportionally: { heading: "CONTROLS.CommonDrawProportional", reference: "CONTROLS.AltClickDrag" },
|
|
place: { heading: "CONTROLS.CommonPlace", reference: "CONTROLS.ClickDrag" },
|
|
chain: { heading: "CONTROLS.CommonChain", content: "CONTROLS.ChainCtrlClick" },
|
|
movePoint: { heading: "CONTROLS.CommonMovePoint", reference: "CONTROLS.ClickDrag" },
|
|
openClose: { heading: "CONTROLS.CommonOpenClose", reference: "CONTROLS.Click" },
|
|
openCloseSilently: { heading: "CONTROLS.CommonOpenCloseSilently", reference: "CONTROLS.AltClick" },
|
|
lock: { heading: "CONTROLS.CommonLock", reference: "CONTROLS.RightClick" },
|
|
lockSilently: { heading: "CONTROLS.CommonLockSilently", reference: "CONTROLS.AltRightClick" },
|
|
onOff: { heading: "CONTROLS.CommonOnOff", reference: "CONTROLS.RightClick" }
|
|
};
|
|
|
|
const buildItems = (...items) => items.map(item => commonControls[item]);
|
|
|
|
// Token Controls
|
|
controls.push({
|
|
name: "token",
|
|
title: "CONTROLS.GroupToken",
|
|
layer: "tokens",
|
|
icon: "fa-solid fa-user-alt",
|
|
tools: [
|
|
{
|
|
name: "select",
|
|
title: "CONTROLS.BasicSelect",
|
|
icon: "fa-solid fa-expand",
|
|
toolclip: {
|
|
src: "toolclips/tools/token-select.webm",
|
|
heading: "CONTROLS.BasicSelect",
|
|
items: [
|
|
{ paragraph: "CONTROLS.BasicSelectP" },
|
|
...buildItems("selectAlt", "selectMultiple", "move", "rotate", "hud", "sheet"),
|
|
...(game.user.isGM ? buildItems("editAlt", "delete") : []),
|
|
{ heading: "CONTROLS.BasicMeasureStart", reference: "CONTROLS.CtrlClickDrag" },
|
|
{ heading: "CONTROLS.BasicMeasureWaypoints", reference: "CONTROLS.CtrlClick" },
|
|
{ heading: "CONTROLS.BasicMeasureFollow", reference: "CONTROLS.Spacebar" }
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "target",
|
|
title: "CONTROLS.TargetSelect",
|
|
icon: "fa-solid fa-bullseye",
|
|
toolclip: {
|
|
src: "toolclips/tools/token-target.webm",
|
|
heading: "CONTROLS.TargetSelect",
|
|
items: [
|
|
{ paragraph: "CONTROLS.TargetSelectP" },
|
|
...buildItems("selectAlt", "selectMultiple")
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "ruler",
|
|
title: "CONTROLS.BasicMeasure",
|
|
icon: "fa-solid fa-ruler",
|
|
toolclip: {
|
|
src: "toolclips/tools/token-measure.webm",
|
|
heading: "CONTROLS.BasicMeasure",
|
|
items: [
|
|
{ heading: "CONTROLS.BasicMeasureStart", reference: "CONTROLS.ClickDrag" },
|
|
{ heading: "CONTROLS.BasicMeasureWaypoints", reference: "CONTROLS.CtrlClick" },
|
|
{ heading: "CONTROLS.BasicMeasureFollow", reference: "CONTROLS.Spacebar" }
|
|
]
|
|
}
|
|
}
|
|
],
|
|
activeTool: "select"
|
|
});
|
|
|
|
// Measurement Layer Tools
|
|
controls.push({
|
|
name: "measure",
|
|
title: "CONTROLS.GroupMeasure",
|
|
layer: "templates",
|
|
icon: "fa-solid fa-ruler-combined",
|
|
visible: game.user.can("TEMPLATE_CREATE"),
|
|
tools: [
|
|
{
|
|
name: "circle",
|
|
title: "CONTROLS.MeasureCircle",
|
|
icon: "fa-regular fa-circle",
|
|
toolclip: {
|
|
src: "toolclips/tools/measure-circle.webm",
|
|
heading: "CONTROLS.MeasureCircle",
|
|
items: buildItems("create", "move", "edit", "hide", "delete")
|
|
}
|
|
},
|
|
{
|
|
name: "cone",
|
|
title: "CONTROLS.MeasureCone",
|
|
icon: "fa-solid fa-angle-left",
|
|
toolclip: {
|
|
src: "toolclips/tools/measure-cone.webm",
|
|
heading: "CONTROLS.MeasureCone",
|
|
items: buildItems("create", "move", "edit", "hide", "delete", "rotate")
|
|
}
|
|
},
|
|
{
|
|
name: "rect",
|
|
title: "CONTROLS.MeasureRect",
|
|
icon: "fa-regular fa-square",
|
|
toolclip: {
|
|
src: "toolclips/tools/measure-rect.webm",
|
|
heading: "CONTROLS.MeasureRect",
|
|
items: buildItems("create", "move", "edit", "hide", "delete", "rotate")
|
|
}
|
|
},
|
|
{
|
|
name: "ray",
|
|
title: "CONTROLS.MeasureRay",
|
|
icon: "fa-solid fa-arrows-alt-v",
|
|
toolclip: {
|
|
src: "toolclips/tools/measure-ray.webm",
|
|
heading: "CONTROLS.MeasureRay",
|
|
items: buildItems("create", "move", "edit", "hide", "delete", "rotate")
|
|
}
|
|
},
|
|
{
|
|
name: "clear",
|
|
title: "CONTROLS.MeasureClear",
|
|
icon: "fa-solid fa-trash",
|
|
visible: isGM,
|
|
onClick: () => canvas.templates.deleteAll(),
|
|
button: true
|
|
}
|
|
],
|
|
activeTool: "circle"
|
|
});
|
|
|
|
// Tiles Layer
|
|
controls.push({
|
|
name: "tiles",
|
|
title: "CONTROLS.GroupTile",
|
|
layer: "tiles",
|
|
icon: "fa-solid fa-cubes",
|
|
visible: isGM,
|
|
tools: [
|
|
{
|
|
name: "select",
|
|
title: "CONTROLS.TileSelect",
|
|
icon: "fa-solid fa-expand",
|
|
toolclip: {
|
|
src: "toolclips/tools/tile-select.webm",
|
|
heading: "CONTROLS.TileSelect",
|
|
items: buildItems("selectAlt", "selectMultiple", "move", "rotate", "hud", "edit", "delete")
|
|
}
|
|
},
|
|
{
|
|
name: "tile",
|
|
title: "CONTROLS.TilePlace",
|
|
icon: "fa-solid fa-cube",
|
|
toolclip: {
|
|
src: "toolclips/tools/tile-place.webm",
|
|
heading: "CONTROLS.TilePlace",
|
|
items: buildItems("create", "move", "rotate", "hud", "edit", "delete")
|
|
}
|
|
},
|
|
{
|
|
name: "browse",
|
|
title: "CONTROLS.TileBrowser",
|
|
icon: "fa-solid fa-folder",
|
|
button: true,
|
|
onClick: () => {
|
|
new FilePicker({
|
|
type: "imagevideo",
|
|
displayMode: "tiles",
|
|
tileSize: true
|
|
}).render(true);
|
|
},
|
|
toolclip: {
|
|
src: "toolclips/tools/tile-browser.webm",
|
|
heading: "CONTROLS.TileBrowser",
|
|
items: buildItems("place", "move", "rotate", "hud", "edit", "delete")
|
|
}
|
|
},
|
|
{
|
|
name: "foreground",
|
|
title: "CONTROLS.TileForeground",
|
|
icon: "fa-solid fa-home",
|
|
toggle: true,
|
|
active: false,
|
|
onClick: active => {
|
|
this.control.foreground = active;
|
|
for ( const tile of canvas.tiles.placeables ) {
|
|
tile.renderFlags.set({refreshState: true});
|
|
if ( tile.controlled ) tile.release();
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: "snap",
|
|
title: "CONTROLS.CommonForceSnap",
|
|
icon: "fa-solid fa-plus",
|
|
toggle: true,
|
|
active: canvas.forceSnapVertices,
|
|
onClick: toggled => canvas.forceSnapVertices = toggled
|
|
}
|
|
],
|
|
activeTool: "select"
|
|
});
|
|
|
|
// Drawing Tools
|
|
controls.push({
|
|
name: "drawings",
|
|
title: "CONTROLS.GroupDrawing",
|
|
layer: "drawings",
|
|
icon: "fa-solid fa-pencil-alt",
|
|
visible: game.user.can("DRAWING_CREATE"),
|
|
tools: [
|
|
{
|
|
name: "select",
|
|
title: "CONTROLS.DrawingSelect",
|
|
icon: "fa-solid fa-expand",
|
|
toolclip: {
|
|
src: "toolclips/tools/drawing-select.webm",
|
|
heading: "CONTROLS.DrawingSelect",
|
|
items: buildItems("selectAlt", "selectMultiple", "move", "hud", "edit", "delete", "rotate")
|
|
}
|
|
},
|
|
{
|
|
name: "rect",
|
|
title: "CONTROLS.DrawingRect",
|
|
icon: "fa-solid fa-square",
|
|
toolclip: {
|
|
src: "toolclips/tools/drawing-rect.webm",
|
|
heading: "CONTROLS.DrawingRect",
|
|
items: buildItems("draw", "move", "hud", "edit", "delete", "rotate")
|
|
}
|
|
},
|
|
{
|
|
name: "ellipse",
|
|
title: "CONTROLS.DrawingEllipse",
|
|
icon: "fa-solid fa-circle",
|
|
toolclip: {
|
|
src: "toolclips/tools/drawing-ellipse.webm",
|
|
heading: "CONTROLS.DrawingEllipse",
|
|
items: buildItems("draw", "move", "hud", "edit", "delete", "rotate")
|
|
}
|
|
},
|
|
{
|
|
name: "polygon",
|
|
title: "CONTROLS.DrawingPoly",
|
|
icon: "fa-solid fa-draw-polygon",
|
|
toolclip: {
|
|
src: "toolclips/tools/drawing-polygon.webm",
|
|
heading: "CONTROLS.DrawingPoly",
|
|
items: [
|
|
{ heading: "CONTROLS.CommonDraw", content: "CONTROLS.DrawingPolyP" },
|
|
...buildItems("move", "hud", "edit", "delete", "rotate")
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "freehand",
|
|
title: "CONTROLS.DrawingFree",
|
|
icon: "fa-solid fa-signature",
|
|
toolclip: {
|
|
src: "toolclips/tools/drawing-free.webm",
|
|
heading: "CONTROLS.DrawingFree",
|
|
items: buildItems("draw", "move", "hud", "edit", "delete", "rotate")
|
|
}
|
|
},
|
|
{
|
|
name: "text",
|
|
title: "CONTROLS.DrawingText",
|
|
icon: "fa-solid fa-font",
|
|
onClick: () => {
|
|
const controlled = canvas.drawings.controlled;
|
|
if ( controlled.length === 1 ) controlled[0].enableTextEditing();
|
|
},
|
|
toolclip: {
|
|
src: "toolclips/tools/drawing-text.webm",
|
|
heading: "CONTROLS.DrawingText",
|
|
items: buildItems("draw", "move", "hud", "edit", "delete", "rotate")
|
|
}
|
|
},
|
|
{
|
|
name: "role",
|
|
title: "CONTROLS.DrawingRole",
|
|
icon: "fa-solid fa-circle-info",
|
|
toggle: true,
|
|
active: false
|
|
},
|
|
{
|
|
name: "snap",
|
|
title: "CONTROLS.CommonForceSnap",
|
|
icon: "fa-solid fa-plus",
|
|
toggle: true,
|
|
active: canvas.forceSnapVertices,
|
|
onClick: toggled => canvas.forceSnapVertices = toggled
|
|
},
|
|
{
|
|
name: "configure",
|
|
title: "CONTROLS.DrawingConfig",
|
|
icon: "fa-solid fa-cog",
|
|
onClick: () => canvas.drawings.configureDefault(),
|
|
button: true
|
|
},
|
|
{
|
|
name: "clear",
|
|
title: "CONTROLS.DrawingClear",
|
|
icon: "fa-solid fa-trash",
|
|
visible: isGM,
|
|
onClick: () => canvas.drawings.deleteAll(),
|
|
button: true
|
|
}
|
|
],
|
|
activeTool: "select"
|
|
});
|
|
|
|
// Walls Layer Tools
|
|
controls.push({
|
|
name: "walls",
|
|
title: "CONTROLS.GroupWall",
|
|
layer: "walls",
|
|
icon: "fa-solid fa-block-brick",
|
|
visible: isGM,
|
|
tools: [
|
|
{
|
|
name: "select",
|
|
title: "CONTROLS.WallSelect",
|
|
icon: "fa-solid fa-expand",
|
|
toolclip: {
|
|
src: "toolclips/tools/wall-select.webm",
|
|
heading: "CONTROLS.WallSelect",
|
|
items: [
|
|
...buildItems("selectAlt", "selectMultiple", "move"),
|
|
{ heading: "CONTROLS.CommonMoveWithoutSnapping", reference: "CONTROLS.ShiftDrag" },
|
|
{ heading: "CONTROLS.CommonEdit", content: "CONTROLS.WallSelectEdit" },
|
|
...buildItems("delete")
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "walls",
|
|
title: "CONTROLS.WallDraw",
|
|
icon: "fa-solid fa-bars",
|
|
toolclip: {
|
|
src: "toolclips/tools/wall-basic.webm",
|
|
heading: "CONTROLS.WallBasic",
|
|
items: [
|
|
{ heading: "CONTROLS.CommonBlocks", content: "CONTROLS.WallBasicBlocks" },
|
|
...buildItems("place", "chain", "movePoint", "edit", "delete")
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "terrain",
|
|
title: "CONTROLS.WallTerrain",
|
|
icon: "fa-solid fa-mountain",
|
|
toolclip: {
|
|
src: "toolclips/tools/wall-terrain.webm",
|
|
heading: "CONTROLS.WallTerrain",
|
|
items: [
|
|
{ heading: "CONTROLS.CommonBlocks", content: "CONTROLS.WallTerrainBlocks" },
|
|
...buildItems("place", "chain", "movePoint", "edit", "delete")
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "invisible",
|
|
title: "CONTROLS.WallInvisible",
|
|
icon: "fa-solid fa-eye-slash",
|
|
toolclip: {
|
|
src: "toolclips/tools/wall-invisible.webm",
|
|
heading: "CONTROLS.WallInvisible",
|
|
items: [
|
|
{ heading: "CONTROLS.CommonBlocks", content: "CONTROLS.WallInvisibleBlocks" },
|
|
...buildItems("place", "chain", "movePoint", "edit", "delete")
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "ethereal",
|
|
title: "CONTROLS.WallEthereal",
|
|
icon: "fa-solid fa-mask",
|
|
toolclip: {
|
|
src: "toolclips/tools/wall-ethereal.webm",
|
|
heading: "CONTROLS.WallEthereal",
|
|
items: [
|
|
{ heading: "CONTROLS.CommonBlocks", content: "CONTROLS.WallEtherealBlocks" },
|
|
...buildItems("place", "chain", "movePoint", "edit", "delete")
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "doors",
|
|
title: "CONTROLS.WallDoors",
|
|
icon: "fa-solid fa-door-open",
|
|
toolclip: {
|
|
src: "toolclips/tools/wall-door.webm",
|
|
heading: "CONTROLS.WallDoors",
|
|
items: [
|
|
{ heading: "CONTROLS.CommonBlocks", content: "CONTROLS.DoorBlocks" },
|
|
...buildItems("openClose", "openCloseSilently", "lock", "lockSilently", "place", "chain", "movePoint", "edit")
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "secret",
|
|
title: "CONTROLS.WallSecret",
|
|
icon: "fa-solid fa-user-secret",
|
|
toolclip: {
|
|
src: "toolclips/tools/wall-secret-door.webm",
|
|
heading: "CONTROLS.WallSecret",
|
|
items: [
|
|
{ heading: "CONTROLS.WallSecretHidden", content: "CONTROLS.WallSecretHiddenP" },
|
|
{ heading: "CONTROLS.CommonBlocks", content: "CONTROLS.DoorBlocks" },
|
|
...buildItems("openClose", "openCloseSilently", "lock", "lockSilently", "place", "chain", "movePoint", "edit")
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "window",
|
|
title: "CONTROLS.WallWindow",
|
|
icon: "fa-solid fa-window-frame",
|
|
toolclip: {
|
|
src: "toolclips/tools/wall-window.webm",
|
|
heading: "CONTROLS.WallWindow",
|
|
items: [
|
|
{ heading: "CONTROLS.CommonBlocks", content: "CONTROLS.WallWindowBlocks" },
|
|
...buildItems("place", "chain", "movePoint", "edit", "delete")
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "clone",
|
|
title: "CONTROLS.WallClone",
|
|
icon: "fa-regular fa-clone"
|
|
},
|
|
{
|
|
name: "snap",
|
|
title: "CONTROLS.CommonForceSnap",
|
|
icon: "fa-solid fa-plus",
|
|
toggle: true,
|
|
active: canvas.forceSnapVertices,
|
|
onClick: toggled => canvas.forceSnapVertices = toggled,
|
|
toolclip: {
|
|
src: "toolclips/tools/wall-snap.webm",
|
|
heading: "CONTROLS.CommonForceSnap",
|
|
items: [{ paragraph: "CONTROLS.WallSnapP" }]
|
|
}
|
|
},
|
|
{
|
|
name: "close-doors",
|
|
title: "CONTROLS.WallCloseDoors",
|
|
icon: "fa-regular fa-door-closed",
|
|
onClick: () => {
|
|
let updates = canvas.walls.placeables.reduce((arr, w) => {
|
|
if ( w.isDoor && (w.document.ds === CONST.WALL_DOOR_STATES.OPEN) ) {
|
|
arr.push({_id: w.id, ds: CONST.WALL_DOOR_STATES.CLOSED});
|
|
}
|
|
return arr;
|
|
}, []);
|
|
if ( !updates.length ) return;
|
|
canvas.scene.updateEmbeddedDocuments("Wall", updates, {sound: false});
|
|
ui.notifications.info(game.i18n.format("CONTROLS.WallDoorsClosed", {number: updates.length}));
|
|
}
|
|
},
|
|
{
|
|
name: "clear",
|
|
title: "CONTROLS.WallClear",
|
|
icon: "fa-solid fa-trash",
|
|
onClick: () => canvas.walls.deleteAll(),
|
|
button: true
|
|
}
|
|
],
|
|
activeTool: "walls"
|
|
});
|
|
|
|
// Lighting Layer Tools
|
|
controls.push({
|
|
name: "lighting",
|
|
title: "CONTROLS.GroupLighting",
|
|
layer: "lighting",
|
|
icon: "fa-regular fa-lightbulb",
|
|
visible: isGM,
|
|
tools: [
|
|
{
|
|
name: "light",
|
|
title: "CONTROLS.LightDraw",
|
|
icon: "fa-solid fa-lightbulb",
|
|
toolclip: {
|
|
src: "toolclips/tools/light-draw.webm",
|
|
heading: "CONTROLS.LightDraw",
|
|
items: buildItems("create", "edit", "rotate", "onOff")
|
|
}
|
|
},
|
|
{
|
|
name: "day",
|
|
title: "CONTROLS.LightDay",
|
|
icon: "fa-solid fa-sun",
|
|
visible: !canvas.scene?.environment.darknessLock,
|
|
onClick: () => canvas.scene.update(
|
|
{environment: {darknessLevel: 0.0}},
|
|
{animateDarkness: CONFIG.Canvas.darknessToDaylightAnimationMS}
|
|
),
|
|
button: true,
|
|
toolclip: {
|
|
src: "toolclips/tools/light-day.webm",
|
|
heading: "CONTROLS.LightDay",
|
|
items: [
|
|
{heading: "CONTROLS.MakeDayH", content: "CONTROLS.MakeDayP"},
|
|
{heading: "CONTROLS.AutoLightToggleH", content: "CONTROLS.AutoLightToggleP"}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "night",
|
|
title: "CONTROLS.LightNight",
|
|
icon: "fa-solid fa-moon",
|
|
visible: !canvas.scene?.environment.darknessLock,
|
|
onClick: () => canvas.scene.update(
|
|
{environment: {darknessLevel: 1.0}},
|
|
{animateDarkness: CONFIG.Canvas.daylightToDarknessAnimationMS}
|
|
),
|
|
button: true,
|
|
toolclip: {
|
|
src: "toolclips/tools/light-night.webm",
|
|
heading: "CONTROLS.LightNight",
|
|
items: [
|
|
{heading: "CONTROLS.MakeNightH", content: "CONTROLS.MakeNightP"},
|
|
{heading: "CONTROLS.AutoLightToggleH", content: "CONTROLS.AutoLightToggleP"}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "reset",
|
|
title: "CONTROLS.LightReset",
|
|
icon: "fa-solid fa-cloud",
|
|
onClick: () => {
|
|
new Dialog({
|
|
title: game.i18n.localize("CONTROLS.FOWResetTitle"),
|
|
content: `<p>${game.i18n.localize("CONTROLS.FOWResetDesc")}</p>`,
|
|
buttons: {
|
|
yes: {
|
|
icon: '<i class="fa-solid fa-check"></i>',
|
|
label: "Yes",
|
|
callback: () => canvas.fog.reset()
|
|
},
|
|
no: {
|
|
icon: '<i class="fa-solid fa-times"></i>',
|
|
label: "No"
|
|
}
|
|
}
|
|
}).render(true);
|
|
},
|
|
button: true,
|
|
toolclip: {
|
|
src: "toolclips/tools/light-reset.webm",
|
|
heading: "CONTROLS.LightReset",
|
|
items: [{ paragraph: "CONTROLS.LightResetP" }]
|
|
}
|
|
},
|
|
{
|
|
name: "clear",
|
|
title: "CONTROLS.LightClear",
|
|
icon: "fa-solid fa-trash",
|
|
onClick: () => canvas.lighting.deleteAll(),
|
|
button: true
|
|
}
|
|
],
|
|
activeTool: "light"
|
|
});
|
|
|
|
// Sounds Layer Tools
|
|
controls.push({
|
|
name: "sounds",
|
|
title: "CONTROLS.GroupSound",
|
|
layer: "sounds",
|
|
icon: "fa-solid fa-music",
|
|
visible: isGM,
|
|
tools: [
|
|
{
|
|
name: "sound",
|
|
title: "CONTROLS.SoundDraw",
|
|
icon: "fa-solid fa-volume-up",
|
|
toolclip: {
|
|
src: "toolclips/tools/sound-draw.webm",
|
|
heading: "CONTROLS.SoundDraw",
|
|
items: buildItems("create", "edit", "rotate", "onOff")
|
|
}
|
|
},
|
|
{
|
|
name: "preview",
|
|
title: `CONTROLS.SoundPreview${clip}`,
|
|
icon: "fa-solid fa-headphones",
|
|
toggle: true,
|
|
active: canvas.sounds?.livePreview ?? false,
|
|
onClick: toggled => {
|
|
canvas.sounds.livePreview = toggled;
|
|
canvas.sounds.refresh();
|
|
},
|
|
toolclip: {
|
|
src: "toolclips/tools/sound-preview.webm",
|
|
heading: "CONTROLS.SoundPreview",
|
|
items: [{ paragraph: "CONTROLS.SoundPreviewP" }]
|
|
}
|
|
},
|
|
{
|
|
name: "clear",
|
|
title: "CONTROLS.SoundClear",
|
|
icon: "fa-solid fa-trash",
|
|
onClick: () => canvas.sounds.deleteAll(),
|
|
button: true
|
|
}
|
|
],
|
|
activeTool: "sound"
|
|
});
|
|
|
|
// Regions Layer Tools
|
|
controls.push({
|
|
name: "regions",
|
|
title: "CONTROLS.GroupRegion",
|
|
layer: "regions",
|
|
icon: "fa-regular fa-game-board",
|
|
visible: game.user.isGM,
|
|
tools: [
|
|
{
|
|
name: "select",
|
|
title: "CONTROLS.RegionSelect",
|
|
icon: "fa-solid fa-expand",
|
|
toolclip: {
|
|
src: "toolclips/tools/region-select.webm",
|
|
heading: "CONTROLS.RegionSelect",
|
|
items: [
|
|
{ paragraph: "CONTROLS.RegionSelectP" },
|
|
...buildItems("selectAlt", "selectMultiple", "edit", "delete")
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "rectangle",
|
|
title: "CONTROLS.RegionRectangle",
|
|
icon: "fa-solid fa-square",
|
|
toolclip: {
|
|
src: "toolclips/tools/region-rectangle.webm",
|
|
heading: "CONTROLS.RegionRectangle",
|
|
items: [
|
|
{ paragraph: "CONTROLS.RegionShape" },
|
|
...buildItems("draw", "drawProportionally"),
|
|
{ paragraph: "CONTROLS.RegionPerformance" },
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "ellipse",
|
|
title: "CONTROLS.RegionEllipse",
|
|
icon: "fa-solid fa-circle",
|
|
toolclip: {
|
|
src: "toolclips/tools/region-ellipse.webm",
|
|
heading: "CONTROLS.RegionEllipse",
|
|
items: [
|
|
{ paragraph: "CONTROLS.RegionShape" },
|
|
...buildItems("draw", "drawProportionally"),
|
|
{ paragraph: "CONTROLS.RegionPerformance" },
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "polygon",
|
|
title: "CONTROLS.RegionPolygon",
|
|
icon: "fa-solid fa-draw-polygon",
|
|
toolclip: {
|
|
src: "toolclips/tools/region-polygon.webm",
|
|
heading: "CONTROLS.RegionPolygon",
|
|
items: [
|
|
{ paragraph: "CONTROLS.RegionShape" },
|
|
...buildItems("draw", "drawProportionally"),
|
|
{ paragraph: "CONTROLS.RegionPerformance" },
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "hole",
|
|
title: "CONTROLS.RegionHole",
|
|
icon: "fa-duotone fa-object-subtract",
|
|
toggle: true,
|
|
active: canvas.regions?._holeMode ?? false,
|
|
onClick: toggled => canvas.regions._holeMode = toggled,
|
|
toolclip: {
|
|
src: "toolclips/tools/region-hole.webm",
|
|
heading: "CONTROLS.RegionHole",
|
|
items: [
|
|
{ paragraph: "CONTROLS.RegionHoleP" },
|
|
...buildItems("draw", "drawProportionally")
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "snap",
|
|
title: "CONTROLS.CommonForceSnap",
|
|
icon: "fa-solid fa-plus",
|
|
toggle: true,
|
|
active: canvas.forceSnapVertices,
|
|
onClick: toggled => canvas.forceSnapVertices = toggled,
|
|
toolclip: {
|
|
src: "toolclips/tools/region-snap.webm",
|
|
heading: "CONTROLS.CommonForceSnap",
|
|
items: [
|
|
{ paragraph: "CONTROLS.RegionSnap" },
|
|
...buildItems("draw", "drawProportionally")
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "clear",
|
|
title: "CONTROLS.RegionClear",
|
|
icon: "fa-solid fa-trash",
|
|
onClick: () => canvas.regions.deleteAll(),
|
|
button: true
|
|
}
|
|
],
|
|
activeTool: "select"
|
|
});
|
|
|
|
// Notes Layer Tools
|
|
controls.push({
|
|
name: "notes",
|
|
title: "CONTROLS.GroupNotes",
|
|
layer: "notes",
|
|
icon: "fa-solid fa-bookmark",
|
|
tools: [
|
|
{
|
|
name: "select",
|
|
title: "CONTROLS.NoteSelect",
|
|
icon: "fa-solid fa-expand"
|
|
},
|
|
{
|
|
name: "journal",
|
|
title: "NOTE.Create",
|
|
visible: game.user.hasPermission("NOTE_CREATE"),
|
|
icon: CONFIG.JournalEntry.sidebarIcon
|
|
},
|
|
{
|
|
name: "toggle",
|
|
title: "CONTROLS.NoteToggle",
|
|
icon: "fa-solid fa-map-pin",
|
|
toggle: true,
|
|
active: game.settings.get("core", NotesLayer.TOGGLE_SETTING),
|
|
onClick: toggled => game.settings.set("core", NotesLayer.TOGGLE_SETTING, toggled)
|
|
},
|
|
{
|
|
name: "clear",
|
|
title: "CONTROLS.NoteClear",
|
|
icon: "fa-solid fa-trash",
|
|
visible: isGM,
|
|
onClick: () => canvas.notes.deleteAll(),
|
|
button: true
|
|
}
|
|
],
|
|
activeTool: "select"
|
|
});
|
|
|
|
// Pass the Scene Controls to a hook function to allow overrides or changes
|
|
Hooks.callAll("getSceneControlButtons", controls);
|
|
return controls;
|
|
}
|
|
}
|