From df3433c55fd1e47d1c8fabe0946f6f9e0a3dc528 Mon Sep 17 00:00:00 2001 From: D8H Date: Thu, 13 Oct 2022 13:39:53 +0200 Subject: [PATCH] Add autocompletion for timers, tweens and other extensions identifiers (#4386) --- .../Builtin/BaseObjectExtension.cpp | 16 +- .../Extensions/Builtin/TimeExtension.cpp | 16 +- .../Extensions/Metadata/ParameterMetadata.h | 3 +- .../IDE/Events/ArbitraryEventsWorker.cpp | 50 +++ .../GDCore/IDE/Events/ArbitraryEventsWorker.h | 95 +++++ .../IDE/Events/EventsIdentifiersFinder.cpp | 254 ++++++++++++ .../IDE/Events/EventsIdentifiersFinder.h | 81 ++++ .../IDE/Events/EventsVariablesFinder.cpp | 274 ++++++------- .../GDCore/IDE/Events/EventsVariablesFinder.h | 58 +-- Core/tests/EventsIdentifiersFinder.cpp | 289 ++++++++++++++ Core/tests/EventsVariablesFinder.cpp | 360 ++++++++++++++++++ Extensions/TweenBehavior/JsExtension.js | 66 ++-- GDevelop.js/Bindings/Bindings.idl | 8 + GDevelop.js/Bindings/Wrapper.cpp | 2 + .../types/gdeventsidentifiersfinder.js | 7 + GDevelop.js/types/libgdevelop.js | 1 + .../EventsFunctionParametersEditor.js | 61 +++ .../ExpressionAutocompletionsDisplayer.js | 1 + .../GenericExpressionField/index.js | 49 ++- .../ParameterFields/IdentifierField.js | 98 +++++ .../EventsSheet/ParameterRenderingService.js | 4 + .../app/src/ExpressionAutocompletion/index.js | 4 + 22 files changed, 1522 insertions(+), 275 deletions(-) create mode 100644 Core/GDCore/IDE/Events/EventsIdentifiersFinder.cpp create mode 100644 Core/GDCore/IDE/Events/EventsIdentifiersFinder.h create mode 100644 Core/tests/EventsIdentifiersFinder.cpp create mode 100644 Core/tests/EventsVariablesFinder.cpp create mode 100644 GDevelop.js/types/gdeventsidentifiersfinder.js create mode 100644 newIDE/app/src/EventsSheet/ParameterFields/IdentifierField.js diff --git a/Core/GDCore/Extensions/Builtin/BaseObjectExtension.cpp b/Core/GDCore/Extensions/Builtin/BaseObjectExtension.cpp index 45f33b02d7..89b0c88268 100644 --- a/Core/GDCore/Extensions/Builtin/BaseObjectExtension.cpp +++ b/Core/GDCore/Extensions/Builtin/BaseObjectExtension.cpp @@ -886,7 +886,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension( "res/conditions/timer24.png", "res/conditions/timer.png") .AddParameter("object", _("Object")) - .AddParameter("string", _("Timer's name")) + .AddParameter("identifier", _("Timer's name"), "objectTimer") .AddParameter("expression", _("Time in seconds")) .SetHidden(); @@ -900,7 +900,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension( "res/conditions/timer24.png", "res/conditions/timer.png") .AddParameter("object", _("Object")) - .AddParameter("string", _("Timer's name")) + .AddParameter("identifier", _("Timer's name"), "objectTimer") .AddParameter("relationalOperator", _("Sign of the test"), "time") .AddParameter("expression", _("Time in seconds")) .SetManipulatedType("number"); @@ -913,7 +913,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension( "res/conditions/timerPaused24.png", "res/conditions/timerPaused.png") .AddParameter("object", _("Object")) - .AddParameter("string", _("Timer's name")) + .AddParameter("identifier", _("Timer's name"), "objectTimer") .MarkAsAdvanced(); obj.AddAction( @@ -926,7 +926,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension( "res/actions/timer24.png", "res/actions/timer.png") .AddParameter("object", _("Object")) - .AddParameter("string", _("Timer's name")); + .AddParameter("identifier", _("Timer's name"), "objectTimer"); obj.AddAction("PauseObjectTimer", _("Pause an object timer"), @@ -936,7 +936,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension( "res/actions/pauseTimer24.png", "res/actions/pauseTimer.png") .AddParameter("object", _("Object")) - .AddParameter("string", _("Timer's name")) + .AddParameter("identifier", _("Timer's name"), "objectTimer") .MarkAsAdvanced(); obj.AddAction("UnPauseObjectTimer", @@ -947,7 +947,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension( "res/actions/unPauseTimer24.png", "res/actions/unPauseTimer.png") .AddParameter("object", _("Object")) - .AddParameter("string", _("Timer's name")) + .AddParameter("identifier", _("Timer's name"), "objectTimer") .MarkAsAdvanced(); obj.AddAction("RemoveObjectTimer", @@ -958,7 +958,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension( "res/actions/timer24.png", "res/actions/timer.png") .AddParameter("object", _("Object")) - .AddParameter("string", _("Timer's name")) + .AddParameter("identifier", _("Timer's name"), "objectTimer") .MarkAsAdvanced(); obj.AddExpression("X", @@ -1127,7 +1127,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension( _("Object timers"), "res/actions/time.png") .AddParameter("object", _("Object")) - .AddParameter("string", _("Timer's name")); + .AddParameter("identifier", _("Timer's name"), "objectTimer"); obj.AddExpression("AngleToObject", _("Angle between two objects"), diff --git a/Core/GDCore/Extensions/Builtin/TimeExtension.cpp b/Core/GDCore/Extensions/Builtin/TimeExtension.cpp index a0be509e07..0f2d4924da 100644 --- a/Core/GDCore/Extensions/Builtin/TimeExtension.cpp +++ b/Core/GDCore/Extensions/Builtin/TimeExtension.cpp @@ -36,7 +36,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension( "res/conditions/timer.png") .AddCodeOnlyParameter("currentScene", "") .AddParameter("expression", _("Time in seconds")) - .AddParameter("string", _("Timer's name")) + .AddParameter("identifier", _("Timer's name"), "sceneTimer") .SetHidden(); extension @@ -50,7 +50,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension( "res/conditions/timer24.png", "res/conditions/timer.png") .AddCodeOnlyParameter("currentScene", "") - .AddParameter("string", _("Timer's name")) + .AddParameter("identifier", _("Timer's name"), "sceneTimer") .AddParameter("relationalOperator", _("Sign of the test"), "time") .AddParameter("expression", _("Time in seconds")) .SetManipulatedType("number"); @@ -78,7 +78,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension( "res/conditions/timerPaused24.png", "res/conditions/timerPaused.png") .AddCodeOnlyParameter("currentScene", "") - .AddParameter("string", _("Timer's name")) + .AddParameter("identifier", _("Timer's name"), "sceneTimer") .MarkAsAdvanced(); extension @@ -93,7 +93,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension( "res/actions/timer24.png", "res/actions/timer.png") .AddCodeOnlyParameter("currentScene", "") - .AddParameter("string", _("Timer's name")); + .AddParameter("identifier", _("Timer's name"), "sceneTimer"); extension .AddAction("PauseTimer", @@ -105,7 +105,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension( "res/actions/pauseTimer24.png", "res/actions/pauseTimer.png") .AddCodeOnlyParameter("currentScene", "") - .AddParameter("string", _("Timer's name")) + .AddParameter("identifier", _("Timer's name"), "sceneTimer") .MarkAsAdvanced(); extension @@ -118,7 +118,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension( "res/actions/unPauseTimer24.png", "res/actions/unPauseTimer.png") .AddCodeOnlyParameter("currentScene", "") - .AddParameter("string", _("Timer's name")) + .AddParameter("identifier", _("Timer's name"), "sceneTimer") .MarkAsAdvanced(); extension @@ -131,7 +131,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension( "res/actions/timer24.png", "res/actions/timer.png") .AddCodeOnlyParameter("currentScene", "") - .AddParameter("string", _("Timer's name")) + .AddParameter("identifier", _("Timer's name"), "sceneTimer") .MarkAsAdvanced(); extension @@ -191,7 +191,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension( "", "res/actions/time.png") .AddCodeOnlyParameter("currentScene", "") - .AddParameter("string", _("Timer's name")); + .AddParameter("identifier", _("Timer's name"), "sceneTimer"); extension .AddExpression("TimeFromStart", diff --git a/Core/GDCore/Extensions/Metadata/ParameterMetadata.h b/Core/GDCore/Extensions/Metadata/ParameterMetadata.h index 10ec075a59..d695b7ec0e 100644 --- a/Core/GDCore/Extensions/Metadata/ParameterMetadata.h +++ b/Core/GDCore/Extensions/Metadata/ParameterMetadata.h @@ -199,7 +199,8 @@ class GD_CORE_API ParameterMetadata { parameterType == "objectAnimationName" || parameterType == "functionParameterName" || parameterType == "externalLayoutName" || - parameterType == "leaderboardId"; + parameterType == "leaderboardId" || + parameterType == "identifier"; } else if (type == "variable") { return parameterType == "objectvar" || parameterType == "globalvar" || parameterType == "scenevar"; diff --git a/Core/GDCore/IDE/Events/ArbitraryEventsWorker.cpp b/Core/GDCore/IDE/Events/ArbitraryEventsWorker.cpp index f9ad025165..db3bdf5b53 100644 --- a/Core/GDCore/IDE/Events/ArbitraryEventsWorker.cpp +++ b/Core/GDCore/IDE/Events/ArbitraryEventsWorker.cpp @@ -73,4 +73,54 @@ bool ArbitraryEventsWorker::VisitInstruction(gd::Instruction& instruction, ArbitraryEventsWorkerWithContext::~ArbitraryEventsWorkerWithContext() {} + +ReadOnlyArbitraryEventsWorker::~ReadOnlyArbitraryEventsWorker() {} + +void ReadOnlyArbitraryEventsWorker::VisitEventList(const gd::EventsList& events) { + DoVisitEventList(events); + + for (std::size_t i = 0; i < events.size(); ++i) { + VisitEvent(events[i]); + + if (events[i].CanHaveSubEvents()) { + VisitEventList(events[i].GetSubEvents()); + } + } +} + +void ReadOnlyArbitraryEventsWorker::VisitEvent(const gd::BaseEvent& event) { + DoVisitEvent(event); + + const vector conditionsVectors = + event.GetAllConditionsVectors(); + for (std::size_t j = 0; j < conditionsVectors.size(); ++j) { + VisitInstructionList(*conditionsVectors[j], true); + } + + const vector actionsVectors = event.GetAllActionsVectors(); + for (std::size_t j = 0; j < actionsVectors.size(); ++j) { + VisitInstructionList(*actionsVectors[j], false); + } +} + +void ReadOnlyArbitraryEventsWorker::VisitInstructionList( + const gd::InstructionsList& instructions, bool areConditions) { + DoVisitInstructionList(instructions, areConditions); + + for (std::size_t i = 0; i < instructions.size(); ++i) { + VisitInstruction(instructions[i], areConditions); + if (!instructions[i].GetSubInstructions().empty()) { + VisitInstructionList(instructions[i].GetSubInstructions(), + areConditions); + } + } +} + +void ReadOnlyArbitraryEventsWorker::VisitInstruction(const gd::Instruction& instruction, + bool isCondition) { + DoVisitInstruction(instruction, isCondition); +} + +ReadOnlyArbitraryEventsWorkerWithContext::~ReadOnlyArbitraryEventsWorkerWithContext() {} + } // namespace gd diff --git a/Core/GDCore/IDE/Events/ArbitraryEventsWorker.h b/Core/GDCore/IDE/Events/ArbitraryEventsWorker.h index e35fee3ee7..566e336c51 100644 --- a/Core/GDCore/IDE/Events/ArbitraryEventsWorker.h +++ b/Core/GDCore/IDE/Events/ArbitraryEventsWorker.h @@ -121,6 +121,101 @@ class GD_CORE_API ArbitraryEventsWorkerWithContext const gd::ObjectsContainer* currentObjectsContainer; }; +/** + * \brief ReadOnlyArbitraryEventsWorker is an abstract class used to browse events (and + * instructions). It can be used to implement autocompletion for example. + * + * \see gd::ReadOnlyArbitraryEventsWorkerWithContext + * + * \ingroup IDE + */ +class GD_CORE_API ReadOnlyArbitraryEventsWorker { + public: + ReadOnlyArbitraryEventsWorker(){}; + virtual ~ReadOnlyArbitraryEventsWorker(); + + /** + * \brief Launch the worker on the specified events list. + */ + void Launch(const gd::EventsList& events) { VisitEventList(events); }; + + private: + void VisitEventList(const gd::EventsList& events); + void VisitEvent(const gd::BaseEvent& event); + void VisitInstructionList(const gd::InstructionsList& instructions, + bool areConditions); + void VisitInstruction(const gd::Instruction& instruction, bool isCondition); + + /** + * Called to do some work on an event list. + */ + virtual void DoVisitEventList(const gd::EventsList& events){}; + + /** + * Called to do some work on an event + */ + virtual void DoVisitEvent(const gd::BaseEvent& event) {}; + + /** + * Called to do some work on an instruction list + */ + virtual void DoVisitInstructionList(const gd::InstructionsList& instructions, + bool areConditions){}; + + /** + * Called to do some work on an instruction. + */ + virtual void DoVisitInstruction(const gd::Instruction& instruction, + bool isCondition) {}; +}; + +/** + * \brief An events worker that will know about the context (the objects + * container). Useful for workers working on expressions notably. + * + * \see gd::ReadOnlyArbitraryEventsWorker + * + * \ingroup IDE + */ +class GD_CORE_API ReadOnlyArbitraryEventsWorkerWithContext + : public ReadOnlyArbitraryEventsWorker { + public: + ReadOnlyArbitraryEventsWorkerWithContext() + : currentGlobalObjectsContainer(nullptr), + currentObjectsContainer(nullptr){}; + virtual ~ReadOnlyArbitraryEventsWorkerWithContext(); + + /** + * \brief Launch the worker on the specified events list, + * giving the objects container on which the events are applying to. + */ + void Launch(const gd::EventsList& events, + const gd::ObjectsContainer& globalObjectsContainer_, + const gd::ObjectsContainer& objectsContainer_) { + currentGlobalObjectsContainer = &globalObjectsContainer_; + currentObjectsContainer = &objectsContainer_; + ReadOnlyArbitraryEventsWorker::Launch(events); + }; + + void Launch(gd::EventsList& events) = delete; + + protected: + const gd::ObjectsContainer& GetGlobalObjectsContainer() { + // Pointers are guaranteed to be not nullptr after + // Launch was called. + return *currentGlobalObjectsContainer; + }; + const gd::ObjectsContainer& GetObjectsContainer() { + // Pointers are guaranteed to be not nullptr after + // Launch was called. + return *currentObjectsContainer; + }; + + private: + const gd::ObjectsContainer* currentGlobalObjectsContainer; + const gd::ObjectsContainer* currentObjectsContainer; +}; + } // namespace gd #endif // GDCORE_ARBITRARYEVENTSWORKER_H diff --git a/Core/GDCore/IDE/Events/EventsIdentifiersFinder.cpp b/Core/GDCore/IDE/Events/EventsIdentifiersFinder.cpp new file mode 100644 index 0000000000..a6d95024f1 --- /dev/null +++ b/Core/GDCore/IDE/Events/EventsIdentifiersFinder.cpp @@ -0,0 +1,254 @@ +/* + * GDevelop Core + * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ + +#include "EventsIdentifiersFinder.h" +#include "GDCore/Events/Event.h" +#include "GDCore/Events/Instruction.h" +#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h" +#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h" +#include "GDCore/Extensions/Metadata/ExpressionMetadata.h" +#include "GDCore/Extensions/Metadata/InstructionMetadata.h" +#include "GDCore/Extensions/Metadata/MetadataProvider.h" +#include "GDCore/Extensions/Platform.h" +#include "GDCore/IDE/Events/ArbitraryEventsWorker.h" +#include "GDCore/Project/Layout.h" +#include "GDCore/Project/Object.h" +#include "GDCore/Project/Project.h" +#include "GDCore/Project/ExternalEvents.h" +#include "GDCore/IDE/DependenciesAnalyzer.h" + +using namespace std; + +namespace gd { +namespace { +/** + * \brief Go through the nodes to search for identifier occurrences. + * + * \see gd::ExpressionParser2 + */ +class GD_CORE_API IdentifierFinderExpressionNodeWorker + : public ExpressionParser2NodeWorker { + public: + IdentifierFinderExpressionNodeWorker(std::set& results_, + const gd::Platform &platform_, + const gd::ObjectsContainer &globalObjectsContainer_, + const gd::ObjectsContainer &objectsContainer_, + const gd::String& identifierType_, + const gd::String& objectName_ = "") + : results(results_), + platform(platform_), + globalObjectsContainer(globalObjectsContainer_), + objectsContainer(objectsContainer_), + identifierType(identifierType_), + objectName(objectName_){}; + virtual ~IdentifierFinderExpressionNodeWorker(){}; + + protected: + void OnVisitSubExpressionNode(SubExpressionNode& node) override { + node.expression->Visit(*this); + } + void OnVisitOperatorNode(OperatorNode& node) override { + node.leftHandSide->Visit(*this); + node.rightHandSide->Visit(*this); + } + void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override { + node.factor->Visit(*this); + } + void OnVisitNumberNode(NumberNode& node) override {} + void OnVisitTextNode(TextNode& node) override {} + void OnVisitVariableNode(VariableNode& node) override { + if (node.child) node.child->Visit(*this); + } + void OnVisitVariableAccessorNode(VariableAccessorNode& node) override { + if (node.child) node.child->Visit(*this); + } + void OnVisitVariableBracketAccessorNode( + VariableBracketAccessorNode& node) override { + node.expression->Visit(*this); + if (node.child) node.child->Visit(*this); + } + void OnVisitIdentifierNode(IdentifierNode& node) override {} + void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {} + void OnVisitFunctionCallNode(FunctionCallNode& node) override { + bool considerFunction = objectName.empty() || node.objectName == objectName; + + const bool isObjectFunction = !node.objectName.empty(); + const gd::ExpressionMetadata &metadata = isObjectFunction ? + MetadataProvider::GetObjectAnyExpressionMetadata( + platform, + GetTypeOfObject(globalObjectsContainer, objectsContainer, objectName), + node.functionName): + MetadataProvider::GetAnyExpressionMetadata(platform, node.functionName); + + if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) { + return; + } + + size_t parameterIndex = 0; + for (size_t metadataIndex = (isObjectFunction ? 1 : 0); metadataIndex < metadata.parameters.size() + && parameterIndex < node.parameters.size(); ++metadataIndex) { + auto& parameterMetadata = metadata.parameters[metadataIndex]; + if (parameterMetadata.IsCodeOnly()) { + continue; + } + auto& parameterNode = node.parameters[parameterIndex]; + ++parameterIndex; + + if (considerFunction && parameterMetadata.GetType() == "identifier" + && parameterMetadata.GetExtraInfo() == identifierType) { + // Store the value of the parameter + results.insert( + gd::ExpressionParser2NodePrinter::PrintNode(*parameterNode)); + } else { + parameterNode->Visit(*this); + } + } + } + void OnVisitEmptyNode(EmptyNode& node) override {} + + private: + const gd::Platform &platform; + const gd::ObjectsContainer &globalObjectsContainer; + const gd::ObjectsContainer &objectsContainer; + + std::set& results; ///< Reference to the std::set where argument + ///< values must be stored. + gd::String identifierType; ///< The type of the parameters to be searched for. + gd::String objectName; ///< If not empty, parameters will be taken into + ///< account only if related to this object. +}; + +/** + * \brief Go through the events to search for identifier occurrences. + */ +class GD_CORE_API IdentifierFinderEventWorker + : public ReadOnlyArbitraryEventsWorkerWithContext { + public: + IdentifierFinderEventWorker(std::set& results_, + const gd::Platform &platform_, + const gd::String& identifierType_, + const gd::String& objectName_ = "") + : results(results_), + platform(platform_), + identifierType(identifierType_), + objectName(objectName_){}; + virtual ~IdentifierFinderEventWorker(){}; + + void DoVisitInstructionList(const gd::InstructionsList& instructions, + bool areConditions) override { + for (std::size_t aId = 0; aId < instructions.size(); ++aId) { + auto& instruction = instructions[aId]; + gd::String lastObjectParameter = ""; + const gd::InstructionMetadata& instrInfos = + areConditions ? MetadataProvider::GetConditionMetadata( + platform, instruction.GetType()) + : MetadataProvider::GetActionMetadata( + platform, instruction.GetType()); + for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) { + // The parameter has the searched type... + if (instrInfos.parameters[pNb].type == "identifier" + && instrInfos.parameters[pNb].supplementaryInformation == identifierType) { + //...remember the value of the parameter. + if (objectName.empty() || lastObjectParameter == objectName) { + results.insert(instruction.GetParameter(pNb).GetPlainString()); + } + } + // Search in expressions + else if (ParameterMetadata::IsExpression( + "number", instrInfos.parameters[pNb].type) || + ParameterMetadata::IsExpression( + "string", instrInfos.parameters[pNb].type)) { + auto node = instruction.GetParameter(pNb).GetRootNode(); + + IdentifierFinderExpressionNodeWorker searcher( + results, + platform, + GetGlobalObjectsContainer(), + GetObjectsContainer(), + identifierType, + objectName); + node->Visit(searcher); + } + // Remember the value of the last "object" parameter. + else if (gd::ParameterMetadata::IsObject( + instrInfos.parameters[pNb].type)) { + lastObjectParameter = + instruction.GetParameter(pNb).GetPlainString(); + } + } + } + }; + + private: + const gd::Platform &platform; + + std::set& results; ///< Reference to the std::set where argument + ///< values must be stored. + gd::String identifierType; ///< The type of the parameters to be searched for. + gd::String objectName; ///< If not empty, parameters will be taken into + ///< account only if related to this object. +}; +} // namespace + +std::set EventsIdentifiersFinder::FindAllIdentifierExpressions( + const gd::Platform& platform, + const gd::Project& project, + const gd::Layout& layout, + const gd::String& identifierType, + const gd::String& contextObjectName) { + std::set results; + + const bool isObjectIdentifier = identifierType.find("object") == 0; + // The object from the context is only relevent for object identifiers. + auto& actualObjectName = isObjectIdentifier ? contextObjectName : ""; + + FindArgumentsInEventsAndDependencies( + results, + platform, + project, + layout, + identifierType, + actualObjectName); + + return results; +} + +void EventsIdentifiersFinder::FindArgumentsInEventsAndDependencies( + std::set& results, + const gd::Platform& platform, + const gd::Project& project, + const gd::Layout& layout, + const gd::String& identifierType, + const gd::String& objectName) { + IdentifierFinderEventWorker eventWorker(results, + platform, + identifierType, + objectName); + eventWorker.Launch(layout.GetEvents(), project, layout); + + DependenciesAnalyzer dependenciesAnalyzer = DependenciesAnalyzer(project, layout); + dependenciesAnalyzer.Analyze(); + for (const gd::String& externalEventName : dependenciesAnalyzer.GetExternalEventsDependencies()) { + const gd::ExternalEvents& externalEvents = project.GetExternalEvents(externalEventName); + + IdentifierFinderEventWorker eventWorker(results, + platform, + identifierType, + objectName); + eventWorker.Launch(externalEvents.GetEvents(), project, layout); + } + for (const gd::String& sceneName : dependenciesAnalyzer.GetScenesDependencies()) { + const gd::Layout& dependencyLayout = project.GetLayout(sceneName); + + IdentifierFinderEventWorker eventWorker(results, + platform, + identifierType, + objectName); + eventWorker.Launch(dependencyLayout.GetEvents(), project, dependencyLayout); + } +} + +} // namespace gd diff --git a/Core/GDCore/IDE/Events/EventsIdentifiersFinder.h b/Core/GDCore/IDE/Events/EventsIdentifiersFinder.h new file mode 100644 index 0000000000..97711dd649 --- /dev/null +++ b/Core/GDCore/IDE/Events/EventsIdentifiersFinder.h @@ -0,0 +1,81 @@ +/* + * GDevelop Core + * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#ifndef EVENTSIDENTIFIERSFINDER_H +#define EVENTSIDENTIFIERSFINDER_H +#include +#include +#include "GDCore/Events/Event.h" +#include "GDCore/String.h" +namespace gd { +class Instruction; +class Platform; +class Object; +class Project; +class Layout; +} // namespace gd + +namespace gd { + +/** + * \brief Perform a search over a layout, searching for layout or object custom + * identifiers. + * + * \todo Refactor this class using ArbitraryEventsWorker + * + * \ingroup IDE + */ +class EventsIdentifiersFinder { + public: + EventsIdentifiersFinder(){}; + virtual ~EventsIdentifiersFinder(){}; + + /** + * Construct a list containing all the expressions for a given identifier used + * in the layout. + * + * \param project The project to use. + * \param layout The layout to use. + * \param identifierType The identifier type to be analyzed. + * \param objectName If not empty, parameters will be taken into account + * only if the last object parameter is filled with + * this value. + * \return A std::set containing the names of all identifiers used. + */ + static std::set FindAllIdentifierExpressions( + const gd::Platform& platform, + const gd::Project& project, + const gd::Layout& layout, + const gd::String& identifierType, + const gd::String& objectName = ""); + + private: + /** + * Construct a list containing all the expressions for a given identifier used + * in the layout. It searches in events dependencies. + * + * \param results A std::set to fill with the expressions used for all parameters of the + * specified identifier type + * \param platform The platform of the project + * \param project The project to use. + * \param layout The layout to use. + * \param events The events to be analyzed + * \param identifierType The identifier type to be analyzed + * \param objectName If not empty, parameters will be taken into account + * only if the last object parameter is filled with + * this value. + */ + static void FindArgumentsInEventsAndDependencies( + std::set& results, + const gd::Platform& platform, + const gd::Project& project, + const gd::Layout& layout, + const gd::String& identifierType, + const gd::String& objectName = ""); +}; + +} // namespace gd + +#endif // EVENTSIDENTIFIERSFINDER_H diff --git a/Core/GDCore/IDE/Events/EventsVariablesFinder.cpp b/Core/GDCore/IDE/Events/EventsVariablesFinder.cpp index a69ffb023f..156268133b 100644 --- a/Core/GDCore/IDE/Events/EventsVariablesFinder.cpp +++ b/Core/GDCore/IDE/Events/EventsVariablesFinder.cpp @@ -13,6 +13,7 @@ #include "GDCore/Extensions/Metadata/InstructionMetadata.h" #include "GDCore/Extensions/Metadata/MetadataProvider.h" #include "GDCore/Extensions/Platform.h" +#include "GDCore/IDE/Events/ArbitraryEventsWorker.h" #include "GDCore/Project/Layout.h" #include "GDCore/Project/Object.h" #include "GDCore/Project/Project.h" @@ -22,28 +23,28 @@ using namespace std; namespace gd { - +namespace { /** - * \brief Go through the nodes and change the given object name to a new one. + * \brief Go through the nodes to search for variable occurrences. * * \see gd::ExpressionParser2 */ -class GD_CORE_API ExpressionParameterSearcher +class GD_CORE_API VariableFinderExpressionNodeWorker : public ExpressionParser2NodeWorker { public: - ExpressionParameterSearcher(const gd::Platform &platform_, + VariableFinderExpressionNodeWorker(std::set& results_, + const gd::Platform &platform_, const gd::ObjectsContainer &globalObjectsContainer_, const gd::ObjectsContainer &objectsContainer_, - std::set& results_, const gd::String& parameterType_, const gd::String& objectName_ = "") - : platform(platform_), + : results(results_), + platform(platform_), globalObjectsContainer(globalObjectsContainer_), objectsContainer(objectsContainer_), - results(results_), parameterType(parameterType_), objectName(objectName_){}; - virtual ~ExpressionParameterSearcher(){}; + virtual ~VariableFinderExpressionNodeWorker(){}; protected: void OnVisitSubExpressionNode(SubExpressionNode& node) override { @@ -74,27 +75,34 @@ class GD_CORE_API ExpressionParameterSearcher void OnVisitFunctionCallNode(FunctionCallNode& node) override { bool considerFunction = objectName.empty() || node.objectName == objectName; - const gd::ExpressionMetadata &metadata = node.objectName.empty() ? - MetadataProvider::GetAnyExpressionMetadata(platform, node.functionName) : + const bool isObjectFunction = !node.objectName.empty(); + const gd::ExpressionMetadata &metadata = isObjectFunction ? MetadataProvider::GetObjectAnyExpressionMetadata( platform, GetTypeOfObject(globalObjectsContainer, objectsContainer, objectName), - node.functionName); + node.functionName): + MetadataProvider::GetAnyExpressionMetadata(platform, node.functionName); if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) { return; } - - for (size_t i = 0; i < node.parameters.size() && - i < metadata.parameters.size(); - ++i) { - auto& parameterMetadata = metadata.parameters[i]; + + size_t parameterIndex = 0; + for (size_t metadataIndex = (isObjectFunction ? 1 : 0); metadataIndex < metadata.parameters.size() + && parameterIndex < node.parameters.size(); ++metadataIndex) { + auto& parameterMetadata = metadata.parameters[metadataIndex]; + if (parameterMetadata.IsCodeOnly()) { + continue; + } + auto& parameterNode = node.parameters[parameterIndex]; + ++parameterIndex; + if (considerFunction && parameterMetadata.GetType() == parameterType) { // Store the value of the parameter results.insert( - gd::ExpressionParser2NodePrinter::PrintNode(*node.parameters[i])); + gd::ExpressionParser2NodePrinter::PrintNode(*parameterNode)); } else { - node.parameters[i]->Visit(*this); + parameterNode->Visit(*this); } } } @@ -112,18 +120,87 @@ class GD_CORE_API ExpressionParameterSearcher ///< account only if related to this object. }; +/** + * \brief Go through the events to search for variable occurrences. + */ +class GD_CORE_API VariableFinderEventWorker + : public ReadOnlyArbitraryEventsWorkerWithContext { + public: + VariableFinderEventWorker(std::set& results_, + const gd::Platform &platform_, + const gd::String& parameterType_, + const gd::String& objectName_ = "") + : results(results_), + platform(platform_), + parameterType(parameterType_), + objectName(objectName_){}; + virtual ~VariableFinderEventWorker(){}; + + void DoVisitInstructionList(const gd::InstructionsList& instructions, + bool areConditions) override { + for (std::size_t aId = 0; aId < instructions.size(); ++aId) { + auto& instruction = instructions[aId]; + gd::String lastObjectParameter = ""; + const gd::InstructionMetadata& instrInfos = + areConditions ? MetadataProvider::GetConditionMetadata( + platform, instruction.GetType()) + : MetadataProvider::GetActionMetadata( + platform, instruction.GetType()); + for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) { + // The parameter has the searched type... + if (instrInfos.parameters[pNb].type == parameterType) { + //...remember the value of the parameter. + if (objectName.empty() || lastObjectParameter == objectName) + results.insert(instruction.GetParameter(pNb).GetPlainString()); + } + // Search in expressions + else if (ParameterMetadata::IsExpression( + "number", instrInfos.parameters[pNb].type) || + ParameterMetadata::IsExpression( + "string", instrInfos.parameters[pNb].type)) { + auto node = instruction.GetParameter(pNb).GetRootNode(); + + VariableFinderExpressionNodeWorker searcher( + results, + platform, + GetGlobalObjectsContainer(), + GetObjectsContainer(), + parameterType, + objectName); + node->Visit(searcher); + } + // Remember the value of the last "object" parameter. + else if (gd::ParameterMetadata::IsObject( + instrInfos.parameters[pNb].type)) { + lastObjectParameter = + instruction.GetParameter(pNb).GetPlainString(); + } + } + } + }; + + private: + const gd::Platform &platform; + + std::set& results; ///< Reference to the std::set where argument + ///< values must be stored. + gd::String parameterType; ///< The type of the parameters to be searched for. + gd::String objectName; ///< If not empty, parameters will be taken into + ///< account only if related to this object. +}; +} // namespace + std::set EventsVariablesFinder::FindAllGlobalVariables( const gd::Platform& platform, const gd::Project& project) { std::set results; for (std::size_t i = 0; i < project.GetLayoutsCount(); ++i) { - std::set results2 = - FindArgumentsInEventsAndDependencies( + FindArgumentsInEventsAndDependencies( + results, platform, project, project.GetLayout(i), "globalvar"); - results.insert(results2.begin(), results2.end()); } return results; @@ -135,9 +212,12 @@ std::set EventsVariablesFinder::FindAllLayoutVariables( const gd::Layout& layout) { std::set results; - std::set results2 = FindArgumentsInEventsAndDependencies( - platform, project, layout, "scenevar"); - results.insert(results2.begin(), results2.end()); + FindArgumentsInEventsAndDependencies( + results, + platform, + project, + layout, + "scenevar"); return results; } @@ -149,159 +229,51 @@ std::set EventsVariablesFinder::FindAllObjectVariables( const gd::Object& object) { std::set results; - std::set results2 = FindArgumentsInEventsAndDependencies( + FindArgumentsInEventsAndDependencies( + results, platform, project, layout, "objectvar", object.GetName()); - results.insert(results2.begin(), results2.end()); return results; } -std::set EventsVariablesFinder::FindArgumentsInInstructions( - const gd::Platform& platform, - const gd::Project& project, - const gd::Layout& layout, - const gd::InstructionsList& instructions, - bool instructionsAreConditions, - const gd::String& parameterType, - const gd::String& objectName) { - std::set results; - - for (std::size_t aId = 0; aId < instructions.size(); ++aId) { - gd::String lastObjectParameter = ""; - const gd::InstructionMetadata& instrInfos = - instructionsAreConditions ? MetadataProvider::GetConditionMetadata( - platform, instructions[aId].GetType()) - : MetadataProvider::GetActionMetadata( - platform, instructions[aId].GetType()); - for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) { - // The parameter has the searched type... - if (instrInfos.parameters[pNb].type == parameterType) { - //...remember the value of the parameter. - if (objectName.empty() || lastObjectParameter == objectName) - results.insert(instructions[aId].GetParameter(pNb).GetPlainString()); - } - // Search in expressions - else if (ParameterMetadata::IsExpression( - "number", instrInfos.parameters[pNb].type) || - ParameterMetadata::IsExpression( - "string", instrInfos.parameters[pNb].type)) { - auto node = instructions[aId].GetParameter(pNb).GetRootNode(); - - ExpressionParameterSearcher searcher( - platform, - project, - layout, - results, - parameterType, - objectName); - node->Visit(searcher); - } - // Remember the value of the last "object" parameter. - else if (gd::ParameterMetadata::IsObject( - instrInfos.parameters[pNb].type)) { - lastObjectParameter = - instructions[aId].GetParameter(pNb).GetPlainString(); - } - } - - if (!instructions[aId].GetSubInstructions().empty()) - FindArgumentsInInstructions(platform, - project, - layout, - instructions[aId].GetSubInstructions(), - instructionsAreConditions, - parameterType); - } - - return results; -} - -std::set EventsVariablesFinder::FindArgumentsInEventsAndDependencies( +void EventsVariablesFinder::FindArgumentsInEventsAndDependencies( + std::set& results, const gd::Platform& platform, const gd::Project& project, const gd::Layout& layout, const gd::String& parameterType, const gd::String& objectName) { - std::set results; - std::set results2 = FindArgumentsInEvents( - platform, project, layout, layout.GetEvents(), parameterType, objectName); - results.insert(results2.begin(), results2.end()); + VariableFinderEventWorker eventWorker(results, + platform, + parameterType, + objectName); + eventWorker.Launch(layout.GetEvents(), project, layout); DependenciesAnalyzer dependenciesAnalyzer = DependenciesAnalyzer(project, layout); dependenciesAnalyzer.Analyze(); for (const gd::String& externalEventName : dependenciesAnalyzer.GetExternalEventsDependencies()) { const gd::ExternalEvents& externalEvents = project.GetExternalEvents(externalEventName); - std::set results3 = FindArgumentsInEvents( - platform, project, layout, externalEvents.GetEvents(), parameterType, objectName); - results.insert(results3.begin(), results3.end()); + VariableFinderEventWorker eventWorker(results, + platform, + parameterType, + objectName); + eventWorker.Launch(externalEvents.GetEvents(), project, layout); } for (const gd::String& sceneName : dependenciesAnalyzer.GetScenesDependencies()) { const gd::Layout& dependencyLayout = project.GetLayout(sceneName); - std::set results3 = FindArgumentsInEvents( - platform, project, dependencyLayout, dependencyLayout.GetEvents(), parameterType, objectName); - results.insert(results3.begin(), results3.end()); + VariableFinderEventWorker eventWorker(results, + platform, + parameterType, + objectName); + eventWorker.Launch(dependencyLayout.GetEvents(), project, dependencyLayout); } - - return results; -} - -std::set EventsVariablesFinder::FindArgumentsInEvents( - const gd::Platform& platform, - const gd::Project& project, - const gd::Layout& layout, - const gd::EventsList& events, - const gd::String& parameterType, - const gd::String& objectName) { - std::set results; - for (std::size_t i = 0; i < events.size(); ++i) { - vector conditionsVectors = - events[i].GetAllConditionsVectors(); - for (std::size_t j = 0; j < conditionsVectors.size(); ++j) { - std::set results2 = - FindArgumentsInInstructions(platform, - project, - layout, - *conditionsVectors[j], - /*conditions=*/true, - parameterType, - objectName); - results.insert(results2.begin(), results2.end()); - } - - vector actionsVectors = - events[i].GetAllActionsVectors(); - for (std::size_t j = 0; j < actionsVectors.size(); ++j) { - std::set results2 = - FindArgumentsInInstructions(platform, - project, - layout, - *actionsVectors[j], - /*conditions=*/false, - parameterType, - objectName); - results.insert(results2.begin(), results2.end()); - } - - if (events[i].CanHaveSubEvents()) { - std::set results2 = - FindArgumentsInEvents(platform, - project, - layout, - events[i].GetSubEvents(), - parameterType, - objectName); - results.insert(results2.begin(), results2.end()); - } - } - - return results; } } // namespace gd diff --git a/Core/GDCore/IDE/Events/EventsVariablesFinder.h b/Core/GDCore/IDE/Events/EventsVariablesFinder.h index 0f315a491b..28fc1eb5ca 100644 --- a/Core/GDCore/IDE/Events/EventsVariablesFinder.h +++ b/Core/GDCore/IDE/Events/EventsVariablesFinder.h @@ -23,7 +23,6 @@ namespace gd { * \brief Perform a search over a project or a layout, searching for layout, * global or object variables. * - * \todo Refactor this class using ArbitraryEventsWorker * \todo Rework this class to return the shapes (maybe even types?) of the * variables (in particular for structures and arrays), so we can use this * for better autocompletions in the variables dialogs in the IDE. @@ -74,34 +73,13 @@ class EventsVariablesFinder { const gd::Object& object); private: - /** - * Construct a list of the value of the arguments for parameters of type @ - * parameterType - * - * \param project The project used - * \param project The layout used - * \param instructions The instructions to be analyzed - * \param instructionsAreConditions True if the instructions are conditions. - * \param parameterType The parameters type to be analyzed - * \param objectName If not empty, parameters will be taken into account only - * if the last object parameter is filled with this value. - * - * \return A std::set filled with the values used for all parameters of the - * specified type - */ - static std::set FindArgumentsInInstructions( - const gd::Platform& platform, - const gd::Project& project, - const gd::Layout& layout, - const gd::InstructionsList& instructions, - bool instructionsAreConditions, - const gd::String& parameterType, - const gd::String& objectName = ""); /** * Construct a list of the value of the arguments for parameters of type @ - * parameterType. It searchs in events dependencies. + * parameterType. It searches in events dependencies. * + * \param results A std::set to fill with the values used for all parameters of the + * specified type * \param platform The platform of the project * \param project The project used * \param layout The layout used @@ -110,40 +88,14 @@ class EventsVariablesFinder { * \param objectName If not empty, parameters will be taken into account * only if the last object parameter is filled with * this value. - * - * \return A std::set filled with the values used for all parameters of the - * specified type */ - static std::set FindArgumentsInEventsAndDependencies( + static void FindArgumentsInEventsAndDependencies( + std::set& results, const gd::Platform& platform, const gd::Project& project, const gd::Layout& layout, const gd::String& parameterType, const gd::String& objectName = ""); - - /** - * Construct a list of the value of the arguments for parameters of type @ - * parameterType. It doesn't search in events dependencies. - * - * \param platform The platform of the project - * \param project The project used - * \param layout The layout used - * \param events The events to be analyzed - * \param parameterType The parameters type to be analyzed - * \param objectName If not empty, parameters will be taken into account - * only if the last object parameter is filled with - * this value. - * - * \return A std::set filled with the values used for all parameters of the - * specified type - */ - static std::set FindArgumentsInEvents( - const gd::Platform& platform, - const gd::Project& project, - const gd::Layout& layout, - const gd::EventsList& events, - const gd::String& parameterType, - const gd::String& objectName); }; } // namespace gd diff --git a/Core/tests/EventsIdentifiersFinder.cpp b/Core/tests/EventsIdentifiersFinder.cpp new file mode 100644 index 0000000000..19225fe83d --- /dev/null +++ b/Core/tests/EventsIdentifiersFinder.cpp @@ -0,0 +1,289 @@ +/* + * 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/Events/EventsIdentifiersFinder.h" +#include "GDCore/Events/Builtin/LinkEvent.h" +#include "GDCore/Events/Builtin/StandardEvent.h" +#include "GDCore/Events/Event.h" +#include "GDCore/Extensions/Builtin/AllBuiltinExtensions.h" +#include "GDCore/Extensions/Platform.h" +#include "GDCore/Project/ExternalEvents.h" +#include "GDCore/Project/Layout.h" +#include "GDCore/Project/Project.h" +#include "catch.hpp" + +namespace { +const void DeclareTimerExtension(gd::Project &project, gd::Platform &platform) { + std::shared_ptr extension = + std::shared_ptr(new gd::PlatformExtension); + gd::BuiltinExtensionsImplementer::ImplementsTimeExtension(*(extension.get())); + gd::BuiltinExtensionsImplementer::ImplementsBaseObjectExtension( + *(extension.get())); + // Add an instruction to test expressions. + extension + ->AddAction("DoSomething", "Do something", "This does something", + "Do something please", "", "", "") + .AddParameter("expression", "Parameter 1 (a number)"); + platform.AddExtension(extension); + project.AddPlatform(platform); +} + +const gd::StandardEvent UseSceneTimer(const gd::String &name) { + gd::StandardEvent event; + gd::Instruction instruction; + instruction.SetType("ResetTimer"); + instruction.SetParametersCount(2); + instruction.SetParameter(0, gd::Expression("scene")); + instruction.SetParameter(1, gd::Expression("\"" + name + "\"")); + event.GetActions().Insert(instruction); + return event; +} + +const gd::StandardEvent UseObjectTimer(const gd::String &objectName, + const gd::String &timerName) { + gd::StandardEvent event; + gd::Instruction instruction; + instruction.SetType("ResetObjectTimer"); + instruction.SetParametersCount(2); + instruction.SetParameter(0, gd::Expression(objectName)); + instruction.SetParameter(1, gd::Expression("\"" + timerName + "\"")); + event.GetActions().Insert(instruction); + return event; +} + +const gd::StandardEvent UseSceneTimerInExpression(const gd::String &name) { + gd::StandardEvent event; + gd::Instruction instruction; + instruction.SetType("DoSomething"); + instruction.SetParametersCount(1); + instruction.SetParameter( + 0, gd::Expression("1 + TimerElapsedTime(\"" + name + "\")")); + event.GetActions().Insert(instruction); + return event; +} + +const gd::StandardEvent +UseObjectTimerInExpression(const gd::String &objectName, + const gd::String &timerName) { + gd::StandardEvent event; + gd::Instruction instruction; + instruction.SetType("DoSomething"); + instruction.SetParametersCount(1); + instruction.SetParameter(0, gd::Expression("1 + " + objectName + + ".ObjectTimerElapsedTime(\"" + timerName + + "\")")); + event.GetActions().Insert(instruction); + return event; +} + +const void UseExternalEvents(gd::Layout &layout, + gd::ExternalEvents &externalEvents) { + gd::LinkEvent linkEvent; + linkEvent.SetTarget(externalEvents.GetName()); + layout.GetEvents().InsertEvent(linkEvent); +} +} // namespace + +TEST_CASE("EventsIdentifiersFinder (scene timers)", "[common]") { + SECTION("Can find scene timers in scenes") { + gd::Project project; + gd::Platform platform; + DeclareTimerExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + layout.GetEvents().InsertEvent(UseSceneTimer("MySceneTimer")); + + auto identifierExpressions = + gd::EventsIdentifiersFinder::FindAllIdentifierExpressions( + platform, project, layout, "sceneTimer"); + + REQUIRE(identifierExpressions.size() == 1); + REQUIRE(*(identifierExpressions.begin()) == "\"MySceneTimer\""); + } + + SECTION("Can find scene timers in scene expressions") { + gd::Project project; + gd::Platform platform; + DeclareTimerExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + layout.GetEvents().InsertEvent(UseSceneTimerInExpression("MySceneTimer")); + + auto identifierExpressions = + gd::EventsIdentifiersFinder::FindAllIdentifierExpressions( + platform, project, layout, "sceneTimer"); + + REQUIRE(identifierExpressions.size() == 1); + REQUIRE(*(identifierExpressions.begin()) == "\"MySceneTimer\""); + } + + SECTION("Can find scene timers in external layouts") { + gd::Project project; + gd::Platform platform; + DeclareTimerExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + auto &externalEvents = project.InsertNewExternalEvents("ExternalEvents", 0); + externalEvents.GetEvents().InsertEvent(UseSceneTimer("MySceneTimer")); + UseExternalEvents(layout, externalEvents); + + auto identifierExpressions = + gd::EventsIdentifiersFinder::FindAllIdentifierExpressions( + platform, project, layout, "sceneTimer"); + + REQUIRE(identifierExpressions.size() == 1); + REQUIRE(*(identifierExpressions.begin()) == "\"MySceneTimer\""); + } + + SECTION("Can find scene timers the right scene") { + gd::Project project; + gd::Platform platform; + DeclareTimerExtension(project, platform); + + auto &layout1 = project.InsertNewLayout("Layout1", 0); + layout1.GetEvents().InsertEvent(UseSceneTimer("MySceneTimerInLayout1")); + + auto &layout2 = project.InsertNewLayout("Layout2", 0); + layout2.GetEvents().InsertEvent(UseSceneTimer("MySceneTimerInLayout2")); + + auto identifierExpressions = + gd::EventsIdentifiersFinder::FindAllIdentifierExpressions( + platform, project, layout1, "sceneTimer"); + + REQUIRE(identifierExpressions.size() == 1); + REQUIRE(*(identifierExpressions.begin()) == "\"MySceneTimerInLayout1\""); + } + + SECTION("Can find scene timers in the right external layouts") { + gd::Project project; + gd::Platform platform; + DeclareTimerExtension(project, platform); + + auto &layout1 = project.InsertNewLayout("Layout1", 0); + auto &externalEvents1 = + project.InsertNewExternalEvents("ExternalEvents1", 0); + externalEvents1.GetEvents().InsertEvent( + UseSceneTimer("MySceneTimerInExternalEvents1")); + UseExternalEvents(layout1, externalEvents1); + + auto &layout2 = project.InsertNewLayout("Layout2", 0); + auto &externalEvents2 = + project.InsertNewExternalEvents("ExternalEvents2", 0); + externalEvents2.GetEvents().InsertEvent( + UseSceneTimer("MySceneTimerInExternalEvents2")); + UseExternalEvents(layout2, externalEvents2); + + auto identifierExpressions = + gd::EventsIdentifiersFinder::FindAllIdentifierExpressions( + platform, project, layout1, "sceneTimer"); + + REQUIRE(identifierExpressions.size() == 1); + REQUIRE(*(identifierExpressions.begin()) == + "\"MySceneTimerInExternalEvents1\""); + } +} + +TEST_CASE("EventsIdentifiersFinder (object timers)", "[common]") { + SECTION("Can find object timers in scenes") { + gd::Project project; + gd::Platform platform; + DeclareTimerExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + auto &object = layout.InsertNewObject(project, "", "MyObject", 0); + layout.GetEvents().InsertEvent(UseObjectTimer("MyObject", "MyObjectTimer")); + + auto identifierExpressions = + gd::EventsIdentifiersFinder::FindAllIdentifierExpressions( + platform, project, layout, "objectTimer", object.GetName()); + + REQUIRE(identifierExpressions.size() == 1); + REQUIRE(*(identifierExpressions.begin()) == "\"MyObjectTimer\""); + } + + SECTION("Can find object timers in scene expression") { + gd::Project project; + gd::Platform platform; + DeclareTimerExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + auto &object = layout.InsertNewObject(project, "", "MyObject", 0); + layout.GetEvents().InsertEvent(UseObjectTimerInExpression("MyObject", "MyObjectTimer")); + + auto identifierExpressions = + gd::EventsIdentifiersFinder::FindAllIdentifierExpressions( + platform, project, layout, "objectTimer", object.GetName()); + + REQUIRE(identifierExpressions.size() == 1); + REQUIRE(*(identifierExpressions.begin()) == "\"MyObjectTimer\""); + } + + SECTION("Can find object timers in external layouts") { + gd::Project project; + gd::Platform platform; + DeclareTimerExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + auto &object = layout.InsertNewObject(project, "", "MyObject", 0); + auto &externalEvents = project.InsertNewExternalEvents("ExternalEvents", 0); + externalEvents.GetEvents().InsertEvent( + UseObjectTimer("MyObject", "MyObjectTimer")); + UseExternalEvents(layout, externalEvents); + + auto identifierExpressions = + gd::EventsIdentifiersFinder::FindAllIdentifierExpressions( + platform, project, layout, "objectTimer", object.GetName()); + + REQUIRE(identifierExpressions.size() == 1); + REQUIRE(*(identifierExpressions.begin()) == "\"MyObjectTimer\""); + } + + SECTION("Can find object timers in scenes for the right object") { + gd::Project project; + gd::Platform platform; + DeclareTimerExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + auto &object1 = layout.InsertNewObject(project, "", "MyObject1", 0); + auto &object2 = layout.InsertNewObject(project, "", "MyObject2", 0); + layout.GetEvents().InsertEvent( + UseObjectTimer("MyObject1", "MyObjectTimer1")); + layout.GetEvents().InsertEvent( + UseObjectTimer("MyObject2", "MyObjectTimer2")); + + auto identifierExpressions = + gd::EventsIdentifiersFinder::FindAllIdentifierExpressions( + platform, project, layout, "objectTimer", object1.GetName()); + + REQUIRE(identifierExpressions.size() == 1); + REQUIRE(*(identifierExpressions.begin()) == "\"MyObjectTimer1\""); + } + + SECTION("Can find object timers in external layouts for the right object") { + gd::Project project; + gd::Platform platform; + DeclareTimerExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + auto &object1 = layout.InsertNewObject(project, "", "MyObject1", 0); + auto &object2 = layout.InsertNewObject(project, "", "MyObject2", 0); + auto &externalEvents = project.InsertNewExternalEvents("ExternalEvents", 0); + externalEvents.GetEvents().InsertEvent( + UseObjectTimer("MyObject1", "MyObjectTimer1")); + externalEvents.GetEvents().InsertEvent( + UseObjectTimer("MyObject2", "MyObjectTimer2")); + UseExternalEvents(layout, externalEvents); + + auto identifierExpressions = + gd::EventsIdentifiersFinder::FindAllIdentifierExpressions( + platform, project, layout, "objectTimer", object1.GetName()); + + REQUIRE(identifierExpressions.size() == 1); + REQUIRE(*(identifierExpressions.begin()) == "\"MyObjectTimer1\""); + } +} diff --git a/Core/tests/EventsVariablesFinder.cpp b/Core/tests/EventsVariablesFinder.cpp new file mode 100644 index 0000000000..9828b1544e --- /dev/null +++ b/Core/tests/EventsVariablesFinder.cpp @@ -0,0 +1,360 @@ +/* + * 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/Events/EventsVariablesFinder.h" +#include "GDCore/Events/Builtin/LinkEvent.h" +#include "GDCore/Events/Builtin/StandardEvent.h" +#include "GDCore/Events/Event.h" +#include "GDCore/Extensions/Builtin/AllBuiltinExtensions.h" +#include "GDCore/Extensions/Platform.h" +#include "GDCore/Project/ExternalEvents.h" +#include "GDCore/Project/Layout.h" +#include "GDCore/Project/Project.h" +#include "GDCore/Project/Variable.h" +#include "catch.hpp" + +namespace { +const void DeclareVariableExtension(gd::Project &project, + gd::Platform &platform) { + std::shared_ptr extension = + std::shared_ptr(new gd::PlatformExtension); + gd::BuiltinExtensionsImplementer::ImplementsVariablesExtension( + *(extension.get())); + gd::BuiltinExtensionsImplementer::ImplementsBaseObjectExtension( + *(extension.get())); + // Add an instruction to test expressions. + extension + ->AddAction("DoSomething", "Do something", "This does something", + "Do something please", "", "", "") + .AddParameter("expression", "Parameter 1 (a number)"); + platform.AddExtension(extension); + project.AddPlatform(platform); +} + +const gd::StandardEvent UseGlobalVariable(const gd::String &name) { + gd::StandardEvent event; + gd::Instruction instruction; + instruction.SetType("ModVarGlobal"); + instruction.SetParametersCount(2); + instruction.SetParameter(0, gd::Expression(name)); + instruction.SetParameter(1, gd::Expression("0")); + event.GetActions().Insert(instruction); + return event; +} + +const gd::StandardEvent UseSceneVariable(const gd::String &name) { + gd::StandardEvent event; + gd::Instruction instruction; + instruction.SetType("ModVarScene"); + instruction.SetParametersCount(2); + instruction.SetParameter(0, gd::Expression(name)); + instruction.SetParameter(1, gd::Expression("0")); + event.GetActions().Insert(instruction); + return event; +} + +const gd::StandardEvent UseObjectVariable(const gd::String &objectName, + const gd::String &variableName) { + gd::StandardEvent event; + gd::Instruction instruction; + instruction.SetType("ModVarObjet"); + instruction.SetParametersCount(3); + instruction.SetParameter(0, gd::Expression(objectName)); + instruction.SetParameter(1, gd::Expression(variableName)); + instruction.SetParameter(2, gd::Expression("0")); + event.GetActions().Insert(instruction); + return event; +} + +const gd::StandardEvent UseGlobalVariableInExpression(const gd::String &name) { + gd::StandardEvent event; + gd::Instruction instruction; + instruction.SetType("DoSomething"); + instruction.SetParametersCount(1); + instruction.SetParameter(0, + gd::Expression("1 + GlobalVariable(" + name + ")")); + event.GetActions().Insert(instruction); + return event; +} + +const gd::StandardEvent UseSceneVariableInExpression(const gd::String &name) { + gd::StandardEvent event; + gd::Instruction instruction; + instruction.SetType("DoSomething"); + instruction.SetParametersCount(1); + instruction.SetParameter(0, gd::Expression("1 + Variable(" + name + ")")); + event.GetActions().Insert(instruction); + return event; +} + +const gd::StandardEvent +UseObjectVariableInExpression(const gd::String &objectName, + const gd::String &variableName) { + gd::StandardEvent event; + gd::Instruction instruction; + instruction.SetType("DoSomething"); + instruction.SetParametersCount(1); + instruction.SetParameter( + 0, + gd::Expression("1 + " + objectName + ".Variable(" + variableName + ")")); + event.GetActions().Insert(instruction); + return event; +} + +const void UseExternalEvents(gd::Layout &layout, + gd::ExternalEvents &externalEvents) { + gd::LinkEvent linkEvent; + linkEvent.SetTarget(externalEvents.GetName()); + layout.GetEvents().InsertEvent(linkEvent); +} +} // namespace + +TEST_CASE("EventsVariablesFinder (FindAllGlobalVariables)", "[common]") { + SECTION("Can find global variables in scenes") { + gd::Project project; + gd::Platform platform; + DeclareVariableExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + layout.GetEvents().InsertEvent(UseGlobalVariable("MyGlobalVariable")); + + auto variableNames = + gd::EventsVariablesFinder::FindAllGlobalVariables(platform, project); + + REQUIRE(variableNames.size() == 1); + REQUIRE(*(variableNames.begin()) == "MyGlobalVariable"); + } + + SECTION("Can find global variables in scene expressions") { + gd::Project project; + gd::Platform platform; + DeclareVariableExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + layout.GetEvents().InsertEvent( + UseGlobalVariableInExpression("MyGlobalVariable")); + + auto variableNames = + gd::EventsVariablesFinder::FindAllGlobalVariables(platform, project); + + REQUIRE(variableNames.size() == 1); + REQUIRE(*(variableNames.begin()) == "MyGlobalVariable"); + } + + SECTION("Can find global variables in external layouts") { + gd::Project project; + gd::Platform platform; + DeclareVariableExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + auto &externalEvents = project.InsertNewExternalEvents("ExternalEvents", 0); + externalEvents.GetEvents().InsertEvent( + UseGlobalVariable("MyGlobalVariable")); + UseExternalEvents(layout, externalEvents); + + auto variableNames = + gd::EventsVariablesFinder::FindAllGlobalVariables(platform, project); + + REQUIRE(variableNames.size() == 1); + REQUIRE(*(variableNames.begin()) == "MyGlobalVariable"); + } +} + +TEST_CASE("EventsVariablesFinder (FindAllLayoutVariables)", "[common]") { + SECTION("Can find scene variables in scenes") { + gd::Project project; + gd::Platform platform; + DeclareVariableExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + layout.GetEvents().InsertEvent(UseSceneVariable("MySceneVariable")); + + auto variableNames = gd::EventsVariablesFinder::FindAllLayoutVariables( + platform, project, layout); + + REQUIRE(variableNames.size() == 1); + REQUIRE(*(variableNames.begin()) == "MySceneVariable"); + } + + SECTION("Can find scene variables in scene expressions") { + gd::Project project; + gd::Platform platform; + DeclareVariableExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + layout.GetEvents().InsertEvent( + UseSceneVariableInExpression("MySceneVariable")); + + auto variableNames = gd::EventsVariablesFinder::FindAllLayoutVariables( + platform, project, layout); + + REQUIRE(variableNames.size() == 1); + REQUIRE(*(variableNames.begin()) == "MySceneVariable"); + } + + SECTION("Can find scene variables in external layouts") { + gd::Project project; + gd::Platform platform; + DeclareVariableExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + auto &externalEvents = project.InsertNewExternalEvents("ExternalEvents", 0); + externalEvents.GetEvents().InsertEvent(UseSceneVariable("MySceneVariable")); + UseExternalEvents(layout, externalEvents); + + auto variableNames = gd::EventsVariablesFinder::FindAllLayoutVariables( + platform, project, layout); + + REQUIRE(variableNames.size() == 1); + REQUIRE(*(variableNames.begin()) == "MySceneVariable"); + } + + SECTION("Can find scene variables the right scene") { + gd::Project project; + gd::Platform platform; + DeclareVariableExtension(project, platform); + + auto &layout1 = project.InsertNewLayout("Layout1", 0); + layout1.GetEvents().InsertEvent( + UseSceneVariable("MySceneVariableInLayout1")); + + auto &layout2 = project.InsertNewLayout("Layout2", 0); + layout2.GetEvents().InsertEvent( + UseSceneVariable("MySceneVariableInLayout2")); + + auto variableNames = gd::EventsVariablesFinder::FindAllLayoutVariables( + platform, project, layout1); + + REQUIRE(variableNames.size() == 1); + REQUIRE(*(variableNames.begin()) == "MySceneVariableInLayout1"); + } + + SECTION("Can find scene variables in the right external layouts") { + gd::Project project; + gd::Platform platform; + DeclareVariableExtension(project, platform); + + auto &layout1 = project.InsertNewLayout("Layout1", 0); + auto &externalEvents1 = + project.InsertNewExternalEvents("ExternalEvents1", 0); + externalEvents1.GetEvents().InsertEvent( + UseSceneVariable("MySceneVariableInExternalEvents1")); + UseExternalEvents(layout1, externalEvents1); + + auto &layout2 = project.InsertNewLayout("Layout2", 0); + auto &externalEvents2 = + project.InsertNewExternalEvents("ExternalEvents2", 0); + externalEvents2.GetEvents().InsertEvent( + UseSceneVariable("MySceneVariableInExternalEvents2")); + UseExternalEvents(layout2, externalEvents2); + + auto variableNames = gd::EventsVariablesFinder::FindAllLayoutVariables( + platform, project, layout1); + + REQUIRE(variableNames.size() == 1); + REQUIRE(*(variableNames.begin()) == "MySceneVariableInExternalEvents1"); + } +} + +TEST_CASE("EventsVariablesFinder (FindAllObjectVariables)", "[common]") { + SECTION("Can find object variables in scenes") { + gd::Project project; + gd::Platform platform; + DeclareVariableExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + auto &object = layout.InsertNewObject(project, "", "MyObject", 0); + layout.GetEvents().InsertEvent( + UseObjectVariable("MyObject", "MyObjectVariable")); + + auto variableNames = gd::EventsVariablesFinder::FindAllObjectVariables( + platform, project, layout, object); + + REQUIRE(variableNames.size() == 1); + REQUIRE(*(variableNames.begin()) == "MyObjectVariable"); + } + + SECTION("Can find object variables in scene expressions") { + gd::Project project; + gd::Platform platform; + DeclareVariableExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + auto &object = layout.InsertNewObject(project, "", "MyObject", 0); + layout.GetEvents().InsertEvent( + UseObjectVariableInExpression("MyObject", "MyObjectVariable")); + + auto variableNames = gd::EventsVariablesFinder::FindAllObjectVariables( + platform, project, layout, object); + + REQUIRE(variableNames.size() == 1); + REQUIRE(*(variableNames.begin()) == "MyObjectVariable"); + } + + SECTION("Can find object variables in external layouts") { + gd::Project project; + gd::Platform platform; + DeclareVariableExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + auto &object = layout.InsertNewObject(project, "", "MyObject", 0); + auto &externalEvents = project.InsertNewExternalEvents("ExternalEvents", 0); + externalEvents.GetEvents().InsertEvent( + UseObjectVariable("MyObject", "MyObjectVariable")); + UseExternalEvents(layout, externalEvents); + + auto variableNames = gd::EventsVariablesFinder::FindAllObjectVariables( + platform, project, layout, object); + + REQUIRE(variableNames.size() == 1); + REQUIRE(*(variableNames.begin()) == "MyObjectVariable"); + } + + SECTION("Can find object variables in scenes for the right object") { + gd::Project project; + gd::Platform platform; + DeclareVariableExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + auto &object1 = layout.InsertNewObject(project, "", "MyObject1", 0); + auto &object2 = layout.InsertNewObject(project, "", "MyObject2", 0); + layout.GetEvents().InsertEvent( + UseObjectVariable("MyObject1", "MyObjectVariable1")); + layout.GetEvents().InsertEvent( + UseObjectVariable("MyObject2", "MyObjectVariable2")); + + auto variableNames = gd::EventsVariablesFinder::FindAllObjectVariables( + platform, project, layout, object1); + + REQUIRE(variableNames.size() == 1); + REQUIRE(*(variableNames.begin()) == "MyObjectVariable1"); + } + + SECTION( + "Can find object variables in external layouts for the right object") { + gd::Project project; + gd::Platform platform; + DeclareVariableExtension(project, platform); + + auto &layout = project.InsertNewLayout("Layout1", 0); + auto &object1 = layout.InsertNewObject(project, "", "MyObject1", 0); + auto &object2 = layout.InsertNewObject(project, "", "MyObject2", 0); + auto &externalEvents = project.InsertNewExternalEvents("ExternalEvents", 0); + externalEvents.GetEvents().InsertEvent( + UseObjectVariable("MyObject1", "MyObjectVariable1")); + externalEvents.GetEvents().InsertEvent( + UseObjectVariable("MyObject2", "MyObjectVariable2")); + UseExternalEvents(layout, externalEvents); + + auto variableNames = gd::EventsVariablesFinder::FindAllObjectVariables( + platform, project, layout, object1); + + REQUIRE(variableNames.size() == 1); + REQUIRE(*(variableNames.begin()) == "MyObjectVariable1"); + } +} diff --git a/Extensions/TweenBehavior/JsExtension.js b/Extensions/TweenBehavior/JsExtension.js index 1278cfabba..27155acde6 100644 --- a/Extensions/TweenBehavior/JsExtension.js +++ b/Extensions/TweenBehavior/JsExtension.js @@ -112,7 +112,7 @@ module.exports = { 'JsPlatform/Extensions/tween_behavior32.png' ) .addCodeOnlyParameter('currentScene', '') - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'sceneTween') .addParameter('scenevar', _('The variable to tween'), '', false) .addParameter('expression', _('Initial value'), '', false) .addParameter('expression', _('Final value'), '', false) @@ -137,7 +137,7 @@ module.exports = { 'JsPlatform/Extensions/tween_behavior32.png' ) .addCodeOnlyParameter('currentScene', '') - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'sceneTween') .addParameter('expression', _('Target X position'), '', false) .addParameter('expression', _('Target Y position'), '', false) .addParameter('layer', _('Layer'), '', true) @@ -162,7 +162,7 @@ module.exports = { 'JsPlatform/Extensions/tween_behavior32.png' ) .addCodeOnlyParameter('currentScene', '') - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'sceneTween') .addParameter('expression', _('Target zoom'), '', false) .addParameter('layer', _('Layer'), '', true) .addParameter('expression', _('Duration'), '', false) @@ -186,7 +186,7 @@ module.exports = { 'JsPlatform/Extensions/tween_behavior32.png' ) .addCodeOnlyParameter('currentScene', '') - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'sceneTween') .addParameter('expression', _('Target rotation'), '', false) .addParameter('layer', _('Layer'), '', true) .addParameter('expression', _('Duration'), '', false) @@ -208,7 +208,7 @@ module.exports = { 'JsPlatform/Extensions/tween_behavior32.png' ) .addCodeOnlyParameter('currentScene', '') - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'sceneTween') .getCodeExtraInformation() .setIncludeFile('Extensions/TweenBehavior/shifty.js') .addIncludeFile('Extensions/TweenBehavior/shifty_setup.js') @@ -226,7 +226,7 @@ module.exports = { 'JsPlatform/Extensions/tween_behavior32.png' ) .addCodeOnlyParameter('currentScene', '') - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'sceneTween') .getCodeExtraInformation() .setIncludeFile('Extensions/TweenBehavior/shifty.js') .addIncludeFile('Extensions/TweenBehavior/shifty_setup.js') @@ -244,7 +244,7 @@ module.exports = { 'JsPlatform/Extensions/tween_behavior32.png' ) .addCodeOnlyParameter('currentScene', '') - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'sceneTween') .getCodeExtraInformation() .setIncludeFile('Extensions/TweenBehavior/shifty.js') .addIncludeFile('Extensions/TweenBehavior/shifty_setup.js') @@ -262,7 +262,7 @@ module.exports = { 'JsPlatform/Extensions/tween_behavior32.png' ) .addCodeOnlyParameter('currentScene', '') - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'sceneTween') .getCodeExtraInformation() .setIncludeFile('Extensions/TweenBehavior/shifty.js') .addIncludeFile('Extensions/TweenBehavior/shifty_setup.js') @@ -280,7 +280,7 @@ module.exports = { 'JsPlatform/Extensions/tween_behavior32.png' ) .addCodeOnlyParameter('currentScene', '') - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'sceneTween') .addParameter('yesorno', _('Jump to the end'), '', false) .getCodeExtraInformation() .setIncludeFile('Extensions/TweenBehavior/shifty.js') @@ -299,7 +299,7 @@ module.exports = { 'JsPlatform/Extensions/tween_behavior32.png' ) .addCodeOnlyParameter('currentScene', '') - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'sceneTween') .getCodeExtraInformation() .setIncludeFile('Extensions/TweenBehavior/shifty.js') .addIncludeFile('Extensions/TweenBehavior/shifty_setup.js') @@ -319,7 +319,7 @@ module.exports = { 'JsPlatform/Extensions/tween_behavior32.png' ) .addCodeOnlyParameter('currentScene', '') - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'sceneTween') .getCodeExtraInformation() .setIncludeFile('Extensions/TweenBehavior/shifty.js') .addIncludeFile('Extensions/TweenBehavior/shifty_setup.js') @@ -379,7 +379,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .addParameter('objectvar', _('Object variable'), '', false) .addParameter('expression', _('From value'), '', false) .addParameter('expression', _('To value'), '', false) @@ -410,7 +410,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .addParameter('expression', _('To X'), '', false) .addParameter('expression', _('To Y'), '', false) .addParameter('stringWithSelector', _('Easing'), easingChoices, false) @@ -440,7 +440,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .addParameter('expression', _('To X'), '', false) .addParameter('stringWithSelector', _('Easing'), easingChoices, false) .setDefaultValue('linear') @@ -469,7 +469,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .addParameter('expression', _('To width'), '', false) .addParameter('stringWithSelector', _('Easing'), easingChoices, false) .setDefaultValue('linear') @@ -498,7 +498,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .addParameter('expression', _('To height'), '', false) .addParameter('stringWithSelector', _('Easing'), easingChoices, false) .setDefaultValue('linear') @@ -527,7 +527,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .addParameter('expression', _('To Y'), '', false) .addParameter('stringWithSelector', _('Easing'), easingChoices, false) .setDefaultValue('linear') @@ -556,7 +556,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .addParameter('expression', _('To angle (in degrees)'), '', false) .addParameter('stringWithSelector', _('Easing'), easingChoices, false) .setDefaultValue('linear') @@ -587,7 +587,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .addParameter('expression', _('To scale X'), '', false) .addParameter('expression', _('To scale Y'), '', false) .addParameter('stringWithSelector', _('Easing'), easingChoices, false) @@ -621,7 +621,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .addParameter('expression', _('To scale X'), '', false) .addParameter('stringWithSelector', _('Easing'), easingChoices, false) .setDefaultValue('linear') @@ -654,7 +654,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .addParameter('expression', _('To scale Y'), '', false) .addParameter('stringWithSelector', _('Easing'), easingChoices, false) .setDefaultValue('linear') @@ -687,7 +687,7 @@ module.exports = { ) .addParameter('object', _('Text object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .addParameter('expression', _('To character size'), '', false) .addParameter('stringWithSelector', _('Easing'), easingChoices, false) .setDefaultValue('linear') @@ -718,7 +718,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .addParameter('expression', _('To opacity'), '', false) .addParameter('stringWithSelector', _('Easing'), easingChoices, false) .setDefaultValue('linear') @@ -749,7 +749,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .addParameter('color', _('To color'), '', false) .addParameter('stringWithSelector', _('Easing'), easingChoices, false) .setDefaultValue('linear') @@ -790,7 +790,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .addParameter('expression', _('To Hue'), '', false) .addParameter('yesorno', _('Animate Hue'), '', false) .setDefaultValue('yes') @@ -823,7 +823,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .getCodeExtraInformation() .setFunctionName('exists'); @@ -839,7 +839,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .getCodeExtraInformation() .setFunctionName('isPlaying'); @@ -855,7 +855,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .getCodeExtraInformation() .setFunctionName('hasFinished'); @@ -871,7 +871,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .getCodeExtraInformation() .setFunctionName('pauseTween'); @@ -887,7 +887,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .addParameter('yesorno', _('Jump to end'), '', false) .getCodeExtraInformation() .setFunctionName('stopTween'); @@ -904,7 +904,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .getCodeExtraInformation() .setFunctionName('resumeTween'); @@ -920,7 +920,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .getCodeExtraInformation() .setFunctionName('removeTween'); @@ -934,7 +934,7 @@ module.exports = { ) .addParameter('object', _('Object'), '', false) .addParameter('behavior', _('Behavior'), 'TweenBehavior', false) - .addParameter('string', _('Tween Identifier'), '', false) + .addParameter('identifier', _('Tween Identifier'), 'objectTween') .getCodeExtraInformation() .setFunctionName('getProgress'); diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index 43bf526925..aef57093c1 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -135,6 +135,14 @@ interface EventsVariablesFinder { //Inherited from ExpressionParser2NodeWorker: }; +interface EventsIdentifiersFinder { + void EventsIdentifiersFinder(); + + [Const, Value] SetString STATIC_FindAllIdentifierExpressions([Const, Ref] Platform platform, [Const, Ref] Project project, [Const, Ref] Layout layout, [Const] DOMString identifierType, [Const] DOMString contextObjectName); + + //Inherited from ExpressionParser2NodeWorker: +}; + interface InstructionOrExpressionGroupMetadata { void InstructionOrExpressionGroupMetadata(); diff --git a/GDevelop.js/Bindings/Wrapper.cpp b/GDevelop.js/Bindings/Wrapper.cpp index aa56a6736e..b6d3456d9d 100644 --- a/GDevelop.js/Bindings/Wrapper.cpp +++ b/GDevelop.js/Bindings/Wrapper.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -598,6 +599,7 @@ typedef ExtensionAndMetadata ExtensionAndExpressionMetadata; #define STATIC_FindAllGlobalVariables FindAllGlobalVariables #define STATIC_FindAllLayoutVariables FindAllLayoutVariables #define STATIC_FindAllObjectVariables FindAllObjectVariables +#define STATIC_FindAllIdentifierExpressions FindAllIdentifierExpressions #define STATIC_SearchInEvents SearchInEvents #define STATIC_UnfoldWhenContaining UnfoldWhenContaining #define STATIC_FoldAll FoldAll diff --git a/GDevelop.js/types/gdeventsidentifiersfinder.js b/GDevelop.js/types/gdeventsidentifiersfinder.js new file mode 100644 index 0000000000..7383ad6647 --- /dev/null +++ b/GDevelop.js/types/gdeventsidentifiersfinder.js @@ -0,0 +1,7 @@ +// Automatically generated by GDevelop.js/scripts/generate-types.js +declare class gdEventsIdentifiersFinder { + constructor(): void; + static findAllIdentifierExpressions(platform: gdPlatform, project: gdProject, layout: gdLayout, identifierType: string, contextObjectName: string): gdSetString; + 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 7069635eec..ecf51dbd8e 100644 --- a/GDevelop.js/types/libgdevelop.js +++ b/GDevelop.js/types/libgdevelop.js @@ -51,6 +51,7 @@ declare class libGDevelop { SetString: Class; ProjectHelper: Class; EventsVariablesFinder: Class; + EventsIdentifiersFinder: Class; InstructionOrExpressionGroupMetadata: Class; VersionWrapper: Class; Platform: Class; diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionParametersEditor.js b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionParametersEditor.js index 28d4c6b734..642e2462f0 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionParametersEditor.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionParametersEditor.js @@ -110,6 +110,14 @@ const getExtraInfoArray = (parameter: gdParameterMetadata) => { return array; }; +const getIdentifierScope = (scopedIdentifier: string) => + scopedIdentifier.startsWith('object') ? 'object' : 'scene'; + +const getIdentifierName = (scopedIdentifier: string) => + scopedIdentifier.startsWith('object') + ? scopedIdentifier.substring('object'.length) + : scopedIdentifier.substring('scene'.length); + export default class EventsFunctionParametersEditor extends React.Component< Props, State @@ -469,6 +477,10 @@ export default class EventsFunctionParametersEditor extends React.Component< value="objectAnimationName" primaryText={t`Object animation (text)`} /> + )} {gd.ParameterMetadata.isObject( @@ -550,6 +562,55 @@ export default class EventsFunctionParametersEditor extends React.Component< /> )} + {parameter.getType() === 'identifier' && ( + Scope} + value={getIdentifierScope( + parameter.getExtraInfo() + )} + onChange={(e, i, value) => { + const identifierName = getIdentifierName( + parameter.getExtraInfo() + ); + parameter.setExtraInfo( + value + identifierName + ); + this.forceUpdate(); + this.props.onParametersUpdated(); + }} + fullWidth + > + + + + )} + {parameter.getType() === 'identifier' && ( + Identifier name + } + floatingLabelFixed + value={getIdentifierName( + parameter.getExtraInfo() + )} + onChange={value => { + const scope = getIdentifierScope( + parameter.getExtraInfo() + ); + parameter.setExtraInfo(scope + value); + this.forceUpdate(); + this.props.onParametersUpdated(); + }} + fullWidth + /> + )} {parameter.getType() === 'stringWithSelector' && ( { : 0; const expression = this.state.validatedValue; - const { - expression: newExpression, - caretLocation: newCaretLocation, - } = insertAutocompletionInExpression( - { expression, caretLocation }, - { - completion: expressionAutocompletion.completion, - replacementStartPosition: - expressionAutocompletion.replacementStartPosition, - replacementEndPosition: expressionAutocompletion.replacementEndPosition, - addParenthesis: expressionAutocompletion.addParenthesis, - addDot: expressionAutocompletion.addDot, - addParameterSeparator: expressionAutocompletion.addParameterSeparator, - addNamespaceSeparator: expressionAutocompletion.addNamespaceSeparator, - hasVisibleParameters: expressionAutocompletion.hasVisibleParameters, - shouldConvertToString: - expressionAutocompletion.kind === 'Expression' - ? expressionAutocompletion.shouldConvertToString - : null, - } - ); + const { expression: newExpression, caretLocation: newCaretLocation } = + expressionAutocompletion.kind === 'FullExpression' + ? { + expression: expressionAutocompletion.completion, + caretLocation: expressionAutocompletion.completion.length, + } + : insertAutocompletionInExpression( + { expression, caretLocation }, + { + completion: expressionAutocompletion.completion, + replacementStartPosition: + expressionAutocompletion.replacementStartPosition, + replacementEndPosition: + expressionAutocompletion.replacementEndPosition, + addParenthesis: expressionAutocompletion.addParenthesis, + addDot: expressionAutocompletion.addDot, + addParameterSeparator: + expressionAutocompletion.addParameterSeparator, + addNamespaceSeparator: + expressionAutocompletion.addNamespaceSeparator, + hasVisibleParameters: + expressionAutocompletion.hasVisibleParameters, + shouldConvertToString: + expressionAutocompletion.kind === 'Expression' + ? expressionAutocompletion.shouldConvertToString + : null, + } + ); if (this._field) { this._field.forceSetValue(newExpression); diff --git a/newIDE/app/src/EventsSheet/ParameterFields/IdentifierField.js b/newIDE/app/src/EventsSheet/ParameterFields/IdentifierField.js new file mode 100644 index 0000000000..2261062141 --- /dev/null +++ b/newIDE/app/src/EventsSheet/ParameterFields/IdentifierField.js @@ -0,0 +1,98 @@ +// @flow +import React from 'react'; +import { type ParameterFieldProps } from './ParameterFieldCommons'; +import GenericExpressionField from './GenericExpressionField'; +import { type ExpressionAutocompletion } from '../../ExpressionAutocompletion'; +import { getLastObjectParameterValue } from './ParameterMetadataTools'; + +const gd: libGDevelop = global.gd; + +type Props = {| + ...ParameterFieldProps, +|}; + +export const IdentifierField = (props: Props) => { + const { + project, + scope, + instructionMetadata, + instruction, + expressionMetadata, + expression, + parameterIndex, + } = props; + const { layout } = scope; + + const objectName = + getLastObjectParameterValue({ + instructionMetadata, + instruction, + expressionMetadata, + expression, + parameterIndex, + }) || ''; + + const autocompletionIdentifierNames: ExpressionAutocompletion[] = React.useMemo( + () => { + if (!parameterIndex) { + return []; + } + const parameterMetadata = instructionMetadata + ? instructionMetadata.getParameter(parameterIndex) + : expressionMetadata + ? expressionMetadata.getParameter(parameterIndex) + : null; + const identifierName = parameterMetadata + ? parameterMetadata.getExtraInfo() + : ''; + + const allIdentifierExpressions = + project && layout + ? gd.EventsIdentifiersFinder.findAllIdentifierExpressions( + project.getCurrentPlatform(), + project, + layout, + identifierName, + objectName + ) + .toNewVectorString() + .toJSArray() + : []; + + return allIdentifierExpressions.map(expression => ({ + kind: 'FullExpression', + completion: expression, + })); + }, + [ + project, + layout, + expressionMetadata, + instructionMetadata, + parameterIndex, + // Users can change the objectName with other fields. + objectName, + ] + ); + + const field = React.useRef(null); + + React.useEffect(() => { + if (field.current) { + field.current.focus(); + } + }, []); + + return ( + + autocompletionIdentifierNames.filter( + ({ completion }) => completion.indexOf(expression) === 0 + ) + } + ref={field} + {...props} + /> + ); +}; diff --git a/newIDE/app/src/EventsSheet/ParameterRenderingService.js b/newIDE/app/src/EventsSheet/ParameterRenderingService.js index 121edfcc3e..d59c991955 100644 --- a/newIDE/app/src/EventsSheet/ParameterRenderingService.js +++ b/newIDE/app/src/EventsSheet/ParameterRenderingService.js @@ -60,6 +60,8 @@ import { type MessageDescriptor } from '../Utils/i18n/MessageDescriptor.flow'; import LeaderboardIdField, { renderInlineLeaderboardIdField, } from './ParameterFields/LeaderboardIdField'; +import { IdentifierField } from './ParameterFields/IdentifierField'; + const gd: libGDevelop = global.gd; const components = { @@ -101,6 +103,7 @@ const components = { functionParameterName: FunctionParameterNameField, externalLayoutName: ExternalLayoutNameField, leaderboardId: LeaderboardIdField, + identifier: IdentifierField, }; const inlineRenderers: { [string]: ParameterInlineRenderer } = { default: renderInlineDefaultField, @@ -151,6 +154,7 @@ const userFriendlyTypeName: { [string]: MessageDescriptor } = { objectAnimationName: t`Object animation name`, functionParameterName: t`Parameter name`, externalLayoutName: t`Name of the external layout`, + identifier: t`Name of the identifier`, }; const ParameterRenderingService = { diff --git a/newIDE/app/src/ExpressionAutocompletion/index.js b/newIDE/app/src/ExpressionAutocompletion/index.js index 7ea6c95090..35e8c78ced 100644 --- a/newIDE/app/src/ExpressionAutocompletion/index.js +++ b/newIDE/app/src/ExpressionAutocompletion/index.js @@ -61,6 +61,10 @@ export type ExpressionAutocompletion = | {| ...BaseExpressionAutocompletion, kind: 'Behavior', + |} + | {| + ...BaseExpressionAutocompletion, + kind: 'FullExpression', |}; type ExpressionAutocompletionContext = {|