mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Add editor for properties of events based behaviors
This commit is contained in:

committed by
Florian Rival

parent
52489c3cc9
commit
b1f7660ffa
@@ -261,6 +261,21 @@
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
{
|
||||
"disabled": false,
|
||||
"folded": false,
|
||||
"type": "BuiltinCommonInstructions::Comment",
|
||||
"color": {
|
||||
"b": 109,
|
||||
"g": 230,
|
||||
"r": 255,
|
||||
"textB": 0,
|
||||
"textG": 0,
|
||||
"textR": 0
|
||||
},
|
||||
"comment": "Should display Hello World 42!",
|
||||
"comment2": ""
|
||||
},
|
||||
{
|
||||
"disabled": false,
|
||||
"folded": false,
|
||||
@@ -711,4 +726,4 @@
|
||||
],
|
||||
"externalLayouts": [],
|
||||
"externalSourceFiles": []
|
||||
}
|
||||
}
|
3
newIDE/app/flow-typed/libGD.js
vendored
3
newIDE/app/flow-typed/libGD.js
vendored
@@ -59,6 +59,9 @@ declare type gdEventsFunctionsExtension = gdEventsFunctionsContainer & gdEmscrip
|
||||
|
||||
declare type gdVectorEventsSearchResult = gdEmscriptenObject;
|
||||
declare type gdMapStringPropertyDescriptor = gdEmscriptenObject;
|
||||
declare type gdPropertyDescriptor = gdEmscriptenObject;
|
||||
declare type gdNamedPropertyDescriptor = gdEmscriptenObject;
|
||||
declare type gdNamedPropertyDescriptorsList = gdEmscriptenObject;
|
||||
|
||||
declare type gdEventsContext = gdEmscriptenObject;
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import Dialog from '../UI/Dialog';
|
||||
import EventsBasedBehaviorEditor from './index';
|
||||
@@ -13,41 +13,56 @@ type Props = {|
|
||||
eventsBasedBehavior: gdEventsBasedBehavior,
|
||||
|};
|
||||
|
||||
export default function EventsBasedBehaviorEditorDialog({
|
||||
onApply,
|
||||
eventsBasedBehavior,
|
||||
eventsFunctionsExtension,
|
||||
project,
|
||||
}: Props) {
|
||||
return (
|
||||
<Dialog
|
||||
noMargin
|
||||
secondaryActions={[
|
||||
<HelpButton
|
||||
key="help"
|
||||
helpPagePath="/behaviors/events-based-behaviors"
|
||||
/>,
|
||||
]}
|
||||
actions={[
|
||||
<FlatButton
|
||||
label={<Trans>Apply</Trans>}
|
||||
primary
|
||||
keyboardFocused
|
||||
onClick={onApply}
|
||||
key={'Apply'}
|
||||
/>,
|
||||
]}
|
||||
modal
|
||||
open
|
||||
onRequestClose={onApply}
|
||||
autoScrollBodyContent
|
||||
title={<Trans>Edit the behavior</Trans>}
|
||||
>
|
||||
<EventsBasedBehaviorEditor
|
||||
project={project}
|
||||
eventsFunctionsExtension={eventsFunctionsExtension}
|
||||
eventsBasedBehavior={eventsBasedBehavior}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
export default class EventsBasedBehaviorEditorDialog extends React.Component<
|
||||
Props,
|
||||
{||}
|
||||
> {
|
||||
render() {
|
||||
const {
|
||||
onApply,
|
||||
eventsBasedBehavior,
|
||||
eventsFunctionsExtension,
|
||||
project,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
noMargin
|
||||
secondaryActions={[
|
||||
<HelpButton
|
||||
key="help"
|
||||
helpPagePath="/behaviors/events-based-behaviors"
|
||||
/>,
|
||||
]}
|
||||
actions={[
|
||||
<FlatButton
|
||||
label={<Trans>Apply</Trans>}
|
||||
primary
|
||||
keyboardFocused
|
||||
onClick={onApply}
|
||||
key={'Apply'}
|
||||
/>,
|
||||
]}
|
||||
modal
|
||||
open
|
||||
onRequestClose={onApply}
|
||||
autoScrollBodyContent
|
||||
title={<Trans>Edit the behavior</Trans>}
|
||||
>
|
||||
<EventsBasedBehaviorEditor
|
||||
project={project}
|
||||
eventsFunctionsExtension={eventsFunctionsExtension}
|
||||
eventsBasedBehavior={eventsBasedBehavior}
|
||||
onTabChanged={
|
||||
() =>
|
||||
this.forceUpdate() /*Force update to ensure dialog is properly positioned*/
|
||||
}
|
||||
onPropertiesUpdated={
|
||||
() =>
|
||||
this.forceUpdate() /*Force update to ensure dialog is properly positioned*/
|
||||
}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,287 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { t } from '@lingui/macro';
|
||||
import { I18n } from '@lingui/react';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
import * as React from 'react';
|
||||
import { Column, Line } from '../UI/Grid';
|
||||
import SelectField from 'material-ui/SelectField';
|
||||
import MenuItem from 'material-ui/MenuItem';
|
||||
import { mapVector } from '../Utils/MapFor';
|
||||
import RaisedButton from 'material-ui/RaisedButton';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import EmptyMessage from '../UI/EmptyMessage';
|
||||
import IconMenu from '../UI/Menu/IconMenu';
|
||||
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert';
|
||||
import SemiControlledTextField from '../UI/SemiControlledTextField';
|
||||
import MiniToolbar from '../UI/MiniToolbar';
|
||||
import { showWarningBox } from '../UI/Messages/MessageBox';
|
||||
import newNameGenerator from '../Utils/NewNameGenerator';
|
||||
|
||||
const gd = global.gd;
|
||||
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
eventsBasedBehavior: gdEventsBasedBehavior,
|
||||
onPropertiesUpdated: () => void,
|
||||
|};
|
||||
|
||||
const styles = {
|
||||
propertiesContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const validatePropertyName = (
|
||||
i18n: I18nType,
|
||||
properties: gdNamedPropertyDescriptorsList,
|
||||
newName: string
|
||||
) => {
|
||||
if (!newName) {
|
||||
showWarningBox(i18n._(t`The name of a property cannot be empty.`));
|
||||
return false;
|
||||
}
|
||||
if (newName === 'name' || newName === 'type') {
|
||||
showWarningBox(
|
||||
i18n._(
|
||||
t`The name of a property cannot be "name" or "type", as they are used by GDevelop internally.`
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (properties.has(newName)) {
|
||||
showWarningBox(
|
||||
i18n._(
|
||||
t`This name is already used by another property. Choose a unique name for each property.`
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!gd.Project.validateObjectName(newName)) {
|
||||
showWarningBox(
|
||||
i18n._(
|
||||
t`This name contains forbidden characters: please only use alphanumeric characters (0-9, a-z) and underscores in your parameter name.`
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export default class EventsBasedBehaviorPropertiesEditor extends React.Component<
|
||||
Props,
|
||||
{||}
|
||||
> {
|
||||
_addProperty = () => {
|
||||
const { eventsBasedBehavior } = this.props;
|
||||
const properties = eventsBasedBehavior.getPropertyDescriptors();
|
||||
|
||||
const newName = newNameGenerator('Property', name => properties.has(name));
|
||||
const property = properties.insertNew(newName, properties.getCount());
|
||||
property.setType('Number');
|
||||
this.forceUpdate();
|
||||
this.props.onPropertiesUpdated();
|
||||
};
|
||||
|
||||
_removeProperty = (name: string) => {
|
||||
const { eventsBasedBehavior } = this.props;
|
||||
const properties = eventsBasedBehavior.getPropertyDescriptors();
|
||||
|
||||
properties.remove(name);
|
||||
this.forceUpdate();
|
||||
this.props.onPropertiesUpdated();
|
||||
};
|
||||
|
||||
_moveProperty = (oldIndex: number, newIndex: number) => {
|
||||
const { eventsBasedBehavior } = this.props;
|
||||
const properties = eventsBasedBehavior.getPropertyDescriptors();
|
||||
|
||||
properties.move(oldIndex, newIndex);
|
||||
this.forceUpdate();
|
||||
this.props.onPropertiesUpdated();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { eventsBasedBehavior } = this.props;
|
||||
|
||||
const properties = eventsBasedBehavior.getPropertyDescriptors();
|
||||
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<Column noMargin>
|
||||
<Line noMargin>
|
||||
<div style={styles.propertiesContainer}>
|
||||
{mapVector(
|
||||
properties,
|
||||
(property: gdNamedPropertyDescriptor, i: number) => (
|
||||
<React.Fragment key={i}>
|
||||
<MiniToolbar>
|
||||
<Column expand noMargin>
|
||||
<SemiControlledTextField
|
||||
commitOnBlur
|
||||
hintText={<Trans>Enter the property name</Trans>}
|
||||
value={property.getName()}
|
||||
onChange={newName => {
|
||||
if (newName === property.getName()) return;
|
||||
if (
|
||||
!validatePropertyName(i18n, properties, newName)
|
||||
)
|
||||
return;
|
||||
|
||||
// TODO: refactor with WholeProjectRefactorer?
|
||||
property.setName(newName);
|
||||
this.forceUpdate();
|
||||
this.props.onPropertiesUpdated();
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
</Column>
|
||||
<IconMenu
|
||||
iconButtonElement={
|
||||
<IconButton>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
}
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: i18n._(t`Delete`),
|
||||
click: () =>
|
||||
this._removeProperty(property.getName()),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: i18n._(t`Move up`),
|
||||
click: () => this._moveProperty(i, i - 1),
|
||||
enabled: i - 1 >= 0,
|
||||
},
|
||||
{
|
||||
label: i18n._(t`Move down`),
|
||||
click: () => this._moveProperty(i, i + 1),
|
||||
enabled: i + 1 < properties.getCount(),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</MiniToolbar>
|
||||
<Line expand noMargin>
|
||||
<Column expand>
|
||||
<SelectField
|
||||
floatingLabelText={<Trans>Type</Trans>}
|
||||
value={property.getType()}
|
||||
onChange={(e, i, value) => {
|
||||
property.setType(value);
|
||||
this.forceUpdate();
|
||||
this.props.onPropertiesUpdated();
|
||||
}}
|
||||
fullWidth
|
||||
>
|
||||
<MenuItem
|
||||
value="Number"
|
||||
primaryText={<Trans>Number</Trans>}
|
||||
/>
|
||||
<MenuItem
|
||||
value="String"
|
||||
primaryText={<Trans>String</Trans>}
|
||||
/>
|
||||
<MenuItem
|
||||
value="Boolean"
|
||||
primaryText={<Trans>Boolean (checkbox)</Trans>}
|
||||
/>
|
||||
</SelectField>
|
||||
</Column>
|
||||
<Column expand>
|
||||
{(property.getType() === 'String' ||
|
||||
property.getType() === 'Number') && (
|
||||
<SemiControlledTextField
|
||||
commitOnBlur
|
||||
floatingLabelText={<Trans>Default value</Trans>}
|
||||
hintText={
|
||||
property.getType() === 'Number' ? '123' : 'ABC'
|
||||
}
|
||||
value={property.getValue()}
|
||||
onChange={newValue => {
|
||||
property.setValue(newValue);
|
||||
this.forceUpdate();
|
||||
this.props.onPropertiesUpdated();
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
{property.getType() === 'Boolean' && (
|
||||
<SelectField
|
||||
floatingLabelText={<Trans>Default value</Trans>}
|
||||
value={
|
||||
property.getValue() === 'true'
|
||||
? 'true'
|
||||
: 'false'
|
||||
}
|
||||
onChange={(e, i, value) => {
|
||||
property.setValue(value);
|
||||
this.forceUpdate();
|
||||
this.props.onPropertiesUpdated();
|
||||
}}
|
||||
fullWidth
|
||||
>
|
||||
<MenuItem
|
||||
value="true"
|
||||
primaryText={<Trans>True (checked)</Trans>}
|
||||
/>
|
||||
<MenuItem
|
||||
value="false"
|
||||
primaryText={<Trans>False (not checked)</Trans>}
|
||||
/>
|
||||
</SelectField>
|
||||
)}
|
||||
</Column>
|
||||
</Line>
|
||||
<Line expand noMargin>
|
||||
<Column expand>
|
||||
<SemiControlledTextField
|
||||
commitOnBlur
|
||||
floatingLabelText={
|
||||
<Trans>Label, shown in the editor</Trans>
|
||||
}
|
||||
hintText={
|
||||
<Trans>
|
||||
This should make the purpose of the property
|
||||
easy to understand
|
||||
</Trans>
|
||||
}
|
||||
floatingLabelFixed
|
||||
value={property.getLabel()}
|
||||
onChange={text => {
|
||||
property.setLabel(text);
|
||||
this.forceUpdate();
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
</Column>
|
||||
</Line>
|
||||
</React.Fragment>
|
||||
)
|
||||
)}
|
||||
{properties.getCount() === 0 ? (
|
||||
<EmptyMessage>
|
||||
<Trans>
|
||||
No properties for this behavior. Add one to store data
|
||||
inside this behavior (for example: health, ammo, speed,
|
||||
etc...)
|
||||
</Trans>
|
||||
</EmptyMessage>
|
||||
) : null}
|
||||
<Line justifyContent="center">
|
||||
<RaisedButton
|
||||
primary
|
||||
label={<Trans>Add a property</Trans>}
|
||||
onClick={this._addProperty}
|
||||
/>
|
||||
</Line>
|
||||
</div>
|
||||
</Line>
|
||||
</Column>
|
||||
)}
|
||||
</I18n>
|
||||
);
|
||||
}
|
||||
}
|
@@ -6,20 +6,34 @@ import TextField from 'material-ui/TextField';
|
||||
import { Column, Spacer } from '../UI/Grid';
|
||||
import SemiControlledTextField from '../UI/SemiControlledTextField';
|
||||
import ObjectTypeSelector from '../ObjectTypeSelector';
|
||||
import { Tabs, Tab } from 'material-ui/Tabs';
|
||||
import DismissableAlertMessage from '../UI/DismissableAlertMessage';
|
||||
import AlertMessage from '../UI/AlertMessage';
|
||||
import EventsBasedBehaviorPropertiesEditor from './EventsBasedBehaviorPropertiesEditor';
|
||||
const gd = global.gd;
|
||||
|
||||
type TabName = 'configuration' | 'properties';
|
||||
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
eventsFunctionsExtension: gdEventsFunctionsExtension,
|
||||
eventsBasedBehavior: gdEventsBasedBehavior,
|
||||
onPropertiesUpdated: () => void,
|
||||
onTabChanged: () => void,
|
||||
|};
|
||||
|
||||
type State = {|
|
||||
currentTab: TabName,
|
||||
|};
|
||||
|
||||
export default class EventsBasedBehaviorEditor extends React.Component<
|
||||
Props,
|
||||
{||}
|
||||
State
|
||||
> {
|
||||
state = {
|
||||
currentTab: 'configuration',
|
||||
};
|
||||
|
||||
// An array containing all the object types that are using the behavior
|
||||
_allObjectTypes: Array<string> = gd.WholeProjectRefactorer.getAllObjectTypesUsingEventsBasedBehavior(
|
||||
this.props.project,
|
||||
@@ -29,102 +43,124 @@ export default class EventsBasedBehaviorEditor extends React.Component<
|
||||
.toNewVectorString()
|
||||
.toJSArray();
|
||||
|
||||
_changeTab = (newTab: TabName) =>
|
||||
this.setState(
|
||||
{
|
||||
currentTab: newTab,
|
||||
},
|
||||
() => this.props.onTabChanged()
|
||||
);
|
||||
|
||||
render() {
|
||||
const { currentTab } = this.state;
|
||||
const { eventsBasedBehavior, project } = this.props;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<DismissableAlertMessage
|
||||
identifier="events-based-behavior-explanation"
|
||||
kind="info"
|
||||
>
|
||||
This is the configuration of your behavior. Make sure to choose a
|
||||
proper internal name as it's hard to change it later. Enter a
|
||||
description explaining what the behavior is doing to the object.
|
||||
</DismissableAlertMessage>
|
||||
<TextField
|
||||
floatingLabelText={<Trans>Internal Name</Trans>}
|
||||
value={eventsBasedBehavior.getName()}
|
||||
disabled
|
||||
fullWidth
|
||||
/>
|
||||
<SemiControlledTextField
|
||||
commitOnBlur
|
||||
floatingLabelText={<Trans>Name displayed in editor</Trans>}
|
||||
value={eventsBasedBehavior.getFullName()}
|
||||
onChange={text => {
|
||||
eventsBasedBehavior.setFullName(text);
|
||||
this.forceUpdate();
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
<SemiControlledTextField
|
||||
commitOnBlur
|
||||
floatingLabelText={<Trans>Description</Trans>}
|
||||
floatingLabelFixed
|
||||
hintText={
|
||||
<Trans>
|
||||
The description of the behavior should explain what the behavior
|
||||
is doing to the object, and, briefly, how to use it.
|
||||
</Trans>
|
||||
}
|
||||
value={eventsBasedBehavior.getDescription()}
|
||||
onChange={text => {
|
||||
eventsBasedBehavior.setDescription(text);
|
||||
this.forceUpdate();
|
||||
}}
|
||||
multiLine
|
||||
fullWidth
|
||||
rows={3}
|
||||
/>
|
||||
<ObjectTypeSelector
|
||||
floatingLabelText={
|
||||
<Trans>Object on which this behavior can be used</Trans>
|
||||
}
|
||||
project={project}
|
||||
value={eventsBasedBehavior.getObjectType()}
|
||||
onChange={(objectType: string) => {
|
||||
eventsBasedBehavior.setObjectType(objectType);
|
||||
this.forceUpdate();
|
||||
}}
|
||||
allowedObjectTypes={
|
||||
this._allObjectTypes.length === 0
|
||||
? undefined /* Allow anything as the behavior is not used */
|
||||
: this._allObjectTypes.length === 1
|
||||
? [
|
||||
'',
|
||||
this._allObjectTypes[0],
|
||||
] /* Allow only the type of the objects using the behavior */
|
||||
: [
|
||||
'',
|
||||
] /* More than one type of object are using the behavior. Only "any object" can be used on this behavior */
|
||||
}
|
||||
/>
|
||||
{this._allObjectTypes.length > 1 && (
|
||||
<AlertMessage kind="info">
|
||||
<Trans>
|
||||
This behavior is being used by multiple types of objects. Thus,
|
||||
you can't restrict its usage to any particular object type. All
|
||||
the object types using this behavior are listed here:
|
||||
{this._allObjectTypes.join(', ')}
|
||||
</Trans>
|
||||
</AlertMessage>
|
||||
)}
|
||||
{eventsBasedBehavior.getEventsFunctions().getEventsFunctionsCount() ===
|
||||
0 && (
|
||||
<DismissableAlertMessage
|
||||
identifier="empty-events-based-behavior-explanation"
|
||||
kind="info"
|
||||
>
|
||||
<Trans>
|
||||
Once you're done, close this dialog and start adding some
|
||||
functions to the behavior. Then, test the behavior by adding it to
|
||||
an object in a scene.
|
||||
</Trans>
|
||||
</DismissableAlertMessage>
|
||||
)}
|
||||
<Spacer />
|
||||
</Column>
|
||||
<Tabs value={currentTab} onChange={this._changeTab}>
|
||||
<Tab label={<Trans>Configuration</Trans>} value="configuration">
|
||||
<Column>
|
||||
<DismissableAlertMessage
|
||||
identifier="events-based-behavior-explanation"
|
||||
kind="info"
|
||||
>
|
||||
This is the configuration of your behavior. Make sure to choose a
|
||||
proper internal name as it's hard to change it later. Enter a
|
||||
description explaining what the behavior is doing to the object.
|
||||
</DismissableAlertMessage>
|
||||
<TextField
|
||||
floatingLabelText={<Trans>Internal Name</Trans>}
|
||||
value={eventsBasedBehavior.getName()}
|
||||
disabled
|
||||
fullWidth
|
||||
/>
|
||||
<SemiControlledTextField
|
||||
commitOnBlur
|
||||
floatingLabelText={<Trans>Name displayed in editor</Trans>}
|
||||
value={eventsBasedBehavior.getFullName()}
|
||||
onChange={text => {
|
||||
eventsBasedBehavior.setFullName(text);
|
||||
this.forceUpdate();
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
<SemiControlledTextField
|
||||
commitOnBlur
|
||||
floatingLabelText={<Trans>Description</Trans>}
|
||||
floatingLabelFixed
|
||||
hintText={
|
||||
<Trans>
|
||||
The description of the behavior should explain what the
|
||||
behavior is doing to the object, and, briefly, how to use it.
|
||||
</Trans>
|
||||
}
|
||||
value={eventsBasedBehavior.getDescription()}
|
||||
onChange={text => {
|
||||
eventsBasedBehavior.setDescription(text);
|
||||
this.forceUpdate();
|
||||
}}
|
||||
multiLine
|
||||
fullWidth
|
||||
rows={3}
|
||||
/>
|
||||
<ObjectTypeSelector
|
||||
floatingLabelText={
|
||||
<Trans>Object on which this behavior can be used</Trans>
|
||||
}
|
||||
project={project}
|
||||
value={eventsBasedBehavior.getObjectType()}
|
||||
onChange={(objectType: string) => {
|
||||
eventsBasedBehavior.setObjectType(objectType);
|
||||
this.forceUpdate();
|
||||
}}
|
||||
allowedObjectTypes={
|
||||
this._allObjectTypes.length === 0
|
||||
? undefined /* Allow anything as the behavior is not used */
|
||||
: this._allObjectTypes.length === 1
|
||||
? [
|
||||
'',
|
||||
this._allObjectTypes[0],
|
||||
] /* Allow only the type of the objects using the behavior */
|
||||
: [
|
||||
'',
|
||||
] /* More than one type of object are using the behavior. Only "any object" can be used on this behavior */
|
||||
}
|
||||
/>
|
||||
{this._allObjectTypes.length > 1 && (
|
||||
<AlertMessage kind="info">
|
||||
<Trans>
|
||||
This behavior is being used by multiple types of objects.
|
||||
Thus, you can't restrict its usage to any particular object
|
||||
type. All the object types using this behavior are listed
|
||||
here:
|
||||
{this._allObjectTypes.join(', ')}
|
||||
</Trans>
|
||||
</AlertMessage>
|
||||
)}
|
||||
{eventsBasedBehavior
|
||||
.getEventsFunctions()
|
||||
.getEventsFunctionsCount() === 0 && (
|
||||
<DismissableAlertMessage
|
||||
identifier="empty-events-based-behavior-explanation"
|
||||
kind="info"
|
||||
>
|
||||
<Trans>
|
||||
Once you're done, close this dialog and start adding some
|
||||
functions to the behavior. Then, test the behavior by adding
|
||||
it to an object in a scene.
|
||||
</Trans>
|
||||
</DismissableAlertMessage>
|
||||
)}
|
||||
<Spacer />
|
||||
</Column>
|
||||
</Tab>
|
||||
<Tab label={<Trans>Properties</Trans>} value="properties">
|
||||
<EventsBasedBehaviorPropertiesEditor
|
||||
project={project}
|
||||
eventsBasedBehavior={eventsBasedBehavior}
|
||||
onPropertiesUpdated={this.props.onPropertiesUpdated}
|
||||
/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -42,6 +42,7 @@ type Props = {|
|
||||
extensionName: string,
|
||||
eventsFunction: gdEventsFunction
|
||||
) => void,
|
||||
onBehaviorEdited?: () => void,
|
||||
initiallyFocusedFunctionName: ?string,
|
||||
initiallyFocusedBehaviorName: ?string,
|
||||
|};
|
||||
@@ -349,24 +350,30 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
|
||||
});
|
||||
};
|
||||
|
||||
_editBehaviorProperties = (
|
||||
editedEventsBasedBehavior: ?gdEventsBasedBehavior
|
||||
) => {
|
||||
this.setState(state => {
|
||||
// If we're closing the properties of a behavior, ensure parameters
|
||||
// are up-to-date in all event functions of the behavior (the object
|
||||
// type might have changed).
|
||||
if (state.editedEventsBasedBehavior && !editedEventsBasedBehavior) {
|
||||
gd.WholeProjectRefactorer.ensureBehaviorEventsFunctionsProperParameters(
|
||||
this.props.eventsFunctionsExtension,
|
||||
state.editedEventsBasedBehavior
|
||||
);
|
||||
}
|
||||
_editBehavior = (editedEventsBasedBehavior: ?gdEventsBasedBehavior) => {
|
||||
this.setState(
|
||||
state => {
|
||||
// If we're closing the properties of a behavior, ensure parameters
|
||||
// are up-to-date in all event functions of the behavior (the object
|
||||
// type might have changed).
|
||||
if (state.editedEventsBasedBehavior && !editedEventsBasedBehavior) {
|
||||
gd.WholeProjectRefactorer.ensureBehaviorEventsFunctionsProperParameters(
|
||||
this.props.eventsFunctionsExtension,
|
||||
state.editedEventsBasedBehavior
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
editedEventsBasedBehavior,
|
||||
};
|
||||
});
|
||||
return {
|
||||
editedEventsBasedBehavior,
|
||||
};
|
||||
},
|
||||
() => {
|
||||
// If we're closing the properties of a behavior, notify parent
|
||||
// that a behavior was edited (to trigger reload of extensions)
|
||||
if (!editedEventsBasedBehavior && this.props.onBehaviorEdited)
|
||||
this.props.onBehaviorEdited();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -556,9 +563,7 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
|
||||
label={<Trans>Edit behavior properties</Trans>}
|
||||
primary
|
||||
onClick={() =>
|
||||
this._editBehaviorProperties(
|
||||
selectedEventsBasedBehavior
|
||||
)
|
||||
this._editBehavior(selectedEventsBasedBehavior)
|
||||
}
|
||||
/>
|
||||
</Line>
|
||||
@@ -597,7 +602,7 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
|
||||
onRenameEventsBasedBehavior={this._makeRenameEventsBasedBehavior(
|
||||
i18n
|
||||
)}
|
||||
onEditProperties={this._editBehaviorProperties}
|
||||
onEditProperties={this._editBehavior}
|
||||
/>
|
||||
</MosaicWindow>
|
||||
),
|
||||
@@ -643,7 +648,7 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
|
||||
project={project}
|
||||
eventsFunctionsExtension={eventsFunctionsExtension}
|
||||
eventsBasedBehavior={editedEventsBasedBehavior}
|
||||
onApply={() => this._editBehaviorProperties(null)}
|
||||
onApply={() => this._editBehavior(null)}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
@@ -301,13 +301,14 @@ export const declareBehaviorPropertiesInstructionAndExpressions = (
|
||||
const setterName = gd.BehaviorCodeGenerator.getBehaviorPropertySetterName(
|
||||
propertyName
|
||||
);
|
||||
const propertyLabel = property.getLabel() || propertyName;
|
||||
|
||||
if (propertyType === 'String' || propertyType === 'Choice') {
|
||||
addObjectAndBehaviorParameters(
|
||||
behaviorMetadata.addStrExpression(
|
||||
propertyName,
|
||||
property.getLabel(),
|
||||
property.getLabel(),
|
||||
propertyLabel,
|
||||
propertyLabel,
|
||||
eventsBasedBehavior.getFullName() || eventsBasedBehavior.getName(),
|
||||
'res/function.png'
|
||||
)
|
||||
@@ -318,8 +319,8 @@ export const declareBehaviorPropertiesInstructionAndExpressions = (
|
||||
addObjectAndBehaviorParameters(
|
||||
behaviorMetadata.addScopedCondition(
|
||||
propertyName,
|
||||
property.getLabel(),
|
||||
i18n._(t`Compare the content of ${property.getLabel()}`),
|
||||
propertyLabel,
|
||||
i18n._(t`Compare the content of ${propertyLabel}`),
|
||||
i18n._(t`Property ${propertyName} of _PARAM0_ is _PARAM2__PARAM3_`),
|
||||
eventsBasedBehavior.getFullName() || eventsBasedBehavior.getName(),
|
||||
'res/function.png'
|
||||
@@ -339,8 +340,8 @@ export const declareBehaviorPropertiesInstructionAndExpressions = (
|
||||
addObjectAndBehaviorParameters(
|
||||
behaviorMetadata.addScopedAction(
|
||||
propertyName,
|
||||
property.getLabel(),
|
||||
i18n._(t`Update the content of ${property.getLabel()}`),
|
||||
propertyLabel,
|
||||
i18n._(t`Update the content of ${propertyLabel}`),
|
||||
i18n._(
|
||||
t`Do _PARAM2__PARAM3_ to property ${propertyName} of _PARAM0_`
|
||||
),
|
||||
@@ -358,8 +359,8 @@ export const declareBehaviorPropertiesInstructionAndExpressions = (
|
||||
addObjectAndBehaviorParameters(
|
||||
behaviorMetadata.addExpression(
|
||||
propertyName,
|
||||
property.getLabel(),
|
||||
property.getLabel(),
|
||||
propertyLabel,
|
||||
propertyLabel,
|
||||
eventsBasedBehavior.getFullName() || eventsBasedBehavior.getName(),
|
||||
'res/function.png'
|
||||
)
|
||||
@@ -370,8 +371,8 @@ export const declareBehaviorPropertiesInstructionAndExpressions = (
|
||||
addObjectAndBehaviorParameters(
|
||||
behaviorMetadata.addScopedCondition(
|
||||
propertyName,
|
||||
property.getLabel(),
|
||||
i18n._(t`Compare the value of ${property.getLabel()}`),
|
||||
propertyLabel,
|
||||
i18n._(t`Compare the value of ${propertyLabel}`),
|
||||
i18n._(t`Property ${propertyName} of _PARAM0_ is _PARAM2__PARAM3_`),
|
||||
eventsBasedBehavior.getFullName() || eventsBasedBehavior.getName(),
|
||||
'res/function.png'
|
||||
@@ -396,8 +397,8 @@ export const declareBehaviorPropertiesInstructionAndExpressions = (
|
||||
addObjectAndBehaviorParameters(
|
||||
behaviorMetadata.addScopedAction(
|
||||
propertyName,
|
||||
property.getLabel(),
|
||||
i18n._(t`Update the value of ${property.getLabel()}`),
|
||||
propertyLabel,
|
||||
i18n._(t`Update the value of ${propertyLabel}`),
|
||||
i18n._(
|
||||
t`Do _PARAM2__PARAM3_ to property ${propertyName} of _PARAM0_`
|
||||
),
|
||||
@@ -415,8 +416,8 @@ export const declareBehaviorPropertiesInstructionAndExpressions = (
|
||||
addObjectAndBehaviorParameters(
|
||||
behaviorMetadata.addScopedCondition(
|
||||
propertyName,
|
||||
property.getLabel(),
|
||||
i18n._(t`Check the value of ${property.getLabel()}`),
|
||||
propertyLabel,
|
||||
i18n._(t`Check the value of ${propertyLabel}`),
|
||||
i18n._(t`Property ${propertyName} of _PARAM0_ is true`),
|
||||
eventsBasedBehavior.getFullName() || eventsBasedBehavior.getName(),
|
||||
'res/function.png',
|
||||
@@ -429,8 +430,8 @@ export const declareBehaviorPropertiesInstructionAndExpressions = (
|
||||
addObjectAndBehaviorParameters(
|
||||
behaviorMetadata.addScopedAction(
|
||||
propertyName,
|
||||
property.getLabel(),
|
||||
i18n._(t`Update the value of ${property.getLabel()}`),
|
||||
propertyLabel,
|
||||
i18n._(t`Update the value of ${propertyLabel}`),
|
||||
i18n._(t`Set property ${propertyName} of _PARAM0_ to _PARAM2_`),
|
||||
eventsBasedBehavior.getFullName() || eventsBasedBehavior.getName(),
|
||||
'res/function.png'
|
||||
|
@@ -232,7 +232,7 @@ function generateBehavior(
|
||||
declareBehaviorPropertiesInstructionAndExpressions(
|
||||
options.i18n,
|
||||
behaviorMetadata,
|
||||
eventsBasedBehavior,
|
||||
eventsBasedBehavior
|
||||
);
|
||||
|
||||
// Declare all the behavior functions
|
||||
|
@@ -33,6 +33,13 @@ export default class EventsFunctionsExtensionEditorWrapper extends BaseEditor {
|
||||
}
|
||||
}
|
||||
|
||||
_onBehaviorEdited = () => {
|
||||
// Immediately trigger the reload/regeneration of extensions
|
||||
// as a change in the properties of a behavior can create changes
|
||||
// in actions/conditions/expressions to manipulate these properties.
|
||||
this.props.onLoadEventsFunctionsExtensions();
|
||||
};
|
||||
|
||||
getEventsFunctionsExtension(): ?gdEventsFunctionsExtension {
|
||||
const { project, eventsFunctionsExtensionName } = this.props;
|
||||
if (
|
||||
@@ -76,6 +83,7 @@ export default class EventsFunctionsExtensionEditorWrapper extends BaseEditor {
|
||||
onCreateEventsFunction={this.props.onCreateEventsFunction}
|
||||
initiallyFocusedFunctionName={this.props.initiallyFocusedFunctionName}
|
||||
initiallyFocusedBehaviorName={this.props.initiallyFocusedBehaviorName}
|
||||
onBehaviorEdited={this._onBehaviorEdited}
|
||||
ref={editor => (this.editor = editor)}
|
||||
/>
|
||||
</div>
|
||||
|
@@ -387,6 +387,26 @@ export const makeTestProject = gd => {
|
||||
);
|
||||
testEventsBasedBehavior.setObjectType('Sprite');
|
||||
|
||||
// Add some properties
|
||||
testEventsBasedBehavior
|
||||
.getPropertyDescriptors()
|
||||
.insertNew('NumberProperty', 0)
|
||||
.setType('Number')
|
||||
.setValue('123')
|
||||
.setLabel('My number property');
|
||||
testEventsBasedBehavior
|
||||
.getPropertyDescriptors()
|
||||
.insertNew('StringProperty', 1)
|
||||
.setType('String')
|
||||
.setValue('Hello World')
|
||||
.setLabel('My string property');
|
||||
testEventsBasedBehavior
|
||||
.getPropertyDescriptors()
|
||||
.insertNew('BooleanProperty', 2)
|
||||
.setType('Boolean')
|
||||
.setValue('true')
|
||||
.setLabel('My boolean property');
|
||||
|
||||
// Add a function
|
||||
const testBehaviorEventsFunction = testEventsBasedBehavior
|
||||
.getEventsFunctions()
|
||||
|
@@ -2086,6 +2086,7 @@ storiesOf('EventsFunctionsExtensionEditor/OptionsEditorDialog', module)
|
||||
));
|
||||
|
||||
storiesOf('EventsBasedBehaviorEditor', module)
|
||||
.addDecorator(paperDecorator)
|
||||
.addDecorator(muiDecorator)
|
||||
.addDecorator(i18nProviderDecorator)
|
||||
.add('default', () => (
|
||||
|
Reference in New Issue
Block a user