Skip to content

Commit 72b69e4

Browse files
authored
Add a sprite editor for custom objects with a locked animation list (#4719)
1 parent 2f11cd6 commit 72b69e4

File tree

5 files changed

+156
-62
lines changed

5 files changed

+156
-62
lines changed

newIDE/app/src/ObjectEditor/Editors/CustomObjectPropertiesEditor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ const CustomObjectPropertiesEditor = (props: Props) => {
9797
const childObjectConfiguration = customObjectConfiguration.getChildObjectConfiguration(
9898
childObject.getName()
9999
);
100-
const editorConfiguration = ObjectsEditorService.getEditorConfiguration(
100+
const editorConfiguration = ObjectsEditorService.getEditorConfigurationForCustomObject(
101101
project,
102102
childObjectConfiguration.getType()
103103
);

newIDE/app/src/ObjectEditor/Editors/SpriteEditor/index.js

Lines changed: 112 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import FlatButton from '../../../UI/FlatButton';
1212
import RaisedButton from '../../../UI/RaisedButton';
1313
import { mapFor } from '../../../Utils/MapFor';
1414
import SemiControlledTextField from '../../../UI/SemiControlledTextField';
15+
import Text from '../../../UI/Text';
1516
import Dialog from '../../../UI/Dialog';
1617
import HelpButton from '../../../UI/HelpButton';
1718
import MiniToolbar, { MiniToolbarText } from '../../../UI/MiniToolbar';
@@ -30,7 +31,7 @@ import {
3031
} from './Utils/SpriteObjectHelper';
3132
import { type EditorProps } from '../EditorProps.flow';
3233
import { type ResourceManagementProps } from '../../../ResourcesList/ResourceSource';
33-
import { Column } from '../../../UI/Grid';
34+
import { Column, Line } from '../../../UI/Grid';
3435
import { ResponsiveLineStackLayout } from '../../../UI/Layout';
3536
import ScrollView from '../../../UI/ScrollView';
3637
import Checkbox from '../../../UI/Checkbox';
@@ -58,6 +59,7 @@ type AnimationProps = {|
5859
) => void,
5960
objectName: string,
6061
onChangeName: string => void,
62+
isAnimationListLocked: boolean,
6163
|};
6264

6365
class Animation extends React.Component<AnimationProps, void> {
@@ -75,50 +77,62 @@ class Animation extends React.Component<AnimationProps, void> {
7577
onReplaceDirection,
7678
objectName,
7779
onChangeName,
80+
isAnimationListLocked,
7881
} = this.props;
7982

8083
const animationName = animation.getName();
8184
return (
82-
<div>
83-
<MiniToolbar noPadding>
84-
<DragHandle />
85-
<MiniToolbarText>Animation #{id} </MiniToolbarText>
86-
<Column expand>
87-
<SemiControlledTextField
88-
commitOnBlur
89-
margin="none"
90-
value={animation.getName()}
91-
translatableHintText={t`Optional animation name`}
92-
onChange={text => onChangeName(text)}
93-
fullWidth
94-
/>
95-
</Column>
96-
<IconButton size="small" onClick={onRemove}>
97-
<Delete />
98-
</IconButton>
99-
</MiniToolbar>
100-
{mapFor(0, animation.getDirectionsCount(), i => {
101-
const direction = animation.getDirection(i);
102-
return (
103-
<SpritesList
104-
direction={direction}
105-
key={i}
106-
project={project}
107-
resourcesLoader={resourcesLoader}
108-
resourceManagementProps={resourceManagementProps}
109-
onSpriteContextMenu={onSpriteContextMenu}
110-
selectedSprites={selectedSprites}
111-
onSelectSprite={onSelectSprite}
112-
onReplaceByDirection={newDirection =>
113-
onReplaceDirection(i, newDirection)
114-
}
115-
objectName={objectName}
116-
animationName={animationName}
117-
onChangeName={onChangeName}
118-
/>
119-
);
120-
})}
121-
</div>
85+
<Line expand>
86+
<Column expand noMargin>
87+
{isAnimationListLocked && (
88+
<Column expand noMargin>
89+
<Text size="block-title">{animation.getName()}</Text>
90+
</Column>
91+
)}
92+
{!isAnimationListLocked && (
93+
<MiniToolbar noPadding>
94+
<DragHandle />
95+
<MiniToolbarText>
96+
{<Trans>Animation #{id}</Trans>}
97+
</MiniToolbarText>
98+
<Column expand>
99+
<SemiControlledTextField
100+
commitOnBlur
101+
margin="none"
102+
value={animation.getName()}
103+
translatableHintText={t`Optional animation name`}
104+
onChange={text => onChangeName(text)}
105+
fullWidth
106+
/>
107+
</Column>
108+
<IconButton size="small" onClick={onRemove}>
109+
<Delete />
110+
</IconButton>
111+
</MiniToolbar>
112+
)}
113+
{mapFor(0, animation.getDirectionsCount(), i => {
114+
const direction = animation.getDirection(i);
115+
return (
116+
<SpritesList
117+
direction={direction}
118+
key={i}
119+
project={project}
120+
resourcesLoader={resourcesLoader}
121+
resourceManagementProps={resourceManagementProps}
122+
onSpriteContextMenu={onSpriteContextMenu}
123+
selectedSprites={selectedSprites}
124+
onSelectSprite={onSelectSprite}
125+
onReplaceByDirection={newDirection =>
126+
onReplaceDirection(i, newDirection)
127+
}
128+
objectName={objectName}
129+
animationName={animationName}
130+
onChangeName={onChangeName}
131+
/>
132+
);
133+
})}
134+
</Column>
135+
</Line>
122136
);
123137
}
124138
}
@@ -140,6 +154,7 @@ const SortableAnimationsList = SortableContainer(
140154
selectedSprites,
141155
onSelectSprite,
142156
onReplaceDirection,
157+
isAnimationListLocked,
143158
}) => {
144159
// Note that it's important to have <ScrollView> *inside* this
145160
// component, otherwise the sortable list won't work (because the
@@ -150,6 +165,7 @@ const SortableAnimationsList = SortableContainer(
150165
const animation = spriteConfiguration.getAnimation(i);
151166
return (
152167
<SortableAnimation
168+
isAnimationListLocked={isAnimationListLocked}
153169
key={i}
154170
index={i}
155171
id={i}
@@ -183,6 +199,7 @@ type AnimationsListContainerProps = {|
183199
onSizeUpdated: () => void,
184200
objectName: string,
185201
onObjectUpdated?: () => void,
202+
isAnimationListLocked: boolean,
186203
|};
187204

188205
type AnimationsListContainerState = {|
@@ -322,6 +339,7 @@ class AnimationsListContainer extends React.Component<
322339
<React.Fragment>
323340
<SpacedDismissableTutorialMessage />
324341
<SortableAnimationsList
342+
isAnimationListLocked={this.props.isAnimationListLocked}
325343
spriteConfiguration={this.props.spriteConfiguration}
326344
objectName={this.props.objectName}
327345
helperClass="sortable-helper"
@@ -345,12 +363,14 @@ class AnimationsListContainer extends React.Component<
345363
noColumnMargin
346364
>
347365
{this.props.extraBottomTools}
348-
<RaisedButton
349-
label={<Trans>Add an animation</Trans>}
350-
primary
351-
onClick={this.addAnimation}
352-
icon={<Add />}
353-
/>
366+
{!this.props.isAnimationListLocked && (
367+
<RaisedButton
368+
label={<Trans>Add an animation</Trans>}
369+
primary
370+
onClick={this.addAnimation}
371+
icon={<Add />}
372+
/>
373+
)}
354374
</ResponsiveLineStackLayout>
355375
</Column>
356376
<ContextMenu
@@ -375,14 +395,41 @@ class AnimationsListContainer extends React.Component<
375395
}
376396
}
377397

378-
export default function SpriteEditor({
398+
export function LockedSpriteEditor({
379399
objectConfiguration,
380400
project,
381401
resourceManagementProps,
382402
onSizeUpdated,
383403
onObjectUpdated,
384404
objectName,
385405
}: EditorProps) {
406+
return (
407+
<SpriteEditor
408+
isAnimationListLocked
409+
objectConfiguration={objectConfiguration}
410+
project={project}
411+
resourceManagementProps={resourceManagementProps}
412+
onSizeUpdated={onSizeUpdated}
413+
onObjectUpdated={onObjectUpdated}
414+
objectName={objectName}
415+
/>
416+
);
417+
}
418+
419+
type SpriteEditorProps = {|
420+
...EditorProps,
421+
isAnimationListLocked?: boolean,
422+
|};
423+
424+
export default function SpriteEditor({
425+
objectConfiguration,
426+
project,
427+
resourceManagementProps,
428+
onSizeUpdated,
429+
onObjectUpdated,
430+
objectName,
431+
isAnimationListLocked = false,
432+
}: SpriteEditorProps) {
386433
const [pointsEditorOpen, setPointsEditorOpen] = React.useState(false);
387434
const [advancedOptionsOpen, setAdvancedOptionsOpen] = React.useState(false);
388435
const [
@@ -395,6 +442,7 @@ export default function SpriteEditor({
395442
return (
396443
<>
397444
<AnimationsListContainer
445+
isAnimationListLocked={isAnimationListLocked}
398446
spriteConfiguration={spriteConfiguration}
399447
resourcesLoader={ResourcesLoader}
400448
resourceManagementProps={resourceManagementProps}
@@ -410,18 +458,22 @@ export default function SpriteEditor({
410458
onClick={() => setCollisionMasksEditorOpen(true)}
411459
disabled={spriteConfiguration.getAnimationsCount() === 0}
412460
/>
413-
<RaisedButton
414-
label={<Trans>Edit points</Trans>}
415-
primary={false}
416-
onClick={() => setPointsEditorOpen(true)}
417-
disabled={spriteConfiguration.getAnimationsCount() === 0}
418-
/>
419-
<FlatButton
420-
label={<Trans>Advanced options</Trans>}
421-
primary={false}
422-
onClick={() => setAdvancedOptionsOpen(true)}
423-
disabled={spriteConfiguration.getAnimationsCount() === 0}
424-
/>
461+
{!isAnimationListLocked && (
462+
<RaisedButton
463+
label={<Trans>Edit points</Trans>}
464+
primary={false}
465+
onClick={() => setPointsEditorOpen(true)}
466+
disabled={spriteConfiguration.getAnimationsCount() === 0}
467+
/>
468+
)}
469+
{!isAnimationListLocked && (
470+
<FlatButton
471+
label={<Trans>Advanced options</Trans>}
472+
primary={false}
473+
onClick={() => setAdvancedOptionsOpen(true)}
474+
disabled={spriteConfiguration.getAnimationsCount() === 0}
475+
/>
476+
)}
425477
</ResponsiveLineStackLayout>
426478
}
427479
/>

newIDE/app/src/ObjectEditor/ObjectsEditorService.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import TextEditor from './Editors/TextEditor';
33
import TiledSpriteEditor from './Editors/TiledSpriteEditor';
44
import PanelSpriteEditor from './Editors/PanelSpriteEditor';
5-
import SpriteEditor from './Editors/SpriteEditor';
5+
import SpriteEditor, { LockedSpriteEditor } from './Editors/SpriteEditor';
66
import EmptyEditor from './Editors/EmptyEditor';
77
import ShapePainterEditor from './Editors/ShapePainterEditor';
88
import ParticleEmitterEditor from './Editors/ParticleEmitterEditor';
@@ -35,6 +35,15 @@ const ObjectsEditorService = {
3535
helpPagePath: '',
3636
});
3737
},
38+
getEditorConfigurationForCustomObject(
39+
project: gdProject,
40+
objectType: string
41+
) {
42+
if (this.editorConfigurationsSpecificToCustomObject[objectType]) {
43+
return this.editorConfigurationsSpecificToCustomObject[objectType];
44+
}
45+
return this.getEditorConfiguration(project, objectType);
46+
},
3847
registerEditorConfiguration: function(
3948
objectType: string,
4049
editorConfiguration: any
@@ -105,6 +114,16 @@ const ObjectsEditorService = {
105114
helpPagePath: options.helpPagePath,
106115
};
107116
},
117+
editorConfigurationsSpecificToCustomObject: {
118+
Sprite: {
119+
component: LockedSpriteEditor,
120+
createNewObject: (): gdSpriteObject => new gd.SpriteObject(),
121+
castToObjectType: (
122+
objectConfiguration: gdObjectConfiguration
123+
): gdSpriteObject => gd.asSpriteConfiguration(objectConfiguration),
124+
helpPagePath: '/objects/sprite',
125+
},
126+
},
108127
editorConfigurations: {
109128
Sprite: {
110129
component: SpriteEditor,

newIDE/app/src/fixtures/TestProject.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ export const makeTestProject = (gd /*: libGDevelop */) /*: TestProject */ => {
280280
}
281281
{
282282
const animation = new gd.Animation();
283+
animation.setName('My animation');
283284
animation.setDirectionsCount(1);
284285
const sprite1 = new gd.Sprite();
285286
sprite1.setImageName('icon128.png');
@@ -305,6 +306,7 @@ export const makeTestProject = (gd /*: libGDevelop */) /*: TestProject */ => {
305306
}
306307
{
307308
const animation = new gd.Animation();
309+
animation.setName('My other animation');
308310
animation.setDirectionsCount(1);
309311
const sprite1 = new gd.Sprite();
310312
sprite1.setImageName('icon128.png');

newIDE/app/src/stories/componentStories/ObjectEditor/SpriteEditor.stories.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,27 @@ export const Default = () => (
4343
</SerializedObjectDisplay>
4444
);
4545

46+
export const AnimationLocked = () => (
47+
<SerializedObjectDisplay object={testProject.spriteObjectConfiguration}>
48+
<DragAndDropContextProvider>
49+
<SpriteEditor
50+
isAnimationListLocked
51+
objectConfiguration={testProject.spriteObjectConfiguration}
52+
project={testProject.project}
53+
resourceManagementProps={{
54+
getStorageProvider: () => emptyStorageProvider,
55+
onFetchNewlyAddedResources: async () => {},
56+
resourceSources: [],
57+
onChooseResource: () => Promise.reject('Unimplemented'),
58+
resourceExternalEditors: fakeResourceExternalEditors,
59+
}}
60+
onSizeUpdated={() => {}}
61+
objectName="FakeObjectName"
62+
/>
63+
</DragAndDropContextProvider>
64+
</SerializedObjectDisplay>
65+
);
66+
4667
export const Points = () => (
4768
<SerializedObjectDisplay object={testProject.spriteObjectConfiguration}>
4869
<DragAndDropContextProvider>

0 commit comments

Comments
 (0)