From 1f73056ab6ebbeaac7db67f32c8ea8a0f6366273 Mon Sep 17 00:00:00 2001 From: Florian Rival Date: Wed, 18 Sep 2024 23:00:34 +0200 Subject: [PATCH] Add collapsible sections in the objects panel --- .../app/src/CompactPropertiesEditor/index.js | 70 +- .../CompactEffectPropertiesEditor.js | 1 + .../CompactBehaviorPropertiesEditor.js | 2 + .../CompactObjectPropertiesEditor/index.js | 689 ++++++++++-------- .../QuickBehaviorsTweaker.js | 1 + .../SquaredDoubleChevronArrowDown.js | 28 + .../SquaredDoubleChevronArrowUp.js | 28 + 7 files changed, 489 insertions(+), 330 deletions(-) create mode 100644 newIDE/app/src/UI/CustomSvgIcons/SquaredDoubleChevronArrowDown.js create mode 100644 newIDE/app/src/UI/CustomSvgIcons/SquaredDoubleChevronArrowUp.js diff --git a/newIDE/app/src/CompactPropertiesEditor/index.js b/newIDE/app/src/CompactPropertiesEditor/index.js index b39e4ca9d6f9..23b97f3ae2cc 100644 --- a/newIDE/app/src/CompactPropertiesEditor/index.js +++ b/newIDE/app/src/CompactPropertiesEditor/index.js @@ -199,6 +199,7 @@ type Props = {| mode?: 'column' | 'row', preventWrap?: boolean, removeSpacers?: boolean, + sectionTitleStyle?: 'level1' | 'level2', // If set, render the "extra" description content from fields // (see getExtraDescription). @@ -235,6 +236,10 @@ const styles = { marginTop: marginsSize, borderTop: '1px solid black', // Border color is changed in the component. }, + level2Separator: { + flex: 1, + borderTop: '1px solid black', // Border color is changed in the component. + }, }; export const Separator = () => { @@ -249,6 +254,18 @@ export const Separator = () => { ); }; +export const Level2Separator = () => { + const gdevelopTheme = React.useContext(GDevelopThemeContext); + return ( +
+ ); +}; + const getDisabled = ({ instances, field, @@ -348,6 +365,7 @@ const CompactPropertiesEditor = ({ resourceManagementProps, preventWrap, removeSpacers, + sectionTitleStyle, }: Props) => { const forceUpdate = useForceUpdate(); @@ -620,7 +638,6 @@ const CompactPropertiesEditor = ({ { instances.forEach(i => setValue(i, parseFloat(newValue) || 0)); @@ -644,7 +661,6 @@ const CompactPropertiesEditor = ({ field, defaultValue: '(Multiple values)', })} - key={field.name} id={field.name} onChange={(newValue: string) => { instances.forEach(i => setValue(i, newValue || '')); @@ -883,16 +899,36 @@ const CompactPropertiesEditor = ({ }, [instances] ); - const renderSectionTitle = React.useCallback((field: SectionTitle) => { - return [ - , - - - {field.title} - - , - ]; - }, []); + const renderSectionTitle = React.useCallback( + (field: { name: string, title: string }) => { + return [ + , + + + {field.title} + + , + ]; + }, + [] + ); + + const renderSectionLevel2Title = React.useCallback( + (field: { name: string, title: string }) => { + return [ + + + + + {field.title} + + + + , + ]; + }, + [] + ); return renderContainer( schema.map(field => { @@ -944,10 +980,12 @@ const CompactPropertiesEditor = ({ if (field.title) { return [ - , - - {field.title} - , + ...(sectionTitleStyle === 'level2' + ? renderSectionLevel2Title({ + title: field.title, + name: field.name, + }) + : renderSectionTitle({ title: field.title, name: field.name })), contentView, ]; } diff --git a/newIDE/app/src/EffectsList/CompactEffectPropertiesEditor.js b/newIDE/app/src/EffectsList/CompactEffectPropertiesEditor.js index 6e53451e253a..c1a0feb5f6c7 100644 --- a/newIDE/app/src/EffectsList/CompactEffectPropertiesEditor.js +++ b/newIDE/app/src/EffectsList/CompactEffectPropertiesEditor.js @@ -26,6 +26,7 @@ export const CompactEffectPropertiesEditor = ({ return ( ); +const TopLevelCollapsibleSection = ({ + title, + isFolded, + toggleFolded, + renderContent, + renderContentAsHiddenWhenFolded, + onEditInFullEditor, + onAdd, +}: {| + title: React.Node, + isFolded: boolean, + toggleFolded: () => void, + renderContent: () => React.Node, + renderContentAsHiddenWhenFolded?: boolean, + onEditInFullEditor: () => void, + onAdd?: () => void, +|}) => ( + <> + + + + + + {isFolded ? ( + + ) : ( + + )} + + + {title} + + + + + + + {onAdd && ( + + + + )} + + + + {isFolded ? ( + renderContentAsHiddenWhenFolded ? ( +
{renderContent()}
+ ) : null + ) : ( + renderContent() + )} + +); + type Props = {| project: gdProject, resourceManagementProps: ResourceManagementProps, @@ -139,6 +200,10 @@ export const CompactObjectPropertiesEditor = ({ showObjectAdvancedOptions, setShowObjectAdvancedOptions, ] = React.useState(false); + const [isPropertiesFolded, setIsPropertiesFolded] = React.useState(false); + const [isBehaviorsFolded, setIsBehaviorsFolded] = React.useState(false); + const [isVariablesFolded, setIsVariablesFolded] = React.useState(false); + const [isEffectsFolded, setIsEffectsFolded] = React.useState(false); const [schemaRecomputeTrigger, forceRecomputeSchema] = useForceRecompute(); const variablesListRef = React.useRef(null); const object = objects[0]; @@ -273,6 +338,8 @@ export const CompactObjectPropertiesEditor = ({ (customObjectConfiguration.isForcedToOverrideEventsBasedObjectChildrenConfiguration() || customObjectConfiguration.isMarkedAsOverridingEventsBasedObjectChildrenConfiguration()); + const helpLink = getHelpLink(objectMetadata.getHelpPath()); + return ( Object properties} @@ -284,7 +351,7 @@ export const CompactObjectPropertiesEditor = ({ key={objects.map((instance: gdObject) => '' + instance.ptr).join(';')} > - + )} - Object - {object.getName()} + {objectMetadata.getFullName()} + {helpLink && ( + { + Window.openExternalURL(helpLink); + }} + > + + + )} - { - onEditObject(object); - }} - > - - - {!hasSomeObjectProperties && ( - - This object has no properties. - - )} - {hasSomeObjectProperties && ( - { - // TODO: undo/redo? - }} - onRefreshAllFields={forceRecomputeSchema} - /> - )} - {!showObjectAdvancedOptions && hasObjectAdvancedProperties && ( - } - label={Show more} - onClick={() => { - setShowObjectAdvancedOptions(true); - }} - /> - )} - {showObjectAdvancedOptions && hasObjectAdvancedProperties && ( - { - // TODO: undo/redo? - }} - onRefreshAllFields={forceRecomputeSchema} - /> - )} - {showObjectAdvancedOptions && hasObjectAdvancedProperties && ( - } - label={Show less} - onClick={() => { - setShowObjectAdvancedOptions(false); - }} - /> - )} - {eventsBasedObject && - customObjectConfiguration && - shouldDisplayEventsBasedObjectChildren && - mapFor(0, eventsBasedObject.getObjects().getObjectsCount(), i => { - const childObject = eventsBasedObject - .getObjects() - .getObjectAt(i); - const childObjectName = childObject.getName(); - const isFolded = customObjectConfiguration.isChildObjectFolded( - childObjectName - ); - return ( - ( - - )} - isFolded={isFolded} - toggleFolded={() => { - customObjectConfiguration.setChildObjectFolded( - childObjectName, - !isFolded - ); - forceUpdate(); + {}} + disabled + /> + + Properties} + isFolded={isPropertiesFolded} + toggleFolded={() => setIsPropertiesFolded(!isPropertiesFolded)} + onEditInFullEditor={() => onEditObject(object, 'properties')} + renderContent={() => ( + + {!hasSomeObjectProperties && ( + + This object has no properties. + + )} + {hasSomeObjectProperties && ( + { + // TODO: undo/redo? }} - title={ - - {childObjectName} - - } + onRefreshAllFields={forceRecomputeSchema} /> - ); - })} - - - - - - Behaviors - - - { - onEditObject(object, 'behaviors'); - }} - > - - - - - - - - - - {!allVisibleBehaviors.length && ( - - There are no behaviors on this object. - - )} - {allVisibleBehaviors.map(behavior => { - const behaviorTypeName = behavior.getTypeName(); - const behaviorMetadata = gd.MetadataProvider.getBehaviorMetadata( - gd.JsPlatform.get(), - behaviorTypeName - ); - - const iconUrl = behaviorMetadata.getIconFilename(); - - return ( - ( - {}} - resourceManagementProps={resourceManagementProps} - /> - )} - isFolded={behavior.isFolded()} - toggleFolded={() => { - behavior.setFolded(!behavior.isFolded()); - forceUpdate(); - }} - title={ - <> - {iconUrl ? ( - } + label={Show more} + onClick={() => { + setShowObjectAdvancedOptions(true); + }} + /> + )} + {showObjectAdvancedOptions && hasObjectAdvancedProperties && ( + { + // TODO: undo/redo? + }} + onRefreshAllFields={forceRecomputeSchema} + /> + )} + {showObjectAdvancedOptions && hasObjectAdvancedProperties && ( + } + label={Show less} + onClick={() => { + setShowObjectAdvancedOptions(false); + }} + /> + )} + {eventsBasedObject && + customObjectConfiguration && + shouldDisplayEventsBasedObjectChildren && + mapFor( + 0, + eventsBasedObject.getObjects().getObjectsCount(), + i => { + const childObject = eventsBasedObject + .getObjects() + .getObjectAt(i); + const childObjectName = childObject.getName(); + const isFolded = customObjectConfiguration.isChildObjectFolded( + childObjectName + ); + return ( + ( + + )} + isFolded={isFolded} + toggleFolded={() => { + customObjectConfiguration.setChildObjectFolded( + childObjectName, + !isFolded + ); + forceUpdate(); + }} + title={ + + {childObjectName} + + } /> - ) : null} - - - {behavior.getName()} - - - } - onRemove={() => { - removeBehavior(behavior.getName()); - }} - /> - ); - })} - - - - - - Object Variables - - - { - onEditObject(object, 'variables'); - }} - > - - - - - - - - - - object && layout - ? EventsRootVariablesFinder.findAllObjectVariables( - project.getCurrentPlatform(), - project, - layout, - object.getName() - ) - : [] - } - historyHandler={historyHandler} - toolbarIconStyle={styles.icon} + ); + } + )} + + )} /> - - {objectMetadata && - objectMetadata.hasDefaultBehavior( - 'EffectCapability::EffectBehavior' - ) && ( - <> - - - - - Effects - - - { - onEditObject(object, 'effects'); - }} - > - - - addEffect(false)}> - - - - - + Behaviors} + isFolded={isBehaviorsFolded} + toggleFolded={() => setIsBehaviorsFolded(!isBehaviorsFolded)} + onEditInFullEditor={() => onEditObject(object, 'behaviors')} + onAdd={openNewBehaviorDialog} + renderContent={() => ( - {effectsContainer.getEffectsCount() === 0 && ( + {!allVisibleBehaviors.length && ( - There are no effects on this object. + There are no behaviors on this object. )} - {mapFor( - 0, - effectsContainer.getEffectsCount(), - (index: number) => { - const effect: gdEffect = effectsContainer.getEffectAt( - index - ); - const effectType = effect.getEffectType(); - const effectMetadata = getEnumeratedEffectMetadata( - allEffectMetadata, - effectType - ); + {allVisibleBehaviors.map(behavior => { + const behaviorTypeName = behavior.getTypeName(); + const behaviorMetadata = gd.MetadataProvider.getBehaviorMetadata( + gd.JsPlatform.get(), + behaviorTypeName + ); - return ( - ( - - chooseEffectType(effect, type)} - > - {all2DEffectMetadata.map(effectMetadata => ( - - ))} - - ( + {}} + resourceManagementProps={resourceManagementProps} + /> + )} + isFolded={behavior.isFolded()} + toggleFolded={() => { + behavior.setFolded(!behavior.isFolded()); + forceUpdate(); + }} + title={ + <> + {iconUrl ? ( + - - )} - isFolded={effect.isFolded()} - toggleFolded={() => { - effect.setFolded(!effect.isFolded()); - forceUpdate(); - }} - title={ + ) : null} + - {effect.getName()} + {behavior.getName()} - } - onRemove={() => { - removeEffect(effect); - }} - /> - ); - } - )} + + } + onRemove={() => { + removeBehavior(behavior.getName()); + }} + /> + ); + })} - - )} + )} + /> + Object Variables} + isFolded={isVariablesFolded} + toggleFolded={() => setIsVariablesFolded(!isVariablesFolded)} + onEditInFullEditor={() => onEditObject(object, 'variables')} + onAdd={() => { + if (variablesListRef.current) { + variablesListRef.current.addVariable(); + } + setIsVariablesFolded(false); + }} + renderContentAsHiddenWhenFolded={ + true /* Allows to keep a ref to the variables list for add button to work. */ + } + renderContent={() => ( + + object && layout + ? EventsRootVariablesFinder.findAllObjectVariables( + project.getCurrentPlatform(), + project, + layout, + object.getName() + ) + : [] + } + historyHandler={historyHandler} + toolbarIconStyle={styles.icon} + /> + )} + /> + {objectMetadata && + objectMetadata.hasDefaultBehavior( + 'EffectCapability::EffectBehavior' + ) && ( + Effects} + isFolded={isEffectsFolded} + toggleFolded={() => setIsEffectsFolded(!isEffectsFolded)} + onEditInFullEditor={() => onEditObject(object, 'effects')} + onAdd={() => addEffect(false)} + renderContent={() => ( + + {effectsContainer.getEffectsCount() === 0 && ( + + There are no effects on this object. + + )} + {mapFor( + 0, + effectsContainer.getEffectsCount(), + (index: number) => { + const effect: gdEffect = effectsContainer.getEffectAt( + index + ); + const effectType = effect.getEffectType(); + const effectMetadata = getEnumeratedEffectMetadata( + allEffectMetadata, + effectType + ); + + return ( + ( + + + chooseEffectType(effect, type) + } + > + {all2DEffectMetadata.map(effectMetadata => ( + + ))} + + + + )} + isFolded={effect.isFolded()} + toggleFolded={() => { + effect.setFolded(!effect.isFolded()); + forceUpdate(); + }} + title={ + + {effect.getName()} + + } + onRemove={() => { + removeEffect(effect); + }} + /> + ); + } + )} + + )} + /> + )} + {newBehaviorDialog} diff --git a/newIDE/app/src/QuickCustomization/QuickBehaviorsTweaker.js b/newIDE/app/src/QuickCustomization/QuickBehaviorsTweaker.js index 3ba736acd64a..561d70b390a8 100644 --- a/newIDE/app/src/QuickCustomization/QuickBehaviorsTweaker.js +++ b/newIDE/app/src/QuickCustomization/QuickBehaviorsTweaker.js @@ -49,6 +49,7 @@ const QuickBehaviorPropertiesEditor = ({ return ( ( + + + + + +)); diff --git a/newIDE/app/src/UI/CustomSvgIcons/SquaredDoubleChevronArrowUp.js b/newIDE/app/src/UI/CustomSvgIcons/SquaredDoubleChevronArrowUp.js new file mode 100644 index 000000000000..89670b2d6dc0 --- /dev/null +++ b/newIDE/app/src/UI/CustomSvgIcons/SquaredDoubleChevronArrowUp.js @@ -0,0 +1,28 @@ +import React from 'react'; +import SvgIcon from '@material-ui/core/SvgIcon'; + +export default React.memo(props => ( + + + + + +));