UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

575 lines (574 loc) 16.6 kB
"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(); // } }