diff --git a/Core/GDCore/Events/EventsList.cpp b/Core/GDCore/Events/EventsList.cpp index b2c9361588ce..36c1d493cc27 100644 --- a/Core/GDCore/Events/EventsList.cpp +++ b/Core/GDCore/Events/EventsList.cpp @@ -53,7 +53,7 @@ void EventsList::InsertEvent(std::shared_ptr event, events.push_back(event); } -gd::BaseEvent& EventsList::InsertNewEvent(gd::Project& project, +gd::BaseEvent& EventsList::InsertNewEvent(const gd::Project& project, const gd::String& eventType, size_t position) { gd::BaseEventSPtr event = project.CreateEvent(eventType); diff --git a/Core/GDCore/Events/EventsList.h b/Core/GDCore/Events/EventsList.h index ecb81b76a849..e98e0f54003a 100644 --- a/Core/GDCore/Events/EventsList.h +++ b/Core/GDCore/Events/EventsList.h @@ -66,7 +66,7 @@ class GD_CORE_API EventsList { * \param position Insertion position. If the position is invalid, the object * is inserted at the end of the objects list. */ - gd::BaseEvent& InsertNewEvent(gd::Project& project, + gd::BaseEvent& InsertNewEvent(const gd::Project& project, const gd::String& eventType, size_t position = (size_t)-1); diff --git a/Core/GDCore/IDE/ExtensionEditor/ChildObjectForwardFunctionGenerator.cpp b/Core/GDCore/IDE/ExtensionEditor/ChildObjectForwardFunctionGenerator.cpp new file mode 100644 index 000000000000..4b045fe50096 --- /dev/null +++ b/Core/GDCore/IDE/ExtensionEditor/ChildObjectForwardFunctionGenerator.cpp @@ -0,0 +1,248 @@ +/* + * GDevelop Core + * Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#include "ChildObjectForwardFunctionGenerator.h" + +#include "GDCore/Events/Builtin/CommentEvent.h" +#include "GDCore/Events/Builtin/StandardEvent.h" +#include "GDCore/Extensions/Metadata/BehaviorMetadata.h" +#include "GDCore/Extensions/Metadata/MetadataProvider.h" +#include "GDCore/Extensions/PlatformExtension.h" +#include "GDCore/IDE/WholeProjectRefactorer.h" +#include "GDCore/Project/EventsBasedObject.h" +#include "GDCore/Project/EventsFunction.h" +#include "GDCore/Project/EventsFunctionsExtension.h" +#include "GDCore/Project/Object.h" +#include "GDCore/Project/Project.h" +#include "GDCore/String.h" + +namespace gd { + +void ChildObjectForwardFunctionGenerator::GenerateChildObjectForwardFunctions( + const gd::Project &project, + const gd::EventsFunctionsExtension &eventsFunctionsExtension, + gd::EventsBasedObject &eventsBasedObject, + const gd::String &childObjectName) { + if (!eventsBasedObject.GetObjects().HasObjectNamed(childObjectName)) { + return; + } + auto &childObject = eventsBasedObject.GetObjects().GetObject(childObjectName); + if (!project.HasEventsBasedObject(childObject.GetType())) { + return; + } + auto &childEventsBasedObject = + project.GetEventsBasedObject(childObject.GetType()); + for (auto &childEventsFunction : + childEventsBasedObject.GetEventsFunctions().GetInternalVector()) { + if (childEventsFunction->IsPrivate() || + childEventsFunction->IsDeprecated() || + gd::EventsBasedObject::IsObjectLifecycleEventsFunction( + childEventsFunction->GetName())) { + continue; + } + ChildObjectForwardFunctionGenerator::GenerateChildObjectForwardFunction( + project, eventsFunctionsExtension, eventsBasedObject, childObjectName, + *childEventsFunction); + } +} + +void ChildObjectForwardFunctionGenerator::GenerateChildObjectForwardFunction( + const gd::Project &project, + const gd::EventsFunctionsExtension &parentEventsFunctionsExtension, + gd::EventsBasedObject &parentEventsBasedObject, + const gd::String &childObjectName, + const gd::EventsFunction &childEventsFunction) { + if (!parentEventsBasedObject.GetObjects().HasObjectNamed(childObjectName)) { + return; + } + auto &childObject = + parentEventsBasedObject.GetObjects().GetObject(childObjectName); + if (!project.HasEventsBasedObject(childObject.GetType())) { + return; + } + auto &childEventsBasedObject = + project.GetEventsBasedObject(childObject.GetType()); + auto &functionName = childEventsFunction.GetName(); + if (parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed( + functionName)) { + return; + } + auto &parentEventsFunction = + parentEventsBasedObject.GetEventsFunctions().InsertEventsFunction( + childEventsFunction, 0); + parentEventsFunction.GetEvents().Clear(); + parentEventsBasedObject.GetEventsFunctions() + .AddMissingFunctionsInRootFolder(); + auto &rootFolder = + parentEventsBasedObject.GetEventsFunctions().GetRootFolder(); + // TODO Handle sub-folders + auto &folder = + !childEventsFunction.GetGroup().empty() + ? rootFolder.GetOrCreateChildFolder(childEventsFunction.GetGroup()) + : rootFolder; + rootFolder.MoveFunctionFolderOrFunctionToAnotherFolder( + rootFolder.GetFunctionNamed(parentEventsFunction.GetName()), folder, + folder.GetChildrenCount()); + gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters( + parentEventsFunctionsExtension, parentEventsBasedObject); + + auto childExtensionName = + gd::PlatformExtension::GetExtensionFromFullObjectType( + childObject.GetType()); + auto childFunctionType = + gd::PlatformExtension::GetObjectEventsFunctionFullType( + childExtensionName, childEventsBasedObject.GetName(), functionName); + + bool hasAnyBooleanParameter = false; + for (auto ¶meter : + parentEventsFunction.GetParameters().GetInternalVector()) { + if (parameter->GetValueTypeMetadata().IsBoolean()) { + hasAnyBooleanParameter = true; + break; + } + } + if (hasAnyBooleanParameter && + parentEventsFunction.GetParameters().GetParametersCount() > 2) { + auto &event = dynamic_cast( + parentEventsFunction.GetEvents().InsertNewEvent( + project, "BuiltinCommonInstructions::Comment", 0)); + event.SetComment("TODO: Please implement this function manually."); + return; + } + auto &event = dynamic_cast( + parentEventsFunction.GetEvents().InsertNewEvent( + project, "BuiltinCommonInstructions::Standard", 0)); + switch (childEventsFunction.GetFunctionType()) { + case gd::EventsFunction::FunctionType::ActionWithOperator: + case gd::EventsFunction::FunctionType::Action: { + if (parentEventsFunction.GetParameters().GetParametersCount() == 2 && + hasAnyBooleanParameter) { + gd::Instruction condition; + condition.SetType("BooleanVariable"); + condition.AddParameter("Value"); + condition.AddParameter("True"); + condition.AddParameter(""); + event.GetConditions().Insert(condition, 0); + + gd::Instruction action; + action.SetType(childFunctionType); + action.AddParameter(childObjectName); + action.AddParameter("yes"); + event.GetActions().Insert(action, 0); + { + auto &event = dynamic_cast( + parentEventsFunction.GetEvents().InsertNewEvent( + project, "BuiltinCommonInstructions::Standard", 0)); + + gd::Instruction condition; + condition.SetType("BooleanVariable"); + condition.AddParameter("Value"); + condition.AddParameter("False"); + condition.AddParameter(""); + event.GetConditions().Insert(condition, 0); + + gd::Instruction action; + action.SetType(childFunctionType); + action.AddParameter(childObjectName); + action.AddParameter("no"); + event.GetActions().Insert(action, 0); + } + } else { + gd::Instruction action; + action.SetType(childFunctionType); + for (auto ¶meter : + childEventsFunction + .GetParametersForEvents( + parentEventsBasedObject.GetEventsFunctions()) + .GetInternalVector()) { + if (childEventsFunction.GetFunctionType() == + gd::EventsFunction::FunctionType::ActionWithOperator && + parameter->GetName() == "Value") { + action.AddParameter("="); + } + action.AddParameter(parameter->GetName()); + } + action.SetParameter(0, childObjectName); + event.GetActions().Insert(action, 0); + if (childEventsFunction.IsAsync()) { + gd::Instruction action; + action.SetType("BuiltinAsync::ResolveAsyncEventsFunction"); + event.GetActions().Insert(action, 1); + } + } + break; + } + case gd::EventsFunction::FunctionType::Condition: { + gd::Instruction condition; + condition.SetType(childFunctionType); + for (auto ¶meter : childEventsFunction + .GetParametersForEvents( + parentEventsBasedObject.GetEventsFunctions()) + .GetInternalVector()) { + condition.AddParameter(parameter->GetName()); + } + condition.SetParameter(0, childObjectName); + event.GetConditions().Insert(condition, 0); + + gd::Instruction action; + action.SetType("SetReturnBoolean"); + action.AddParameter("True"); + event.GetActions().Insert(action, 0); + if (childEventsFunction.IsAsync()) { + gd::Instruction action; + action.SetType("BuiltinAsync::ResolveAsyncEventsFunction"); + event.GetActions().Insert(action, 1); + } + break; + } + case gd::EventsFunction::FunctionType::ExpressionAndCondition: + case gd::EventsFunction::FunctionType::Expression: { + gd::Instruction action; + action.SetType(childEventsFunction.GetExpressionType().IsNumber() + ? "SetReturnNumber" + : "SetReturnString"); + gd::String expression = + childObjectName + "." + childEventsFunction.GetName() + "("; + auto ¶meters = childEventsFunction.GetParametersForEvents( + parentEventsBasedObject.GetEventsFunctions()); + for (size_t i = 1; i < parameters.GetParametersCount(); i++) { + if (i > 1) { + expression += ", "; + } + expression += parameters.GetParameter(i).GetName(); + } + expression += ")"; + action.AddParameter(expression); + event.GetActions().Insert(action, 0); + break; + } + default: + break; + } +} + +bool ChildObjectForwardFunctionGenerator::HasAnyChildCustomObject( + const gd::Project &project, gd::EventsBasedObject &eventsBasedObject) { + for (auto &childObject : eventsBasedObject.GetObjects().GetObjects()) { + if (project.HasEventsBasedObject(childObject->GetType())) { + return true; + } + } + return false; +} + +std::vector +ChildObjectForwardFunctionGenerator::GetChildCustomObjectNames( + const gd::Project &project, gd::EventsBasedObject &eventsBasedObject) { + std::vector objectNames; + for (auto &childObject : eventsBasedObject.GetObjects().GetObjects()) { + if (project.HasEventsBasedObject(childObject->GetType())) { + objectNames.push_back(childObject->GetName()); + } + } + return objectNames; +} + +} // namespace gd diff --git a/Core/GDCore/IDE/ExtensionEditor/ChildObjectForwardFunctionGenerator.h b/Core/GDCore/IDE/ExtensionEditor/ChildObjectForwardFunctionGenerator.h new file mode 100644 index 000000000000..2509f543998a --- /dev/null +++ b/Core/GDCore/IDE/ExtensionEditor/ChildObjectForwardFunctionGenerator.h @@ -0,0 +1,47 @@ +/* + * GDevelop Core + * Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#pragma once + +#include "GDCore/String.h" +#include + +namespace gd { +class Project; +class EventsFunctionsExtension; +class EventsBasedObject; +class EventsFunction; +class Object; +} // namespace gd + +namespace gd { +/** + * Generate events functions in a custom object that forwards the call to one of + * its child object. It allows users to quickly expose features from a child + * object. + */ +class GD_CORE_API ChildObjectForwardFunctionGenerator { +public: + static void GenerateChildObjectForwardFunctions( + const gd::Project &project, + const gd::EventsFunctionsExtension &parentEventsFunctionsExtension, + gd::EventsBasedObject &parentEventsBasedObject, + const gd::String &childObjectName); + + static void GenerateChildObjectForwardFunction( + const gd::Project &project, + const gd::EventsFunctionsExtension &parentEventsFunctionsExtension, + gd::EventsBasedObject &parentEventsBasedObject, + const gd::String &childObjectName, + const gd::EventsFunction &eventsFunction); + + static bool HasAnyChildCustomObject(const gd::Project &project, + gd::EventsBasedObject &eventsBasedObject); + + static std::vector + GetChildCustomObjectNames(const gd::Project &project, + gd::EventsBasedObject &eventsBasedObject); +}; +} // namespace gd diff --git a/Core/GDCore/IDE/EventsFunctionsExtensionExtractor.cpp b/Core/GDCore/IDE/ExtensionEditor/EventsFunctionsExtensionExtractor.cpp similarity index 100% rename from Core/GDCore/IDE/EventsFunctionsExtensionExtractor.cpp rename to Core/GDCore/IDE/ExtensionEditor/EventsFunctionsExtensionExtractor.cpp diff --git a/Core/GDCore/IDE/EventsFunctionsExtensionExtractor.h b/Core/GDCore/IDE/ExtensionEditor/EventsFunctionsExtensionExtractor.h similarity index 100% rename from Core/GDCore/IDE/EventsFunctionsExtensionExtractor.h rename to Core/GDCore/IDE/ExtensionEditor/EventsFunctionsExtensionExtractor.h diff --git a/Core/GDCore/IDE/PropertyFunctionGenerator.cpp b/Core/GDCore/IDE/ExtensionEditor/PropertyFunctionGenerator.cpp similarity index 100% rename from Core/GDCore/IDE/PropertyFunctionGenerator.cpp rename to Core/GDCore/IDE/ExtensionEditor/PropertyFunctionGenerator.cpp diff --git a/Core/GDCore/IDE/PropertyFunctionGenerator.h b/Core/GDCore/IDE/ExtensionEditor/PropertyFunctionGenerator.h similarity index 100% rename from Core/GDCore/IDE/PropertyFunctionGenerator.h rename to Core/GDCore/IDE/ExtensionEditor/PropertyFunctionGenerator.h diff --git a/Core/GDCore/Project/EventsBasedObject.cpp b/Core/GDCore/Project/EventsBasedObject.cpp index a1780820fdf4..e3fe875b9fad 100644 --- a/Core/GDCore/Project/EventsBasedObject.cpp +++ b/Core/GDCore/Project/EventsBasedObject.cpp @@ -88,4 +88,10 @@ void EventsBasedObject::UnserializeDefaultVariantFrom( defaultVariant.SetName(""); } +bool EventsBasedObject::IsObjectLifecycleEventsFunction( + const gd::String &functionName) { + return functionName == "onCreated" || functionName == "doStepPostEvents" || + functionName == "onDestroy" || functionName == "onHotReloading"; +} + } // namespace gd diff --git a/Core/GDCore/Project/EventsBasedObject.h b/Core/GDCore/Project/EventsBasedObject.h index 60d6b988895e..07ecad05dcc9 100644 --- a/Core/GDCore/Project/EventsBasedObject.h +++ b/Core/GDCore/Project/EventsBasedObject.h @@ -391,7 +391,19 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity { void UnserializeDefaultVariantFrom(gd::Project &project, const SerializerElement &element); - private: + /** \name Lifecycle event functions + */ + ///@{ + /** + * Check if the name of the function is the name of a lifecycle function (for + * events-based objects), that will be called automatically by the game + * engine. + */ + static bool + IsObjectLifecycleEventsFunction(const gd::String &eventsFunctionName); + ///@} + +private: gd::String defaultName; gd::String assetStoreTag; bool isRenderedIn3D; diff --git a/Core/GDCore/Project/EventsFunctionsContainer.cpp b/Core/GDCore/Project/EventsFunctionsContainer.cpp index 4b233b28ce1b..ca2ef4acf6a6 100644 --- a/Core/GDCore/Project/EventsFunctionsContainer.cpp +++ b/Core/GDCore/Project/EventsFunctionsContainer.cpp @@ -58,6 +58,7 @@ void EventsFunctionsContainer::AddMissingFunctionsInRootFolder() { } } const gd::String &group = groupSource->GetGroup(); + // TODO Handle sub-folders auto &folder = !group.empty() ? rootFolder->GetOrCreateChildFolder(group) : *rootFolder; folder.InsertFunction(&function); diff --git a/Core/GDCore/Project/Project.cpp b/Core/GDCore/Project/Project.cpp index 0f673aa228d6..db176268b6be 100644 --- a/Core/GDCore/Project/Project.cpp +++ b/Core/GDCore/Project/Project.cpp @@ -231,7 +231,7 @@ const gd::EventsBasedBehavior& Project::GetEventsBasedBehavior( } std::shared_ptr Project::CreateEvent( - const gd::String& type, const gd::String& platformName) { + const gd::String& type, const gd::String& platformName) const { for (std::size_t i = 0; i < platforms.size(); ++i) { if (!platformName.empty() && platforms[i]->GetName() != platformName) continue; diff --git a/Core/GDCore/Project/Project.h b/Core/GDCore/Project/Project.h index febc6d7c573b..0f1da1f343e5 100644 --- a/Core/GDCore/Project/Project.h +++ b/Core/GDCore/Project/Project.h @@ -569,7 +569,7 @@ class GD_CORE_API Project { * first platform supporting the object is used. */ std::shared_ptr CreateEvent( - const gd::String& type, const gd::String& platformName = ""); + const gd::String& type, const gd::String& platformName = "") const; ///@} /** \name Layouts management diff --git a/Core/tests/ChildObjectForwardFunctionGenerator.cpp b/Core/tests/ChildObjectForwardFunctionGenerator.cpp new file mode 100644 index 000000000000..e39aeb9e4750 --- /dev/null +++ b/Core/tests/ChildObjectForwardFunctionGenerator.cpp @@ -0,0 +1,527 @@ +/* + * GDevelop Core + * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +/** + * @file Tests covering common features of GDevelop Core. + */ +#include "GDCore/IDE/ExtensionEditor/ChildObjectForwardFunctionGenerator.h" +#include "DummyPlatform.h" +#include "GDCore/Events/Builtin/CommentEvent.h" +#include "GDCore/Events/Builtin/StandardEvent.h" +#include "GDCore/Extensions/Metadata/ValueTypeMetadata.h" +#include "GDCore/Extensions/Platform.h" +#include "GDCore/IDE/WholeProjectRefactorer.h" +#include "GDCore/Project/EventsBasedBehavior.h" +#include "GDCore/Project/EventsFunctionsExtension.h" +#include "GDCore/Project/Project.h" +#include "GDCore/String.h" +#include "catch.hpp" + +TEST_CASE("ChildObjectForwardFunctionGenerator", "[common]") { + SECTION("Can generate a function to forward an action") { + gd::Platform platform; + gd::Project project; + SetupProjectWithDummyPlatform(project, platform); + auto &extension = + project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0); + auto &parentEventsBasedObject = extension.GetEventsBasedObjects().InsertNew( + "MyParentEventsBasedObject", 0); + auto &childEventsBasedObject = extension.GetEventsBasedObjects().InsertNew( + "MyChildEventsBasedObject", 0); + parentEventsBasedObject.GetObjects().InsertNewObject( + project, "MyEventsExtension::MyChildEventsBasedObject", "MyChildObject", + 0); + + auto &childFunction = + childEventsBasedObject.GetEventsFunctions().InsertNewEventsFunction( + "MyFunction", 0); + childFunction.SetFunctionType(gd::EventsFunction::FunctionType::Action); + childFunction.SetFullName("My function"); + gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters( + extension, childEventsBasedObject); + { + auto ¶meter = + childFunction.GetParameters().AddNewParameter("MyStringParameter"); + parameter.SetType("string"); + } + { + auto ¶meter = + childFunction.GetParameters().AddNewParameter("MyNumberParameter"); + parameter.SetType("number"); + } + + gd::ChildObjectForwardFunctionGenerator:: + GenerateChildObjectForwardFunctions( + project, extension, parentEventsBasedObject, "MyChildObject"); + + REQUIRE(parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed( + "MyFunction")); + auto &parentFunction = + parentEventsBasedObject.GetEventsFunctions().GetEventsFunction( + "MyFunction"); + + REQUIRE(parentFunction.GetFullName() == "My function"); + REQUIRE(parentFunction.GetParameters().GetParametersCount() == 3); + REQUIRE(parentFunction.GetParameters().GetParameter(0).GetType() == + "object"); + REQUIRE(parentFunction.GetParameters().GetParameter(0).GetExtraInfo() == + "MyEventsExtension::MyParentEventsBasedObject"); + REQUIRE(parentFunction.GetParameters().GetParameter(1).GetType() == + "string"); + REQUIRE(parentFunction.GetParameters().GetParameter(2).GetType() == + "number"); + + REQUIRE(parentFunction.GetEvents().GetEventsCount() == 1); + auto &event = parentFunction.GetEvents().GetEvent(0); + REQUIRE(event.GetInstructionList("conditions")->GetCount() == 0); + REQUIRE(event.GetInstructionList("actions")->GetCount() == 1); + auto &action = event.GetInstructionList("actions")->Get(0); + REQUIRE(action.GetType() == + "MyEventsExtension::MyChildEventsBasedObject::MyFunction"); + REQUIRE(action.GetParametersCount() == 3); + REQUIRE(action.GetParameter(0).GetPlainString() == "MyChildObject"); + REQUIRE(action.GetParameter(1).GetPlainString() == "MyStringParameter"); + REQUIRE(action.GetParameter(2).GetPlainString() == "MyNumberParameter"); + } + + SECTION("Can generate a function to forward a condition") { + gd::Platform platform; + gd::Project project; + SetupProjectWithDummyPlatform(project, platform); + auto &extension = + project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0); + auto &parentEventsBasedObject = extension.GetEventsBasedObjects().InsertNew( + "MyParentEventsBasedObject", 0); + auto &childEventsBasedObject = extension.GetEventsBasedObjects().InsertNew( + "MyChildEventsBasedObject", 0); + + parentEventsBasedObject.GetObjects().InsertNewObject( + project, "MyEventsExtension::MyChildEventsBasedObject", "MyChildObject", + 0); + auto &childFunction = + childEventsBasedObject.GetEventsFunctions().InsertNewEventsFunction( + "MyFunction", 0); + childFunction.SetFunctionType(gd::EventsFunction::FunctionType::Condition); + childFunction.SetFullName("My function"); + gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters( + extension, childEventsBasedObject); + { + auto ¶meter = + childFunction.GetParameters().AddNewParameter("MyStringParameter"); + parameter.SetType("string"); + } + { + auto ¶meter = + childFunction.GetParameters().AddNewParameter("MyNumberParameter"); + parameter.SetType("number"); + } + + gd::ChildObjectForwardFunctionGenerator:: + GenerateChildObjectForwardFunctions( + project, extension, parentEventsBasedObject, "MyChildObject"); + + REQUIRE(parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed( + "MyFunction")); + auto &parentFunction = + parentEventsBasedObject.GetEventsFunctions().GetEventsFunction( + "MyFunction"); + + REQUIRE(parentFunction.GetFullName() == "My function"); + REQUIRE(parentFunction.GetParameters().GetParametersCount() == 3); + REQUIRE(parentFunction.GetParameters().GetParameter(0).GetType() == + "object"); + REQUIRE(parentFunction.GetParameters().GetParameter(0).GetExtraInfo() == + "MyEventsExtension::MyParentEventsBasedObject"); + REQUIRE(parentFunction.GetParameters().GetParameter(1).GetType() == + "string"); + REQUIRE(parentFunction.GetParameters().GetParameter(2).GetType() == + "number"); + + REQUIRE(parentFunction.GetEvents().GetEventsCount() == 1); + auto &event = parentFunction.GetEvents().GetEvent(0); + REQUIRE(event.GetInstructionList("conditions")->GetCount() == 1); + auto &condition = event.GetInstructionList("conditions")->Get(0); + REQUIRE(condition.GetType() == + "MyEventsExtension::MyChildEventsBasedObject::MyFunction"); + REQUIRE(condition.GetParametersCount() == 3); + REQUIRE(condition.GetParameter(0).GetPlainString() == "MyChildObject"); + REQUIRE(condition.GetParameter(1).GetPlainString() == "MyStringParameter"); + REQUIRE(condition.GetParameter(2).GetPlainString() == "MyNumberParameter"); + + REQUIRE(event.GetInstructionList("actions")->GetCount() == 1); + auto &action = event.GetInstructionList("actions")->Get(0); + REQUIRE(action.GetType() == "SetReturnBoolean"); + REQUIRE(action.GetParametersCount() == 1); + REQUIRE(action.GetParameter(0).GetPlainString() == "True"); + } + + SECTION("Can generate a function to forward an expression") { + gd::Platform platform; + gd::Project project; + SetupProjectWithDummyPlatform(project, platform); + auto &extension = + project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0); + auto &parentEventsBasedObject = extension.GetEventsBasedObjects().InsertNew( + "MyParentEventsBasedObject", 0); + auto &childEventsBasedObject = extension.GetEventsBasedObjects().InsertNew( + "MyChildEventsBasedObject", 0); + + parentEventsBasedObject.GetObjects().InsertNewObject( + project, "MyEventsExtension::MyChildEventsBasedObject", "MyChildObject", + 0); + auto &childFunction = + childEventsBasedObject.GetEventsFunctions().InsertNewEventsFunction( + "MyFunction", 0); + childFunction.SetFunctionType(gd::EventsFunction::FunctionType::Expression); + childFunction.SetFullName("My function"); + gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters( + extension, childEventsBasedObject); + { + auto ¶meter = + childFunction.GetParameters().AddNewParameter("MyStringParameter"); + parameter.SetType("string"); + } + { + auto ¶meter = + childFunction.GetParameters().AddNewParameter("MyNumberParameter"); + parameter.SetType("number"); + } + + gd::ChildObjectForwardFunctionGenerator:: + GenerateChildObjectForwardFunctions( + project, extension, parentEventsBasedObject, "MyChildObject"); + + REQUIRE(parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed( + "MyFunction")); + auto &parentFunction = + parentEventsBasedObject.GetEventsFunctions().GetEventsFunction( + "MyFunction"); + + REQUIRE(parentFunction.GetFullName() == "My function"); + REQUIRE(parentFunction.GetFunctionType() == + gd::EventsFunction::FunctionType::Expression); + REQUIRE(parentFunction.GetExpressionType().IsNumber()); + REQUIRE(parentFunction.GetParameters().GetParametersCount() == 3); + REQUIRE(parentFunction.GetParameters().GetParameter(0).GetType() == + "object"); + REQUIRE(parentFunction.GetParameters().GetParameter(0).GetExtraInfo() == + "MyEventsExtension::MyParentEventsBasedObject"); + REQUIRE(parentFunction.GetParameters().GetParameter(1).GetType() == + "string"); + REQUIRE(parentFunction.GetParameters().GetParameter(2).GetType() == + "number"); + + REQUIRE(parentFunction.GetEvents().GetEventsCount() == 1); + auto &event = parentFunction.GetEvents().GetEvent(0); + REQUIRE(event.GetInstructionList("conditions")->GetCount() == 0); + REQUIRE(event.GetInstructionList("actions")->GetCount() == 1); + auto &action = event.GetInstructionList("actions")->Get(0); + REQUIRE(action.GetType() == "SetReturnNumber"); + REQUIRE(action.GetParametersCount() == 1); + REQUIRE(action.GetParameter(0).GetPlainString() == + "MyChildObject.MyFunction(MyStringParameter, MyNumberParameter)"); + } + + SECTION("Can generate a function to forward a getter and setter") { + gd::Platform platform; + gd::Project project; + SetupProjectWithDummyPlatform(project, platform); + auto &extension = + project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0); + auto &parentEventsBasedObject = extension.GetEventsBasedObjects().InsertNew( + "MyParentEventsBasedObject", 0); + auto &childEventsBasedObject = extension.GetEventsBasedObjects().InsertNew( + "MyChildEventsBasedObject", 0); + + parentEventsBasedObject.GetObjects().InsertNewObject( + project, "MyEventsExtension::MyChildEventsBasedObject", "MyChildObject", + 0); + { + auto &childFunction = + childEventsBasedObject.GetEventsFunctions().InsertNewEventsFunction( + "MyGetterFunction", 0); + childFunction.SetFunctionType( + gd::EventsFunction::FunctionType::ExpressionAndCondition); + childFunction.GetExpressionType().SetName("number"); + gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters( + extension, childEventsBasedObject); + } + { + auto &childFunction = + childEventsBasedObject.GetEventsFunctions().InsertNewEventsFunction( + "MySetterFunction", 1); + childFunction.SetFunctionType( + gd::EventsFunction::FunctionType::ActionWithOperator); + childFunction.SetGetterName("MyGetterFunction"); + gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters( + extension, childEventsBasedObject); + } + + gd::ChildObjectForwardFunctionGenerator:: + GenerateChildObjectForwardFunctions( + project, extension, parentEventsBasedObject, "MyChildObject"); + + { + REQUIRE( + parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed( + "MyGetterFunction")); + auto &parentFunction = + parentEventsBasedObject.GetEventsFunctions().GetEventsFunction( + "MyGetterFunction"); + REQUIRE(parentFunction.GetFunctionType() == + gd::EventsFunction::FunctionType::ExpressionAndCondition); + REQUIRE(parentFunction.GetExpressionType().IsNumber()); + REQUIRE(parentFunction.GetParameters().GetParametersCount() == 1); + REQUIRE(parentFunction.GetParameters().GetParameter(0).GetType() == + "object"); + REQUIRE(parentFunction.GetParameters().GetParameter(0).GetExtraInfo() == + "MyEventsExtension::MyParentEventsBasedObject"); + + REQUIRE(parentFunction.GetEvents().GetEventsCount() == 1); + auto &event = parentFunction.GetEvents().GetEvent(0); + REQUIRE(event.GetInstructionList("conditions")->GetCount() == 0); + + REQUIRE(event.GetInstructionList("actions")->GetCount() == 1); + auto &action = event.GetInstructionList("actions")->Get(0); + REQUIRE(action.GetType() == "SetReturnNumber"); + REQUIRE(action.GetParametersCount() == 1); + REQUIRE(action.GetParameter(0).GetPlainString() == + "MyChildObject.MyGetterFunction()"); + } + { + REQUIRE( + parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed( + "MySetterFunction")); + auto &parentFunction = + parentEventsBasedObject.GetEventsFunctions().GetEventsFunction( + "MySetterFunction"); + REQUIRE(parentFunction.GetFunctionType() == + gd::EventsFunction::FunctionType::ActionWithOperator); + REQUIRE(parentFunction.GetGetterName() == "MyGetterFunction"); + REQUIRE(parentFunction.GetParameters().GetParametersCount() == 1); + REQUIRE(parentFunction.GetParameters().GetParameter(0).GetType() == + "object"); + REQUIRE(parentFunction.GetParameters().GetParameter(0).GetExtraInfo() == + "MyEventsExtension::MyParentEventsBasedObject"); + + REQUIRE(parentFunction.GetEvents().GetEventsCount() == 1); + auto &event = parentFunction.GetEvents().GetEvent(0); + REQUIRE(event.GetInstructionList("conditions")->GetCount() == 0); + + REQUIRE(event.GetInstructionList("actions")->GetCount() == 1); + auto &action = event.GetInstructionList("actions")->Get(0); + REQUIRE(action.GetType() == + "MyEventsExtension::MyChildEventsBasedObject::MySetterFunction"); + REQUIRE(action.GetParametersCount() == 3); + REQUIRE(action.GetParameter(0).GetPlainString() == "MyChildObject"); + REQUIRE(action.GetParameter(1).GetPlainString() == "="); + REQUIRE(action.GetParameter(2).GetPlainString() == "Value"); + } + } + + SECTION("Can generate a function to forward a boolean setter") { + gd::Platform platform; + gd::Project project; + SetupProjectWithDummyPlatform(project, platform); + auto &extension = + project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0); + auto &parentEventsBasedObject = extension.GetEventsBasedObjects().InsertNew( + "MyParentEventsBasedObject", 0); + auto &childEventsBasedObject = extension.GetEventsBasedObjects().InsertNew( + "MyChildEventsBasedObject", 0); + + parentEventsBasedObject.GetObjects().InsertNewObject( + project, "MyEventsExtension::MyChildEventsBasedObject", "MyChildObject", + 0); + + auto &childFunction = + childEventsBasedObject.GetEventsFunctions().InsertNewEventsFunction( + "MySetterFunction", 1); + childFunction.SetFunctionType(gd::EventsFunction::FunctionType::Action); + gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters( + extension, childEventsBasedObject); + { + auto ¶meter = childFunction.GetParameters().AddNewParameter("Value"); + parameter.SetType("yesorno"); + } + + gd::ChildObjectForwardFunctionGenerator:: + GenerateChildObjectForwardFunctions( + project, extension, parentEventsBasedObject, "MyChildObject"); + + REQUIRE(parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed( + "MySetterFunction")); + auto &parentFunction = + parentEventsBasedObject.GetEventsFunctions().GetEventsFunction( + "MySetterFunction"); + REQUIRE(parentFunction.GetFunctionType() == + gd::EventsFunction::FunctionType::Action); + REQUIRE(parentFunction.GetParameters().GetParametersCount() == 2); + REQUIRE(parentFunction.GetParameters().GetParameter(0).GetType() == + "object"); + REQUIRE(parentFunction.GetParameters().GetParameter(0).GetExtraInfo() == + "MyEventsExtension::MyParentEventsBasedObject"); + REQUIRE(parentFunction.GetParameters().GetParameter(1).GetType() == + "yesorno"); + + REQUIRE(parentFunction.GetEvents().GetEventsCount() == 2); + { + auto &event = parentFunction.GetEvents().GetEvent(0); + REQUIRE(event.GetInstructionList("conditions")->GetCount() == 1); + auto &condition = event.GetInstructionList("conditions")->Get(0); + REQUIRE(condition.GetType() == "BooleanVariable"); + REQUIRE(condition.GetParametersCount() == 3); + REQUIRE(condition.GetParameter(0).GetPlainString() == "Value"); + REQUIRE(condition.GetParameter(1).GetPlainString() == "False"); + REQUIRE(condition.GetParameter(2).GetPlainString() == ""); + + REQUIRE(event.GetInstructionList("actions")->GetCount() == 1); + auto &action = event.GetInstructionList("actions")->Get(0); + REQUIRE(action.GetType() == + "MyEventsExtension::MyChildEventsBasedObject::MySetterFunction"); + REQUIRE(action.GetParametersCount() == 2); + REQUIRE(action.GetParameter(0).GetPlainString() == "MyChildObject"); + REQUIRE(action.GetParameter(1).GetPlainString() == "no"); + } + { + auto &event = parentFunction.GetEvents().GetEvent(1); + REQUIRE(event.GetInstructionList("conditions")->GetCount() == 1); + auto &condition = event.GetInstructionList("conditions")->Get(0); + REQUIRE(condition.GetType() == "BooleanVariable"); + REQUIRE(condition.GetParametersCount() == 3); + REQUIRE(condition.GetParameter(0).GetPlainString() == "Value"); + REQUIRE(condition.GetParameter(1).GetPlainString() == "True"); + REQUIRE(condition.GetParameter(2).GetPlainString() == ""); + + REQUIRE(event.GetInstructionList("actions")->GetCount() == 1); + auto &action = event.GetInstructionList("actions")->Get(0); + REQUIRE(action.GetType() == + "MyEventsExtension::MyChildEventsBasedObject::MySetterFunction"); + REQUIRE(action.GetParametersCount() == 2); + REQUIRE(action.GetParameter(0).GetPlainString() == "MyChildObject"); + REQUIRE(action.GetParameter(1).GetPlainString() == "yes"); + } + } + + SECTION("Can generate a todo comment for function with boolean parameters") { + gd::Platform platform; + gd::Project project; + SetupProjectWithDummyPlatform(project, platform); + auto &extension = + project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0); + auto &parentEventsBasedObject = extension.GetEventsBasedObjects().InsertNew( + "MyParentEventsBasedObject", 0); + auto &childEventsBasedObject = extension.GetEventsBasedObjects().InsertNew( + "MyChildEventsBasedObject", 0); + + parentEventsBasedObject.GetObjects().InsertNewObject( + project, "MyEventsExtension::MyChildEventsBasedObject", "MyChildObject", + 0); + + auto &childFunction = + childEventsBasedObject.GetEventsFunctions().InsertNewEventsFunction( + "MySetterFunction", 1); + childFunction.SetFunctionType(gd::EventsFunction::FunctionType::Action); + gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters( + extension, childEventsBasedObject); + { + auto ¶meter = childFunction.GetParameters().AddNewParameter("Value"); + parameter.SetType("yesorno"); + } + { + auto ¶meter = + childFunction.GetParameters().AddNewParameter("MyNumberParameter"); + parameter.SetType("number"); + } + + gd::ChildObjectForwardFunctionGenerator:: + GenerateChildObjectForwardFunctions( + project, extension, parentEventsBasedObject, "MyChildObject"); + + REQUIRE(parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed( + "MySetterFunction")); + auto &parentFunction = + parentEventsBasedObject.GetEventsFunctions().GetEventsFunction( + "MySetterFunction"); + REQUIRE(parentFunction.GetFunctionType() == + gd::EventsFunction::FunctionType::Action); + REQUIRE(parentFunction.GetParameters().GetParametersCount() == 3); + REQUIRE(parentFunction.GetParameters().GetParameter(0).GetType() == + "object"); + REQUIRE(parentFunction.GetParameters().GetParameter(0).GetExtraInfo() == + "MyEventsExtension::MyParentEventsBasedObject"); + REQUIRE(parentFunction.GetParameters().GetParameter(1).GetType() == + "yesorno"); + REQUIRE(parentFunction.GetParameters().GetParameter(2).GetType() == + "number"); + + REQUIRE(parentFunction.GetEvents().GetEventsCount() == 1); + { + auto &event = parentFunction.GetEvents().GetEvent(0); + REQUIRE(event.GetType() == "BuiltinCommonInstructions::Comment"); + auto &comment = dynamic_cast(event); + REQUIRE(comment.GetComment() == + "TODO: Please implement this function manually."); + } + } + + SECTION("Can generate a function to forward an asynchronous action") { + gd::Platform platform; + gd::Project project; + SetupProjectWithDummyPlatform(project, platform); + auto &extension = + project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0); + auto &parentEventsBasedObject = extension.GetEventsBasedObjects().InsertNew( + "MyParentEventsBasedObject", 0); + auto &childEventsBasedObject = extension.GetEventsBasedObjects().InsertNew( + "MyChildEventsBasedObject", 0); + parentEventsBasedObject.GetObjects().InsertNewObject( + project, "MyEventsExtension::MyChildEventsBasedObject", "MyChildObject", + 0); + + auto &childFunction = + childEventsBasedObject.GetEventsFunctions().InsertNewEventsFunction( + "MyFunction", 0); + childFunction.SetFunctionType(gd::EventsFunction::FunctionType::Action); + childFunction.SetAsync(true); + gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters( + extension, childEventsBasedObject); + + gd::ChildObjectForwardFunctionGenerator:: + GenerateChildObjectForwardFunctions( + project, extension, parentEventsBasedObject, "MyChildObject"); + + REQUIRE(parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed( + "MyFunction")); + auto &parentFunction = + parentEventsBasedObject.GetEventsFunctions().GetEventsFunction( + "MyFunction"); + REQUIRE(parentFunction.GetFunctionType() == + gd::EventsFunction::FunctionType::Action); + REQUIRE(parentFunction.IsAsync()); + REQUIRE(parentFunction.GetParameters().GetParametersCount() == 1); + REQUIRE(parentFunction.GetParameters().GetParameter(0).GetType() == + "object"); + REQUIRE(parentFunction.GetParameters().GetParameter(0).GetExtraInfo() == + "MyEventsExtension::MyParentEventsBasedObject"); + + REQUIRE(parentFunction.GetEvents().GetEventsCount() == 1); + auto &event = parentFunction.GetEvents().GetEvent(0); + REQUIRE(event.GetInstructionList("conditions")->GetCount() == 0); + REQUIRE(event.GetInstructionList("actions")->GetCount() == 2); + { + auto &action = event.GetInstructionList("actions")->Get(0); + REQUIRE(action.GetType() == + "MyEventsExtension::MyChildEventsBasedObject::MyFunction"); + REQUIRE(action.GetParametersCount() == 1); + REQUIRE(action.GetParameter(0).GetPlainString() == "MyChildObject"); + } + { + auto &action = event.GetInstructionList("actions")->Get(1); + REQUIRE(action.GetType() == "BuiltinAsync::ResolveAsyncEventsFunction"); + REQUIRE(action.GetParametersCount() == 0); + } + } +} diff --git a/Core/tests/DummyPlatform.cpp b/Core/tests/DummyPlatform.cpp index 2f81fec23039..7fb42eced174 100644 --- a/Core/tests/DummyPlatform.cpp +++ b/Core/tests/DummyPlatform.cpp @@ -11,6 +11,7 @@ #include "GDCore/Project/Layout.h" #include "GDCore/Project/Project.h" #include "GDCore/Tools/Localization.h" +#include "GDCore/Events/Builtin/CommentEvent.h" #include "GDCore/Events/Builtin/StandardEvent.h" #include "GDCore/Events/Builtin/ForEachChildVariableEvent.h" #include "GDCore/Events/Builtin/RepeatEvent.h" @@ -175,6 +176,7 @@ void SetupProjectWithDummyPlatform(gd::Project& project, commonInstructionsExtension->AddEvent("Else", "Else event", "", "", "", std::make_shared()); commonInstructionsExtension->AddEvent("ForEachChildVariable", "For each child variable event", "", "", "", std::make_shared()); commonInstructionsExtension->AddEvent("Repeat", "Repeat event", "", "", "", std::make_shared()); + commonInstructionsExtension->AddEvent("Comment", "Comment event", "", "", "", std::make_shared()); std::shared_ptr baseObjectExtension = std::shared_ptr(new gd::PlatformExtension); diff --git a/Core/tests/PropertyFunctionGenerator.cpp b/Core/tests/PropertyFunctionGenerator.cpp index 076ac77e24da..ab4eadfb8153 100644 --- a/Core/tests/PropertyFunctionGenerator.cpp +++ b/Core/tests/PropertyFunctionGenerator.cpp @@ -6,7 +6,7 @@ /** * @file Tests covering common features of GDevelop Core. */ -#include "GDCore/IDE/PropertyFunctionGenerator.h" +#include "GDCore/IDE/ExtensionEditor/PropertyFunctionGenerator.h" #include "DummyPlatform.h" #include "GDCore/Events/Builtin/StandardEvent.h" #include "GDCore/Extensions/Metadata/ValueTypeMetadata.h" diff --git a/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp b/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp index e7397e40505f..3a601dc2a8d9 100644 --- a/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp +++ b/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp @@ -413,8 +413,7 @@ bool MetadataDeclarationHelper::IsBehaviorLifecycleEventsFunction( */ bool MetadataDeclarationHelper::IsObjectLifecycleEventsFunction( const gd::String &functionName) { - return functionName == "onCreated" || functionName == "doStepPostEvents" || - functionName == "onDestroy" || functionName == "onHotReloading"; + return gd::EventsBasedObject::IsObjectLifecycleEventsFunction(functionName); } /** diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index d3f2e4bec026..2821aced0fce 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -3134,6 +3134,20 @@ interface PropertyFunctionGenerator { }; +interface ChildObjectForwardFunctionGenerator { + void STATIC_GenerateChildObjectForwardFunctions( + [Const, Ref] Project project, + [Const, Ref] EventsFunctionsExtension extension, + [Ref] EventsBasedObject eventsBasedObject, + [Const] DOMString childObjectName); + boolean STATIC_HasAnyChildCustomObject( + [Const, Ref] Project project, + [Ref] EventsBasedObject eventsBasedObject); + [Value] VectorString STATIC_GetChildCustomObjectNames( + [Const, Ref] Project project, + [Ref] EventsBasedObject eventsBasedObject); +}; + interface UsedExtensionsResult { [Const, Ref] SetString GetUsedExtensions(); }; diff --git a/GDevelop.js/Bindings/Wrapper.cpp b/GDevelop.js/Bindings/Wrapper.cpp index 83ad7f3f65c3..7bcbc178ee19 100644 --- a/GDevelop.js/Bindings/Wrapper.cpp +++ b/GDevelop.js/Bindings/Wrapper.cpp @@ -53,7 +53,9 @@ #include #include #include -#include +#include +#include +#include #include #include #include @@ -65,7 +67,6 @@ #include #include #include -#include #include #include #include @@ -762,6 +763,9 @@ typedef std::vector VectorPropertyDescriptorChoice #define STATIC_CanGenerateGetterAndSetter CanGenerateGetterAndSetter #define STATIC_GenerateConditionSkeleton GenerateConditionSkeleton #define STATIC_GenerateExpressionSkeleton GenerateExpressionSkeleton +#define STATIC_GenerateChildObjectForwardFunctions GenerateChildObjectForwardFunctions +#define STATIC_HasAnyChildCustomObject HasAnyChildCustomObject +#define STATIC_GetChildCustomObjectNames GetChildCustomObjectNames #define STATIC_UpdateReturnActionType UpdateReturnActionType #define STATIC_CreateRectangle CreateRectangle #define STATIC_SanityCheckBehaviorProperty SanityCheckBehaviorProperty diff --git a/GDevelop.js/types.d.ts b/GDevelop.js/types.d.ts index 7c2eb3ea2e87..d9c57711dd7b 100644 --- a/GDevelop.js/types.d.ts +++ b/GDevelop.js/types.d.ts @@ -2268,6 +2268,12 @@ export class PropertyFunctionGenerator extends EmscriptenObject { static updateReturnActionType(project: Project, eventsFunction: EventsFunction): void; } +export class ChildObjectForwardFunctionGenerator extends EmscriptenObject { + static generateChildObjectForwardFunctions(project: Project, extension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject, childObjectName: string): void; + static hasAnyChildCustomObject(project: Project, eventsBasedObject: EventsBasedObject): boolean; + static getChildCustomObjectNames(project: Project, eventsBasedObject: EventsBasedObject): VectorString; +} + export class UsedExtensionsResult extends EmscriptenObject { getUsedExtensions(): SetString; } diff --git a/GDevelop.js/types/gdchildobjectforwardfunctiongenerator.js b/GDevelop.js/types/gdchildobjectforwardfunctiongenerator.js new file mode 100644 index 000000000000..a304ca9b34ad --- /dev/null +++ b/GDevelop.js/types/gdchildobjectforwardfunctiongenerator.js @@ -0,0 +1,8 @@ +// Automatically generated by GDevelop.js/scripts/generate-types.js +declare class gdChildObjectForwardFunctionGenerator { + static generateChildObjectForwardFunctions(project: gdProject, extension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, childObjectName: string): void; + static hasAnyChildCustomObject(project: gdProject, eventsBasedObject: gdEventsBasedObject): boolean; + static getChildCustomObjectNames(project: gdProject, eventsBasedObject: gdEventsBasedObject): gdVectorString; + delete(): void; + ptr: number; +}; \ No newline at end of file diff --git a/GDevelop.js/types/libgdevelop.js b/GDevelop.js/types/libgdevelop.js index 9b7af1e07e6d..f752b4340dc2 100644 --- a/GDevelop.js/types/libgdevelop.js +++ b/GDevelop.js/types/libgdevelop.js @@ -214,6 +214,7 @@ declare class libGDevelop { ObjectTools: Class; EventsBasedObjectDependencyFinder: Class; PropertyFunctionGenerator: Class; + ChildObjectForwardFunctionGenerator: Class; UsedExtensionsResult: Class; UsedExtensionsFinder: Class; UsedObjectTypeFinder: Class; diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsBasedBehaviorOrObjectEditor/ChildObjectForwardFunctionGenerationDialog.js b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsBasedBehaviorOrObjectEditor/ChildObjectForwardFunctionGenerationDialog.js new file mode 100644 index 000000000000..74079da434fb --- /dev/null +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsBasedBehaviorOrObjectEditor/ChildObjectForwardFunctionGenerationDialog.js @@ -0,0 +1,76 @@ +// @flow +import { Trans } from '@lingui/macro'; + +import * as React from 'react'; +import FlatButton from '../../UI/FlatButton'; +import RaisedButton from '../../UI/RaisedButton'; +import Text from '../../UI/Text'; +import Dialog from '../../UI/Dialog'; +import HelpButton from '../../UI/HelpButton'; +import ObjectSelector from '../../ObjectsList/ObjectSelector'; +import { ProjectScopedContainersAccessor } from '../../InstructionOrExpression/EventsScope'; + +const excludedObjectOrGroupNames = ['Object']; + +type Props = {| + project: gdProject, + projectScopedContainersAccessor: ProjectScopedContainersAccessor, + onCancel: () => void, + onChoose: (childObjectName: string) => void, +|}; + +export default function ChildObjectForwardFunctionGenerationDialog({ + project, + projectScopedContainersAccessor, + onChoose, + onCancel, +}: Props): React.Node { + const [objectName, setObjectName] = React.useState(''); + + return ( + Choose an object} + secondaryActions={[ + , + ]} + actions={[ + Cancel} + onClick={onCancel} + key={'close'} + />, + Generate} + primary + keyboardFocused={true} + onClick={() => onChoose(objectName)} + key={'generate'} + />, + ]} + open + onRequestClose={onCancel} + maxWidth="sm" + > + + + Functions from this child-object will be forwarded by the parent. + + + Choose an object} + fullWidth + openOnFocus={false} + /> + + ); +} diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsBasedBehaviorOrObjectEditor/EventsBasedObjectEditor.js b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsBasedBehaviorOrObjectEditor/EventsBasedObjectEditor.js index a769b1080811..f8d049f05002 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsBasedBehaviorOrObjectEditor/EventsBasedObjectEditor.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsBasedBehaviorOrObjectEditor/EventsBasedObjectEditor.js @@ -18,12 +18,16 @@ import { CompactTextAreaField } from '../../UI/CompactTextAreaField'; import CompactSemiControlledTextField from '../../UI/CompactSemiControlledTextField'; import { CompactToggleField } from '../../UI/CompactToggleField'; import { CompactIconField } from '../OptionsEditorDialog/CompactIconField'; +import { ProjectScopedContainersAccessor } from '../../InstructionOrExpression/EventsScope'; +import ChildObjectForwardFunctionGenerationDialog from './ChildObjectForwardFunctionGenerationDialog'; const gd: libGDevelop = global.gd; const isDev = Window.isDev(); type Props = {| + project: gdProject, + projectScopedContainersAccessor: ProjectScopedContainersAccessor, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, onOpenCustomObjectEditor: () => void, @@ -34,6 +38,8 @@ type Props = {| |}; export default function EventsBasedObjectEditor({ + project, + projectScopedContainersAccessor, eventsFunctionsExtension, eventsBasedObject, onOpenCustomObjectEditor, @@ -42,6 +48,10 @@ export default function EventsBasedObjectEditor({ }: Props): React.Node { const forceUpdate = useForceUpdate(); const [isLoading, setIsLoading] = React.useState(false); + const [ + isChildObjectForwardFunctionGenerationDialogShown, + setChildObjectForwardFunctionGenerationDialogShown, + ] = React.useState(false); const onChange = React.useCallback( () => { @@ -53,6 +63,22 @@ export default function EventsBasedObjectEditor({ [forceUpdate, unsavedChanges] ); + const generateChildObjectForwardFunctions = React.useCallback( + (childObjectName: string) => { + if (!childObjectName) { + return; + } + gd.ChildObjectForwardFunctionGenerator.generateChildObjectForwardFunctions( + project, + eventsFunctionsExtension, + eventsBasedObject, + childObjectName + ); + onChange(); + }, + [eventsBasedObject, eventsFunctionsExtension, onChange, project] + ); + return ( {({ i18n }) => ( @@ -237,12 +263,46 @@ export default function EventsBasedObjectEditor({ /> )} + {gd.ChildObjectForwardFunctionGenerator.hasAnyChildCustomObject( + project, + eventsBasedObject + ) ? ( + ( + Generate functions} + onClick={() => + setChildObjectForwardFunctionGenerationDialogShown(true) + } + /> + )} + > + + Some functions can be generated to forward child-object's + functions. + + + ) : null} + {isChildObjectForwardFunctionGenerationDialogShown && ( + { + generateChildObjectForwardFunctions(childObjectName); + setChildObjectForwardFunctionGenerationDialogShown(false); + }} + onCancel={() => + setChildObjectForwardFunctionGenerationDialogShown(false) + } + /> + )} )} diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsBasedBehaviorOrObjectEditor/index.js b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsBasedBehaviorOrObjectEditor/index.js index 199105e96169..b15aaecc7079 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsBasedBehaviorOrObjectEditor/index.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsBasedBehaviorOrObjectEditor/index.js @@ -205,6 +205,10 @@ export const EventsBasedBehaviorOrObjectEditor: React.ComponentType<{ /> ) : eventsBasedObject ? ( ( openVariableEditorDialog={setVariableEditorOpen} globalObjectsContainer={props.globalObjectsContainer} objectsContainer={props.objectsContainer} + project={project} projectScopedContainersAccessor={projectScopedContainersAccessor} scope={scope} getVariableSourceFromIdentifier={getVariableSourceFromIdentifier} diff --git a/newIDE/app/src/EventsSheet/ParameterFields/ParameterFieldCommons.js b/newIDE/app/src/EventsSheet/ParameterFields/ParameterFieldCommons.js index 114d7db1a35a..fda09cf786c6 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/ParameterFieldCommons.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/ParameterFieldCommons.js @@ -20,7 +20,7 @@ type CommonProps = {| value: string, // Context - project?: gdProject, + project: gdProject, scope: EventsScope, globalObjectsContainer: gdObjectsContainer, objectsContainer: gdObjectsContainer, diff --git a/newIDE/app/src/EventsSheet/ParameterFields/SceneVariableField.js b/newIDE/app/src/EventsSheet/ParameterFields/SceneVariableField.js index 421e9855a468..e56e22b2051c 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/SceneVariableField.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/SceneVariableField.js @@ -89,6 +89,7 @@ export default (React.forwardRef( openVariableEditorDialog={setVariableEditorOpen} globalObjectsContainer={props.globalObjectsContainer} objectsContainer={props.objectsContainer} + project={project} projectScopedContainersAccessor={projectScopedContainersAccessor} scope={scope} id={ diff --git a/newIDE/app/src/ObjectGroupEditor/index.js b/newIDE/app/src/ObjectGroupEditor/index.js index f197f89847ce..fa2a50056885 100644 --- a/newIDE/app/src/ObjectGroupEditor/index.js +++ b/newIDE/app/src/ObjectGroupEditor/index.js @@ -18,7 +18,7 @@ const styles = { }; type Props = {| - project: ?gdProject, + project: gdProject, projectScopedContainersAccessor: ProjectScopedContainersAccessor, globalObjectsContainer: gdObjectsContainer | null, objectsContainer: gdObjectsContainer, diff --git a/newIDE/app/src/ObjectsList/CompactObjectSelector.js b/newIDE/app/src/ObjectsList/CompactObjectSelector.js index 21f464621d4b..b609bd6027cf 100644 --- a/newIDE/app/src/ObjectsList/CompactObjectSelector.js +++ b/newIDE/app/src/ObjectsList/CompactObjectSelector.js @@ -18,7 +18,7 @@ import CompactPropertiesEditorRowField from '../CompactPropertiesEditor/CompactP const gd: libGDevelop = global.gd; type Props = {| - project: ?gdProject, + project: gdProject, projectScopedContainersAccessor: ProjectScopedContainersAccessor, /** If specified, only this object type should be allowed to be selected. */ @@ -38,6 +38,8 @@ type Props = {| /** A list of object names to exclude from the autocomplete list (for example if they have already been selected). */ excludedObjectOrGroupNames?: Array, + requireCustomObject?: boolean, + onChoose?: string => void, onChange: string => void, onRequestClose?: () => void, @@ -62,6 +64,7 @@ const CompactObjectSelector = (props: Props): React.Node => { project, projectScopedContainersAccessor, allowedObjectType, + requireCustomObject, noGroups, errorTextIfInvalid, onRequestClose, @@ -88,6 +91,7 @@ const CompactObjectSelector = (props: Props): React.Node => { allowedObjectType, requiredCapabilitiesBehaviorTypes, excludedObjectOrGroupNames, + requireCustomObject, }); }, [ @@ -96,6 +100,7 @@ const CompactObjectSelector = (props: Props): React.Node => { noGroups, project, projectScopedContainersAccessor, + requireCustomObject, requiredCapabilitiesBehaviorTypes, ] ); diff --git a/newIDE/app/src/ObjectsList/ObjectSelector.js b/newIDE/app/src/ObjectsList/ObjectSelector.js index 0851d00881e2..3a8d2366c87e 100644 --- a/newIDE/app/src/ObjectsList/ObjectSelector.js +++ b/newIDE/app/src/ObjectsList/ObjectSelector.js @@ -19,7 +19,7 @@ import { ProjectScopedContainersAccessor } from '../InstructionOrExpression/Even const gd: libGDevelop = global.gd; type Props = {| - project: ?gdProject, + project: gdProject, projectScopedContainersAccessor: ProjectScopedContainersAccessor, /** If specified, only this object type should be allowed to be selected. */ @@ -39,6 +39,8 @@ type Props = {| /** A list of object names to exclude from the autocomplete list (for example if they have already been selected). */ excludedObjectOrGroupNames?: Array, + requireCustomObject?: boolean, + onChoose?: string => void, onChange: string => void, onRequestClose?: () => void, @@ -66,19 +68,28 @@ export const getObjectsAndGroupsDataSource = ({ allowedObjectType, requiredCapabilitiesBehaviorTypes, excludedObjectOrGroupNames, + requireCustomObject, }: {| - project: ?gdProject, + project: gdProject, objectsContainersList: gdObjectsContainersList, noGroups: ?boolean, allowedObjectType: ?string, requiredCapabilitiesBehaviorTypes?: Array, excludedObjectOrGroupNames: ?Array, + requireCustomObject: ?boolean, |}): DataSource => { - const { allObjectsList, allGroupsList } = enumerateObjectsAndGroups( + let { allObjectsList, allGroupsList } = enumerateObjectsAndGroups( objectsContainersList, allowedObjectType || undefined, requiredCapabilitiesBehaviorTypes || [] ); + + allObjectsList = requireCustomObject + ? allObjectsList.filter(({ object }) => + project.hasEventsBasedObject(object.getType()) + ) + : allObjectsList; + const objects = allObjectsList.map(({ object }) => { return { text: object.getName(), @@ -105,19 +116,20 @@ export const getObjectsAndGroupsDataSource = ({ }; }); - const fullList = + let fullList = groups.length === 0 ? objects : [...objects, { type: 'separator' }, ...groups]; - return excludedObjectOrGroupNames - ? // $FlowFixMe[incompatible-type] - fullList.filter( + fullList = excludedObjectOrGroupNames + ? fullList.filter( //$FlowFixMe[incompatible-type] ({ value }) => !excludedObjectOrGroupNames.includes(value) ) - : // $FlowFixMe[incompatible-type] - fullList; + : fullList; + + // $FlowFixMe[incompatible-type] + return fullList; }; export const checkHasRequiredBehaviors = ({ @@ -193,6 +205,7 @@ const ObjectSelector: React.ComponentType<{ onApply, id, excludedObjectOrGroupNames, + requireCustomObject, hintText, requiredCapabilitiesBehaviorTypes, requiredVisibleBehaviorTypes, @@ -211,6 +224,7 @@ const ObjectSelector: React.ComponentType<{ allowedObjectType, requiredCapabilitiesBehaviorTypes, excludedObjectOrGroupNames, + requireCustomObject, }); const hasValidChoice = diff --git a/newIDE/app/src/fixtures/TestProject.js b/newIDE/app/src/fixtures/TestProject.js index 42e33f5781dc..e7cee0eb1314 100644 --- a/newIDE/app/src/fixtures/TestProject.js +++ b/newIDE/app/src/fixtures/TestProject.js @@ -45,6 +45,9 @@ export type TestProject = {| testBehaviorEventsFunction: gdEventsFunction, testBehaviorLifecycleEventsFunction: gdEventsFunction, testEventsBasedObject: gdEventsBasedObject, + composedEventBasedObject: gdEventsBasedObject, + eventBasedObjectProjectScopedContainersAccessor: ProjectScopedContainersAccessor, + composedEventBasedObjectProjectScopedContainersAccessor: ProjectScopedContainersAccessor, testObjectEventsFunction: gdEventsFunction, layerWithEffects: gdLayer, layerWith3DEffects: gdLayer, @@ -182,6 +185,30 @@ export const makeTestProject = (gd /*: libGDevelop */) /*: TestProject */ => { .getEvents() .insertNewEvent(project, 'BuiltinCommonInstructions::Standard', 0); + const composedEventBasedObject = buttonExtension + .getEventsBasedObjects() + .insertNew('ComposedEventBasedObject', 0); + composedEventBasedObject + .getObjects() + .insertNewObject(project, 'Button::PanelSpriteButton', 'Button', 0); + + const eventBasedObjectProjectScopedContainersAccessor = new ProjectScopedContainersAccessor( + { + project, + eventsFunctionsExtension: buttonExtension, + eventsBasedObject: buttonEventBasedObject, + }, + new gd.ObjectsContainer(gd.ObjectsContainer.Function) + ); + const composedEventBasedObjectProjectScopedContainersAccessor = new ProjectScopedContainersAccessor( + { + project, + eventsFunctionsExtension: buttonExtension, + eventsBasedObject: composedEventBasedObject, + }, + new gd.ObjectsContainer(gd.ObjectsContainer.Function) + ); + // Create and expose some objects const testLayout = project.insertNewLayout('TestLayout', 0); const customObject = testLayout @@ -1013,6 +1040,9 @@ export const makeTestProject = (gd /*: libGDevelop */) /*: TestProject */ => { testBehaviorEventsFunction, testBehaviorLifecycleEventsFunction, testEventsBasedObject: buttonEventBasedObject, + composedEventBasedObject, + eventBasedObjectProjectScopedContainersAccessor, + composedEventBasedObjectProjectScopedContainersAccessor, testObjectEventsFunction, layerWithEffects, layerWith3DEffects, diff --git a/newIDE/app/src/stories/componentStories/EventsBasedObjectEditor/EventsBasedObjectEditor.stories.js b/newIDE/app/src/stories/componentStories/EventsBasedObjectEditor/EventsBasedObjectEditor.stories.js index 0345dffa701a..515367f37f58 100644 --- a/newIDE/app/src/stories/componentStories/EventsBasedObjectEditor/EventsBasedObjectEditor.stories.js +++ b/newIDE/app/src/stories/componentStories/EventsBasedObjectEditor/EventsBasedObjectEditor.stories.js @@ -17,6 +17,10 @@ export default { export const Default = (): React.Node => ( ( )} /> ); + +export const WithChildCustomObject = (): React.Node => ( + +); diff --git a/newIDE/app/src/stories/componentStories/ParameterFields/OperatorField.stories.js b/newIDE/app/src/stories/componentStories/ParameterFields/OperatorField.stories.js index eb5e17604d1d..b946709de22f 100644 --- a/newIDE/app/src/stories/componentStories/ParameterFields/OperatorField.stories.js +++ b/newIDE/app/src/stories/componentStories/ParameterFields/OperatorField.stories.js @@ -19,6 +19,7 @@ export const OperatorFieldString = (): React.Node => ( initialValue="" render={(value, onChange) => ( ( initialValue="" render={(value, onChange) => ( ( initialValue="" render={(value, onChange) => ( ( initialValue="" render={(value, onChange) => ( ( initialValue="" render={(value, onChange) => ( ( initialValue="" render={(value, onChange) => ( ( initialValue="" render={(value, onChange) => ( ( initialValue="" render={(value, onChange) => ( ( ( ( ( ( ( ( ( ( (