mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Remember collapsed state of object behaviors configuration panels (#5641)
This commit is contained in:
@@ -31,10 +31,10 @@ namespace gd {
|
||||
*/
|
||||
class GD_CORE_API BehaviorConfigurationContainer {
|
||||
public:
|
||||
|
||||
BehaviorConfigurationContainer(){};
|
||||
BehaviorConfigurationContainer(const gd::String& name_, const gd::String& type_)
|
||||
: name(name_), type(type_){};
|
||||
BehaviorConfigurationContainer() : folded(false){};
|
||||
BehaviorConfigurationContainer(const gd::String& name_,
|
||||
const gd::String& type_)
|
||||
: name(name_), type(type_), folded(false){};
|
||||
virtual ~BehaviorConfigurationContainer();
|
||||
virtual BehaviorConfigurationContainer* Clone() const { return new BehaviorConfigurationContainer(*this); }
|
||||
|
||||
@@ -61,7 +61,7 @@ class GD_CORE_API BehaviorConfigurationContainer {
|
||||
/**
|
||||
* \brief Called when the IDE wants to know about the custom properties of the
|
||||
* behavior.
|
||||
*
|
||||
*
|
||||
* \return a std::map with properties names as key.
|
||||
* \see gd::PropertyDescriptor
|
||||
*/
|
||||
@@ -104,6 +104,17 @@ class GD_CORE_API BehaviorConfigurationContainer {
|
||||
const gd::SerializerElement& GetContent() const { return content; };
|
||||
gd::SerializerElement& GetContent() { return content; };
|
||||
|
||||
/**
|
||||
* \brief Set if the behavior configuration panel should be folded in the UI.
|
||||
*/
|
||||
void SetFolded(bool fold = true) { folded = fold; }
|
||||
|
||||
/**
|
||||
* \brief True if the behavior configuration panel should be folded in the UI.
|
||||
*/
|
||||
bool IsFolded() const { return folded; }
|
||||
|
||||
|
||||
protected:
|
||||
/**
|
||||
* \brief Called when the IDE wants to know about the custom properties of the
|
||||
@@ -148,6 +159,7 @@ protected:
|
||||
///< in the form "ExtensionName::BehaviorTypeName"
|
||||
|
||||
gd::SerializerElement content; // Storage for the behavior properties
|
||||
bool folded;
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -573,6 +573,9 @@ interface Behavior {
|
||||
void SerializeTo([Ref] SerializerElement element);
|
||||
void UnserializeFrom([Const, Ref] SerializerElement element);
|
||||
|
||||
boolean IsFolded();
|
||||
void SetFolded(boolean folded);
|
||||
|
||||
boolean IsDefaultBehavior();
|
||||
};
|
||||
|
||||
|
@@ -10,6 +10,8 @@ declare class gdBehavior {
|
||||
initializeContent(): void;
|
||||
serializeTo(element: gdSerializerElement): void;
|
||||
unserializeFrom(element: gdSerializerElement): void;
|
||||
isFolded(): boolean;
|
||||
setFolded(folded: boolean): void;
|
||||
isDefaultBehavior(): boolean;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
|
@@ -62,6 +62,210 @@ export const useBehaviorOverridingAlertDialog = () => {
|
||||
};
|
||||
};
|
||||
|
||||
type BehaviorConfigurationEditorInterface = {||};
|
||||
type BehaviorConfigurationEditorProps = {|
|
||||
project: gdProject,
|
||||
object: gdObject,
|
||||
behavior: gdBehavior,
|
||||
resourceManagementProps: ResourceManagementProps,
|
||||
onBehaviorsUpdated: () => void,
|
||||
onChangeBehaviorName: (behavior: gdBehavior, newName: string) => void,
|
||||
onRemoveBehavior: (behaviorName: string) => void,
|
||||
copyBehavior: (behaviorName: string) => void,
|
||||
canPasteBehaviors: boolean,
|
||||
pasteBehaviors: () => Promise<void>,
|
||||
openExtension: (behaviorType: string) => void,
|
||||
|};
|
||||
|
||||
const BehaviorConfigurationEditor = React.forwardRef<
|
||||
BehaviorConfigurationEditorProps,
|
||||
BehaviorConfigurationEditorInterface
|
||||
>(
|
||||
(
|
||||
{
|
||||
project,
|
||||
object,
|
||||
behavior,
|
||||
resourceManagementProps,
|
||||
onBehaviorsUpdated,
|
||||
onChangeBehaviorName,
|
||||
onRemoveBehavior,
|
||||
copyBehavior,
|
||||
canPasteBehaviors,
|
||||
pasteBehaviors,
|
||||
openExtension,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { values } = React.useContext(PreferencesContext);
|
||||
const forceUpdate = useForceUpdate();
|
||||
const behaviorName = behavior.getName();
|
||||
const behaviorTypeName = behavior.getTypeName();
|
||||
|
||||
if (behavior.isDefaultBehavior()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const expanded = !behavior.isFolded();
|
||||
|
||||
const behaviorMetadata = gd.MetadataProvider.getBehaviorMetadata(
|
||||
gd.JsPlatform.get(),
|
||||
behaviorTypeName
|
||||
);
|
||||
if (gd.MetadataProvider.isBadBehaviorMetadata(behaviorMetadata)) {
|
||||
return (
|
||||
<Accordion
|
||||
defaultExpanded
|
||||
id={`behavior-parameters-${behaviorName}`}
|
||||
ref={ref}
|
||||
>
|
||||
<AccordionHeader
|
||||
actions={[
|
||||
<IconButton
|
||||
key="delete"
|
||||
onClick={ev => {
|
||||
ev.stopPropagation();
|
||||
onRemoveBehavior(behaviorName);
|
||||
}}
|
||||
>
|
||||
<Trash />
|
||||
</IconButton>,
|
||||
]}
|
||||
>
|
||||
<MiniToolbarText firstChild>
|
||||
<Trans>Unknown behavior</Trans>{' '}
|
||||
</MiniToolbarText>
|
||||
<Column noMargin expand>
|
||||
<TextField margin="none" value={behaviorName} disabled />
|
||||
</Column>
|
||||
</AccordionHeader>
|
||||
<AccordionBody>
|
||||
<EmptyMessage>
|
||||
<Trans>
|
||||
This behavior is unknown. It might be a behavior that was
|
||||
defined in an extension and that was later removed. You should
|
||||
delete it.
|
||||
</Trans>
|
||||
</EmptyMessage>
|
||||
</AccordionBody>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
|
||||
const BehaviorComponent = BehaviorsEditorService.getEditor(
|
||||
behaviorTypeName
|
||||
);
|
||||
const tutorialIds = getBehaviorTutorialIds(behaviorTypeName);
|
||||
const enabledTutorialIds = tutorialIds.filter(
|
||||
tutorialId => !values.hiddenTutorialHints[tutorialId]
|
||||
);
|
||||
const iconUrl = behaviorMetadata.getIconFilename();
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
expanded={expanded}
|
||||
onChange={(_, newExpanded) => {
|
||||
behavior.setFolded(!newExpanded);
|
||||
forceUpdate();
|
||||
}}
|
||||
id={`behavior-parameters-${behaviorName}`}
|
||||
ref={ref}
|
||||
>
|
||||
<AccordionHeader
|
||||
actions={[
|
||||
<HelpIcon
|
||||
key="help"
|
||||
size="small"
|
||||
helpPagePath={behaviorMetadata.getHelpPath()}
|
||||
/>,
|
||||
<ElementWithMenu
|
||||
key="menu"
|
||||
element={
|
||||
<IconButton size="small">
|
||||
<ThreeDotsMenu />
|
||||
</IconButton>
|
||||
}
|
||||
buildMenuTemplate={(i18n: I18nType) => [
|
||||
{
|
||||
label: i18n._(t`Delete`),
|
||||
click: () => onRemoveBehavior(behaviorName),
|
||||
},
|
||||
{
|
||||
label: i18n._(t`Copy`),
|
||||
click: () => copyBehavior(behaviorName),
|
||||
},
|
||||
{
|
||||
label: i18n._(t`Paste`),
|
||||
click: pasteBehaviors,
|
||||
enabled: canPasteBehaviors,
|
||||
},
|
||||
...(project.hasEventsBasedBehavior(behaviorTypeName)
|
||||
? [
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: i18n._(t`Edit this behavior`),
|
||||
click: () => openExtension(behaviorTypeName),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>,
|
||||
]}
|
||||
>
|
||||
{iconUrl ? (
|
||||
<IconContainer
|
||||
src={iconUrl}
|
||||
alt={behaviorMetadata.getFullName()}
|
||||
size={20}
|
||||
/>
|
||||
) : null}
|
||||
<Column expand>
|
||||
<TextField
|
||||
value={behaviorName}
|
||||
translatableHintText={t`Behavior name`}
|
||||
margin="none"
|
||||
fullWidth
|
||||
disabled
|
||||
onChange={(e, text) => onChangeBehaviorName(behavior, text)}
|
||||
id={`behavior-${behaviorName}-name-text-field`}
|
||||
/>
|
||||
</Column>
|
||||
</AccordionHeader>
|
||||
<AccordionBody>
|
||||
<Column
|
||||
expand
|
||||
noMargin
|
||||
// Avoid Physics2 behavior overflow on small screens
|
||||
noOverflowParent
|
||||
>
|
||||
{enabledTutorialIds.length ? (
|
||||
<Line>
|
||||
<ColumnStackLayout expand>
|
||||
{tutorialIds.map(tutorialId => (
|
||||
<DismissableTutorialMessage
|
||||
key={tutorialId}
|
||||
tutorialId={tutorialId}
|
||||
/>
|
||||
))}
|
||||
</ColumnStackLayout>
|
||||
</Line>
|
||||
) : null}
|
||||
<Line>
|
||||
<BehaviorComponent
|
||||
behavior={behavior}
|
||||
project={project}
|
||||
object={object}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
onBehaviorUpdated={onBehaviorsUpdated}
|
||||
/>
|
||||
</Line>
|
||||
</Column>
|
||||
</AccordionBody>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
eventsFunctionsExtension?: gdEventsFunctionsExtension,
|
||||
@@ -82,8 +286,8 @@ const BehaviorsEditor = (props: Props) => {
|
||||
justAddedBehaviorName,
|
||||
setJustAddedBehaviorName,
|
||||
] = React.useState<?string>(null);
|
||||
const justAddedBehaviorAccordionElement = React.useRef(
|
||||
(null: ?React$Component<any, any>)
|
||||
const justAddedBehaviorAccordionElement = React.useRef<?BehaviorConfigurationEditorInterface>(
|
||||
null
|
||||
);
|
||||
|
||||
React.useEffect(
|
||||
@@ -125,8 +329,6 @@ const BehaviorsEditor = (props: Props) => {
|
||||
.filter(behavior => !behavior.isDefaultBehavior());
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const { values } = React.useContext(PreferencesContext);
|
||||
|
||||
const addBehavior = React.useCallback(
|
||||
(type: string, defaultName: string) => {
|
||||
const wasBehaviorAdded = addBehaviorToObject(
|
||||
@@ -412,68 +614,6 @@ const BehaviorsEditor = (props: Props) => {
|
||||
<ScrollView ref={scrollView}>
|
||||
{allVisibleBehaviors.map((behavior, index) => {
|
||||
const behaviorName = behavior.getName();
|
||||
const behaviorTypeName = behavior.getTypeName();
|
||||
|
||||
if (behavior.isDefaultBehavior()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const behaviorMetadata = gd.MetadataProvider.getBehaviorMetadata(
|
||||
gd.JsPlatform.get(),
|
||||
behaviorTypeName
|
||||
);
|
||||
if (gd.MetadataProvider.isBadBehaviorMetadata(behaviorMetadata)) {
|
||||
return (
|
||||
<Accordion
|
||||
key={behaviorName}
|
||||
defaultExpanded
|
||||
id={`behavior-parameters-${behaviorName}`}
|
||||
>
|
||||
<AccordionHeader
|
||||
actions={[
|
||||
<IconButton
|
||||
key="delete"
|
||||
onClick={ev => {
|
||||
ev.stopPropagation();
|
||||
onRemoveBehavior(behaviorName);
|
||||
}}
|
||||
>
|
||||
<Trash />
|
||||
</IconButton>,
|
||||
]}
|
||||
>
|
||||
<MiniToolbarText firstChild>
|
||||
<Trans>Unknown behavior</Trans>{' '}
|
||||
</MiniToolbarText>
|
||||
<Column noMargin expand>
|
||||
<TextField
|
||||
margin="none"
|
||||
value={behaviorName}
|
||||
disabled
|
||||
/>
|
||||
</Column>
|
||||
</AccordionHeader>
|
||||
<AccordionBody>
|
||||
<EmptyMessage>
|
||||
<Trans>
|
||||
This behavior is unknown. It might be a behavior that
|
||||
was defined in an extension and that was later
|
||||
removed. You should delete it.
|
||||
</Trans>
|
||||
</EmptyMessage>
|
||||
</AccordionBody>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
|
||||
const BehaviorComponent = BehaviorsEditorService.getEditor(
|
||||
behaviorTypeName
|
||||
);
|
||||
const tutorialIds = getBehaviorTutorialIds(behaviorTypeName);
|
||||
const enabledTutorialIds = tutorialIds.filter(
|
||||
tutorialId => !values.hiddenTutorialHints[tutorialId]
|
||||
);
|
||||
const iconUrl = behaviorMetadata.getIconFilename();
|
||||
|
||||
const ref =
|
||||
justAddedBehaviorName === behaviorName
|
||||
@@ -481,107 +621,21 @@ const BehaviorsEditor = (props: Props) => {
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
key={behaviorName}
|
||||
defaultExpanded
|
||||
id={`behavior-parameters-${behaviorName}`}
|
||||
<BehaviorConfigurationEditor
|
||||
ref={ref}
|
||||
>
|
||||
<AccordionHeader
|
||||
actions={[
|
||||
<HelpIcon
|
||||
key="help"
|
||||
size="small"
|
||||
helpPagePath={behaviorMetadata.getHelpPath()}
|
||||
/>,
|
||||
<ElementWithMenu
|
||||
key="menu"
|
||||
element={
|
||||
<IconButton size="small">
|
||||
<ThreeDotsMenu />
|
||||
</IconButton>
|
||||
}
|
||||
buildMenuTemplate={(i18n: I18nType) => [
|
||||
{
|
||||
label: i18n._(t`Delete`),
|
||||
click: () => onRemoveBehavior(behaviorName),
|
||||
},
|
||||
{
|
||||
label: i18n._(t`Copy`),
|
||||
click: () => copyBehavior(behaviorName),
|
||||
},
|
||||
{
|
||||
label: i18n._(t`Paste`),
|
||||
click: pasteBehaviors,
|
||||
enabled: isClipboardContainingBehaviors,
|
||||
},
|
||||
...(project.hasEventsBasedBehavior(behaviorTypeName)
|
||||
? [
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: i18n._(t`Edit this behavior`),
|
||||
click: () => openExtension(behaviorTypeName),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>,
|
||||
]}
|
||||
>
|
||||
{iconUrl ? (
|
||||
<IconContainer
|
||||
src={iconUrl}
|
||||
alt={behaviorMetadata.getFullName()}
|
||||
size={20}
|
||||
/>
|
||||
) : null}
|
||||
<Column expand>
|
||||
<TextField
|
||||
value={behaviorName}
|
||||
translatableHintText={t`Behavior name`}
|
||||
margin="none"
|
||||
fullWidth
|
||||
disabled
|
||||
onChange={(e, text) =>
|
||||
onChangeBehaviorName(behavior, text)
|
||||
}
|
||||
id={`behavior-${behaviorName}-name-text-field`}
|
||||
/>
|
||||
</Column>
|
||||
</AccordionHeader>
|
||||
<AccordionBody>
|
||||
<Column
|
||||
expand
|
||||
noMargin
|
||||
// Avoid Physics2 behavior overflow on small screens
|
||||
noOverflowParent
|
||||
>
|
||||
{enabledTutorialIds.length ? (
|
||||
<Line>
|
||||
<ColumnStackLayout expand>
|
||||
{tutorialIds.map(tutorialId => (
|
||||
<DismissableTutorialMessage
|
||||
key={tutorialId}
|
||||
tutorialId={tutorialId}
|
||||
/>
|
||||
))}
|
||||
</ColumnStackLayout>
|
||||
</Line>
|
||||
) : null}
|
||||
<Line>
|
||||
<BehaviorComponent
|
||||
behavior={behavior}
|
||||
project={project}
|
||||
object={object}
|
||||
resourceManagementProps={
|
||||
props.resourceManagementProps
|
||||
}
|
||||
onBehaviorUpdated={onBehaviorsUpdated}
|
||||
/>
|
||||
</Line>
|
||||
</Column>
|
||||
</AccordionBody>
|
||||
</Accordion>
|
||||
key={behaviorName}
|
||||
project={project}
|
||||
object={object}
|
||||
behavior={behavior}
|
||||
copyBehavior={copyBehavior}
|
||||
onRemoveBehavior={onRemoveBehavior}
|
||||
onBehaviorsUpdated={onBehaviorsUpdated}
|
||||
onChangeBehaviorName={onChangeBehaviorName}
|
||||
openExtension={openExtension}
|
||||
canPasteBehaviors={isClipboardContainingBehaviors}
|
||||
pasteBehaviors={pasteBehaviors}
|
||||
resourceManagementProps={props.resourceManagementProps}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
|
@@ -42,6 +42,9 @@ const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
// Those names are used internally by GDevelop.
|
||||
const PROTECTED_PROPERTY_NAMES = ['name', 'type'];
|
||||
|
||||
const getValidatedPropertyName = (
|
||||
i18n: I18nType,
|
||||
properties: gdNamedPropertyDescriptorsList,
|
||||
@@ -52,8 +55,7 @@ const getValidatedPropertyName = (
|
||||
tentativeNewName => {
|
||||
if (
|
||||
properties.has(tentativeNewName) ||
|
||||
// The name of a property cannot be "name" or "type", as they are used by GDevelop internally.
|
||||
(tentativeNewName === 'name' || tentativeNewName === 'type')
|
||||
PROTECTED_PROPERTY_NAMES.includes(tentativeNewName)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
@@ -122,7 +122,7 @@ type AccordionProps = {|
|
||||
|
||||
// Use accordion in controlled mode
|
||||
expanded?: boolean,
|
||||
onChange?: (open: boolean) => void,
|
||||
onChange?: (event: any, open: boolean) => void,
|
||||
id?: string,
|
||||
|};
|
||||
|
||||
|
@@ -24,7 +24,9 @@ type Props = {|
|
||||
|
||||
export type ScrollViewInterface = {|
|
||||
getScrollPosition: () => number,
|
||||
scrollTo: (target: ?React$Component<any, any>) => void,
|
||||
scrollTo: (
|
||||
target: ?React$Component<any, any> | ?React.ElementRef<any>
|
||||
) => void,
|
||||
scrollToPosition: (number: number) => void,
|
||||
scrollToBottom: () => void,
|
||||
|};
|
||||
|
Reference in New Issue
Block a user