diff --git a/Core/GDCore/IDE/Events/BehaviorParametersFiller.cpp b/Core/GDCore/IDE/Events/BehaviorParametersFiller.cpp new file mode 100644 index 0000000000..816b5694f6 --- /dev/null +++ b/Core/GDCore/IDE/Events/BehaviorParametersFiller.cpp @@ -0,0 +1,65 @@ +/* + * GDevelop Core + * Copyright 2008-2024 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#include "BehaviorParametersFiller.h" + +#include +#include +#include + +#include "GDCore/Events/Instruction.h" +#include "GDCore/Extensions/Metadata/MetadataProvider.h" +#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h" +#include "GDCore/String.h" +#include "GDCore/Tools/Log.h" + +namespace gd { + +bool BehaviorParametersFiller::DoVisitInstruction(gd::Instruction &instruction, + bool isCondition) { + const auto &metadata = isCondition + ? gd::MetadataProvider::GetConditionMetadata( + platform, instruction.GetType()) + : gd::MetadataProvider::GetActionMetadata( + platform, instruction.GetType()); + + gd::ParameterMetadataTools::IterateOverParametersWithIndex( + instruction.GetParameters(), metadata.GetParameters(), + [&](const gd::ParameterMetadata ¶meterMetadata, + const gd::Expression ¶meterValue, size_t parameterIndex, + const gd::String &lastObjectName) { + if (parameterMetadata.GetValueTypeMetadata().IsBehavior() && + parameterValue.GetPlainString().length() == 0) { + + auto &expectedBehaviorTypeName = + parameterMetadata.GetValueTypeMetadata().GetExtraInfo(); + + auto &objectsContainersList = + projectScopedContainers.GetObjectsContainersList(); + auto behaviorNames = + objectsContainersList.GetBehaviorsOfObject(lastObjectName, true); + + gd::String foundBehaviorName = ""; + for (auto &behaviorName : behaviorNames) { + auto behaviorTypeName = + objectsContainersList.GetTypeOfBehavior(behaviorName, false); + if (behaviorTypeName == expectedBehaviorTypeName) { + foundBehaviorName = behaviorName; + break; + } + } + if (!foundBehaviorName.empty()) { + instruction.SetParameter(parameterIndex, + gd::Expression(foundBehaviorName)); + } + } + }); + + return false; +} + +BehaviorParametersFiller::~BehaviorParametersFiller() {} + +} // namespace gd diff --git a/Core/GDCore/IDE/Events/BehaviorParametersFiller.h b/Core/GDCore/IDE/Events/BehaviorParametersFiller.h new file mode 100644 index 0000000000..1255a28641 --- /dev/null +++ b/Core/GDCore/IDE/Events/BehaviorParametersFiller.h @@ -0,0 +1,45 @@ +/* + * GDevelop Core + * Copyright 2008-2024 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#pragma once + +#include "GDCore/IDE/Events/ArbitraryEventsWorker.h" +#include "GDCore/String.h" +#include +#include +#include + +namespace gd { +class Instruction; +class Platform; +class ProjectScopedContainers; +} // namespace gd + +namespace gd { + +/** + * \brief Fill empty behavior parameters with any behavior that matches the + * required behavior type. + * + * \ingroup IDE + */ +class GD_CORE_API BehaviorParametersFiller : public ArbitraryEventsWorker { +public: + BehaviorParametersFiller( + const gd::Platform &platform_, + const gd::ProjectScopedContainers &projectScopedContainers_) + : platform(platform_), + projectScopedContainers(projectScopedContainers_){}; + virtual ~BehaviorParametersFiller(); + +private: + bool DoVisitInstruction(gd::Instruction &instruction, + bool isCondition) override; + + const gd::Platform &platform; + const gd::ProjectScopedContainers &projectScopedContainers; +}; + +} // namespace gd diff --git a/Core/GDCore/IDE/WholeProjectRefactorer.cpp b/Core/GDCore/IDE/WholeProjectRefactorer.cpp index d20aeb9fab..13b4ccda82 100644 --- a/Core/GDCore/IDE/WholeProjectRefactorer.cpp +++ b/Core/GDCore/IDE/WholeProjectRefactorer.cpp @@ -26,6 +26,7 @@ #include "GDCore/IDE/Events/InstructionsTypeRenamer.h" #include "GDCore/IDE/Events/LinkEventTargetRenamer.h" #include "GDCore/IDE/Events/ProjectElementRenamer.h" +#include "GDCore/IDE/Events/BehaviorParametersFiller.h" #include "GDCore/IDE/EventsFunctionTools.h" #include "GDCore/IDE/Project/ArbitraryBehaviorSharedDataWorker.h" #include "GDCore/IDE/Project/ArbitraryEventBasedBehaviorsWorker.h" @@ -1537,6 +1538,16 @@ void WholeProjectRefactorer::ObjectRemovedInLayout( } } +void WholeProjectRefactorer::BehaviorsAddedToObjectInLayout( + gd::Project &project, gd::Layout &layout, const gd::String &objectName) { + auto projectScopedContainers = gd::ProjectScopedContainers:: + MakeNewProjectScopedContainersForProjectAndLayout(project, layout); + gd::BehaviorParametersFiller behaviorParameterFiller( + project.GetCurrentPlatform(), projectScopedContainers); + gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents( + project, layout, behaviorParameterFiller); +} + void WholeProjectRefactorer::ObjectOrGroupRenamedInLayout( gd::Project &project, gd::Layout &layout, const gd::String &oldName, const gd::String &newName, bool isObjectGroup) { @@ -1801,6 +1812,17 @@ void WholeProjectRefactorer::GlobalObjectRemoved( } } +void WholeProjectRefactorer::BehaviorsAddedToGlobalObject( + gd::Project &project, const gd::String &objectName) { + for (std::size_t i = 0; i < project.GetLayoutsCount(); ++i) { + gd::Layout &layout = project.GetLayout(i); + if (layout.HasObjectNamed(objectName)) + continue; + + BehaviorsAddedToObjectInLayout(project, layout, objectName); + } +} + void WholeProjectRefactorer::RemoveLayer(gd::Project &project, gd::Layout &layout, const gd::String &layerName) { diff --git a/Core/GDCore/IDE/WholeProjectRefactorer.h b/Core/GDCore/IDE/WholeProjectRefactorer.h index ca54a0d778..193c278c44 100644 --- a/Core/GDCore/IDE/WholeProjectRefactorer.h +++ b/Core/GDCore/IDE/WholeProjectRefactorer.h @@ -395,6 +395,18 @@ class GD_CORE_API WholeProjectRefactorer { gd::Layout& layout, const gd::String& objectName); + /** + * \brief Refactor the project after behaviors are added to an object in a + * layout. + * + * This will update the layout, all external events associated with it. + * The refactor is actually applied to all objects because it allow to handle + * groups. + */ + static void BehaviorsAddedToObjectInLayout(gd::Project &project, + gd::Layout &layout, + const gd::String &objectName); + /** * \brief Refactor the project after an object is removed in an events-based * object. @@ -467,6 +479,16 @@ class GD_CORE_API WholeProjectRefactorer { static void GlobalObjectRemoved(gd::Project& project, const gd::String& objectName); + /** + * \brief Refactor the project after behaviors are added a global object. + * + * This will update all the layouts, all external events associated with them. + * The refactor is actually applied to all objects because it allow to handle + * groups. + */ + void BehaviorsAddedToGlobalObject(gd::Project &project, + const gd::String &objectName); + /** * \brief Return the set of all the types of the objects that are using the * given behavior. diff --git a/Core/tests/WholeProjectRefactorer.cpp b/Core/tests/WholeProjectRefactorer.cpp index c9b72c0e53..b9a17e757e 100644 --- a/Core/tests/WholeProjectRefactorer.cpp +++ b/Core/tests/WholeProjectRefactorer.cpp @@ -1475,6 +1475,41 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { } } + SECTION("Behaviors added to an object (in layout)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + + auto &scene = project.InsertNewLayout("Scene", 0); + auto &object = + scene.InsertNewObject(project, "MyExtension::Sprite", "Object", 0); + + gd::StandardEvent &event = + dynamic_cast(scene.GetEvents().InsertNewEvent( + project, "BuiltinCommonInstructions::Standard")); + // Add a behavior instruction using an object that doesn't have the + // behavior. + { + gd::Instruction action; + action.SetType("MyExtension::BehaviorDoSomething"); + action.SetParametersCount(3); + action.SetParameter(0, gd::Expression("Object")); + // The behavior parameter is left empty. + action.SetParameter(1, gd::Expression("")); + action.SetParameter(2, gd::Expression("0")); + event.GetActions().Insert(action); + } + + // Attach the behavior to the object. + object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior"); + gd::WholeProjectRefactorer::BehaviorsAddedToObjectInLayout(project, scene, + "Object"); + + // The behavior parameter is now filled. + REQUIRE(event.GetActions()[0].GetParameter(1).GetPlainString() == + "MyBehavior"); + } + SECTION("Object renamed (in events function)") { SECTION("Group") { gd::Project project; diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index 9183848b8f..04fdbbbf40 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -2438,6 +2438,10 @@ interface WholeProjectRefactorer { [Ref] Project project, [Ref] Layout layout, [Const] DOMString objectName); + void STATIC_BehaviorsAddedToObjectInLayout( + [Ref] Project project, + [Ref] Layout layout, + [Const] DOMString objectName); void STATIC_ObjectOrGroupRenamedInEventsFunction([Ref] Project project, [Ref] EventsFunction eventsFunction, [Ref] ObjectsContainer globalObjectsContainer, [Ref] ObjectsContainer objectsContainer, [Const] DOMString oldName, [Const] DOMString newName, boolean isObjectGroup); void STATIC_ObjectRemovedInEventsFunction( [Ref] Project project, @@ -2456,6 +2460,9 @@ interface WholeProjectRefactorer { void STATIC_GlobalObjectRemoved( [Ref] Project project, [Const] DOMString objectName); + void STATIC_BehaviorsAddedToGlobalObject( + [Ref] Project project, + [Const] DOMString objectName); [Value] SetString STATIC_GetAllObjectTypesUsingEventsBasedBehavior([Const, Ref] Project project, [Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, [Const, Ref] EventsBasedBehavior eventsBasedBehavior); void STATIC_EnsureBehaviorEventsFunctionsProperParameters([Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, [Const, Ref] EventsBasedBehavior eventsBasedBehavior); void STATIC_EnsureObjectEventsFunctionsProperParameters([Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, [Const, Ref] EventsBasedObject eventsBasedObject); diff --git a/GDevelop.js/Bindings/Wrapper.cpp b/GDevelop.js/Bindings/Wrapper.cpp index 6e4eed95c3..b3083b6dbf 100644 --- a/GDevelop.js/Bindings/Wrapper.cpp +++ b/GDevelop.js/Bindings/Wrapper.cpp @@ -644,6 +644,7 @@ typedef ExtensionAndMetadata ExtensionAndExpressionMetadata; #define STATIC_Date Date #define STATIC_ObjectOrGroupRenamedInLayout ObjectOrGroupRenamedInLayout #define STATIC_ObjectRemovedInLayout ObjectRemovedInLayout +#define STATIC_BehaviorsAddedToObjectInLayout BehaviorsAddedToObjectInLayout #define STATIC_ObjectRemovedInEventsFunction \ ObjectRemovedInEventsFunction #define STATIC_ObjectOrGroupRenamedInEventsFunction \ @@ -654,6 +655,7 @@ typedef ExtensionAndMetadata ExtensionAndExpressionMetadata; ObjectOrGroupRenamedInEventsBasedObject #define STATIC_GlobalObjectOrGroupRenamed GlobalObjectOrGroupRenamed #define STATIC_GlobalObjectRemoved GlobalObjectRemoved +#define STATIC_BehaviorsAddedToGlobalObject BehaviorsAddedToGlobalObject #define STATIC_GetAllObjectTypesUsingEventsBasedBehavior \ GetAllObjectTypesUsingEventsBasedBehavior #define STATIC_EnsureBehaviorEventsFunctionsProperParameters \ diff --git a/GDevelop.js/types.d.ts b/GDevelop.js/types.d.ts index 342bbe74a0..4bc884fe10 100644 --- a/GDevelop.js/types.d.ts +++ b/GDevelop.js/types.d.ts @@ -1829,12 +1829,14 @@ export class WholeProjectRefactorer extends EmscriptenObject { static renameObjectEffect(project: Project, layout: Layout, gdObject: gdObject, oldName: string, newName: string): void; static objectOrGroupRenamedInLayout(project: Project, layout: Layout, oldName: string, newName: string, isObjectGroup: boolean): void; static objectRemovedInLayout(project: Project, layout: Layout, objectName: string): void; + static behaviorsAddedToObjectInLayout(project: Project, layout: Layout, objectName: string): void; static objectOrGroupRenamedInEventsFunction(project: Project, eventsFunction: EventsFunction, globalObjectsContainer: ObjectsContainer, objectsContainer: ObjectsContainer, oldName: string, newName: string, isObjectGroup: boolean): void; static objectRemovedInEventsFunction(project: Project, eventsFunction: EventsFunction, globalObjectsContainer: ObjectsContainer, objectsContainer: ObjectsContainer, objectName: string): void; static objectOrGroupRenamedInEventsBasedObject(project: Project, globalObjectsContainer: ObjectsContainer, eventsBasedObject: EventsBasedObject, oldName: string, newName: string, isObjectGroup: boolean): void; static objectRemovedInEventsBasedObject(project: Project, eventsBasedObject: EventsBasedObject, globalObjectsContainer: ObjectsContainer, objectsContainer: ObjectsContainer, objectName: string): void; static globalObjectOrGroupRenamed(project: Project, oldName: string, newName: string, isObjectGroup: boolean): void; static globalObjectRemoved(project: Project, objectName: string): void; + static behaviorsAddedToGlobalObject(project: Project, objectName: string): void; static getAllObjectTypesUsingEventsBasedBehavior(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedBehavior: EventsBasedBehavior): SetString; static ensureBehaviorEventsFunctionsProperParameters(eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedBehavior: EventsBasedBehavior): void; static ensureObjectEventsFunctionsProperParameters(eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject): void; diff --git a/GDevelop.js/types/gdwholeprojectrefactorer.js b/GDevelop.js/types/gdwholeprojectrefactorer.js index 8d6522f936..512dd66305 100644 --- a/GDevelop.js/types/gdwholeprojectrefactorer.js +++ b/GDevelop.js/types/gdwholeprojectrefactorer.js @@ -25,12 +25,14 @@ declare class gdWholeProjectRefactorer { static renameObjectEffect(project: gdProject, layout: gdLayout, gdObject: gdObject, oldName: string, newName: string): void; static objectOrGroupRenamedInLayout(project: gdProject, layout: gdLayout, oldName: string, newName: string, isObjectGroup: boolean): void; static objectRemovedInLayout(project: gdProject, layout: gdLayout, objectName: string): void; + static behaviorsAddedToObjectInLayout(project: gdProject, layout: gdLayout, objectName: string): void; static objectOrGroupRenamedInEventsFunction(project: gdProject, eventsFunction: gdEventsFunction, globalObjectsContainer: gdObjectsContainer, objectsContainer: gdObjectsContainer, oldName: string, newName: string, isObjectGroup: boolean): void; static objectRemovedInEventsFunction(project: gdProject, eventsFunction: gdEventsFunction, globalObjectsContainer: gdObjectsContainer, objectsContainer: gdObjectsContainer, objectName: string): void; static objectOrGroupRenamedInEventsBasedObject(project: gdProject, globalObjectsContainer: gdObjectsContainer, eventsBasedObject: gdEventsBasedObject, oldName: string, newName: string, isObjectGroup: boolean): void; static objectRemovedInEventsBasedObject(project: gdProject, eventsBasedObject: gdEventsBasedObject, globalObjectsContainer: gdObjectsContainer, objectsContainer: gdObjectsContainer, objectName: string): void; static globalObjectOrGroupRenamed(project: gdProject, oldName: string, newName: string, isObjectGroup: boolean): void; static globalObjectRemoved(project: gdProject, objectName: string): void; + static behaviorsAddedToGlobalObject(project: gdProject, objectName: string): void; static getAllObjectTypesUsingEventsBasedBehavior(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior): gdSetString; static ensureBehaviorEventsFunctionsProperParameters(eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior): void; static ensureObjectEventsFunctionsProperParameters(eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject): void; diff --git a/newIDE/app/src/SceneEditor/index.js b/newIDE/app/src/SceneEditor/index.js index 4580f8e466..42a333ef74 100644 --- a/newIDE/app/src/SceneEditor/index.js +++ b/newIDE/app/src/SceneEditor/index.js @@ -1832,6 +1832,18 @@ export default class SceneEditor extends React.Component { this.reloadResourcesFor( editedObjectWithContext.object ); + if (editedObjectWithContext.global) { + gd.WholeProjectRefactorer.behaviorsAddedToGlobalObject( + project, + editedObjectWithContext.object.getName() + ); + } else { + gd.WholeProjectRefactorer.behaviorsAddedToObjectInLayout( + project, + layout, + editedObjectWithContext.object.getName() + ); + } } this.editObject(null); this.updateBehaviorsSharedData();