UNPKG

infamous

Version:

A CSS3D/WebGL UI library.

352 lines (282 loc) 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _lowclass = _interopRequireDefault(require("lowclass")); var _Mixin = _interopRequireDefault(require("./Mixin")); var _Observable = _interopRequireDefault(require("./Observable")); var _TreeNode = _interopRequireDefault(require("./TreeNode")); var _XYZSizeModeValues = _interopRequireDefault(require("./XYZSizeModeValues")); var _XYZNonNegativeValues = _interopRequireDefault(require("./XYZNonNegativeValues")); var _Motor = _interopRequireDefault(require("./Motor")); var _props = require("./props"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } // working variables (used synchronously only, to avoid making new variables in // repeatedly-called methods) let propFunction = null; var _default = (0, _Mixin.default)(Base => { const Parent = _Observable.default.mixin(_TreeNode.default.mixin(Base)); // Sizeable extends TreeNode because Sizeable knows about its `parent` when // calculating proportional sizes. Also Transformable knows about it's parent // in order to calculate it's world matrix based on it's parent's. const Sizeable = (0, _lowclass.default)('Sizeable').extends(Parent, ({ Super }) => ({ static: { props: _objectSpread({}, Parent.props ? Parent.props : {}, { size: _props.props.XYZNonNegativeValues, // FIXME the whole app breaks on a negative value. Handle the error. sizeMode: _props.props.XYZSizeModeValues }) }, constructor(options = {}) { const self = Super(this).constructor(options); self._propertyFunctions = null; self._calculatedSize = { x: 0, y: 0, z: 0 // self._props = {} // SkateJS now creates this (because TreeNode extends from SkateJS withUpdate). }; self._properties = self._props; // alias to the SkateJS _props cache, so that other code referring to this._properties is unchanged self._setDefaultProperties(); self._setPropertyObservers(); self.properties = options; return self; }, _setDefaultProperties() { Object.assign(this._properties, { sizeMode: new _XYZSizeModeValues.default('literal', 'literal', 'literal'), size: new _XYZNonNegativeValues.default(100, 100, 100) }); }, // TODO change all event values to objects. See here for reasoning: // https://github.com/airbnb/javascript#events _setPropertyObservers() { this._properties.sizeMode.on('valuechanged', () => this.trigger('propertychange', 'sizeMode')); this._properties.size.on('valuechanged', () => this.trigger('propertychange', 'size')); }, updated(oldProps, oldState, modifiedProps) { if (!this.isConnected) return; // this covers single-valued properties like opacity, but has the // sideeffect of trigger propertychange more than needed for // XYZValues (here, and in the above valuechanged handlers). // // TODO FIXME we want to batch Observable updates so that this doesn't // happen. Maybe we'll do it by batching events that have the same // name. We should make it possible to choose to have sync or async // events, and whether they should batch or not. for (const [prop, modified] of Object.entries(modifiedProps)) if (modified) this.trigger('propertychange', prop); }, _calcSize() { const calculatedSize = this._calculatedSize; const previousSize = _objectSpread({}, calculatedSize); const props = this._properties; const parentSize = this._getParentSize(); if (props.sizeMode.x == 'literal') { calculatedSize.x = props.size.x; } else { // proportional calculatedSize.x = parentSize.x * props.size.x; } if (props.sizeMode.y == 'literal') { calculatedSize.y = props.size.y; } else { // proportional calculatedSize.y = parentSize.y * props.size.y; } if (props.sizeMode.z == 'literal') { calculatedSize.z = props.size.z; } else { // proportional calculatedSize.z = parentSize.z * props.size.z; } if (previousSize.x !== calculatedSize.x || previousSize.y !== calculatedSize.y || previousSize.z !== calculatedSize.z) { this.trigger('sizechange', _objectSpread({}, calculatedSize)); } }, _getParentSize() { return this.parent ? this.parent._calculatedSize : { x: 0, y: 0, z: 0 }; }, _handleXYZPropertyFunction(fn, name) { if (!this._propertyFunctions) this._propertyFunctions = new Map(); if (propFunction = this._propertyFunctions.get(name)) { _Motor.default.removeRenderTask(propFunction); propFunction = null; } this._propertyFunctions.set(name, _Motor.default.addRenderTask(time => { const result = fn(this._properties[name].x, this._properties[name].y, this._properties[name].z, time); if (result === false) { this._propertyFunctions.delete(name); return false; } // mark this true, so that the following set of this[name] // doesn't override the prop function (normally a // user can set this[name] to a value that isn't a function // to disable the prop function). this._settingValueFromPropFunction = true; this[name] = result; })); }, _handleSinglePropertyFunction(fn, name) { if (!this._propertyFunctions) this._propertyFunctions = new Map(); if (propFunction = this._propertyFunctions.get(name)) { _Motor.default.removeRenderTask(propFunction); propFunction = null; } this._propertyFunctions.set(name, _Motor.default.addRenderTask(time => { const result = fn(this._properties[name], time); if (result === false) { this._propertyFunctions.delete(name); return false; } this._settingValueFromPropFunction = true; this[name] = result; })); }, // remove property function (render task) if any. _removePropertyFunction(name) { if (this._propertyFunctions && (propFunction = this._propertyFunctions.get(name))) { _Motor.default.removeRenderTask(propFunction); this._propertyFunctions.delete(name); propFunction = null; } }, _setPropertyXYZ(Class, name, newValue) { if (typeof newValue === 'function') { this._handleXYZPropertyFunction(newValue, name); } else { if (!this._settingValueFromPropFunction) this._removePropertyFunction(name);else this._settingValueFromPropFunction = false; Super(this)[name] = newValue; } }, _setPropertySingle(name, newValue) { if (typeof newValue === 'function') { this._handleSinglePropertyFunction(newValue, name); } else { if (!this._settingValueFromPropFunction) this._removePropertyFunction(name);else this._settingValueFromPropFunction = false; Super(this)[name] = newValue; } }, /** * Set the size mode for each axis. Possible size modes are "literal" * and "proportional". The default values are "literal" for all axes. * * @param {Object} newValue * @param {number} [newValue.x] The x-axis sizeMode to apply. Default: `"literal"` * @param {number} [newValue.y] The y-axis sizeMode to apply. Default: `"literal"` * @param {number} [newValue.z] The z-axis sizeMode to apply. Default: `"literal"` */ set sizeMode(newValue) { if (typeof newValue === 'function') throw new TypeError('property functions are not allowed for sizeMode'); this._setPropertyXYZ(null, 'sizeMode', newValue); }, get sizeMode() { return Super(this).sizeMode; }, // TODO: A "differential" size would be cool. Good for padding, // borders, etc. Inspired from Famous' differential sizing. // // TODO: A "target" size where sizing can be relative to another node. // This would be tricky though, because there could be circular size // dependencies. Maybe we'd throw an error in that case, because there'd be no original size to base off of. /** * Set the size of each axis. The size for each axis depends on the * sizeMode for each axis. For example, if node.sizeMode is set to * `sizeMode = ['literal', 'proportional', 'literal']`, then setting * `size = [20, 0.5, 30]` means that X size is a literal value of 20, * Y size is 0.5 of it's parent Y size, and Z size is a literal value * of 30. It is easy this way to mix literal and proportional sizes for * the different axes. * * Literal sizes can be any value (the literal size that you want) and * proportional sizes are a number between 0 and 1 representing a * proportion of the parent node size. 0 means 0% of the parent size, * and 1.0 means 100% of the parent size. * * All size values must be positive numbers. * * @param {Object} newValue * @param {number} [newValue.x] The x-axis size to apply. * @param {number} [newValue.y] The y-axis size to apply. * @param {number} [newValue.z] The z-axis size to apply. */ set size(newValue) { this._setPropertyXYZ(Sizeable, 'size', newValue); }, get size() { return Super(this).size; }, /** * Get the actual size of the Node. This can be useful when size is * proportional, as the actual size of the Node depends on the size of * it's parent. * * @readonly * * @return {Array.number} An Oject with x, y, and z properties, each * property representing the computed size of the x, y, and z axes * respectively. */ get calculatedSize() { const { x, y, z } = this._calculatedSize; return { x, y, z }; }, /** * Set all properties of a Sizeable in one method. * * @param {Object} properties Properties object - see example * * @example * node.properties = { * sizeMode: {x:'literal', y:'proportional', z:'literal'}, * size: {x:300, y:0.2, z:200}, * } */ set properties(properties) { this.props = properties; }, get properties() { return this.props; } })); return Sizeable; }); exports.default = _default;