@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
575 lines (574 loc) • 16.6 kB
JavaScript
"use strict";
import { ParamType } from "../../poly/ParamType";
import { ParamEvent } from "../../poly/ParamEvent";
import { CoreGraphNode } from "../../../core/graph/CoreGraphNode";
import { ColorConversion } from "../../../core/Color";
import { isString, isArray, isFunction } from "../../../core/Type";
import { arrayDifference, arrayCompact, arrayUniq } from "../../../core/ArrayUtils";
import { objectCloneDeep, objectIsEqual } from "../../../core/ObjectUtils";
const CALLBACK_OPTION = "callback";
const CALLBACK_STRING_OPTION = "callbackString";
const COMPUTE_ON_DIRTY = "computeOnDirty";
const COOK_OPTION = "cook";
const FILE_BROWSE_OPTION = "fileBrowse";
const FILE_BROWSE_EXTENSIONS = "extensions";
const EXPRESSION = "expression";
const FOR_ENTITIES = "forEntities";
const LABEL = "label";
const HIDE_LABEL = "hideLabel";
const LEVEL = "level";
const MENU = "menu";
const MENU_STRING = "menuString";
const ENTRIES = "entries";
const MULTILINE_OPTION = "multiline";
const LANGUAGE_OPTION = "language";
const NODE_SELECTION = "nodeSelection";
const NODE_SELECTION_CONTEXT = "context";
const NODE_SELECTION_TYPES = "types";
const OBJECT_MASK = "objectMask";
const OBJECT_MASK_INPUT_INDEX = "inputIndex";
const DEPENDENT_ON_FOUND_NODE = "dependentOnFoundNode";
const DEPENDENT_ON_FOUND_PARAM = "dependentOnFoundParam";
const RANGE_OPTION = "range";
const RANGE_LOCKED_OPTION = "rangeLocked";
const STEP_OPTION = "step";
const SPARE_OPTION = "spare";
const TEXTURE_OPTION = "texture";
const ENV_OPTION = "env";
const HIDDEN_OPTION = "hidden";
const FIELD_OPTION = "field";
const VISIBLE_IF_OPTION = "visibleIf";
const COLOR_CONVERSION = "conversion";
const SEPARATOR_BEFORE_OPTION = "separatorBefore";
const SEPARATOR_AFTER_OPTION = "separatorAfter";
const JOIN_TO_PREVIOUS_PARAM = "joinToPreviousParam";
const AS_QUATERNION = "asQuaternion";
export const PARAM_OPTION_NAMES = {
CALLBACK_OPTION
};
const EDITABLE = "editable";
export var StringParamLanguage = /* @__PURE__ */ ((StringParamLanguage2) => {
StringParamLanguage2["CSS"] = "css";
StringParamLanguage2["GLSL"] = "glsl";
StringParamLanguage2["HTML"] = "html";
StringParamLanguage2["TYPESCRIPT"] = "typescript";
StringParamLanguage2["JSON"] = "json";
return StringParamLanguage2;
})(StringParamLanguage || {});
export var FileType = /* @__PURE__ */ ((FileType2) => {
FileType2["AUDIO"] = "audio";
FileType2["TEXTURE_IMAGE"] = "texture_image";
FileType2["TEXTURE_VIDEO"] = "texture_video";
FileType2["GEOMETRY"] = "geometry";
FileType2["FONT"] = "font";
FileType2["SVG"] = "svg";
FileType2["JSON"] = "json";
return FileType2;
})(FileType || {});
const NON_OVERRIDABLE_OPTIONS = [EDITABLE];
export class OptionsController {
constructor(_param) {
this._param = _param;
this._programaticVisibleState = true;
this._callbackAllowed = false;
this._updateVisibilityAndRemoveDirtyBound = this.updateVisibilityAndRemoveDirty.bind(this);
this._ui_data_dependency_set = false;
}
dispose() {
var _a;
try {
this._options[CALLBACK_OPTION] = void 0;
this._options[CALLBACK_STRING_OPTION] = void 0;
} catch (err) {
}
(_a = this._visibility_graph_node) == null ? void 0 : _a.dispose();
}
set(options) {
if (isString(options)) {
console.warn("options input invalid", options, typeof options);
}
this._default_options = options;
this._options = objectCloneDeep(this._default_options);
this.postSetOptions();
}
copy(options_controller) {
this._default_options = objectCloneDeep(options_controller.default());
this._options = objectCloneDeep(options_controller.current());
this.postSetOptions();
}
setOption(optionName, value) {
if (!this._validateOption(optionName, value)) {
return;
}
this._options[optionName] = value;
if (this._param.components) {
for (const component of this._param.components) {
component.options.setOption(optionName, value);
}
}
}
_validateOption(optionName, value) {
if (optionName == CALLBACK_OPTION) {
return isFunction(value);
}
return true;
}
postSetOptions() {
this._handleComputeOnDirty();
}
param() {
return this._param;
}
node() {
return this._param.node;
}
default() {
return this._default_options;
}
current() {
return this._options;
}
// utils
hasOptionsOverridden() {
return !objectIsEqual(this._options, this._default_options);
}
overriddenOptions() {
const overriden = {};
const optionNames = Object.keys(this._options);
const optionNamesToCheck = [];
arrayDifference(optionNames, NON_OVERRIDABLE_OPTIONS, optionNamesToCheck);
for (const optionName of optionNamesToCheck) {
if (!objectIsEqual(this._options[optionName], this._default_options[optionName])) {
const cloned_option = objectCloneDeep(this._options[optionName]);
Object.assign(overriden, { [optionName]: cloned_option });
}
}
return overriden;
}
overriddenOptionNames() {
return Object.keys(this.overriddenOptions());
}
// compute on dirty
computeOnDirty() {
return this._options[COMPUTE_ON_DIRTY] || false;
}
_handleComputeOnDirty() {
if (this.computeOnDirty()) {
if (!this._computeOnDirtyCallbackAdded) {
this.param().addPostDirtyHook("computeOnDirty", this._computeParam.bind(this));
this._computeOnDirtyCallbackAdded = true;
}
}
}
async _computeParam() {
await this.param().compute();
}
// callback
hasCallback() {
return this._options[CALLBACK_OPTION] != null || this._options[CALLBACK_STRING_OPTION] != null;
}
allowCallback() {
this._callbackAllowed = true;
}
async executeCallback() {
if (!this._callbackAllowed) {
return;
}
const node = this.node();
if (!node) {
return;
}
const scene = node.scene();
if (!scene) {
return;
}
const callback = this.getCallback(node, scene);
if (!callback) {
return;
}
if (!scene.loadingController.loaded()) {
return;
}
const parentParam = this.param().parentParam();
if (parentParam) {
parentParam.options.executeCallback();
} else {
await callback(node, this.param());
}
}
getCallback(node, scene) {
if (this.hasCallback()) {
return this._options[CALLBACK_OPTION] = this._options[CALLBACK_OPTION] || this.createCallbackFromString(node, scene);
}
}
createCallbackFromString(node, scene) {
const callbackString = this._options[CALLBACK_STRING_OPTION];
if (callbackString) {
const callbackFunction = new Function("node", "scene", "window", "location", callbackString);
return () => {
callbackFunction(node, scene, null, null);
};
}
}
// color
colorConversion() {
return this._options[COLOR_CONVERSION] || ColorConversion.NONE;
}
// cook
makesNodeDirtyWhenDirty() {
if (this.param().parentParam() != null) {
return false;
}
const cookOptions = this._options[COOK_OPTION];
if (cookOptions != null) {
return cookOptions;
}
return true;
}
// desktop
fileBrowseOption() {
return this._options[FILE_BROWSE_OPTION];
}
fileBrowseAllowed() {
return this.fileBrowseOption() != null;
}
fileBrowseExtensions() {
const option = this.fileBrowseOption();
if (option) {
return option[FILE_BROWSE_EXTENSIONS];
} else {
return null;
}
}
// separator
separatorBefore() {
return this._options[SEPARATOR_BEFORE_OPTION];
}
separatorAfter() {
return this._options[SEPARATOR_AFTER_OPTION];
}
joinToPreviousParam() {
return this._options[JOIN_TO_PREVIOUS_PARAM];
}
// editable
setEditableState(state) {
const currentState = this._options[EDITABLE];
const param = this.param();
if (currentState != state) {
this._options[EDITABLE] = state;
param.emit(ParamEvent.EDITABLE_UPDATED);
}
if (param.components) {
for (const component of param.components) {
component.options.setEditableState(state);
}
}
}
editable() {
const state = this._options[EDITABLE];
if (state != null) {
return state;
}
return true;
}
// expression
// get displays_expression_only() {
// return this._options[EXPRESSION_ONLY_OPTION] === true;
// }
isExpressionForEntities() {
const expr_option = this._options[EXPRESSION];
if (expr_option) {
return expr_option[FOR_ENTITIES] || false;
}
return false;
}
// folder
level() {
return this._options[LEVEL] || 0;
}
// menu
hasMenu() {
return this.menuOptions() != null || this.menuStringOptions() != null;
}
menuOptions() {
return this._options[MENU];
}
menuStringOptions() {
return this._options[MENU_STRING];
}
menuEntries() {
const options = this.menuOptions() || this.menuStringOptions();
if (options) {
return options[ENTRIES];
} else {
return [];
}
}
ensureValueInMenuEntries(value) {
const options = this.menuOptions();
if (!options) {
return value;
}
const entries = options[ENTRIES];
if (entries.length == 0) {
return value;
}
for (const entry of entries) {
if (value == entry.value) {
return value;
}
}
return entries[0].value;
}
// multiline
isMultiline() {
return this._options[MULTILINE_OPTION] === true;
}
language() {
return this._options[LANGUAGE_OPTION];
}
isCode() {
return this.language() != null;
}
// node selection
nodeSelectionOptions() {
return this._options[NODE_SELECTION];
}
nodeSelectionContext() {
const options = this.nodeSelectionOptions();
if (options) {
return options[NODE_SELECTION_CONTEXT];
}
}
nodeSelectionTypes() {
const options = this.nodeSelectionOptions();
if (options) {
return options[NODE_SELECTION_TYPES];
}
}
displayObjectMaskSelection() {
const value = this._options[OBJECT_MASK];
return value != null && value != false;
}
objectMaskInputIndex() {
const value = this._options[OBJECT_MASK];
const input = value != null ? value[OBJECT_MASK_INPUT_INDEX] : 0;
return input || 0;
}
objectMaskFromInputOnly() {
const value = this._options[OBJECT_MASK];
const fromInputOnly = value.fromInputOnly;
return fromInputOnly != false;
}
dependentOnFoundNode() {
if (DEPENDENT_ON_FOUND_NODE in this._options) {
return this._options[DEPENDENT_ON_FOUND_NODE];
} else {
return true;
}
}
dependentOnFoundParam() {
if (DEPENDENT_ON_FOUND_PARAM in this._options) {
return this._options[DEPENDENT_ON_FOUND_PARAM];
} else {
return true;
}
}
// param selection
isSelectingParam() {
return this.param().type() == ParamType.PARAM_PATH;
}
// paramSelectionOptions() {
// return this._options[PARAM_SELECTION];
// }
// paramSelectionType() {
// const options = this.paramSelectionOptions();
// if (options) {
// const type_or_boolean = options;
// if (!isBoolean(type_or_boolean)) {
// return type_or_boolean;
// }
// }
// }
// range
range() {
return this._options[RANGE_OPTION] || [0, 1];
}
step() {
return this._options[STEP_OPTION];
}
asQuaternion() {
return this._options[AS_QUATERNION] == true;
}
rangeLocked() {
return this._options[RANGE_LOCKED_OPTION] || [false, false];
}
ensureInRange(value) {
const range = this.range();
if (value >= range[0] && value <= range[1]) {
return value;
} else {
if (value < range[0]) {
return this.rangeLocked()[0] === true ? range[0] : value;
} else {
return this.rangeLocked()[1] === true ? range[1] : value;
}
}
}
// spare
isSpare() {
return this._options[SPARE_OPTION] || false;
}
// texture
textureOptions() {
return this._options[TEXTURE_OPTION];
}
textureAsEnv() {
const texture_options = this.textureOptions();
if (texture_options != null) {
return texture_options[ENV_OPTION] === true;
}
return false;
}
// visible
isHidden() {
return this._options[HIDDEN_OPTION] === true || this._programaticVisibleState === false;
}
isVisible() {
return !this.isHidden();
}
setVisibleState(state) {
this._options[HIDDEN_OPTION] = !state;
this.param().emit(ParamEvent.VISIBLE_UPDATED);
}
// label
label() {
return this._options[LABEL];
}
isLabelHidden() {
const type = this.param().type();
switch (type) {
case ParamType.BUTTON: {
return true;
}
case ParamType.BOOLEAN: {
return this.isFieldHidden();
}
case ParamType.RAMP: {
return this._options[HIDE_LABEL] || false;
}
case ParamType.STRING: {
return this.isCode() && this._options[HIDE_LABEL] || false;
}
}
return false;
}
isFieldHidden() {
return this._options[FIELD_OPTION] === false;
}
// programatic visibility
uiDataDependsOnOtherParams() {
return VISIBLE_IF_OPTION in this._options;
}
visibilityPredecessors() {
const visibilityOptions = this._options[VISIBLE_IF_OPTION];
if (!visibilityOptions) {
return [];
}
let predecessorNames = [];
if (isArray(visibilityOptions)) {
arrayUniq(visibilityOptions.map((options) => Object.keys(options)).flat(), predecessorNames);
} else {
predecessorNames = Object.keys(visibilityOptions);
}
const node = this.param().node;
const params = [];
arrayCompact(
predecessorNames.map((name) => {
const param = node.params.get(name);
if (param) {
return param;
} else {
console.error(
`param ${name} not found as visibility condition for ${this.param().name()} in node ${this.param().node.type()}`
);
}
}),
params
);
return params;
}
setUiDataDependency() {
if (this._ui_data_dependency_set) {
return;
}
this._ui_data_dependency_set = true;
const predecessors = this.visibilityPredecessors();
if (predecessors.length > 0) {
this._visibility_graph_node = new CoreGraphNode(this.param().scene(), "param_visibility");
for (const predecessor of predecessors) {
this._visibility_graph_node.addGraphInput(predecessor);
}
this._visibility_graph_node.addPostDirtyHook(
"_update_visibility_and_remove_dirty",
this._updateVisibilityAndRemoveDirtyBound
);
}
}
updateVisibilityAndRemoveDirty() {
this.updateVisibility();
this.param().removeDirtyState();
}
async updateVisibility() {
const options = this._options[VISIBLE_IF_OPTION];
if (options) {
const node = this.param().node;
const params = this.visibilityPredecessors();
const promises = params.map((p) => {
if (p.isDirty()) {
return p.compute();
}
});
this._programaticVisibleState = false;
await Promise.all(promises);
if (isArray(options)) {
for (const optionsSet of options) {
const optionSetParamNames = Object.keys(optionsSet);
const optionSetParams = [];
arrayCompact(
optionSetParamNames.map((paramName) => node.params.get(paramName)),
optionSetParams
);
const satisfiedValues = optionSetParams.filter((param) => param.value == optionsSet[param.name()]);
if (satisfiedValues.length == optionSetParams.length) {
this._programaticVisibleState = true;
}
}
} else {
const satisfiedValues = params.filter((param) => param.value == options[param.name()]);
this._programaticVisibleState = satisfiedValues.length == params.length;
}
this.param().emit(ParamEvent.VISIBLE_UPDATED);
}
}
/*
*
*
*
*/
// private _callbacksByOptionName: Map<keyof ParamOptions, OptionChangeCallback> | undefined;
// onOptionChange<K extends keyof ParamOptions>(optionName: K, callback: OptionChangeCallback) {
// this._callbacksByOptionName = this._callbacksByOptionName || new Map();
// this._callbacksByOptionName.set(optionName, callback);
// }
// private _runOptionCallback(optionName: keyof ParamOptions) {
// if (!this._callbacksByOptionName) {
// return;
// }
// const callback = this._callbacksByOptionName.get(optionName);
// if (!callback) {
// return;
// }
// callback();
// }
}