Move the object variables editor into the objects editor (easier to find and faster to access) (#3263)

* Also rework the other variables editor dialogs to have the toolbar buttons always at the bottom of the window.
This commit is contained in:
Leo_Red
2021-11-24 18:51:38 +05:30
committed by GitHub
parent 93a57b1a31
commit 0971a4b464
14 changed files with 152 additions and 165 deletions

View File

@@ -64,10 +64,17 @@ export default class GlobalVariableField extends React.Component<
}}
emptyExplanationMessage={
<Trans>
Global variables are variables that are persisted across the
scenes during the game.
Global variables are variables that are shared amongst all the
scenes of the game.
</Trans>
}
emptyExplanationSecondMessage={
<Trans>
For example, you can have a variable called UnlockedLevelsCount
representing the number of levels unlocked by the player.
</Trans>
}
helpPagePath={'/all-features/variables/global-variables'}
onComputeAllVariableNames={onComputeAllVariableNames}
/>
)}

View File

@@ -89,6 +89,20 @@ export default class ObjectVariableField extends React.Component<
title={<Trans>Object Variables</Trans>}
open={this.state.editorOpen}
variablesContainer={variablesContainer}
emptyExplanationMessage={
<Trans>
When you add variables to an object, any instance of the object
put on the scene or created during the game will have these
variables attached to it.
</Trans>
}
emptyExplanationSecondMessage={
<Trans>
For example, you can have a variable called Life representing
the health of the object.
</Trans>
}
helpPagePath={'/all-features/variables/object-variables'}
onComputeAllVariableNames={onComputeAllVariableNames}
onCancel={() => this.setState({ editorOpen: false })}
onApply={() => {

View File

@@ -76,6 +76,7 @@ export default class SceneVariableField extends React.Component<
the current score of the player.
</Trans>
}
helpPagePath={'/all-features/variables/scene-variables'}
onComputeAllVariableNames={onComputeAllVariableNames}
/>
)}

View File

@@ -26,7 +26,6 @@ type Props = {|
instances: Array<gdInitialInstance>,
onEditObjectByName: string => void,
onInstancesModified?: (Array<gdInitialInstance>) => void,
editObjectVariables: (?gdObject) => void,
editInstanceVariables: gdInitialInstance => void,
unsavedChanges?: ?UnsavedChanges,
i18n: I18nType,

View File

@@ -23,6 +23,7 @@ import HotReloadPreviewButton, {
type HotReloadPreviewButtonProps,
} from '../HotReload/HotReloadPreviewButton';
import EffectsList from '../EffectsList';
import VariablesList from '../VariablesList/index';
type Props = {|
open: boolean,
@@ -37,6 +38,7 @@ type Props = {|
// Passed down to object editors:
project: gdProject,
onComputeAllVariableNames: () => Array<string>,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
@@ -123,6 +125,11 @@ const InnerDialog = (props: InnerDialogProps) => {
value={'behaviors'}
key={'behaviors'}
/>
<Tab
label={<Trans>Variables</Trans>}
value={'variables'}
key={'variables'}
/>
<Tab
label={<Trans>Effects</Trans>}
value={'effects'}
@@ -188,6 +195,29 @@ const InnerDialog = (props: InnerDialogProps) => {
onUpdateBehaviorsSharedData={props.onUpdateBehaviorsSharedData}
/>
)}
{currentTab === 'variables' && (
<VariablesList
variablesContainer={props.object.getVariables()}
emptyExplanationMessage={
<Trans>
When you add variables to an object, any instance of the object
put on the scene or created during the game will have these
variables attached to it.
</Trans>
}
emptyExplanationSecondMessage={
<Trans>
For example, you can have a variable called Life representing the
health of the object.
</Trans>
}
helpPagePath={'/all-features/variables/object-variables'}
onSizeUpdated={
forceUpdate /*Force update to ensure dialog is properly positioned*/
}
onComputeAllVariableNames={props.onComputeAllVariableNames}
/>
)}
{currentTab === 'effects' && (
<EffectsList
target="object"

View File

@@ -10,7 +10,6 @@ import SortableVirtualizedItemList from '../UI/SortableVirtualizedItemList';
import Background from '../UI/Background';
import SearchBar from '../UI/SearchBar';
import NewObjectDialog from '../AssetStore/NewObjectDialog';
import VariablesEditorDialog from '../VariablesList/VariablesEditorDialog';
import newNameGenerator from '../Utils/NewNameGenerator';
import Clipboard, { SafeExtractor } from '../Utils/Clipboard';
import Window from '../Utils/Window';
@@ -46,7 +45,6 @@ import {
type ChooseResourceFunction,
} from '../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
import EventsRootVariablesFinder from '../Utils/EventsRootVariablesFinder';
const styles = {
listContainer: {
@@ -78,7 +76,6 @@ const getPasteLabel = isGlobalObject => {
type State = {|
newObjectDialogOpen: boolean,
renamedObjectWithContext: ?ObjectWithContext,
variablesEditedObject: ?gdObject,
searchText: string,
tagEditedObject: ?gdObject,
|};
@@ -123,7 +120,6 @@ export default class ObjectsList extends React.Component<Props, State> {
state = {
newObjectDialogOpen: false,
renamedObjectWithContext: null,
variablesEditedObject: null,
searchText: '',
tagEditedObject: null,
};
@@ -140,7 +136,6 @@ export default class ObjectsList extends React.Component<Props, State> {
this.state.newObjectDialogOpen !== nextState.newObjectDialogOpen ||
this.state.renamedObjectWithContext !==
nextState.renamedObjectWithContext ||
this.state.variablesEditedObject !== nextState.variablesEditedObject ||
this.state.searchText !== nextState.searchText ||
this.state.tagEditedObject !== nextState.tagEditedObject
)
@@ -320,12 +315,6 @@ export default class ObjectsList extends React.Component<Props, State> {
);
};
_editVariables = (object: ?gdObject) => {
this.setState({
variablesEditedObject: object,
});
};
_rename = (objectWithContext: ObjectWithContext, newName: string) => {
const { object } = objectWithContext;
this.setState({
@@ -466,7 +455,7 @@ export default class ObjectsList extends React.Component<Props, State> {
},
{
label: i18n._(t`Edit object variables`),
click: () => this._editVariables(object),
click: () => this.props.onEditObject(object, 'variables'),
},
{
label: i18n._(t`Edit behaviors`),
@@ -640,43 +629,6 @@ export default class ObjectsList extends React.Component<Props, State> {
resourceExternalEditors={resourceExternalEditors}
/>
)}
{this.state.variablesEditedObject && (
<VariablesEditorDialog
open
variablesContainer={
this.state.variablesEditedObject &&
this.state.variablesEditedObject.getVariables()
}
onCancel={() => this._editVariables(null)}
onApply={() => this._editVariables(null)}
title={<Trans>Object Variables</Trans>}
emptyExplanationMessage={
<Trans>
When you add variables to an object, any instance of the object
put on the scene or created during the game will have these
variables attached to it.
</Trans>
}
emptyExplanationSecondMessage={
<Trans>
For example, you can have a variable called Life representing
the health of the object.
</Trans>
}
hotReloadPreviewButtonProps={this.props.hotReloadPreviewButtonProps}
onComputeAllVariableNames={() => {
const variablesEditedObject = this.state.variablesEditedObject;
return layout && variablesEditedObject
? EventsRootVariablesFinder.findAllObjectVariables(
project.getCurrentPlatform(),
project,
layout,
variablesEditedObject
)
: [];
}}
/>
)}
{tagEditedObject && (
<EditTagsDialog
tagsString={tagEditedObject.getTags()}

View File

@@ -72,6 +72,7 @@ export const ExamplesAccordion = ({
<Column>
{examples.map(example => (
<ContributionLine
key={example.name}
shortDescription={example.shortDescription}
fullName={example.name}
previewIconUrl={

View File

@@ -1183,6 +1183,7 @@ export default class ProjectManager extends React.Component<Props, State> {
representing the number of levels unlocked by the player.
</Trans>
}
helpPagePath={'/all-features/variables/global-variables'}
hotReloadPreviewButtonProps={this.props.hotReloadPreviewButtonProps}
onComputeAllVariableNames={() =>
EventsRootVariablesFinder.findAllGlobalVariables(

View File

@@ -34,6 +34,7 @@ export default (props: Props) => {
current score of the player.
</Trans>
}
helpPagePath={'/all-features/variables/scene-variables'}
hotReloadPreviewButtonProps={props.hotReloadPreviewButtonProps}
onComputeAllVariableNames={() =>
EventsRootVariablesFinder.findAllLayoutVariables(

View File

@@ -136,7 +136,6 @@ type State = {|
editedObjectWithContext: ?ObjectWithContext,
editedObjectInitialTab: ?string,
variablesEditedInstance: ?gdInitialInstance,
variablesEditedObject: ?gdObject,
selectedObjectNames: Array<string>,
newObjectInstanceSceneCoordinates: ?[number, number],
@@ -189,7 +188,6 @@ export default class SceneEditor extends React.Component<Props, State> {
editedObjectWithContext: null,
editedObjectInitialTab: 'properties',
variablesEditedInstance: null,
variablesEditedObject: null,
selectedObjectNames: [],
newObjectInstanceSceneCoordinates: null,
editedGroup: null,
@@ -359,10 +357,6 @@ export default class SceneEditor extends React.Component<Props, State> {
this.setState({ variablesEditedInstance: instance });
};
editObjectVariables = (object: ?gdObject) => {
this.setState({ variablesEditedObject: object });
};
editLayoutVariables = (open: boolean = true) => {
this.setState({ layoutVariablesDialogOpen: open });
};
@@ -1000,7 +994,6 @@ export default class SceneEditor extends React.Component<Props, State> {
layout={layout}
instances={selectedInstances}
editInstanceVariables={this.editInstanceVariables}
editObjectVariables={this.editObjectVariables}
onEditObjectByName={this.editObjectByName}
onInstancesModified={instances =>
this.forceUpdateInstancesList()
@@ -1150,7 +1143,9 @@ export default class SceneEditor extends React.Component<Props, State> {
project={project}
layout={layout}
onEditObject={this.props.onEditObject || this.editObject}
onEditObjectVariables={this.editObjectVariables}
onEditObjectVariables={object => {
this.editObject(object, 'variables');
}}
onOpenSceneProperties={this.openSceneProperties}
onOpenSceneVariables={this.editLayoutVariables}
onEditObjectGroup={this.editGroup}
@@ -1194,6 +1189,17 @@ export default class SceneEditor extends React.Component<Props, State> {
resourceSources={resourceSources}
resourceExternalEditors={resourceExternalEditors}
onChooseResource={onChooseResource}
onComputeAllVariableNames={() => {
const { editedObjectWithContext } = this.state;
if (!editedObjectWithContext) return [];
return EventsRootVariablesFinder.findAllObjectVariables(
project.getCurrentPlatform(),
project,
layout,
editedObjectWithContext.object
);
}}
onCancel={() => {
if (this.state.editedObjectWithContext) {
this.reloadResourcesFor(
@@ -1307,10 +1313,11 @@ export default class SceneEditor extends React.Component<Props, State> {
onApply={() => this.editInstanceVariables(null)}
emptyExplanationMessage={
<Trans>
Instance variables will override the default values of the
Instance variables will overwrite the default values of the
variables of the object.
</Trans>
}
helpPagePath={'/all-features/variables/instance-variables'}
title={<Trans>Instance Variables</Trans>}
onEditObjectVariables={() => {
if (!this.instancesSelection.hasSelectedInstances()) {
@@ -1325,7 +1332,7 @@ export default class SceneEditor extends React.Component<Props, State> {
associatedObjectName
);
if (object) {
this.editObjectVariables(object);
this.editObject(object, 'variables');
this.editInstanceVariables(null);
}
}}
@@ -1352,37 +1359,6 @@ export default class SceneEditor extends React.Component<Props, State> {
}}
/>
)}
{!!this.state.variablesEditedObject && (
<VariablesEditorDialog
open
variablesContainer={
this.state.variablesEditedObject &&
this.state.variablesEditedObject.getVariables()
}
onCancel={() => this.editObjectVariables(null)}
onApply={() => this.editObjectVariables(null)}
emptyExplanationMessage={
<Trans>
When you add variables to an object, any instance of the object
put on the scene or created during the game will have these
variables attached to it.
</Trans>
}
title={<Trans>Object Variables</Trans>}
hotReloadPreviewButtonProps={this.props.hotReloadPreviewButtonProps}
onComputeAllVariableNames={() => {
const variablesEditedObject = this.state.variablesEditedObject;
return variablesEditedObject
? EventsRootVariablesFinder.findAllObjectVariables(
project.getCurrentPlatform(),
project,
layout,
variablesEditedObject
)
: [];
}}
/>
)}
{!!this.state.layerRemoveDialogOpen && (
<LayerRemoveDialog
open

View File

@@ -21,6 +21,7 @@ type Props = {|
variablesContainer: gdVariablesContainer,
hotReloadPreviewButtonProps?: ?HotReloadPreviewButtonProps,
onComputeAllVariableNames: () => Array<string>,
helpPagePath: ?string,
|};
const VariablesEditorDialog = ({
@@ -34,6 +35,7 @@ const VariablesEditorDialog = ({
variablesContainer,
hotReloadPreviewButtonProps,
onComputeAllVariableNames,
helpPagePath,
}: Props) => {
const forceUpdate = useForceUpdate();
const onCancelChanges = useSerializableObjectCancelableEditor({
@@ -79,6 +81,8 @@ const VariablesEditorDialog = ({
) : null,
]}
title={title}
flexBody
fullHeight
>
<VariablesList
commitVariableValueOnBlur={
@@ -95,6 +99,7 @@ const VariablesEditorDialog = ({
forceUpdate /*Force update to ensure dialog is properly positioned*/
}
onComputeAllVariableNames={onComputeAllVariableNames}
helpPagePath={helpPagePath}
/>
</Dialog>
);

View File

@@ -3,11 +3,9 @@ import * as React from 'react';
import flatten from 'lodash/flatten';
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
import { mapFor } from '../Utils/MapFor';
import EmptyMessage from '../UI/EmptyMessage';
import newNameGenerator from '../Utils/NewNameGenerator';
import VariableRow from './VariableRow';
import EditVariableRow from './EditVariableRow';
import styles from './styles';
import {
getInitialSelection,
hasSelection,
@@ -21,13 +19,25 @@ import {
unserializeFromJSObject,
} from '../Utils/Serializer';
import { type VariableOrigin } from './VariablesList.flow';
import HelpButton from '../UI/HelpButton';
import { EmptyPlaceholder } from '../UI/EmptyPlaceholder';
import { Trans } from '@lingui/macro';
import Text from '../UI/Text';
import { Column } from '../UI/Grid';
import ScrollView from '../UI/ScrollView';
const gd: libGDevelop = global.gd;
const styles = {
variablesContainer: {
// Somehow ensure the scrollbar is not shown when not needed.
marginBottom: 10,
},
};
const SortableVariableRow = SortableElement(VariableRow);
const SortableAddVariableRow = SortableElement(EditVariableRow);
const SortableVariablesListBody = SortableContainer(({ children }) => (
<div>{children}</div>
<div style={styles.variablesContainer}>{children}</div>
));
SortableVariablesListBody.muiName = 'TableBody';
@@ -41,6 +51,7 @@ type Props = {|
emptyExplanationSecondMessage?: React.Node,
onSizeUpdated?: () => void,
commitVariableValueOnBlur?: boolean,
helpPagePath?: ?string,
|};
type State = {|
nameErrors: { [string]: string },
@@ -356,27 +367,6 @@ export default class VariablesList extends React.Component<Props, State> {
);
}
_renderEmpty() {
return (
!!this.props.emptyExplanationMessage && (
<div>
<EmptyMessage
style={styles.emptyExplanation}
messageStyle={styles.emptyExplanationMessage}
>
{this.props.emptyExplanationMessage}
</EmptyMessage>
<EmptyMessage
style={styles.emptyExplanation}
messageStyle={styles.emptyExplanationMessage}
>
{this.props.emptyExplanationSecondMessage}
</EmptyMessage>
</div>
)
);
}
render() {
const { variablesContainer, inheritedVariablesContainer } = this.props;
if (!variablesContainer) return null;
@@ -417,32 +407,6 @@ export default class VariablesList extends React.Component<Props, State> {
}
);
const editRow = (
<SortableAddVariableRow
index={0}
key={'add-variable-row'}
disabled
onAdd={() => {
const variable = new gd.Variable();
variable.setString('');
const name = newNameGenerator('Variable', name =>
inheritedVariablesContainer
? inheritedVariablesContainer.has(name) ||
variablesContainer.has(name)
: variablesContainer.has(name)
);
variablesContainer.insert(name, variable, variablesContainer.count());
this.forceUpdate();
if (this.props.onSizeUpdated) this.props.onSizeUpdated();
}}
onCopy={this.copySelection}
onPaste={this.paste}
onDeleteSelection={this.deleteSelection}
hasSelection={hasSelection(this.state.selectedVariables)}
hasClipboard={Clipboard.has(CLIPBOARD_KIND)}
/>
);
// Put all variables in the **same** array so that if a variable that was shown
// as inherited is redefined by the user, React can reconcile the variable rows
// (VariableRow going from containerInheritedVariablesTree array to
@@ -453,20 +417,63 @@ export default class VariablesList extends React.Component<Props, State> {
];
return (
<SortableVariablesListBody
variablesContainer={this.props.variablesContainer}
onSortEnd={({ oldIndex, newIndex }) => {
this.props.variablesContainer.move(oldIndex, newIndex);
this.forceUpdate();
}}
helperClass="sortable-helper"
useDragHandle
lockToContainerEdges
>
{allVariables}
{!allVariables.length && this._renderEmpty()}
{editRow}
</SortableVariablesListBody>
<Column noMargin expand useFullHeight>
{allVariables.length ? (
<ScrollView autoHideScrollbar>
<SortableVariablesListBody
variablesContainer={this.props.variablesContainer}
onSortEnd={({ oldIndex, newIndex }) => {
this.props.variablesContainer.move(oldIndex, newIndex);
this.forceUpdate();
}}
helperClass="sortable-helper"
useDragHandle
lockToContainerEdges
>
{allVariables}
</SortableVariablesListBody>
</ScrollView>
) : !!this.props.emptyExplanationMessage ? (
<Column noMargin expand justifyContent="center">
<EmptyPlaceholder
renderButtons={() => (
<HelpButton helpPagePath={this.props.helpPagePath} />
)}
>
<Text>
<Trans>{this.props.emptyExplanationMessage}</Trans>
</Text>
<Text>
<Trans>{this.props.emptyExplanationSecondMessage}</Trans>
</Text>
</EmptyPlaceholder>
</Column>
) : null}
<EditVariableRow
onAdd={() => {
const variable = new gd.Variable();
variable.setString('');
const name = newNameGenerator('Variable', name =>
inheritedVariablesContainer
? inheritedVariablesContainer.has(name) ||
variablesContainer.has(name)
: variablesContainer.has(name)
);
variablesContainer.insert(
name,
variable,
variablesContainer.count()
);
this.forceUpdate();
if (this.props.onSizeUpdated) this.props.onSizeUpdated();
}}
onCopy={this.copySelection}
onPaste={this.paste}
onDeleteSelection={this.deleteSelection}
hasSelection={hasSelection(this.state.selectedVariables)}
hasClipboard={Clipboard.has(CLIPBOARD_KIND)}
/>
</Column>
);
}
}

View File

@@ -17,12 +17,6 @@ export default {
flexShrink: 0,
},
indentIconColor: '#DDD', //TODO: Use theme color instead
emptyExplanation: {
justifyContent: 'flex-start',
},
emptyExplanationMessage: {
textAlign: 'left',
},
fadedButton: {
opacity: 0.7,
},

View File

@@ -3876,7 +3876,6 @@ storiesOf('InstancePropertiesEditor', module)
layout={testProject.testLayout}
instances={[testProject.testLayoutInstance1]}
editInstanceVariables={action('edit instance variables')}
editObjectVariables={action('edit object variables')}
onEditObjectByName={action('edit object')}
/>
</SerializedObjectDisplay>