From 3aa8fd42978ae9c3bbea2b2e6af5ca79db3feadd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Mon, 15 Dec 2025 18:21:59 +0100 Subject: [PATCH] Allow to set variant bounds from the property panel --- ...ventsBasedObjectVariantPropertiesSchema.js | 332 ++++++++++++++++++ .../index.js | 100 ++++++ .../src/SceneEditor/EditorsDisplay.flow.js | 1 + .../EventsBasedObjectScenePropertiesDialog.js | 3 +- ...stanceOrObjectPropertiesEditorContainer.js | 54 ++- .../SceneEditor/MosaicEditorsDisplay/index.js | 10 + .../SwipeableDrawerEditorsDisplay/index.js | 10 + newIDE/app/src/SceneEditor/index.js | 3 + newIDE/app/src/UI/ErrorBoundary.js | 1 + 9 files changed, 506 insertions(+), 8 deletions(-) create mode 100644 newIDE/app/src/SceneEditor/CompactEventsBasedObjectVariantPropertiesEditor/CompactEventsBasedObjectVariantPropertiesSchema.js create mode 100644 newIDE/app/src/SceneEditor/CompactEventsBasedObjectVariantPropertiesEditor/index.js diff --git a/newIDE/app/src/SceneEditor/CompactEventsBasedObjectVariantPropertiesEditor/CompactEventsBasedObjectVariantPropertiesSchema.js b/newIDE/app/src/SceneEditor/CompactEventsBasedObjectVariantPropertiesEditor/CompactEventsBasedObjectVariantPropertiesSchema.js new file mode 100644 index 000000000000..9406a8eb6ca4 --- /dev/null +++ b/newIDE/app/src/SceneEditor/CompactEventsBasedObjectVariantPropertiesEditor/CompactEventsBasedObjectVariantPropertiesSchema.js @@ -0,0 +1,332 @@ +// @flow + +import * as React from 'react'; +import { type I18n as I18nType } from '@lingui/core'; +import { t } from '@lingui/macro'; + +import { type Schema } from '../../CompactPropertiesEditor'; +import { styles } from '.'; +import Rectangle from '../../Utils/Rectangle'; + +import Object3d from '../../UI/CustomSvgIcons/Object3d'; +import Object2d from '../../UI/CustomSvgIcons/Object2d'; + +const getFitToContentButton = ({ + i18n, + eventsBasedObjectVariant, + getContentAABB, + onEventsBasedObjectChildrenEdited, + forceUpdate, +}: {| + i18n: I18nType, + eventsBasedObjectVariant: gdEventsBasedObjectVariant, + getContentAABB: () => Rectangle | null, + onEventsBasedObjectChildrenEdited: () => void, + forceUpdate: () => void, +|}) => ({ + label: i18n._(t`Fit to content`), + nonFieldType: 'button', + onClick: (instance: gdInitialInstance) => { + const contentAABB = getContentAABB(); + if (!contentAABB) { + return; + } + if (contentAABB.width() > 0) { + eventsBasedObjectVariant.setAreaMinX(contentAABB.left); + eventsBasedObjectVariant.setAreaMinY(contentAABB.top); + } + if (contentAABB.height() > 0) { + eventsBasedObjectVariant.setAreaMaxX(contentAABB.right); + eventsBasedObjectVariant.setAreaMaxY(contentAABB.bottom); + } + if (contentAABB.depth() > 0) { + eventsBasedObjectVariant.setAreaMinZ(contentAABB.zMin); + eventsBasedObjectVariant.setAreaMaxZ(contentAABB.zMax); + } + onEventsBasedObjectChildrenEdited(); + forceUpdate(); + }, + disabled: 'onValuesDifferent', + getValue: () => '', +}); + +const getTitleRow = ({ + i18n, + eventsBasedObject, +}: {| + i18n: I18nType, + eventsBasedObject: gdEventsBasedObject, +|}) => ({ + name: 'Variant', + title: i18n._(t`Variant`), + renderLeftIcon: className => + eventsBasedObject.isRenderedIn3D() ? ( + + ) : ( + + ), + getValue: (instance: gdInitialInstance) => + eventsBasedObject.getFullName() || eventsBasedObject.getName(), + defaultValue: '', + nonFieldType: 'title', +}); + +const getAreaMinXField = ({ + i18n, + eventsBasedObjectVariant, + forceUpdate, + onEventsBasedObjectChildrenEdited, +}: {| + i18n: I18nType, + eventsBasedObjectVariant: gdEventsBasedObjectVariant, + forceUpdate: () => void, + onEventsBasedObjectChildrenEdited: () => void, +|}) => ({ + name: 'AreaMinX', + getLabel: () => i18n._(t`Left`), + valueType: 'number', + getValue: () => eventsBasedObjectVariant.getAreaMinX(), + setValue: (instance: gdInitialInstance, newValue: number) => { + if (newValue === eventsBasedObjectVariant.getAreaMinX()) { + return; + } + eventsBasedObjectVariant.setAreaMinX(newValue); + if (newValue > eventsBasedObjectVariant.getAreaMaxX()) { + eventsBasedObjectVariant.setAreaMaxX(newValue); + forceUpdate(); + } + onEventsBasedObjectChildrenEdited(); + }, +}); + +const getAreaMaxXField = ({ + i18n, + eventsBasedObjectVariant, + forceUpdate, + onEventsBasedObjectChildrenEdited, +}: {| + i18n: I18nType, + eventsBasedObjectVariant: gdEventsBasedObjectVariant, + forceUpdate: () => void, + onEventsBasedObjectChildrenEdited: () => void, +|}) => ({ + name: 'AreaMaxX', + getLabel: () => i18n._(t`Right`), + valueType: 'number', + getValue: () => eventsBasedObjectVariant.getAreaMaxX(), + setValue: (instance: gdInitialInstance, newValue: number) => { + if (newValue === eventsBasedObjectVariant.getAreaMaxX()) { + return; + } + eventsBasedObjectVariant.setAreaMaxX(newValue); + if (newValue < eventsBasedObjectVariant.getAreaMinX()) { + eventsBasedObjectVariant.setAreaMinX(newValue); + forceUpdate(); + } + onEventsBasedObjectChildrenEdited(); + }, +}); + +const getAreaMinYField = ({ + i18n, + eventsBasedObjectVariant, + forceUpdate, + onEventsBasedObjectChildrenEdited, +}: {| + i18n: I18nType, + eventsBasedObjectVariant: gdEventsBasedObjectVariant, + forceUpdate: () => void, + onEventsBasedObjectChildrenEdited: () => void, +|}) => ({ + name: 'AreaMinY', + getLabel: () => i18n._(t`Top`), + valueType: 'number', + getValue: () => eventsBasedObjectVariant.getAreaMinY(), + setValue: (instance: gdInitialInstance, newValue: number) => { + if (newValue === eventsBasedObjectVariant.getAreaMinY()) { + return; + } + eventsBasedObjectVariant.setAreaMinY(newValue); + if (newValue > eventsBasedObjectVariant.getAreaMaxY()) { + eventsBasedObjectVariant.setAreaMaxY(newValue); + forceUpdate(); + } + onEventsBasedObjectChildrenEdited(); + }, +}); + +const getAreaMaxYField = ({ + i18n, + eventsBasedObjectVariant, + forceUpdate, + onEventsBasedObjectChildrenEdited, +}: {| + i18n: I18nType, + eventsBasedObjectVariant: gdEventsBasedObjectVariant, + forceUpdate: () => void, + onEventsBasedObjectChildrenEdited: () => void, +|}) => ({ + name: 'AreaMaxY', + getLabel: () => i18n._(t`Bottom`), + valueType: 'number', + getValue: () => eventsBasedObjectVariant.getAreaMaxY(), + setValue: (instance: gdInitialInstance, newValue: number) => { + if (newValue === eventsBasedObjectVariant.getAreaMaxY()) { + return; + } + eventsBasedObjectVariant.setAreaMaxY(newValue); + if (newValue < eventsBasedObjectVariant.getAreaMinY()) { + eventsBasedObjectVariant.setAreaMinY(newValue); + forceUpdate(); + } + onEventsBasedObjectChildrenEdited(); + }, +}); + +const getAreaMinZField = ({ + i18n, + eventsBasedObjectVariant, + forceUpdate, + onEventsBasedObjectChildrenEdited, +}: {| + i18n: I18nType, + eventsBasedObjectVariant: gdEventsBasedObjectVariant, + forceUpdate: () => void, + onEventsBasedObjectChildrenEdited: () => void, +|}) => ({ + name: 'AreaMinZ', + getLabel: () => i18n._(t`Z min`), + valueType: 'number', + getValue: () => eventsBasedObjectVariant.getAreaMinZ(), + setValue: (instance: gdInitialInstance, newValue: number) => { + if (newValue === eventsBasedObjectVariant.getAreaMinZ()) { + return; + } + eventsBasedObjectVariant.setAreaMinZ(newValue); + if (newValue > eventsBasedObjectVariant.getAreaMaxZ()) { + eventsBasedObjectVariant.setAreaMaxZ(newValue); + forceUpdate(); + } + onEventsBasedObjectChildrenEdited(); + }, +}); + +const getAreaMaxZField = ({ + i18n, + eventsBasedObjectVariant, + forceUpdate, + onEventsBasedObjectChildrenEdited, +}: {| + i18n: I18nType, + eventsBasedObjectVariant: gdEventsBasedObjectVariant, + forceUpdate: () => void, + onEventsBasedObjectChildrenEdited: () => void, +|}) => ({ + name: 'AreaMaxZ', + getLabel: () => i18n._(t`Z max`), + valueType: 'number', + getValue: () => eventsBasedObjectVariant.getAreaMaxZ(), + setValue: (instance: gdInitialInstance, newValue: number) => { + if (newValue === eventsBasedObjectVariant.getAreaMaxZ()) { + return; + } + eventsBasedObjectVariant.setAreaMaxZ(newValue); + if (newValue < eventsBasedObjectVariant.getAreaMinZ()) { + eventsBasedObjectVariant.setAreaMinZ(newValue); + forceUpdate(); + } + onEventsBasedObjectChildrenEdited(); + }, +}); + +export const makeSchema = ({ + i18n, + forceUpdate, + eventsBasedObject, + eventsBasedObjectVariant, + getContentAABB, + onEventsBasedObjectChildrenEdited, +}: {| + i18n: I18nType, + forceUpdate: () => void, + eventsBasedObject: gdEventsBasedObject, + eventsBasedObjectVariant: gdEventsBasedObjectVariant, + getContentAABB: () => Rectangle | null, + onEventsBasedObjectChildrenEdited: () => void, +|}): Schema => { + return [ + getTitleRow({ i18n, eventsBasedObject }), + { + name: 'Bounds', + title: i18n._(t`Bounds`), + nonFieldType: 'sectionTitle', + getValue: undefined, + }, + { + name: 'AreaBoundX', + type: 'row', + preventWrap: true, + children: [ + getAreaMinXField({ + i18n, + eventsBasedObjectVariant, + forceUpdate, + onEventsBasedObjectChildrenEdited, + }), + getAreaMaxXField({ + i18n, + eventsBasedObjectVariant, + forceUpdate, + onEventsBasedObjectChildrenEdited, + }), + ], + }, + { + name: 'AreaBoundY', + type: 'row', + preventWrap: true, + children: [ + getAreaMinYField({ + i18n, + eventsBasedObjectVariant, + forceUpdate, + onEventsBasedObjectChildrenEdited, + }), + getAreaMaxYField({ + i18n, + eventsBasedObjectVariant, + forceUpdate, + onEventsBasedObjectChildrenEdited, + }), + ], + }, + eventsBasedObject.isRenderedIn3D() + ? { + name: 'AreaBoundZ', + type: 'row', + preventWrap: true, + children: [ + getAreaMinZField({ + i18n, + eventsBasedObjectVariant, + forceUpdate, + onEventsBasedObjectChildrenEdited, + }), + getAreaMaxZField({ + i18n, + eventsBasedObjectVariant, + forceUpdate, + onEventsBasedObjectChildrenEdited, + }), + ], + } + : null, + getFitToContentButton({ + i18n, + eventsBasedObjectVariant, + getContentAABB, + onEventsBasedObjectChildrenEdited, + forceUpdate, + }), + ].filter(Boolean); +}; diff --git a/newIDE/app/src/SceneEditor/CompactEventsBasedObjectVariantPropertiesEditor/index.js b/newIDE/app/src/SceneEditor/CompactEventsBasedObjectVariantPropertiesEditor/index.js new file mode 100644 index 000000000000..3d1e4f723571 --- /dev/null +++ b/newIDE/app/src/SceneEditor/CompactEventsBasedObjectVariantPropertiesEditor/index.js @@ -0,0 +1,100 @@ +// @flow +import * as React from 'react'; +import { Trans } from '@lingui/macro'; +import { type I18n as I18nType } from '@lingui/core'; + +import CompactPropertiesEditor from '../../CompactPropertiesEditor'; +import { type Schema } from '../../CompactPropertiesEditor'; +import { Column, Spacer, marginsSize } from '../../UI/Grid'; +import { type UnsavedChanges } from '../../MainFrame/UnsavedChangesContext'; +import ScrollView, { type ScrollViewInterface } from '../../UI/ScrollView'; +import useForceUpdate from '../../Utils/UseForceUpdate'; +import ErrorBoundary from '../../UI/ErrorBoundary'; +import { makeSchema } from './CompactEventsBasedObjectVariantPropertiesSchema'; +import Rectangle from '../../Utils/Rectangle'; + +export const styles = { + icon: { + fontSize: 18, + }, + scrollView: { paddingTop: marginsSize }, +}; + +const noRefreshOfAllFields = () => { + console.warn( + "An instance tried to refresh all fields, but the editor doesn't support it." + ); +}; + +type Props = {| + i18n: I18nType, + unsavedChanges?: ?UnsavedChanges, + eventsBasedObject: gdEventsBasedObject, + eventsBasedObjectVariant: gdEventsBasedObjectVariant, + getContentAABB: () => Rectangle | null, + onEventsBasedObjectChildrenEdited: () => void, +|}; + +export const CompactEventsBasedObjectVariantPropertiesEditor = ({ + i18n, + unsavedChanges, + eventsBasedObject, + eventsBasedObjectVariant, + getContentAABB, + onEventsBasedObjectChildrenEdited, +}: Props) => { + const forceUpdate = useForceUpdate(); + + const scrollViewRef = React.useRef(null); + + const instanceSchema = React.useMemo( + () => + makeSchema({ + i18n, + forceUpdate, + eventsBasedObject, + eventsBasedObjectVariant, + getContentAABB, + onEventsBasedObjectChildrenEdited, + }), + [ + i18n, + forceUpdate, + eventsBasedObject, + eventsBasedObjectVariant, + getContentAABB, + onEventsBasedObjectChildrenEdited, + ] + ); + + return ( + Variant properties} + scope="scene-editor-events-based-object-variant-properties" + > + + + + onEventsBasedObjectChildrenEdited()} + onRefreshAllFields={noRefreshOfAllFields} + /> + + + + + + ); +}; diff --git a/newIDE/app/src/SceneEditor/EditorsDisplay.flow.js b/newIDE/app/src/SceneEditor/EditorsDisplay.flow.js index e4923b599c7e..e7fb71eb7e31 100644 --- a/newIDE/app/src/SceneEditor/EditorsDisplay.flow.js +++ b/newIDE/app/src/SceneEditor/EditorsDisplay.flow.js @@ -156,6 +156,7 @@ export type SceneEditorsDisplayProps = {| onOpenedEditorsChanged: () => void, onRestartInGameEditor: (reason: string) => void, showRestartInGameEditorAfterErrorButton: boolean, + onEventsBasedObjectChildrenEdited: gdEventsBasedObject => void, |}; export type SceneEditorsDisplayInterface = {| diff --git a/newIDE/app/src/SceneEditor/EventsBasedObjectScenePropertiesDialog.js b/newIDE/app/src/SceneEditor/EventsBasedObjectScenePropertiesDialog.js index 5c2ff0cc89f4..cb2faf1f7310 100644 --- a/newIDE/app/src/SceneEditor/EventsBasedObjectScenePropertiesDialog.js +++ b/newIDE/app/src/SceneEditor/EventsBasedObjectScenePropertiesDialog.js @@ -135,7 +135,8 @@ const EventsBasedObjectScenePropertiesDialog = ({ maxWidth="sm" secondaryActions={[ Fit to content} fullWidth onClick={() => { diff --git a/newIDE/app/src/SceneEditor/InstanceOrObjectPropertiesEditorContainer.js b/newIDE/app/src/SceneEditor/InstanceOrObjectPropertiesEditorContainer.js index 59e0a437226f..03138f13a7e2 100644 --- a/newIDE/app/src/SceneEditor/InstanceOrObjectPropertiesEditorContainer.js +++ b/newIDE/app/src/SceneEditor/InstanceOrObjectPropertiesEditorContainer.js @@ -14,6 +14,8 @@ import { CompactObjectPropertiesEditor } from '../ObjectEditor/CompactObjectProp import { type ObjectEditorTab } from '../ObjectEditor/ObjectEditorDialog'; import { type ResourceManagementProps } from '../ResourcesList/ResourceSource'; import { CompactLayerPropertiesEditor } from '../LayersList/CompactLayerPropertiesEditor'; +import { CompactEventsBasedObjectVariantPropertiesEditor } from '../SceneEditor/CompactEventsBasedObjectVariantPropertiesEditor'; +import Rectangle from '../Utils/Rectangle'; export const styles = { paper: { @@ -32,8 +34,6 @@ type Props = {| unsavedChanges?: ?UnsavedChanges, i18n: I18nType, lastSelectionType: 'instance' | 'object' | 'layer', - - // For objects or instances: historyHandler?: HistoryHandler, isVariableListLocked: boolean, layout?: ?gdLayout, @@ -75,6 +75,14 @@ type Props = {| onEditLayer: (layer: gdLayer) => void, onEditLayerEffects: (layer: gdLayer) => void, onLayersModified: (layers: Array) => void, + + // For variants: + eventsBasedObject: gdEventsBasedObject | null, + eventsBasedObjectVariant: gdEventsBasedObjectVariant | null, + getContentAABB: () => Rectangle | null, + onEventsBasedObjectChildrenEdited: ( + eventsBasedObject: gdEventsBasedObject + ) => void, |}; export type InstanceOrObjectPropertiesEditorInterface = {| @@ -98,6 +106,11 @@ export const InstanceOrObjectPropertiesEditorContainer = React.forwardRef< })); const { + project, + layersContainer, + projectScopedContainersAccessor, + unsavedChanges, + i18n, lastSelectionType, // For objects: @@ -129,14 +142,18 @@ export const InstanceOrObjectPropertiesEditorContainer = React.forwardRef< onEditLayerEffects, onLayersModified, + // For variants + eventsBasedObject, + eventsBasedObjectVariant, + getContentAABB, + onEventsBasedObjectChildrenEdited, + // For objects or instances: historyHandler, isVariableListLocked, layout, objectsContainer, globalObjectsContainer, - - ...commonProps } = props; return ( @@ -155,7 +172,11 @@ export const InstanceOrObjectPropertiesEditorContainer = React.forwardRef< layout={layout} objectsContainer={objectsContainer} globalObjectsContainer={globalObjectsContainer} - {...commonProps} + layersContainer={layersContainer} + project={project} + projectScopedContainersAccessor={projectScopedContainersAccessor} + unsavedChanges={unsavedChanges} + i18n={i18n} /> ) : !!objects.length && lastSelectionType === 'object' ? ( ) : layer && lastSelectionType === 'layer' ? ( + ) : eventsBasedObject && eventsBasedObjectVariant ? ( + + onEventsBasedObjectChildrenEdited(eventsBasedObject) + } + unsavedChanges={unsavedChanges} + i18n={i18n} /> ) : ( diff --git a/newIDE/app/src/SceneEditor/MosaicEditorsDisplay/index.js b/newIDE/app/src/SceneEditor/MosaicEditorsDisplay/index.js index 92aad93b6ed2..09dbba5c08a7 100644 --- a/newIDE/app/src/SceneEditor/MosaicEditorsDisplay/index.js +++ b/newIDE/app/src/SceneEditor/MosaicEditorsDisplay/index.js @@ -318,6 +318,16 @@ const MosaicEditorsDisplay = React.forwardRef< onEditLayerEffects={props.editLayerEffects} onEditLayer={props.editLayer} onLayersModified={props.onLayersModified} + eventsBasedObject={props.eventsBasedObject} + eventsBasedObjectVariant={props.eventsBasedObjectVariant} + getContentAABB={ + editorRef.current + ? editorRef.current.getContentAABB + : () => null + } + onEventsBasedObjectChildrenEdited={ + props.onEventsBasedObjectChildrenEdited + } /> )} diff --git a/newIDE/app/src/SceneEditor/SwipeableDrawerEditorsDisplay/index.js b/newIDE/app/src/SceneEditor/SwipeableDrawerEditorsDisplay/index.js index 1ee17e9572e3..91d4e2c763c2 100644 --- a/newIDE/app/src/SceneEditor/SwipeableDrawerEditorsDisplay/index.js +++ b/newIDE/app/src/SceneEditor/SwipeableDrawerEditorsDisplay/index.js @@ -482,6 +482,16 @@ const SwipeableDrawerEditorsDisplay = React.forwardRef< onEditLayerEffects={props.editLayerEffects} onEditLayer={props.editLayer} onLayersModified={props.onLayersModified} + eventsBasedObject={props.eventsBasedObject} + eventsBasedObjectVariant={props.eventsBasedObjectVariant} + getContentAABB={ + editorRef.current + ? editorRef.current.getContentAABB + : () => null + } + onEventsBasedObjectChildrenEdited={ + props.onEventsBasedObjectChildrenEdited + } /> )} diff --git a/newIDE/app/src/SceneEditor/index.js b/newIDE/app/src/SceneEditor/index.js index 3401c35ccabe..c3748b17f1ba 100644 --- a/newIDE/app/src/SceneEditor/index.js +++ b/newIDE/app/src/SceneEditor/index.js @@ -2859,6 +2859,9 @@ export default class SceneEditor extends React.Component { onWillInstallExtension={this.props.onWillInstallExtension} onExtensionInstalled={this.props.onExtensionInstalled} editorViewPosition2D={this.editorViewPosition2D} + onEventsBasedObjectChildrenEdited={ + this.props.onEventsBasedObjectChildrenEdited + } /> {({ i18n }) => ( diff --git a/newIDE/app/src/UI/ErrorBoundary.js b/newIDE/app/src/UI/ErrorBoundary.js index 81316f9383ff..74bc88c715fb 100644 --- a/newIDE/app/src/UI/ErrorBoundary.js +++ b/newIDE/app/src/UI/ErrorBoundary.js @@ -47,6 +47,7 @@ type ErrorBoundaryScope = | 'scene-editor-instance-properties' | 'scene-editor-object-properties' | 'scene-editor-layer-properties' + | 'scene-editor-events-based-object-variant-properties' | 'scene-editor-objects-list' | 'scene-editor-object-groups-list' | 'scene-editor-canvas'