diff --git a/Extensions/Physics2Behavior/JsExtension.js b/Extensions/Physics2Behavior/JsExtension.js index 683333dab207..a2240f4c1026 100644 --- a/Extensions/Physics2Behavior/JsExtension.js +++ b/Extensions/Physics2Behavior/JsExtension.js @@ -49,86 +49,78 @@ module.exports = { } if (propertyName === 'shapeDimensionA') { newValue = parseFloat(newValue); - if (newValue < 0) newValue = 0; + if (newValue !== newValue) return false; behaviorContent.shapeDimensionA = newValue; return true; } if (propertyName === 'shapeDimensionB') { newValue = parseFloat(newValue); - if (newValue < 0) newValue = 0; + if (newValue !== newValue) return false; behaviorContent.shapeDimensionB = newValue; return true; } if (propertyName === 'shapeOffsetX') { - behaviorContent.shapeOffsetX = parseFloat(newValue); + newValue = parseFloat(newValue); + if (newValue !== newValue) return false; + behaviorContent.shapeOffsetX = newValue; return true; } if (propertyName === 'shapeOffsetY') { - behaviorContent.shapeOffsetY = parseFloat(newValue); + newValue = parseFloat(newValue); + if (newValue !== newValue) return false; + behaviorContent.shapeOffsetY = newValue; + return true; + } + if (propertyName === 'polygonOrigin') { + behaviorContent.polygonOrigin = newValue; + return true; + } + if (propertyName === 'vertices') { + behaviorContent.vertices = JSON.parse(newValue); return true; } if (propertyName === 'density') { - newValue = parseFloat(newValue); - if (newValue < 0) newValue = 0; - behaviorContent.density = newValue; + behaviorContent.density = parseFloat(newValue); return true; } if (propertyName === 'friction') { newValue = parseFloat(newValue); - if (newValue < 0) newValue = 0; + if (newValue !== newValue) return false; behaviorContent.friction = newValue; return true; } if (propertyName === 'restitution') { newValue = parseFloat(newValue); - if (newValue < 0) newValue = 0; + if (newValue !== newValue) return false; behaviorContent.restitution = newValue; return true; } if (propertyName === 'linearDamping') { - behaviorContent.linearDamping = parseFloat(newValue); + newValue = parseFloat(newValue); + if (newValue !== newValue) return false; + behaviorContent.linearDamping = newValue; return true; } if (propertyName === 'angularDamping') { - behaviorContent.angularDamping = parseFloat(newValue); + newValue = parseFloat(newValue); + if (newValue !== newValue) return false; + behaviorContent.angularDamping = newValue; return true; } if (propertyName === 'gravityScale') { - behaviorContent.gravityScale = parseFloat(newValue); + newValue = parseFloat(newValue); + if (newValue !== newValue) return false; + behaviorContent.gravityScale = newValue; return true; } if (propertyName === 'layers') { - // The given binary string is reverse, fix it - newValue = newValue - .split('') - .reverse() - .join(''); - // Convert it into a decimal - newValue = parseInt(newValue, 2); - // If it can't be converted, cancel the edit - if (isNaN(newValue)) return false; - // Layers minimum and maximum values - if (newValue < 0) newValue = 0; - if (newValue > 65535) newValue = 65535; // 65535 is the decimal form of 1111111111111111 (16 layer bits flagged) - // Save the valid decimal - behaviorContent.layers = newValue; + behaviorContent.layers = parseInt(newValue); return true; } if (propertyName === 'masks') { - // Same than layers - newValue = newValue - .split('') - .reverse() - .join(''); - newValue = parseInt(newValue, 2); - if (isNaN(newValue)) return false; - if (newValue < 0) newValue = 0; - if (newValue > 65535) newValue = 65535; - behaviorContent.masks = newValue; + behaviorContent.masks = parseInt(newValue); return true; } - - return false; }; physics2Behavior.getProperties = function(behaviorContent) { var behaviorProperties = new gd.MapStringPropertyDescriptor(); @@ -169,8 +161,8 @@ module.exports = { .setLabel('Shape') .addExtraInfo('Box') .addExtraInfo('Circle') - // .addExtraInfo("Polygon") Needs an editor to be useful .addExtraInfo('Edge') + .addExtraInfo('Polygon') ); behaviorProperties.set( 'shapeDimensionA', @@ -196,6 +188,21 @@ module.exports = { .setType('Number') .setLabel('Shape Offset Y') ); + behaviorProperties.set( + 'polygonOrigin', + new gd.PropertyDescriptor(behaviorContent.polygonOrigin || 'Center') + .setType('Choice') + .setLabel('Polygon Origin') + .addExtraInfo('Center') + .addExtraInfo('Origin') + .addExtraInfo('TopLeft') + ); + behaviorProperties.set( + 'vertices', + new gd.PropertyDescriptor( + JSON.stringify(behaviorContent.vertices || []) + ).setLabel('Vertices') + ); behaviorProperties.set( 'density', new gd.PropertyDescriptor(behaviorContent.density.toString(10)) @@ -232,38 +239,22 @@ module.exports = { .setType('Number') .setLabel('Gravity Scale') ); - - // Waiting for a layers/masks editor - /* - // Transform the layers number into a binary string - var layers = behaviorContent.layers.toString(2); - // Reverse the string (so the first layer bit is shown at the left) - layers = layers - .split('') - .reverse() - .join(''); - // Add zeros until the total size is 16 - if (layers.length < 16) layers = layers + '0'.repeat(16 - layers.length); - // Expose the converted string behaviorProperties.set( 'layers', - new gd.PropertyDescriptor(layers).setLabel('Layers') + new gd.PropertyDescriptor(behaviorContent.layers.toString(10)) + .setType('Number') + .setLabel('Layers') ); - // Same than layers - var masks = behaviorContent.masks.toString(2); - masks = masks - .split('') - .reverse() - .join(''); - if (masks.length < 16) masks = masks + '0'.repeat(16 - masks.length); behaviorProperties.set( 'masks', - new gd.PropertyDescriptor(masks).setLabel('Masks') + new gd.PropertyDescriptor(behaviorContent.masks.toString(10)) + .setType('Number') + .setLabel('Masks') ); - */ return behaviorProperties; }; + physics2Behavior.setRawJSONContent( JSON.stringify({ type: 'Dynamic', @@ -275,6 +266,8 @@ module.exports = { shapeDimensionB: 0, shapeOffsetX: 0, shapeOffsetY: 0, + polygonOrigin: 'Center', + vertices: [], density: 1.0, friction: 0.3, restitution: 0.1, @@ -293,23 +286,27 @@ module.exports = { newValue ) { if (propertyName === 'gravityX') { - sharedContent.gravityX = parseInt(newValue, 10); + newValue = parseFloat(newValue); + if (newValue !== newValue) return false; + behaviorContent.gravityX = newValue; return true; } if (propertyName === 'gravityY') { - sharedContent.gravityY = parseInt(newValue, 10); + newValue = parseFloat(newValue); + if (newValue !== newValue) return false; + behaviorContent.gravityY = newValue; return true; } if (propertyName === 'scaleX') { newValue = parseInt(newValue, 10); - if (newValue <= 0) newValue = 1; - sharedContent.scaleX = newValue; + if (newValue !== newValue) return false; + behaviorContent.scaleX = newValue; return true; } if (propertyName === 'scaleY') { newValue = parseInt(newValue, 10); - if (newValue <= 0) newValue = 1; - sharedContent.scaleY = newValue; + if (newValue !== newValue) return false; + behaviorContent.scaleY = newValue; return true; } @@ -721,7 +718,7 @@ module.exports = { t( 'Modify an object shape scale. It affects custom shape dimensions and shape offset, if custom dimensions are not set the body will be scaled automatically to the object size.' ), - t('Do to _PARAM2__PARAM3_ to the shape scale of _PARAM0_'), + t('Do _PARAM2__PARAM3_ to the shape scale of _PARAM0_'), t('Body settings'), 'res/physics24.png', 'res/physics16.png' diff --git a/Extensions/Physics2Behavior/physics2runtimebehavior.js b/Extensions/Physics2Behavior/physics2runtimebehavior.js index dd3f3ed01439..5fc4b040122b 100644 --- a/Extensions/Physics2Behavior/physics2runtimebehavior.js +++ b/Extensions/Physics2Behavior/physics2runtimebehavior.js @@ -203,6 +203,8 @@ gdjs.Physics2RuntimeBehavior = function(runtimeScene, behaviorData, owner) { this.shapeDimensionB = behaviorData.content.shapeDimensionB; this.shapeOffsetX = behaviorData.content.shapeOffsetX; this.shapeOffsetY = behaviorData.content.shapeOffsetY; + this.polygonOrigin = behaviorData.content.polygonOrigin; + this.polygon = this.getPolygon(behaviorData.content.vertices); this.density = behaviorData.content.density; this.friction = behaviorData.content.friction; this.restitution = behaviorData.content.restitution; @@ -227,6 +229,7 @@ gdjs.Physics2RuntimeBehavior = function(runtimeScene, behaviorData, owner) { this._objectOldAngle = 0; this._objectOldWidth = 0; this._objectOldHeight = 0; + this._verticesBuffer = 0; // Stores a Box2D pointer of created vertices }; gdjs.Physics2RuntimeBehavior.prototype = Object.create( @@ -251,6 +254,11 @@ gdjs.Physics2RuntimeBehavior.prototype.onDeActivate = function() { if (this._body !== null) { // When a body is deleted, Box2D removes automatically its joints, leaving an invalid pointer in our joints list this._sharedData.clearBodyJoints(this._body); + // Delete the vertices + if (this._verticesBuffer) { + Box2D._free(this._verticesBuffer); + this._verticesBuffer = 0; + } // Delete the body this._sharedData.world.DestroyBody(this._body); this._body = null; @@ -261,6 +269,15 @@ gdjs.Physics2RuntimeBehavior.prototype.ownerRemovedFromScene = function() { this.onDeActivate(); }; +gdjs.Physics2RuntimeBehavior.prototype.getPolygon = function(verticesData) { + var polygon = new gdjs.Polygon(); + var maxVertices = 8; + for (var i = 0, len = verticesData.length; i < Math.min(len, maxVertices); i++) { + polygon.vertices.push([verticesData[i].x, verticesData[i].y]); + } + return polygon; +}; + gdjs.Physics2RuntimeBehavior.prototype.createShape = function() { // Get the scaled offset var offsetX = this.shapeOffsetX @@ -290,7 +307,55 @@ gdjs.Physics2RuntimeBehavior.prototype.createShape = function() { // Set the offset shape.set_m_p(this.b2Vec2(offsetX, offsetY)); } else if (this.shape === 'Polygon') { - // Needs a vertices editor + shape = new Box2D.b2PolygonShape(); + // Not convex, fall back to a box + if (!this.polygon.isConvex()) { + var width = + (this.owner.getWidth() > 0 ? this.owner.getWidth() : 1) * + this._sharedData.invScaleX; + var height = + (this.owner.getHeight() > 0 ? this.owner.getHeight() : 1) * + this._sharedData.invScaleY; + // Set the shape box + shape.SetAsBox(width / 2, height / 2, this.b2Vec2(offsetX, offsetY), 0); + } else { + var originOffsetX = 0; + var originOffsetY = 0; + if(this.polygonOrigin === "Origin"){ + originOffsetX = (this.owner.getWidth() > 0 ? -this.owner.getWidth()/2 : 0) + + (this.owner.getX() - this.owner.getDrawableX()); + originOffsetY = (this.owner.getHeight() > 0 ? -this.owner.getHeight()/2 : 0) + + (this.owner.getY() - this.owner.getDrawableY()); + } + else if(this.polygonOrigin === "TopLeft"){ + originOffsetX = this.owner.getWidth() > 0 ? -this.owner.getWidth()/2 : 0; + originOffsetY = this.owner.getHeight() > 0 ? -this.owner.getHeight()/2 : 0; + } + // Generate vertices if not done already + if (!this._verticesBuffer) { + // Store the vertices using a memory allocation function + var buffer = Box2D._malloc( + this.polygon.vertices.length * 8, + 'float', + Box2D.ALLOC_STACK + ); + this._verticesBuffer = buffer; + } + // Overwrite the vertices stored in the buffer + var offset = 0; + for (var i = 0, len = this.polygon.vertices.length; i < len; i++) { + Box2D.HEAPF32[(this._verticesBuffer + offset) >> 2] = + (this.polygon.vertices[i][0] * this.shapeScale + originOffsetX) * this._sharedData.invScaleX + + offsetX; + Box2D.HEAPF32[(this._verticesBuffer + (offset + 4)) >> 2] = + (this.polygon.vertices[i][1] * this.shapeScale + originOffsetY) * this._sharedData.invScaleY + + offsetY; + offset += 8; + } + // Set the shape vertices + var b2Vertices = Box2D.wrapPointer(this._verticesBuffer, Box2D.b2Vec2); + shape.Set(b2Vertices, this.polygon.vertices.length); + } } else if (this.shape === 'Edge') { shape = new Box2D.b2EdgeShape(); // Length from the custom dimension or from the object width diff --git a/newIDE/app/src/BehaviorsEditor/BehaviorsEditorService.js b/newIDE/app/src/BehaviorsEditor/BehaviorsEditorService.js new file mode 100644 index 000000000000..d677fcc83d81 --- /dev/null +++ b/newIDE/app/src/BehaviorsEditor/BehaviorsEditorService.js @@ -0,0 +1,20 @@ +// @flow +import BehaviorPropertiesEditor from './Editors/BehaviorPropertiesEditor'; +import Physics2Editor from './Editors/Physics2Editor'; + +/** + * A service returning editor components for each behavior type. + */ +export default { + getEditor(behaviorType: string) { + if (!this.components[behaviorType]) { + return BehaviorPropertiesEditor; // Default properties editor + } + return this.components[behaviorType].component; // Custom behavior editor + }, + components: { + 'Physics2::Physics2Behavior': { + component: Physics2Editor, + }, + }, +}; diff --git a/newIDE/app/src/BehaviorsEditor/Editors/BehaviorEditorProps.flow.js b/newIDE/app/src/BehaviorsEditor/Editors/BehaviorEditorProps.flow.js new file mode 100644 index 000000000000..b37fc26a1ccc --- /dev/null +++ b/newIDE/app/src/BehaviorsEditor/Editors/BehaviorEditorProps.flow.js @@ -0,0 +1,16 @@ +import { + type ResourceSource, + type ChooseResourceFunction, +} from '../../ResourcesList/ResourceSource.flow'; +import { type ResourceExternalEditor } from '../../ResourcesList/ResourceExternalEditor.flow'; + +/** + * The props given to any behavior editor + */ +export type BehaviorEditorProps = {| + behavior: Object, + project: gdProject, + resourceSources: Array, + onChooseResource: ChooseResourceFunction, + resourceExternalEditors: Array, +|}; diff --git a/newIDE/app/src/BehaviorsEditor/Editors/BehaviorPropertiesEditor.js b/newIDE/app/src/BehaviorsEditor/Editors/BehaviorPropertiesEditor.js new file mode 100644 index 000000000000..2f858168a736 --- /dev/null +++ b/newIDE/app/src/BehaviorsEditor/Editors/BehaviorPropertiesEditor.js @@ -0,0 +1,35 @@ +// @flow +import * as React from 'react'; +import PropertiesEditor from '../../PropertiesEditor'; +import propertiesMapToSchema from '../../PropertiesEditor/PropertiesMapToSchema'; +import EmptyMessage from '../../UI/EmptyMessage'; +import { Column } from '../../UI/Grid'; +import { type BehaviorEditorProps } from './BehaviorEditorProps.flow'; + +type Props = BehaviorEditorProps; + +export default class BehaviorPropertiesEditor extends React.Component { + render() { + const { behavior, project } = this.props; + const properties = behavior.getProperties(project); + + const propertiesSchema = propertiesMapToSchema( + properties, + behavior => behavior.getProperties(project), + (behavior, name, value) => behavior.updateProperty(name, value, project) + ); + + return ( + + {propertiesSchema.length ? ( + + ) : ( + + There is nothing to configure for this behavior. You can still use + events to interact with the object and this behavior. + + )} + + ); + } +} diff --git a/newIDE/app/src/BehaviorsEditor/Editors/Physics2Editor/PolygonEditor.js b/newIDE/app/src/BehaviorsEditor/Editors/Physics2Editor/PolygonEditor.js new file mode 100644 index 000000000000..c40eb5d0d3ff --- /dev/null +++ b/newIDE/app/src/BehaviorsEditor/Editors/Physics2Editor/PolygonEditor.js @@ -0,0 +1,134 @@ +// @flow +import * as React from 'react'; +import { + Table, + TableRow, + TableRowColumn, + TableBody, + TableHeader, + TableHeaderColumn, +} from 'material-ui/Table'; +import SemiControlledTextField from '../../../UI/SemiControlledTextField'; +import Warning from 'material-ui/svg-icons/alert/warning'; +import IconButton from 'material-ui/IconButton'; +import AddCircle from 'material-ui/svg-icons/content/add-circle'; +import Delete from 'material-ui/svg-icons/action/delete'; + +export type Vertex = {| + x: number, + y: number, +|}; + +type Props = {| + vertices: Array, + onChangeVertexX: (newValue: number, index: number) => void, + onChangeVertexY: (newValue: number, index: number) => void, + onAdd: () => void, + onRemove: (index: number) => void, +|}; + +export default class PolygonEditor extends React.Component { + _isPolygonConvex(vertices: Array) { + // Get edges + var edges = []; + var v1 = null; + var v2 = null; + for (var i = 0; i < vertices.length; i++) { + v1 = vertices[i]; + if (i + 1 >= vertices.length) v2 = vertices[0]; + else v2 = vertices[i + 1]; + edges.push({ x: v2.x - v1.x, y: v2.y - v1.y }); + } + // Check convexity + if (edges.length < 3) return false; + + const zProductIsPositive = + edges[0].x * edges[0 + 1].y - edges[0].y * edges[0 + 1].x > 0; + + for (i = 1; i < edges.length - 1; ++i) { + var zCrossProduct = + edges[i].x * edges[i + 1].y - edges[i].y * edges[i + 1].x; + var zCrossProductIsPositive = zCrossProduct > 0; + if (zCrossProductIsPositive !== zProductIsPositive) return false; + } + + var lastZCrossProduct = + edges[edges.length - 1].x * edges[0].y - + edges[edges.length - 1].y * edges[0].x; + var lastZCrossProductIsPositive = lastZCrossProduct > 0; + if (lastZCrossProductIsPositive !== zProductIsPositive) return false; + + return true; + } + + render() { + const { + vertices, + onChangeVertexX, + onChangeVertexY, + onAdd, + onRemove, + } = this.props; + + return ( + + + + + X + Y + + + + + {vertices.map((value, index) => { + return ( + + + {!this._isPolygonConvex(vertices) && } + + + + onChangeVertexX(parseFloat(newValue) || 0, index) + } + type="number" + /> + + + + onChangeVertexY(parseFloat(newValue) || 0, index) + } + type="number" + /> + + + onRemove(index)}> + + + + + ); + })} + + + + + + + + + + + +
+ ); + } +} diff --git a/newIDE/app/src/BehaviorsEditor/Editors/Physics2Editor/ShapePreview.js b/newIDE/app/src/BehaviorsEditor/Editors/Physics2Editor/ShapePreview.js new file mode 100644 index 000000000000..7425f5b3e9f8 --- /dev/null +++ b/newIDE/app/src/BehaviorsEditor/Editors/Physics2Editor/ShapePreview.js @@ -0,0 +1,209 @@ +// @flow +import * as React from 'react'; +import { type Vertex } from './PolygonEditor'; + +type Props = {| + shape: string, + dimensionA: number, + dimensionB: number, + offsetX: number, + offsetY: number, + polygonOrigin: string, + vertices: Array, + width: number, + height: number, + onMoveVertex: (index: number, newX: number, newY: number) => void, +|}; + +type State = {| + draggedVertex: ?Vertex, + draggedIndex: number, +|}; + +export default class ShapePreview extends React.Component { + constructor(props: Props) { + super(props); + this.state = { draggedVertex: null, draggedIndex: -1 }; + } + + _svg: any; + + _onVertexDown = (vertex: Vertex, index: number) => { + if (this.state.draggedVertex) return; + this.setState({ draggedVertex: vertex, draggedIndex: index }); + }; + + _onMouseUp = () => { + const draggingWasDone = !!this.state.draggedVertex; + const { draggedVertex, draggedIndex } = this.state; + this.setState( + { + draggedVertex: null, + }, + () => { + if (draggingWasDone) + this.props.onMoveVertex( + draggedIndex, + Math.round(draggedVertex ? draggedVertex.x : 0), + Math.round(draggedVertex ? draggedVertex.y : 0) + ); + } + ); + }; + + _onMouseMove = (event: any) => { + const { offsetX, offsetY, polygonOrigin, width, height } = this.props; + const { draggedVertex } = this.state; + if (!draggedVertex) return; + + const pointOnScreen = this._svg.createSVGPoint(); + pointOnScreen.x = event.clientX; + pointOnScreen.y = event.clientY; + const screenToSvgMatrix = this._svg.getScreenCTM().inverse(); + const pointOnSvg = pointOnScreen.matrixTransform(screenToSvgMatrix); + + draggedVertex.x = + pointOnSvg.x - offsetX - (polygonOrigin === 'Center' ? width / 2 : 0); + draggedVertex.y = + pointOnSvg.y - offsetY - (polygonOrigin === 'Center' ? height / 2 : 0); + + this.forceUpdate(); + }; + + renderBox() { + const { + dimensionA, + dimensionB, + width, + height, + offsetX, + offsetY, + } = this.props; + const fixedWidth = dimensionA > 0 ? dimensionA : width > 0 ? width : 1; + const fixedHeight = dimensionB > 0 ? dimensionB : height > 0 ? height : 1; + + return ( + + ); + } + + renderCircle() { + const { dimensionA, width, height, offsetX, offsetY } = this.props; + + return ( + 0 + ? dimensionA + : width + height > 0 + ? (width + height) / 4 + : 1 + } + /> + ); + } + + renderEdge() { + const { + dimensionA, + dimensionB, + width, + height, + offsetX, + offsetY, + } = this.props; + const halfLength = + (dimensionA > 0 ? dimensionA : width > 0 ? width : 1) / 2; + const cos = Math.cos((dimensionB * Math.PI) / 180); + const sin = Math.sin((dimensionB * Math.PI) / 180); + + return ( + + ); + } + + renderPolygon() { + const { + vertices, + polygonOrigin, + width, + height, + offsetX, + offsetY, + } = this.props; + + return ( + + + `${vertex.x + + offsetX + + (polygonOrigin === 'Center' ? width / 2 : 0)},${vertex.y + + offsetY + + (polygonOrigin === 'Center' ? height / 2 : 0)}` + ) + .join(' ')} + /> + {vertices.map((vertex, index) => ( + this._onVertexDown(vertex, index)} + key={`vertex-${index}`} + fill="rgba(150,0,0,0.75)" + strokeWidth={1} + cx={ + vertex.x + offsetX + (polygonOrigin === 'Center' ? width / 2 : 0) + } + cy={ + vertex.y + offsetY + (polygonOrigin === 'Center' ? height / 2 : 0) + } + r={5} + /> + ))} + + ); + } + + render() { + const { shape } = this.props; + + return ( + (this._svg = svg)} + > + {shape === 'Box' && this.renderBox()} + {shape === 'Circle' && this.renderCircle()} + {shape === 'Edge' && this.renderEdge()} + {shape === 'Polygon' && this.renderPolygon()} + + ); + } +} diff --git a/newIDE/app/src/BehaviorsEditor/Editors/Physics2Editor/index.js b/newIDE/app/src/BehaviorsEditor/Editors/Physics2Editor/index.js new file mode 100644 index 000000000000..0fb53da58dff --- /dev/null +++ b/newIDE/app/src/BehaviorsEditor/Editors/Physics2Editor/index.js @@ -0,0 +1,553 @@ +// @flow +import * as React from 'react'; +import { Line, Column } from '../../../UI/Grid'; +import Checkbox from 'material-ui/Checkbox'; +import SelectField from 'material-ui/SelectField'; +import MenuItem from 'material-ui/MenuItem'; +import SemiControlledTextField from '../../../UI/SemiControlledTextField'; +import ImagePreview from '../../../ResourcesList/ResourcePreview/ImagePreview'; +import ResourceSelector from '../../../ResourcesList/ResourceSelector'; +import ResourcesLoader from '../../../ResourcesLoader'; +import ShapePreview from './ShapePreview.js'; +import PolygonEditor from './PolygonEditor.js'; +import { type BehaviorEditorProps } from '../BehaviorEditorProps.flow'; + +type Props = BehaviorEditorProps; + +type State = {| + image: string, + imageWidth: number, + imageHeight: number, +|}; + +function NumericProperty(props: {| + properties: gdMapStringPropertyDescriptor, + propertyName: string, + step: number, + onUpdate: (newValue: string) => void, +|}) { + const { properties, propertyName, step, onUpdate } = props; + + return ( + + ); +} + +function BitProperty(props: {| + enabled: boolean, + propertyName: string, + pos: number, + spacing: boolean, + onUpdate: (enabled: boolean) => void, +|}) { + const { enabled, propertyName, pos, spacing, onUpdate } = props; + + return ( +
+ { + onUpdate(checked)} + /> + } + {spacing && ( +
+ )} +
+ ); +} + +export default class Physics2Editor extends React.Component { + resourcesLoader = ResourcesLoader; + + state = { + image: '', + imageWidth: 0, + imageHeight: 0, + }; + + _setImageSize = (width: number, height: number) => { + this.setState({ + imageWidth: width, + imageHeight: height, + }); + }; + + _isBitEnabled(bitsValue: number, pos: number) { + return !!(bitsValue & (1 << pos)); + } + + _enableBit(bitsValue: number, pos: number, enable: boolean) { + if (enable) bitsValue |= 1 << pos; + else bitsValue &= ~(1 << pos); + return bitsValue; + } + + render() { + const { behavior, project } = this.props; + + const properties = behavior.getProperties(project); + const bits = Array(16).fill(null); + const shape = properties.get('shape').getValue(); + const layersValues = parseInt(properties.get('layers').getValue(), 10); + const masksValues = parseInt(properties.get('masks').getValue(), 10); + + return ( + + + { + behavior.updateProperty('type', newValue, project); + this.forceUpdate(); + }} + > + {[ + , + , + , + ]} + + + + + { + behavior.updateProperty('bullet', checked ? '1' : '0', project); + this.forceUpdate(); + }} + /> + + + { + behavior.updateProperty( + 'fixedRotation', + checked ? '1' : '0', + project + ); + this.forceUpdate(); + }} + /> + + + { + behavior.updateProperty( + 'canSleep', + checked ? '1' : '0', + project + ); + this.forceUpdate(); + }} + /> + + + + { + behavior.updateProperty('shape', newValue, project); + this.forceUpdate(); + }} + > + + + + + + + + {shape !== 'Polygon' && ( + { + behavior.updateProperty( + shape === 'Polygon' ? 'PolygonOriginX' : 'shapeDimensionA', + newValue, + project + ); + this.forceUpdate(); + }} + type="number" + /> + )} + {shape !== 'Polygon' && shape !== 'Circle' && ( + { + behavior.updateProperty( + shape === 'Polygon' ? 'PolygonOriginY' : 'shapeDimensionB', + newValue, + project + ); + this.forceUpdate(); + }} + type="number" + /> + )} + {shape === 'Polygon' && ( + { + behavior.updateProperty('polygonOrigin', newValue, project); + this.forceUpdate(); + }} + > + {[ + , + , + , + ]} + + )} + { + behavior.updateProperty('shapeOffsetX', newValue, project); + this.forceUpdate(); + }} + /> + { + this.props.behavior.updateProperty( + 'shapeOffsetY', + newValue, + this.props.project + ); + this.forceUpdate(); + }} + /> + + + + + + { + let vertices = JSON.parse( + properties.get('vertices').getValue() + ); + vertices[index].x = newX; + vertices[index].y = newY; + behavior.updateProperty( + 'vertices', + JSON.stringify(vertices), + project + ); + this.forceUpdate(); + }} + /> + + + + { + this.setState({ image: resourceName }); + this.forceUpdate(); + }} + /> + + + {shape === 'Polygon' && ( + { + let vertices = JSON.parse( + properties.get('vertices').getValue() + ); + vertices[index].x = newValue; + behavior.updateProperty( + 'vertices', + JSON.stringify(vertices), + project + ); + this.forceUpdate(); + }} + onChangeVertexY={(newValue, index) => { + let vertices = JSON.parse( + properties.get('vertices').getValue() + ); + vertices[index].y = newValue; + behavior.updateProperty( + 'vertices', + JSON.stringify(vertices), + project + ); + this.forceUpdate(); + }} + onAdd={() => { + let vertices = JSON.parse( + properties.get('vertices').getValue() + ); + if (vertices.length >= 8) return; + vertices.push({ x: 0, y: 0 }); + behavior.updateProperty( + 'vertices', + JSON.stringify(vertices), + project + ); + this.forceUpdate(); + }} + onRemove={index => { + let vertices = JSON.parse( + properties.get('vertices').getValue() + ); + vertices.splice(index, 1); + behavior.updateProperty( + 'vertices', + JSON.stringify(vertices), + project + ); + this.forceUpdate(); + }} + /> + )} + + + + { + behavior.updateProperty( + 'density', + parseFloat(newValue) > 0 ? newValue : '0', + project + ); + this.forceUpdate(); + }} + /> + + + { + behavior.updateProperty('gravityScale', newValue, project); + this.forceUpdate(); + }} + /> + + + + + { + behavior.updateProperty( + 'friction', + parseFloat(newValue) > 0 ? newValue : '0', + project + ); + this.forceUpdate(); + }} + /> + + + { + behavior.updateProperty( + 'restitution', + parseFloat(newValue) > 0 ? newValue : '0', + project + ); + this.forceUpdate(); + }} + /> + + + + + { + behavior.updateProperty('linearDamping', newValue, project); + this.forceUpdate(); + }} + /> + + + { + behavior.updateProperty('angularDamping', newValue, project); + this.forceUpdate(); + }} + /> + + + + + {bits.map((value, index) => { + return ( + { + const newValue = this._enableBit( + layersValues, + index, + enabled + ); + this.props.behavior.updateProperty( + 'layers', + newValue.toString(10), + this.props.project + ); + this.forceUpdate(); + }} + key={`layer${index}`} + /> + ); + })} + + + + {bits.map((value, index) => { + return ( + { + const newValue = this._enableBit(masksValues, index, enabled); + this.props.behavior.updateProperty( + 'masks', + newValue.toString(10), + this.props.project + ); + this.forceUpdate(); + }} + key={`mask${index}`} + /> + ); + })} + + + ); + } +} diff --git a/newIDE/app/src/BehaviorsEditor/index.js b/newIDE/app/src/BehaviorsEditor/index.js index d527dc5728c7..e05db6a3052f 100644 --- a/newIDE/app/src/BehaviorsEditor/index.js +++ b/newIDE/app/src/BehaviorsEditor/index.js @@ -6,11 +6,10 @@ import IconButton from 'material-ui/IconButton'; import EmptyMessage from '../UI/EmptyMessage'; import MiniToolbar from '../UI/MiniToolbar'; import HelpIcon from '../UI/HelpIcon'; -import PropertiesEditor from '../PropertiesEditor'; -import propertiesMapToSchema from '../PropertiesEditor/PropertiesMapToSchema'; import newNameGenerator from '../Utils/NewNameGenerator'; import NewBehaviorDialog from './NewBehaviorDialog'; import { getBehaviorHelpPagePath } from './BehaviorsHelpPagePaths'; +import BehaviorsEditorService from './BehaviorsEditorService'; const styles = { addBehaviorLine: { @@ -20,9 +19,6 @@ const styles = { addBehaviorText: { justifyContent: 'flex-end', }, - propertiesContainer: { - padding: 10, - }, behaviorTitle: { flex: 1, }, @@ -143,13 +139,8 @@ export default class BehaviorsEditor extends Component { {allBehaviorNames .map((behaviorName, index) => { const behavior = object.getBehavior(behaviorName); - - const properties = behavior.getProperties(project); - const propertiesSchema = propertiesMapToSchema( - properties, - behavior => behavior.getProperties(project), - (behavior, name, value) => - behavior.updateProperty(name, value, project) + const BehaviorComponent = BehaviorsEditorService.getEditor( + behavior.getTypeName() ); return ( @@ -177,20 +168,13 @@ export default class BehaviorsEditor extends Component { /> -
- {propertiesSchema.length ? ( - - ) : ( - - There is nothing to configure for this behavior. You can - still use events to interact with the object and this - behavior. - - )} -
+
); }) diff --git a/newIDE/app/src/ObjectEditor/ObjectEditorDialog.js b/newIDE/app/src/ObjectEditor/ObjectEditorDialog.js index 9cb44dcfe37d..d05cb7c58c5e 100644 --- a/newIDE/app/src/ObjectEditor/ObjectEditorDialog.js +++ b/newIDE/app/src/ObjectEditor/ObjectEditorDialog.js @@ -106,6 +106,9 @@ export class ObjectEditorDialog extends Component<*, StateType> { this.forceUpdate() /*Force update to ensure dialog is properly positionned*/