diff --git a/Core/GDCore/Extensions/Metadata/ExpressionMetadata.cpp b/Core/GDCore/Extensions/Metadata/ExpressionMetadata.cpp index 4ab05415c0..e41caf5f43 100644 --- a/Core/GDCore/Extensions/Metadata/ExpressionMetadata.cpp +++ b/Core/GDCore/Extensions/Metadata/ExpressionMetadata.cpp @@ -44,8 +44,10 @@ gd::ExpressionMetadata& ExpressionMetadata::AddParameter( info.SetExtraInfo( // For objects/behavior, the supplementary information // parameter is an object/behavior type... - (gd::ParameterMetadata::IsObject(type) || + ((gd::ParameterMetadata::IsObject(type) || gd::ParameterMetadata::IsBehavior(type)) + // Prefix with the namespace if it's not already there. + && !(supplementaryInformation.rfind(extensionNamespace, 0) == 0)) ? (supplementaryInformation.empty() ? "" : extensionNamespace + diff --git a/Core/GDCore/Extensions/Metadata/ExpressionMetadata.h b/Core/GDCore/Extensions/Metadata/ExpressionMetadata.h index e179fea093..3cc642f44d 100644 --- a/Core/GDCore/Extensions/Metadata/ExpressionMetadata.h +++ b/Core/GDCore/Extensions/Metadata/ExpressionMetadata.h @@ -222,6 +222,18 @@ class GD_CORE_API ExpressionMetadata { return *this; }; + /** + * \brief Set the additional information, used for some parameters + * with special type (for example, it can contains the type of object accepted + * by the parameter), for the last added parameter. + * + * \see AddParameter + */ + ExpressionMetadata &SetParameterExtraInfo(const gd::String &extraInfo) { + if (!parameters.empty()) parameters.back().SetExtraInfo(extraInfo); + return *this; + } + /** * \brief Mark this (object) expression as requiring the specified capability, * offered by the base object. @@ -256,7 +268,30 @@ class GD_CORE_API ExpressionMetadata { */ ExpressionCodeGenerationInformation& GetCodeExtraInformation() { return codeExtraInformation; - }; + } + + /** + * \brief Erase any existing include file and add the specified include. + */ + ExpressionMetadata &SetIncludeFile(const gd::String &includeFile) { + codeExtraInformation.SetIncludeFile(includeFile); + return *this; + } + + /** + * \brief Add a file to the already existing include files. + */ + ExpressionMetadata &AddIncludeFile(const gd::String &includeFile) { + codeExtraInformation.AddIncludeFile(includeFile); + return *this; + } + + /** + * \brief Get the files that must be included to use the instruction. + */ + const std::vector& GetIncludeFiles() const { + return codeExtraInformation.GetIncludeFiles(); + } ExpressionCodeGenerationInformation codeExtraInformation; diff --git a/Core/GDCore/Extensions/Metadata/InstructionMetadata.cpp b/Core/GDCore/Extensions/Metadata/InstructionMetadata.cpp index 201fda44c9..cc58ce5bf2 100644 --- a/Core/GDCore/Extensions/Metadata/InstructionMetadata.cpp +++ b/Core/GDCore/Extensions/Metadata/InstructionMetadata.cpp @@ -62,8 +62,10 @@ InstructionMetadata& InstructionMetadata::AddParameter( info.SetExtraInfo( // For objects/behavior, the supplementary information // parameter is an object/behavior type... - (gd::ParameterMetadata::IsObject(type) || + ((gd::ParameterMetadata::IsObject(type) || gd::ParameterMetadata::IsBehavior(type)) + // Prefix with the namespace if it's not already there. + && !(supplementaryInformation.rfind(extensionNamespace, 0) == 0)) ? (supplementaryInformation.empty() ? "" : extensionNamespace + @@ -92,8 +94,10 @@ InstructionMetadata& InstructionMetadata::AddCodeOnlyParameter( } InstructionMetadata& InstructionMetadata::UseStandardOperatorParameters( - const gd::String& type) { - SetManipulatedType(type); + const gd::String& type, const gd::String& typeExtraInfo) { + const gd::String& expressionValueType = + gd::ValueTypeMetadata::GetPrimitiveValueType(type); + SetManipulatedType(expressionValueType); if (type == "boolean") { AddParameter("yesorno", _("New value")); @@ -117,8 +121,8 @@ InstructionMetadata& InstructionMetadata::UseStandardOperatorParameters( "_PARAM" + gd::String::From(valueParamIndex) + "_"); } } else { - AddParameter("operator", _("Modification's sign"), type); - AddParameter(type == "number" ? "expression" : type, _("Value")); + AddParameter("operator", _("Modification's sign"), expressionValueType); + AddParameter(type, _("Value"), typeExtraInfo); size_t operatorParamIndex = parameters.size() - 2; size_t valueParamIndex = parameters.size() - 1; @@ -151,8 +155,10 @@ InstructionMetadata& InstructionMetadata::UseStandardOperatorParameters( InstructionMetadata& InstructionMetadata::UseStandardRelationalOperatorParameters( - const gd::String& type) { - SetManipulatedType(type); + const gd::String& type, const gd::String& typeExtraInfo) { + const gd::String& expressionValueType = + gd::ValueTypeMetadata::GetPrimitiveValueType(type); + SetManipulatedType(expressionValueType); if (type == "boolean") { if (isObjectInstruction || isBehaviorInstruction) { @@ -168,8 +174,8 @@ InstructionMetadata::UseStandardRelationalOperatorParameters( templateSentence.FindAndReplace("", sentence); } } else { - AddParameter("relationalOperator", _("Sign of the test"), type); - AddParameter(type == "number" ? "expression" : type, _("Value to compare")); + AddParameter("relationalOperator", _("Sign of the test"), expressionValueType); + AddParameter(type, _("Value to compare"), typeExtraInfo); size_t operatorParamIndex = parameters.size() - 2; size_t valueParamIndex = parameters.size() - 1; diff --git a/Core/GDCore/Extensions/Metadata/InstructionMetadata.h b/Core/GDCore/Extensions/Metadata/InstructionMetadata.h index 6b6c087545..843a845a37 100644 --- a/Core/GDCore/Extensions/Metadata/InstructionMetadata.h +++ b/Core/GDCore/Extensions/Metadata/InstructionMetadata.h @@ -206,7 +206,7 @@ class GD_CORE_API InstructionMetadata { if (!parameters.empty()) parameters.back().SetLongDescription(longDescription); return *this; - }; + } /** * \brief Set the additional information, used for some parameters @@ -218,20 +218,26 @@ class GD_CORE_API InstructionMetadata { InstructionMetadata &SetParameterExtraInfo(const gd::String &extraInfo) { if (!parameters.empty()) parameters.back().SetExtraInfo(extraInfo); return *this; - }; + } /** * \brief Add the default parameters for an instruction manipulating the * specified type ("string", "number") with the default operators. + * + * \note The type "string" can be declined in several subtypes. + * \see ParameterMetadata */ - InstructionMetadata &UseStandardOperatorParameters(const gd::String &type); + InstructionMetadata &UseStandardOperatorParameters(const gd::String &type, const gd::String& typeExtraInfo = ""); /** * \brief Add the default parameters for an instruction comparing the * specified type ("string", "number") with the default relational operators. + * + * \note The type "string" can be declined in several subtypes. + * \see ParameterMetadata */ InstructionMetadata &UseStandardRelationalOperatorParameters( - const gd::String &type); + const gd::String &type, const gd::String& typeExtraInfo = ""); /** * \brief Mark the instruction as an object instruction. Automatically called @@ -276,7 +282,7 @@ class GD_CORE_API InstructionMetadata { */ const gd::String &GetRequiredBaseObjectCapability() const { return requiredBaseObjectCapability; - }; + } /** * \brief Consider that the instruction is easy for a user to understand. @@ -487,6 +493,29 @@ class GD_CORE_API InstructionMetadata { return codeExtraInformation.SetAsyncFunctionName(functionName); } + /** + * \brief Erase any existing include file and add the specified include. + */ + InstructionMetadata &SetIncludeFile(const gd::String &includeFile) { + codeExtraInformation.SetIncludeFile(includeFile); + return *this; + } + + /** + * \brief Add a file to the already existing include files. + */ + InstructionMetadata &AddIncludeFile(const gd::String &includeFile) { + codeExtraInformation.AddIncludeFile(includeFile); + return *this; + } + + /** + * \brief Get the files that must be included to use the instruction. + */ + const std::vector& GetIncludeFiles() const { + return codeExtraInformation.GetIncludeFiles(); + }; + std::vector parameters; private: diff --git a/Core/GDCore/Extensions/Metadata/MultipleInstructionMetadata.h b/Core/GDCore/Extensions/Metadata/MultipleInstructionMetadata.h index 6f2d93c780..c5037fe749 100644 --- a/Core/GDCore/Extensions/Metadata/MultipleInstructionMetadata.h +++ b/Core/GDCore/Extensions/Metadata/MultipleInstructionMetadata.h @@ -80,6 +80,16 @@ class GD_CORE_API MultipleInstructionMetadata { return *this; }; + /** + * \see gd::InstructionMetadata::SetParameterExtraInfo + */ + MultipleInstructionMetadata &SetParameterExtraInfo(const gd::String &defaultValue) { + if (expression) expression->SetParameterExtraInfo(defaultValue); + if (condition) condition->SetParameterExtraInfo(defaultValue); + if (action) action->SetParameterExtraInfo(defaultValue); + return *this; + }; + /** * \see gd::InstructionMetadata::SetParameterLongDescription */ @@ -116,9 +126,9 @@ class GD_CORE_API MultipleInstructionMetadata { * \see gd::InstructionMetadata::UseStandardOperatorParameters * \see gd::InstructionMetadata::UseStandardRelationalOperatorParameters */ - MultipleInstructionMetadata &UseStandardParameters(const gd::String &type) { - if (condition) condition->UseStandardRelationalOperatorParameters(type); - if (action) action->UseStandardOperatorParameters(type); + MultipleInstructionMetadata &UseStandardParameters(const gd::String &type, const gd::String& typeExtraInfo = "") { + if (condition) condition->UseStandardRelationalOperatorParameters(type, typeExtraInfo); + if (action) action->UseStandardOperatorParameters(type, typeExtraInfo); return *this; } @@ -154,6 +164,34 @@ class GD_CORE_API MultipleInstructionMetadata { return *this; } + /** + * \brief Get the files that must be included to use the instruction. + */ + const std::vector& GetIncludeFiles() const { + if (expression) + return expression->GetCodeExtraInformation().GetIncludeFiles(); + if (condition) + return condition->GetCodeExtraInformation().GetIncludeFiles(); + if (action) + return action->GetCodeExtraInformation().GetIncludeFiles(); + // It can't actually happen. + throw std::logic_error("no instruction metadata"); + } + + /** + * Set that the instruction is private - it can't be used outside of the + * object/ behavior that it is attached too. + */ + MultipleInstructionMetadata &SetPrivate() { + if (expression) + expression->SetPrivate(); + if (condition) + condition->SetPrivate(); + if (action) + action->SetPrivate(); + return *this; + } + /** * \see gd::InstructionMetadata::MarkAsSimple */ diff --git a/Core/GDCore/Extensions/Metadata/ParameterMetadata.cpp b/Core/GDCore/Extensions/Metadata/ParameterMetadata.cpp index b04f8a432e..9b2b0f995c 100644 --- a/Core/GDCore/Extensions/Metadata/ParameterMetadata.cpp +++ b/Core/GDCore/Extensions/Metadata/ParameterMetadata.cpp @@ -10,12 +10,10 @@ namespace gd { -ParameterMetadata::ParameterMetadata() : optional(false), codeOnly(false) {} +ParameterMetadata::ParameterMetadata() : codeOnly(false) {} void ParameterMetadata::SerializeTo(SerializerElement& element) const { - element.SetAttribute("type", type); - element.SetAttribute("supplementaryInformation", supplementaryInformation); - element.SetAttribute("optional", optional); + valueTypeMetadata.SerializeTo(element); element.SetAttribute("description", description); element.SetAttribute("longDescription", longDescription); element.SetAttribute("codeOnly", codeOnly); @@ -24,10 +22,7 @@ void ParameterMetadata::SerializeTo(SerializerElement& element) const { } void ParameterMetadata::UnserializeFrom(const SerializerElement& element) { - type = element.GetStringAttribute("type"); - supplementaryInformation = - element.GetStringAttribute("supplementaryInformation"); - optional = element.GetBoolAttribute("optional"); + valueTypeMetadata.UnserializeFrom(element); description = element.GetStringAttribute("description"); longDescription = element.GetStringAttribute("longDescription"); codeOnly = element.GetBoolAttribute("codeOnly"); @@ -35,18 +30,4 @@ void ParameterMetadata::UnserializeFrom(const SerializerElement& element) { name = element.GetStringAttribute("name"); } - // TODO factorize in a file with an enum and helpers? - const gd::String ParameterMetadata::numberType = "number"; - const gd::String ParameterMetadata::stringType = "string"; - - const gd::String &ParameterMetadata::GetExpressionValueType(const gd::String ¶meterType) { - if (parameterType == "number" || gd::ParameterMetadata::IsExpression("number", parameterType)) { - return ParameterMetadata::numberType; - } - if (parameterType == "string" || gd::ParameterMetadata::IsExpression("string", parameterType)) { - return ParameterMetadata::stringType; - } - return parameterType; - } - } // namespace gd diff --git a/Core/GDCore/Extensions/Metadata/ParameterMetadata.h b/Core/GDCore/Extensions/Metadata/ParameterMetadata.h index 6ef14148eb..229a93e3cb 100644 --- a/Core/GDCore/Extensions/Metadata/ParameterMetadata.h +++ b/Core/GDCore/Extensions/Metadata/ParameterMetadata.h @@ -6,16 +6,13 @@ #ifndef PARAMETER_METADATA_H #define PARAMETER_METADATA_H -#if defined(GD_IDE_ONLY) #include #include #include "GDCore/String.h" +#include "GDCore/Extensions/Metadata/ValueTypeMetadata.h" + namespace gd { -class Project; -class Layout; -class EventsCodeGenerator; -class EventsCodeGenerationContext; class SerializerElement; } // namespace gd @@ -32,17 +29,32 @@ class GD_CORE_API ParameterMetadata { ParameterMetadata(); virtual ~ParameterMetadata(){}; + /** + * \brief Return the metadata of the parameter type. + */ + gd::ValueTypeMetadata &GetValueTypeMetadata() { return valueTypeMetadata; } + + /** + * \brief Set the metadata of the parameter type. + */ + ParameterMetadata &SetValueTypeMetadata(const gd::ValueTypeMetadata &valueTypeMetadata_) { + valueTypeMetadata = valueTypeMetadata_; + return *this; + } + /** * \brief Return the type of the parameter. * \see gd::ParameterMetadata::IsObject + * \deprecated Use gd::ValueTypeMetadata instead. */ - const gd::String &GetType() const { return type; } + const gd::String &GetType() const { return valueTypeMetadata.GetName(); } /** * \brief Set the type of the parameter. + * \deprecated Use gd::ValueTypeMetadata instead. */ ParameterMetadata &SetType(const gd::String &type_) { - type = type_; + valueTypeMetadata.SetName(type_); return *this; } @@ -71,29 +83,33 @@ class GD_CORE_API ParameterMetadata { * \brief Return an optional additional information, used for some parameters * with special type (for example, it can contains the type of object accepted * by the parameter). + * \deprecated Use gd::ValueTypeMetadata instead. */ - const gd::String &GetExtraInfo() const { return supplementaryInformation; } + const gd::String &GetExtraInfo() const { return valueTypeMetadata.GetExtraInfo(); } /** * \brief Set an optional additional information, used for some parameters * with special type (for example, it can contains the type of object accepted * by the parameter). + * \deprecated Use gd::ValueTypeMetadata instead. */ ParameterMetadata &SetExtraInfo(const gd::String &supplementaryInformation_) { - supplementaryInformation = supplementaryInformation_; + valueTypeMetadata.SetExtraInfo(supplementaryInformation_); return *this; } /** * \brief Return true if the parameter is optional. + * \deprecated Use gd::ValueTypeMetadata instead. */ - bool IsOptional() const { return optional; } + bool IsOptional() const { return valueTypeMetadata.IsOptional(); } /** * \brief Set if the parameter is optional. + * \deprecated Use gd::ValueTypeMetadata instead. */ ParameterMetadata &SetOptional(bool optional_ = true) { - optional = optional_; + valueTypeMetadata.SetOptional(optional_); return *this; } @@ -151,26 +167,27 @@ class GD_CORE_API ParameterMetadata { return *this; } + // TODO Remove these deprecated functions. + /** * \brief Return true if the type of the parameter is representing one object * (or more, i.e: an object group). * * \see gd::ParameterMetadata::GetType + * \deprecated Use gd::ValueTypeMetadata instead. */ static bool IsObject(const gd::String ¶meterType) { - return parameterType == "object" || parameterType == "objectPtr" || - parameterType == "objectList" || - parameterType == "objectListOrEmptyIfJustDeclared" || - parameterType == "objectListOrEmptyWithoutPicking"; + return gd::ValueTypeMetadata::IsTypeObject(parameterType); } /** * \brief Return true if the type of the parameter is "behavior". * * \see gd::ParameterMetadata::GetType + * \deprecated Use gd::ValueTypeMetadata instead. */ static bool IsBehavior(const gd::String ¶meterType) { - return parameterType == "behavior"; + return gd::ValueTypeMetadata::IsTypeBehavior(parameterType); } /** @@ -179,43 +196,22 @@ class GD_CORE_API ParameterMetadata { * \note If you had a new type of parameter, also add it in the IDE ( * see EventsFunctionParametersEditor, ParameterRenderingService * and ExpressionAutocompletion) and in the EventsCodeGenerator. + * \deprecated Use gd::ValueTypeMetadata instead. */ static bool IsExpression(const gd::String &type, const gd::String ¶meterType) { - if (type == "number") { - return parameterType == "expression" || parameterType == "camera" || - parameterType == "forceMultiplier"; - } else if (type == "string") { - return parameterType == "string" || parameterType == "layer" || - parameterType == "color" || parameterType == "file" || - parameterType == "joyaxis" || - parameterType == "stringWithSelector" || - parameterType == "sceneName" || - parameterType == "layerEffectName" || - parameterType == "layerEffectParameterName" || - parameterType == "objectEffectName" || - parameterType == "objectEffectParameterName" || - parameterType == "objectPointName" || - parameterType == "objectAnimationName" || - parameterType == "functionParameterName" || - parameterType == "externalLayoutName" || - parameterType == "leaderboardId" || - parameterType == "identifier"; - } else if (type == "variable") { - return parameterType == "objectvar" || parameterType == "globalvar" || - parameterType == "scenevar"; - } - return false; + return gd::ValueTypeMetadata::IsTypeExpression(type, parameterType); } /** * \brief Return the expression type from the parameter type. * Declinations of "number" and "string" types (like "forceMultiplier" or * "sceneName") are replaced by "number" and "string". + * \deprecated Use gd::ValueTypeMetadata instead. */ - static const gd::String &GetExpressionValueType(const gd::String ¶meterType); - static const gd::String numberType; - static const gd::String stringType; + static const gd::String &GetExpressionValueType(const gd::String ¶meterType) { + return gd::ValueTypeMetadata::GetPrimitiveValueType(parameterType); + } /** \name Serialization */ @@ -238,9 +234,7 @@ class GD_CORE_API ParameterMetadata { bool codeOnly; ///< True if parameter is relative to code generation only, ///< i.e. must not be shown in editor private: - gd::String type; ///< Parameter type - gd::String supplementaryInformation; ///< Used if needed - bool optional; ///< True if the parameter is optional + gd::ValueTypeMetadata valueTypeMetadata; ///< Parameter type gd::String longDescription; ///< Long description shown in the editor. gd::String defaultValue; ///< Used as a default value in editor or if an ///< optional parameter is empty. @@ -250,5 +244,4 @@ class GD_CORE_API ParameterMetadata { } // namespace gd -#endif #endif // PARAMETER_METADATA_H diff --git a/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.cpp b/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.cpp new file mode 100644 index 0000000000..47f22d8803 --- /dev/null +++ b/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.cpp @@ -0,0 +1,49 @@ +/* + * GDevelop Core + * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#include "ValueTypeMetadata.h" + +#include "GDCore/CommonTools.h" +#include "GDCore/Serialization/SerializerElement.h" + +namespace gd { + +ValueTypeMetadata::ValueTypeMetadata() : optional(false) {} + +void ValueTypeMetadata::SerializeTo(SerializerElement& element) const { + element.SetAttribute("type", name); + if (!supplementaryInformation.empty()) { + element.SetAttribute("supplementaryInformation", supplementaryInformation); + } + if (optional) { + element.SetAttribute("optional", optional); + } + if (!defaultValue.empty()) { + element.SetAttribute("defaultValue", defaultValue); + } +} + +void ValueTypeMetadata::UnserializeFrom(const SerializerElement& element) { + name = element.GetStringAttribute("type"); + supplementaryInformation = + element.GetStringAttribute("supplementaryInformation"); + optional = element.GetBoolAttribute("optional"); + defaultValue = element.GetStringAttribute("defaultValue"); +} + +const gd::String ValueTypeMetadata::numberType = "number"; +const gd::String ValueTypeMetadata::stringType = "string"; + +const gd::String &ValueTypeMetadata::GetPrimitiveValueType(const gd::String ¶meterType) { + if (parameterType == "number" || gd::ValueTypeMetadata::IsTypeExpression("number", parameterType)) { + return ValueTypeMetadata::numberType; + } + if (parameterType == "string" || gd::ValueTypeMetadata::IsTypeExpression("string", parameterType)) { + return ValueTypeMetadata::stringType; + } + return parameterType; +} + +} // namespace gd diff --git a/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.h b/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.h new file mode 100644 index 0000000000..7c0548991e --- /dev/null +++ b/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.h @@ -0,0 +1,219 @@ +/* + * GDevelop Core + * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ + +#ifndef VALUE_TYPE_METADATA_H +#define VALUE_TYPE_METADATA_H +#include +#include + +#include "GDCore/String.h" +namespace gd { +class SerializerElement; +} // namespace gd + +namespace gd { + +/** + * \brief Define a type for parameters of a function (action, condition or + * expression) or the returned value of an expression. + * + * \see gd::EventsFunction + * \ingroup Events + */ +class GD_CORE_API ValueTypeMetadata { + public: + ValueTypeMetadata(); + virtual ~ValueTypeMetadata(){}; + + /** + * \brief Return the string representation of the type. + */ + const gd::String &GetName() const { return name; } + + /** + * \brief Set the string representation of the type. + */ + ValueTypeMetadata &SetName(const gd::String &name_) { + name = name_; + return *this; + } + + /** + * \brief Return an optional additional information, used for some parameters + * with special type (for example, it can contains the type of object accepted + * by the parameter). + */ + const gd::String &GetExtraInfo() const { return supplementaryInformation; } + + /** + * \brief Set an optional additional information, used for some parameters + * with special type (for example, it can contains the type of object accepted + * by the parameter). + */ + ValueTypeMetadata &SetExtraInfo(const gd::String &supplementaryInformation_) { + supplementaryInformation = supplementaryInformation_; + return *this; + } + + /** + * \brief Return true if the parameter is optional. + */ + bool IsOptional() const { return optional; } + + /** + * \brief Set if the parameter is optional. + */ + ValueTypeMetadata &SetOptional(bool optional_ = true) { + optional = optional_; + return *this; + } + + /** + * \brief Get the default value for the parameter. + */ + const gd::String &GetDefaultValue() const { return defaultValue; } + + /** + * \brief Set the default value, if the parameter is optional. + */ + ValueTypeMetadata &SetDefaultValue(const gd::String &defaultValue_) { + defaultValue = defaultValue_; + return *this; + } + + /** + * \brief Return true if the type is defined. + */ + bool IsDefined() const { + return !name.empty(); + } + + /** + * \brief Return true if the type is representing one object + * (or more, i.e: an object group). + */ + bool IsObject() const { + return gd::ValueTypeMetadata::IsTypeObject(name); + } + + /** + * \brief Return true if the type is "behavior". + */ + bool IsBehavior() const { + return gd::ValueTypeMetadata::IsTypeBehavior(name); + } + + /** + * \brief Return true if the type is an expression of the + * given type. + */ + bool IsNumber() const { + return gd::ValueTypeMetadata::IsTypeExpression("number", name); + } + + /** + * \brief Return true if the type is a string. + */ + bool IsString() const { + return gd::ValueTypeMetadata::IsTypeExpression("string", name); + } + + /** + * \brief Return true if the type of the parameter is a number. + * \note If you had a new type of parameter, also add it in the IDE ( + * see EventsFunctionParametersEditor, ParameterRenderingService + * and ExpressionAutocompletion) and in the EventsCodeGenerator. + */ + bool IsVariable() const { + return gd::ValueTypeMetadata::IsTypeExpression("variable", name); + } + + /** + * \brief Return true if the type is representing one object + * (or more, i.e: an object group). + */ + static bool IsTypeObject(const gd::String ¶meterType) { + return parameterType == "object" || parameterType == "objectPtr" || + parameterType == "objectList" || + parameterType == "objectListOrEmptyIfJustDeclared" || + parameterType == "objectListOrEmptyWithoutPicking"; + } + + /** + * \brief Return true if the type is "behavior". + */ + static bool IsTypeBehavior(const gd::String ¶meterType) { + return parameterType == "behavior"; + } + + /** + * \brief Return true if the type is an expression of the given type. + * \note If you are adding a new type of parameter, also add it in the IDE ( + * see EventsFunctionParametersEditor, ParameterRenderingService + * and ExpressionAutocompletion) and in the EventsCodeGenerator. + */ + static bool IsTypeExpression(const gd::String &type, + const gd::String ¶meterType) { + if (type == "number") { + return parameterType == "number" || parameterType == "expression" || + parameterType == "camera" || parameterType == "forceMultiplier"; + } else if (type == "string") { + return parameterType == "string" || parameterType == "layer" || + parameterType == "color" || parameterType == "file" || + parameterType == "joyaxis" || + parameterType == "stringWithSelector" || + parameterType == "sceneName" || + parameterType == "layerEffectName" || + parameterType == "layerEffectParameterName" || + parameterType == "objectEffectName" || + parameterType == "objectEffectParameterName" || + parameterType == "objectPointName" || + parameterType == "objectAnimationName" || + parameterType == "functionParameterName" || + parameterType == "externalLayoutName" || + parameterType == "leaderboardId" || + parameterType == "identifier"; + } else if (type == "variable") { + return parameterType == "objectvar" || parameterType == "globalvar" || + parameterType == "scenevar"; + } + return false; + } + + /** + * \brief Return the expression type from the parameter type. + * Declinations of "number" and "string" types (like "forceMultiplier" or + * "sceneName") are replaced by "number" and "string". + */ + static const gd::String &GetPrimitiveValueType(const gd::String ¶meterType); + static const gd::String numberType; + static const gd::String stringType; + + /** \name Serialization + */ + ///@{ + /** + * \brief Serialize the ParameterMetadata to the specified element + */ + void SerializeTo(gd::SerializerElement &element) const; + + /** + * \brief Load the ParameterMetadata from the specified element + */ + void UnserializeFrom(const gd::SerializerElement &element); + ///@} + + private: + gd::String name; ///< Parameter type + gd::String supplementaryInformation; ///< Used if needed + bool optional; ///< True if the parameter is optional + gd::String defaultValue; ///< Used as a default value in editor or if an + ///< optional parameter is empty. +}; + +} // namespace gd + +#endif // VALUE_TYPE_METADATA_H diff --git a/Core/GDCore/IDE/EventsFunctionTools.cpp b/Core/GDCore/IDE/EventsFunctionTools.cpp index 6dae9b7b58..cfc7a0fae4 100644 --- a/Core/GDCore/IDE/EventsFunctionTools.cpp +++ b/Core/GDCore/IDE/EventsFunctionTools.cpp @@ -20,6 +20,7 @@ namespace gd { void EventsFunctionTools::FreeEventsFunctionToObjectsContainer( const gd::Project& project, + const gd::EventsFunctionsContainer functionContainer, const gd::EventsFunction& eventsFunction, gd::ObjectsContainer& outputGlobalObjectsContainer, gd::ObjectsContainer& outputObjectsContainer) { @@ -31,8 +32,12 @@ void EventsFunctionTools::FreeEventsFunctionToObjectsContainer( // to parameters outputObjectsContainer.GetObjects().clear(); outputObjectsContainer.GetObjectGroups().Clear(); + + auto ¶meters = eventsFunction.GetParametersForEvents(functionContainer); gd::ParameterMetadataTools::ParametersToObjectsContainer( - project, eventsFunction.GetParameters(), outputObjectsContainer); + project, + parameters, + outputObjectsContainer); outputObjectsContainer.GetObjectGroups() = eventsFunction.GetObjectGroups(); } @@ -44,6 +49,7 @@ void EventsFunctionTools::BehaviorEventsFunctionToObjectsContainer( gd::ObjectsContainer& outputObjectsContainer) { // The context is build the same way as free function... FreeEventsFunctionToObjectsContainer(project, + eventsBasedBehavior.GetEventsFunctions(), eventsFunction, outputGlobalObjectsContainer, outputObjectsContainer); @@ -81,6 +87,7 @@ void EventsFunctionTools::ObjectEventsFunctionToObjectsContainer( gd::ObjectsContainer& outputObjectsContainer) { // The context is build the same way as free function... FreeEventsFunctionToObjectsContainer(project, + eventsBasedObject.GetEventsFunctions(), eventsFunction, outputGlobalObjectsContainer, outputObjectsContainer); diff --git a/Core/GDCore/IDE/EventsFunctionTools.h b/Core/GDCore/IDE/EventsFunctionTools.h index 8352ada52f..80d6386a5d 100644 --- a/Core/GDCore/IDE/EventsFunctionTools.h +++ b/Core/GDCore/IDE/EventsFunctionTools.h @@ -10,6 +10,7 @@ #include "GDCore/String.h" namespace gd { class Project; +class EventsFunctionsContainer; class ObjectsContainer; class ParameterMetadata; class EventsFunction; @@ -34,6 +35,7 @@ class GD_CORE_API EventsFunctionTools { */ static void FreeEventsFunctionToObjectsContainer( const gd::Project& project, + const gd::EventsFunctionsContainer functionContainer, const gd::EventsFunction& eventsFunction, gd::ObjectsContainer& outputGlobalObjectsContainer, gd::ObjectsContainer& outputObjectsContainer); diff --git a/Core/GDCore/IDE/WholeProjectRefactorer.cpp b/Core/GDCore/IDE/WholeProjectRefactorer.cpp index bcf558ed2f..171ce9e06d 100644 --- a/Core/GDCore/IDE/WholeProjectRefactorer.cpp +++ b/Core/GDCore/IDE/WholeProjectRefactorer.cpp @@ -154,7 +154,11 @@ void WholeProjectRefactorer::ExposeProjectEvents( gd::ObjectsContainer globalObjectsAndGroups; gd::ObjectsContainer objectsAndGroups; gd::EventsFunctionTools::FreeEventsFunctionToObjectsContainer( - project, *eventsFunction, globalObjectsAndGroups, objectsAndGroups); + project, + eventsFunctionsExtension, + *eventsFunction, + globalObjectsAndGroups, + objectsAndGroups); worker.Launch(eventsFunction->GetEvents(), globalObjectsAndGroups, @@ -334,8 +338,10 @@ void WholeProjectRefactorer::RenameEventsFunctionsExtension( [&project, &oldName, &newName]( const gd::EventsBasedBehavior& eventsBasedBehavior, const gd::EventsFunction& eventsFunction) { - if (eventsFunction.GetFunctionType() == gd::EventsFunction::Action || - eventsFunction.GetFunctionType() == gd::EventsFunction::Condition) { + if (eventsFunction.IsExpression()) { + // Nothing to do, expressions are not including the extension name + } + if (eventsFunction.IsAction() || eventsFunction.IsCondition()) { gd::InstructionsTypeRenamer renamer = gd::InstructionsTypeRenamer( project, GetBehaviorEventsFunctionFullType(oldName, @@ -345,11 +351,6 @@ void WholeProjectRefactorer::RenameEventsFunctionsExtension( eventsBasedBehavior.GetName(), eventsFunction.GetName())); ExposeProjectEvents(project, renamer); - } else if (eventsFunction.GetFunctionType() == - gd::EventsFunction::Expression || - eventsFunction.GetFunctionType() == - gd::EventsFunction::StringExpression) { - // Nothing to do, expressions are not including the extension name } }; @@ -394,8 +395,10 @@ void WholeProjectRefactorer::RenameEventsFunctionsExtension( [&project, &oldName, &newName]( const gd::EventsBasedObject& eventsBasedObject, const gd::EventsFunction& eventsFunction) { - if (eventsFunction.GetFunctionType() == gd::EventsFunction::Action || - eventsFunction.GetFunctionType() == gd::EventsFunction::Condition) { + if (eventsFunction.IsExpression()) { + // Nothing to do, expressions are not including the extension name + } + if (eventsFunction.IsAction() || eventsFunction.IsCondition()) { gd::InstructionsTypeRenamer renamer = gd::InstructionsTypeRenamer( project, GetObjectEventsFunctionFullType(oldName, @@ -405,11 +408,6 @@ void WholeProjectRefactorer::RenameEventsFunctionsExtension( eventsBasedObject.GetName(), eventsFunction.GetName())); ExposeProjectEvents(project, renamer); - } else if (eventsFunction.GetFunctionType() == - gd::EventsFunction::Expression || - eventsFunction.GetFunctionType() == - gd::EventsFunction::StringExpression) { - // Nothing to do, expressions are not including the extension name } }; @@ -456,9 +454,7 @@ void WholeProjectRefactorer::RenameEventsFunctionsExtension( // Free expressions for (auto&& eventsFunction : eventsFunctionsExtension.GetInternalVector()) { - if (eventsFunction->GetFunctionType() == gd::EventsFunction::Expression || - eventsFunction->GetFunctionType() == - gd::EventsFunction::StringExpression) { + if (eventsFunction->IsExpression()) { renameEventsFunction(*eventsFunction); } } @@ -467,9 +463,7 @@ void WholeProjectRefactorer::RenameEventsFunctionsExtension( eventsFunctionsExtension.GetEventsBasedBehaviors().GetInternalVector()) { auto& behaviorEventsFunctions = eventsBasedBehavior->GetEventsFunctions(); for (auto&& eventsFunction : behaviorEventsFunctions.GetInternalVector()) { - if (eventsFunction->GetFunctionType() == gd::EventsFunction::Expression || - eventsFunction->GetFunctionType() == - gd::EventsFunction::StringExpression) { + if (eventsFunction->IsExpression()) { renameBehaviorEventsFunction(*eventsBasedBehavior, *eventsFunction); } } @@ -477,8 +471,7 @@ void WholeProjectRefactorer::RenameEventsFunctionsExtension( // Free instructions for (auto&& eventsFunction : eventsFunctionsExtension.GetInternalVector()) { - if (eventsFunction->GetFunctionType() == gd::EventsFunction::Action || - eventsFunction->GetFunctionType() == gd::EventsFunction::Condition) { + if (eventsFunction->IsAction() || eventsFunction->IsCondition()) { renameEventsFunction(*eventsFunction); } } @@ -488,8 +481,7 @@ void WholeProjectRefactorer::RenameEventsFunctionsExtension( eventsFunctionsExtension.GetEventsBasedBehaviors().GetInternalVector()) { auto& behaviorEventsFunctions = eventsBasedBehavior->GetEventsFunctions(); for (auto&& eventsFunction : behaviorEventsFunctions.GetInternalVector()) { - if (eventsFunction->GetFunctionType() == gd::EventsFunction::Action || - eventsFunction->GetFunctionType() == gd::EventsFunction::Condition) { + if (eventsFunction->IsAction() || eventsFunction->IsCondition()) { renameBehaviorEventsFunction(*eventsBasedBehavior, *eventsFunction); } } @@ -510,8 +502,7 @@ void WholeProjectRefactorer::RenameEventsFunctionsExtension( eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) { auto& objectEventsFunctions = eventsBasedObject->GetEventsFunctions(); for (auto&& eventsFunction : objectEventsFunctions.GetInternalVector()) { - if (eventsFunction->GetFunctionType() == gd::EventsFunction::Action || - eventsFunction->GetFunctionType() == gd::EventsFunction::Condition) { + if (eventsFunction->IsAction() || eventsFunction->IsCondition()) { renameObjectEventsFunction(*eventsBasedObject, *eventsFunction); } } @@ -563,6 +554,16 @@ void WholeProjectRefactorer::RenameEventsFunction( oldFunctionName), GetEventsFunctionFullType(eventsFunctionsExtension.GetName(), newFunctionName)); + + if (eventsFunction.GetFunctionType() == gd::EventsFunction::ExpressionAndCondition) { + for (auto&& otherFunction : eventsFunctionsExtension.GetInternalVector()) + { + if (otherFunction->GetFunctionType() == gd::EventsFunction::ActionWithOperator && + otherFunction->GetGetterName() == oldFunctionName) { + otherFunction->SetGetterName(newFunctionName); + } + } + } } void WholeProjectRefactorer::RenameBehaviorEventsFunction( @@ -577,8 +578,20 @@ void WholeProjectRefactorer::RenameBehaviorEventsFunction( const gd::EventsFunction& eventsFunction = eventsFunctions.GetEventsFunction(oldFunctionName); - if (eventsFunction.GetFunctionType() == gd::EventsFunction::Action || - eventsFunction.GetFunctionType() == gd::EventsFunction::Condition) { + // Order is important: we first rename the expressions then the instructions, + // to avoid being unable to fetch the metadata (the types of parameters) of + // instructions after they are renamed. + if (eventsFunction.IsExpression()) { + gd::ExpressionsRenamer renamer = + gd::ExpressionsRenamer(project.GetCurrentPlatform()); + renamer.SetReplacedBehaviorExpression( + GetBehaviorFullType(eventsFunctionsExtension.GetName(), + eventsBasedBehavior.GetName()), + oldFunctionName, + newFunctionName); + ExposeProjectEvents(project, renamer); + } + if (eventsFunction.IsAction() || eventsFunction.IsCondition()) { gd::InstructionsTypeRenamer renamer = gd::InstructionsTypeRenamer( project, GetBehaviorEventsFunctionFullType(eventsFunctionsExtension.GetName(), @@ -588,18 +601,15 @@ void WholeProjectRefactorer::RenameBehaviorEventsFunction( eventsBasedBehavior.GetName(), newFunctionName)); ExposeProjectEvents(project, renamer); - } else if (eventsFunction.GetFunctionType() == - gd::EventsFunction::Expression || - eventsFunction.GetFunctionType() == - gd::EventsFunction::StringExpression) { - gd::ExpressionsRenamer renamer = - gd::ExpressionsRenamer(project.GetCurrentPlatform()); - renamer.SetReplacedBehaviorExpression( - GetBehaviorFullType(eventsFunctionsExtension.GetName(), - eventsBasedBehavior.GetName()), - oldFunctionName, - newFunctionName); - ExposeProjectEvents(project, renamer); + } + if (eventsFunction.GetFunctionType() == gd::EventsFunction::ExpressionAndCondition) { + for (auto&& otherFunction : eventsBasedBehavior.GetEventsFunctions().GetInternalVector()) + { + if (otherFunction->GetFunctionType() == gd::EventsFunction::ActionWithOperator && + otherFunction->GetGetterName() == oldFunctionName) { + otherFunction->SetGetterName(newFunctionName); + } + } } } @@ -615,8 +625,17 @@ void WholeProjectRefactorer::RenameObjectEventsFunction( const gd::EventsFunction& eventsFunction = eventsFunctions.GetEventsFunction(oldFunctionName); - if (eventsFunction.GetFunctionType() == gd::EventsFunction::Action || - eventsFunction.GetFunctionType() == gd::EventsFunction::Condition) { + if (eventsFunction.IsExpression()) { + gd::ExpressionsRenamer renamer = + gd::ExpressionsRenamer(project.GetCurrentPlatform()); + renamer.SetReplacedObjectExpression( + GetObjectFullType(eventsFunctionsExtension.GetName(), + eventsBasedObject.GetName()), + oldFunctionName, + newFunctionName); + ExposeProjectEvents(project, renamer); + } + if (eventsFunction.IsAction() || eventsFunction.IsCondition()) { gd::InstructionsTypeRenamer renamer = gd::InstructionsTypeRenamer( project, GetObjectEventsFunctionFullType(eventsFunctionsExtension.GetName(), @@ -626,18 +645,15 @@ void WholeProjectRefactorer::RenameObjectEventsFunction( eventsBasedObject.GetName(), newFunctionName)); ExposeProjectEvents(project, renamer); - } else if (eventsFunction.GetFunctionType() == - gd::EventsFunction::Expression || - eventsFunction.GetFunctionType() == - gd::EventsFunction::StringExpression) { - gd::ExpressionsRenamer renamer = - gd::ExpressionsRenamer(project.GetCurrentPlatform()); - renamer.SetReplacedObjectExpression( - GetObjectFullType(eventsFunctionsExtension.GetName(), - eventsBasedObject.GetName()), - oldFunctionName, - newFunctionName); - ExposeProjectEvents(project, renamer); + } + if (eventsFunction.GetFunctionType() == gd::EventsFunction::ExpressionAndCondition) { + for (auto&& otherFunction : eventsBasedObject.GetEventsFunctions().GetInternalVector()) + { + if (otherFunction->GetFunctionType() == gd::EventsFunction::ActionWithOperator && + otherFunction->GetGetterName() == oldFunctionName) { + otherFunction->SetGetterName(newFunctionName); + } + } } } @@ -655,21 +671,22 @@ void WholeProjectRefactorer::MoveEventsFunctionParameter( const gd::String& eventsFunctionType = GetEventsFunctionFullType( eventsFunctionsExtension.GetName(), functionName); - if (eventsFunction.GetFunctionType() == gd::EventsFunction::Action || - eventsFunction.GetFunctionType() == gd::EventsFunction::Condition) { - gd::InstructionsParameterMover mover = gd::InstructionsParameterMover( - project, eventsFunctionType, oldIndex, newIndex); - ExposeProjectEvents(project, mover); - } else if (eventsFunction.GetFunctionType() == - gd::EventsFunction::Expression || - eventsFunction.GetFunctionType() == - gd::EventsFunction::StringExpression) { + if (eventsFunction.IsExpression()) { gd::ExpressionsParameterMover mover = gd::ExpressionsParameterMover(project.GetCurrentPlatform()); mover.SetFreeExpressionMovedParameter( eventsFunctionType, oldIndex, newIndex); ExposeProjectEvents(project, mover); } + if (eventsFunction.IsAction() || eventsFunction.IsCondition()) { + const int operatorIndexOffset = eventsFunction.IsExpression() ? 2 : 0; + gd::InstructionsParameterMover mover = gd::InstructionsParameterMover( + project, + eventsFunctionType, + oldIndex + operatorIndexOffset, + newIndex + operatorIndexOffset); + ExposeProjectEvents(project, mover); + } } void WholeProjectRefactorer::MoveBehaviorEventsFunctionParameter( @@ -690,15 +707,7 @@ void WholeProjectRefactorer::MoveBehaviorEventsFunctionParameter( eventsBasedBehavior.GetName(), functionName); - if (eventsFunction.GetFunctionType() == gd::EventsFunction::Action || - eventsFunction.GetFunctionType() == gd::EventsFunction::Condition) { - gd::InstructionsParameterMover mover = gd::InstructionsParameterMover( - project, eventsFunctionType, oldIndex, newIndex); - ExposeProjectEvents(project, mover); - } else if (eventsFunction.GetFunctionType() == - gd::EventsFunction::Expression || - eventsFunction.GetFunctionType() == - gd::EventsFunction::StringExpression) { + if (eventsFunction.IsExpression()) { gd::ExpressionsParameterMover mover = gd::ExpressionsParameterMover(project.GetCurrentPlatform()); mover.SetBehaviorExpressionMovedParameter( @@ -709,6 +718,15 @@ void WholeProjectRefactorer::MoveBehaviorEventsFunctionParameter( newIndex); ExposeProjectEvents(project, mover); } + if (eventsFunction.IsAction() || eventsFunction.IsCondition()) { + const int operatorIndexOffset = eventsFunction.IsExpression() ? 2 : 0; + gd::InstructionsParameterMover mover = gd::InstructionsParameterMover( + project, + eventsFunctionType, + oldIndex + operatorIndexOffset, + newIndex + operatorIndexOffset); + ExposeProjectEvents(project, mover); + } } void WholeProjectRefactorer::MoveObjectEventsFunctionParameter( @@ -729,15 +747,7 @@ void WholeProjectRefactorer::MoveObjectEventsFunctionParameter( eventsBasedObject.GetName(), functionName); - if (eventsFunction.GetFunctionType() == gd::EventsFunction::Action || - eventsFunction.GetFunctionType() == gd::EventsFunction::Condition) { - gd::InstructionsParameterMover mover = gd::InstructionsParameterMover( - project, eventsFunctionType, oldIndex, newIndex); - ExposeProjectEvents(project, mover); - } else if (eventsFunction.GetFunctionType() == - gd::EventsFunction::Expression || - eventsFunction.GetFunctionType() == - gd::EventsFunction::StringExpression) { + if (eventsFunction.IsExpression()) { gd::ExpressionsParameterMover mover = gd::ExpressionsParameterMover(project.GetCurrentPlatform()); mover.SetObjectExpressionMovedParameter( @@ -748,6 +758,15 @@ void WholeProjectRefactorer::MoveObjectEventsFunctionParameter( newIndex); ExposeProjectEvents(project, mover); } + if (eventsFunction.IsAction() || eventsFunction.IsCondition()) { + const int operatorIndexOffset = eventsFunction.IsExpression() ? 2 : 0; + gd::InstructionsParameterMover mover = gd::InstructionsParameterMover( + project, + eventsFunctionType, + oldIndex + operatorIndexOffset, + newIndex + operatorIndexOffset); + ExposeProjectEvents(project, mover); + } } void WholeProjectRefactorer::RenameEventsBasedBehaviorProperty( @@ -1088,8 +1107,11 @@ void WholeProjectRefactorer::RenameEventsBasedBehavior( &eventsFunctionsExtension, &oldBehaviorName, &newBehaviorName](const gd::EventsFunction& eventsFunction) { - if (eventsFunction.GetFunctionType() == gd::EventsFunction::Action || - eventsFunction.GetFunctionType() == gd::EventsFunction::Condition) { + if (eventsFunction.IsExpression()) { + // Nothing to do, expressions are not including the name of the + // behavior + } + if (eventsFunction.IsAction() || eventsFunction.IsCondition()) { gd::InstructionsTypeRenamer renamer = gd::InstructionsTypeRenamer( project, GetBehaviorEventsFunctionFullType( @@ -1101,12 +1123,6 @@ void WholeProjectRefactorer::RenameEventsBasedBehavior( newBehaviorName, eventsFunction.GetName())); ExposeProjectEvents(project, renamer); - } else if (eventsFunction.GetFunctionType() == - gd::EventsFunction::Expression || - eventsFunction.GetFunctionType() == - gd::EventsFunction::StringExpression) { - // Nothing to do, expressions are not including the name of the - // behavior } }; @@ -1151,17 +1167,14 @@ void WholeProjectRefactorer::RenameEventsBasedBehavior( // Behavior expressions for (auto&& eventsFunction : behaviorEventsFunctions.GetInternalVector()) { - if (eventsFunction->GetFunctionType() == gd::EventsFunction::Expression || - eventsFunction->GetFunctionType() == - gd::EventsFunction::StringExpression) { + if (eventsFunction->IsExpression()) { renameBehaviorEventsFunction(*eventsFunction); } } // Behavior instructions for (auto&& eventsFunction : behaviorEventsFunctions.GetInternalVector()) { - if (eventsFunction->GetFunctionType() == gd::EventsFunction::Action || - eventsFunction->GetFunctionType() == gd::EventsFunction::Condition) { + if (eventsFunction->IsAction() || eventsFunction->IsCondition()) { renameBehaviorEventsFunction(*eventsFunction); } } @@ -1197,8 +1210,11 @@ void WholeProjectRefactorer::RenameEventsBasedObject( &eventsFunctionsExtension, &oldObjectName, &newObjectName](const gd::EventsFunction& eventsFunction) { - if (eventsFunction.GetFunctionType() == gd::EventsFunction::Action || - eventsFunction.GetFunctionType() == gd::EventsFunction::Condition) { + if (eventsFunction.IsExpression()) { + // Nothing to do, expressions are not including the name of the + // object + } + if (eventsFunction.IsAction() || eventsFunction.IsCondition()) { gd::InstructionsTypeRenamer renamer = gd::InstructionsTypeRenamer( project, GetObjectEventsFunctionFullType( @@ -1210,12 +1226,6 @@ void WholeProjectRefactorer::RenameEventsBasedObject( newObjectName, eventsFunction.GetName())); ExposeProjectEvents(project, renamer); - } else if (eventsFunction.GetFunctionType() == - gd::EventsFunction::Expression || - eventsFunction.GetFunctionType() == - gd::EventsFunction::StringExpression) { - // Nothing to do, expressions are not including the name of the - // object } }; @@ -1260,17 +1270,14 @@ void WholeProjectRefactorer::RenameEventsBasedObject( // Object expressions for (auto&& eventsFunction : objectEventsFunctions.GetInternalVector()) { - if (eventsFunction->GetFunctionType() == gd::EventsFunction::Expression || - eventsFunction->GetFunctionType() == - gd::EventsFunction::StringExpression) { + if (eventsFunction->IsExpression()) { renameObjectEventsFunction(*eventsFunction); } } // Object instructions for (auto&& eventsFunction : objectEventsFunctions.GetInternalVector()) { - if (eventsFunction->GetFunctionType() == gd::EventsFunction::Action || - eventsFunction->GetFunctionType() == gd::EventsFunction::Condition) { + if (eventsFunction->IsAction() || eventsFunction->IsCondition()) { renameObjectEventsFunction(*eventsFunction); } } @@ -1292,20 +1299,20 @@ void WholeProjectRefactorer::DoRenameEventsFunction( const gd::EventsFunction& eventsFunction, const gd::String& oldFullType, const gd::String& newFullType) { - if (eventsFunction.GetFunctionType() == gd::EventsFunction::Action || - eventsFunction.GetFunctionType() == gd::EventsFunction::Condition) { - gd::InstructionsTypeRenamer renamer = - gd::InstructionsTypeRenamer(project, oldFullType, newFullType); - ExposeProjectEvents(project, renamer); - } else if (eventsFunction.GetFunctionType() == - gd::EventsFunction::Expression || - eventsFunction.GetFunctionType() == - gd::EventsFunction::StringExpression) { + // Order is important: we first rename the expressions then the instructions, + // to avoid being unable to fetch the metadata (the types of parameters) of + // instructions after they are renamed. + if (eventsFunction.IsExpression()) { gd::ExpressionsRenamer renamer = gd::ExpressionsRenamer(project.GetCurrentPlatform()); renamer.SetReplacedFreeExpression(oldFullType, newFullType); ExposeProjectEvents(project, renamer); } + if (eventsFunction.IsAction() || eventsFunction.IsCondition()) { + gd::InstructionsTypeRenamer renamer = + gd::InstructionsTypeRenamer(project, oldFullType, newFullType); + ExposeProjectEvents(project, renamer); + } } void WholeProjectRefactorer::DoRenameBehavior( diff --git a/Core/GDCore/Project/AbstractEventsBasedEntity.cpp b/Core/GDCore/Project/AbstractEventsBasedEntity.cpp index 18b419822b..baa8ad0891 100644 --- a/Core/GDCore/Project/AbstractEventsBasedEntity.cpp +++ b/Core/GDCore/Project/AbstractEventsBasedEntity.cpp @@ -10,8 +10,10 @@ namespace gd { -AbstractEventsBasedEntity::AbstractEventsBasedEntity(const gd::String& _name) - : name(_name), fullName("") {} +AbstractEventsBasedEntity::AbstractEventsBasedEntity( + const gd::String& _name, + gd::EventsFunctionsContainer::FunctionOwner functionContainerSource) + : name(_name), fullName(""), eventsFunctionsContainer(functionContainerSource) {} void AbstractEventsBasedEntity::SerializeTo(SerializerElement& element) const { element.SetAttribute("description", description); diff --git a/Core/GDCore/Project/AbstractEventsBasedEntity.h b/Core/GDCore/Project/AbstractEventsBasedEntity.h index d4a2530f8d..32d2ebbf8f 100644 --- a/Core/GDCore/Project/AbstractEventsBasedEntity.h +++ b/Core/GDCore/Project/AbstractEventsBasedEntity.h @@ -29,7 +29,9 @@ namespace gd { */ class GD_CORE_API AbstractEventsBasedEntity { public: - AbstractEventsBasedEntity(const gd::String& _name); + AbstractEventsBasedEntity( + const gd::String& _name, + gd::EventsFunctionsContainer::FunctionOwner functionContainerSource); virtual ~AbstractEventsBasedEntity(){}; /** diff --git a/Core/GDCore/Project/EventsBasedBehavior.cpp b/Core/GDCore/Project/EventsBasedBehavior.cpp index e6411c6b62..67e6da8ca3 100644 --- a/Core/GDCore/Project/EventsBasedBehavior.cpp +++ b/Core/GDCore/Project/EventsBasedBehavior.cpp @@ -11,7 +11,9 @@ namespace gd { EventsBasedBehavior::EventsBasedBehavior() - : AbstractEventsBasedEntity("MyBehavior") {} + : AbstractEventsBasedEntity( + "MyBehavior", + gd::EventsFunctionsContainer::FunctionOwner::Behavior) {} void EventsBasedBehavior::SerializeTo(SerializerElement& element) const { AbstractEventsBasedEntity::SerializeTo(element); diff --git a/Core/GDCore/Project/EventsBasedObject.cpp b/Core/GDCore/Project/EventsBasedObject.cpp index 8260fb01e2..270dd098af 100644 --- a/Core/GDCore/Project/EventsBasedObject.cpp +++ b/Core/GDCore/Project/EventsBasedObject.cpp @@ -10,7 +10,10 @@ namespace gd { EventsBasedObject::EventsBasedObject() - : AbstractEventsBasedEntity("MyObject"), ObjectsContainer() { + : AbstractEventsBasedEntity( + "MyObject", + gd::EventsFunctionsContainer::FunctionOwner::Object), + ObjectsContainer() { } EventsBasedObject::~EventsBasedObject() {} diff --git a/Core/GDCore/Project/EventsFunction.cpp b/Core/GDCore/Project/EventsFunction.cpp index 1bd0051f3f..82981014e4 100644 --- a/Core/GDCore/Project/EventsFunction.cpp +++ b/Core/GDCore/Project/EventsFunction.cpp @@ -7,10 +7,54 @@ #include "EventsFunction.h" #include #include "GDCore/Serialization/SerializerElement.h" +#include "GDCore/Project/EventsFunctionsContainer.h" +#include "GDCore/Extensions/Metadata/ParameterMetadata.h" namespace gd { -EventsFunction::EventsFunction() : functionType(Action) {} +EventsFunction::EventsFunction() : functionType(Action) { + expressionType.SetName("expression"); +} + +const std::vector& EventsFunction::GetParametersForEvents( + const gd::EventsFunctionsContainer& functionsContainer) const { + if (functionType != FunctionType::ActionWithOperator) { + // For most function types, the parameters are specified in the function. + return parameters; + } + // For ActionWithOperator, the parameters are auto generated. + actionWithOperationParameters.clear(); + if (!functionsContainer.HasEventsFunctionNamed(getterName)) { + return actionWithOperationParameters; + } + const auto& expression = functionsContainer.GetEventsFunction(getterName); + const auto& expressionParameters = expression.parameters; + const auto functionsSource = functionsContainer.GetOwner(); + const int expressionValueParameterIndex = + functionsSource == gd::EventsFunctionsContainer::FunctionOwner::Behavior ? + 2 : + functionsSource == gd::EventsFunctionsContainer::FunctionOwner::Object ? + 1 : + 0; + + for (size_t i = 0; + i < expressionValueParameterIndex && i < expressionParameters.size(); + i++) + { + actionWithOperationParameters.push_back(expressionParameters[i]); + } + gd::ParameterMetadata parameterMetadata; + parameterMetadata.SetName("Value").SetValueTypeMetadata(expression.expressionType); + actionWithOperationParameters.push_back(parameterMetadata); + for (size_t i = expressionValueParameterIndex; + i < expressionParameters.size(); + i++) + { + actionWithOperationParameters.push_back(expressionParameters[i]); + } + + return actionWithOperationParameters; +} void EventsFunction::SerializeTo(SerializerElement& element) const { element.SetAttribute("name", name); @@ -18,18 +62,33 @@ void EventsFunction::SerializeTo(SerializerElement& element) const { element.SetAttribute("description", description); element.SetAttribute("sentence", sentence); element.SetAttribute("group", group); + element.SetAttribute("getterName", getterName); element.SetBoolAttribute("private", isPrivate); events.SerializeTo(element.AddChild("events")); gd::String functionTypeStr = "Action"; if (functionType == Condition) functionTypeStr = "Condition"; - else if (functionType == Expression) + else if (functionType == Expression) { functionTypeStr = "Expression"; - else if (functionType == StringExpression) - functionTypeStr = "StringExpression"; + + // Compatibility code for version 5.1.147 and older. + // There is no longer distinction between number and string in the function + // type directly. The expression type is now used for this. + if (expressionType.IsString()) { + functionTypeStr = "StringExpression"; + } + } + else if (functionType == ExpressionAndCondition) { + functionTypeStr = "ExpressionAndCondition"; + } + else if (functionType == ActionWithOperator) + functionTypeStr = "ActionWithOperator"; element.SetAttribute("functionType", functionTypeStr); + if (this->IsExpression()) { + expressionType.SerializeTo(element.AddChild("expressionType")); + } gd::SerializerElement& parametersElement = element.AddChild("parameters"); parametersElement.ConsiderAsArrayOf("parameter"); for (const auto& parameter : parameters) { @@ -46,16 +105,32 @@ void EventsFunction::UnserializeFrom(gd::Project& project, description = element.GetStringAttribute("description"); sentence = element.GetStringAttribute("sentence"); group = element.GetStringAttribute("group"); + getterName = element.GetStringAttribute("getterName"); isPrivate = element.GetBoolAttribute("private"); events.UnserializeFrom(project, element.GetChild("events")); gd::String functionTypeStr = element.GetStringAttribute("functionType"); + if (functionTypeStr == "Condition") functionType = Condition; - else if (functionTypeStr == "Expression") + else if (functionTypeStr == "Expression" || functionTypeStr == "StringExpression") { functionType = Expression; - else if (functionTypeStr == "StringExpression") - functionType = StringExpression; + if (element.HasChild("expressionType")) { + expressionType.UnserializeFrom(element.GetChild("expressionType")); + } + else { + // Compatibility code for version 5.1.147 and older. + // There is no longer distinction between number and string in the function + // type directly. The expression type is now used for this. + expressionType.SetName(functionTypeStr == "StringExpression" ? "string" : "expression"); + } + } + else if (functionTypeStr == "ExpressionAndCondition") { + functionType = ExpressionAndCondition; + expressionType.UnserializeFrom(element.GetChild("expressionType")); + } + else if (functionTypeStr == "ActionWithOperator") + functionType = ActionWithOperator; else functionType = Action; diff --git a/Core/GDCore/Project/EventsFunction.h b/Core/GDCore/Project/EventsFunction.h index d746d201b2..9f3809c76f 100644 --- a/Core/GDCore/Project/EventsFunction.h +++ b/Core/GDCore/Project/EventsFunction.h @@ -12,6 +12,7 @@ #include "GDCore/Events/EventsList.h" #include "GDCore/Project/ObjectGroupsContainer.h" #include "GDCore/String.h" +#include "GDCore/Extensions/Metadata/ValueTypeMetadata.h" // TODO: In theory (for separation of concerns between Project and // extensions/events), this include should be removed and gd::ParameterMetadata // replaced by a new gd::EventsFunctionParameter class. @@ -19,6 +20,7 @@ namespace gd { class SerializerElement; class Project; +class EventsFunctionsContainer; } // namespace gd namespace gd { @@ -115,7 +117,40 @@ class GD_CORE_API EventsFunction { return *this; } - enum FunctionType { Action, Condition, Expression, StringExpression }; + /** + * \brief Get the name of the ExpressionAndCondition to use as an operand + * that is defined in the editor. + */ + const gd::String& GetGetterName() const { return getterName; }; + + /** + * \brief Set the name of the ExpressionAndCondition to use as an operand + * that is defined in the editor. + */ + EventsFunction& SetGetterName(const gd::String& getterName_) { + getterName = getterName_; + return *this; + } + + /** + * \brief Set the type of the expression + */ + EventsFunction& SetExpressionType(const gd::ValueTypeMetadata& type) { + expressionType = type; + return *this; + } + + /** + * \brief Get the type of the expression + */ + const gd::ValueTypeMetadata& GetExpressionType() const { return expressionType; } + + enum FunctionType { + Action, + Condition, + Expression, + ExpressionAndCondition, + ActionWithOperator }; /** * \brief Set the type of the function @@ -123,12 +158,40 @@ class GD_CORE_API EventsFunction { EventsFunction& SetFunctionType(FunctionType type) { functionType = type; return *this; - }; + } /** * \brief Get the type of the function */ - FunctionType GetFunctionType() const { return functionType; }; + FunctionType GetFunctionType() const { return functionType; } + + /** + * \brief Return true if the function is an action. + */ + bool IsAction() const { + return functionType == gd::EventsFunction::Action || + functionType == gd::EventsFunction::ActionWithOperator; + } + + /** + * \brief Return true if the function is an expression. + * + * Note that a function can be both an expression and a condition. + */ + bool IsExpression() const { + return functionType == gd::EventsFunction::Expression || + functionType == gd::EventsFunction::ExpressionAndCondition; + } + + /** + * \brief Return true if the function is a condition. + * + * Note that a function can be both an expression and a condition. + */ + bool IsCondition() const { + return functionType == gd::EventsFunction::Condition || + functionType == gd::EventsFunction::ExpressionAndCondition; + } /** * \brief Returns true if the function is private. @@ -154,7 +217,20 @@ class GD_CORE_API EventsFunction { gd::EventsList& GetEvents() { return events; }; /** - * \brief Return the parameters of the function. + * \brief Return the parameters of the function that are used in the events. + * + * \note During code/extension generation, new parameters are added + * to the generated function, like "runtimeScene" and "eventsFunctionContext". + * This should be transparent to the user. + */ + const std::vector& GetParametersForEvents( + const gd::EventsFunctionsContainer& functionsContainer) const; + + /** + * \brief Return the parameters of the function that are filled in the editor. + * + * \note They won't be used for ActionWithOperator, but they need to be kept + * to avoid to loose them when the function type is changed. * * \note During code/extension generation, new parameters are added * to the generated function, like "runtimeScene" and "eventsFunctionContext". @@ -202,9 +278,12 @@ class GD_CORE_API EventsFunction { gd::String description; gd::String sentence; gd::String group; + gd::String getterName; + gd::ValueTypeMetadata expressionType; gd::EventsList events; FunctionType functionType; std::vector parameters; + mutable std::vector actionWithOperationParameters; gd::ObjectGroupsContainer objectGroups; bool isPrivate = false; }; diff --git a/Core/GDCore/Project/EventsFunctionsContainer.h b/Core/GDCore/Project/EventsFunctionsContainer.h index c3d3d0d321..ae1b950e29 100644 --- a/Core/GDCore/Project/EventsFunctionsContainer.h +++ b/Core/GDCore/Project/EventsFunctionsContainer.h @@ -25,7 +25,24 @@ namespace gd { */ class GD_CORE_API EventsFunctionsContainer : private SerializableWithNameList { - public: +public: + enum FunctionOwner { + Extension, + Object, + Behavior}; + + EventsFunctionsContainer(FunctionOwner source_) : owner(source_) {} + + /** + * \brief Get the source of the function container. + * + * \note For instance, it can be useful to handle specific parameters for + * behaviors. + */ + FunctionOwner GetOwner() const { + return owner; + } + /** \name Events Functions management */ ///@{ @@ -139,6 +156,9 @@ class GD_CORE_API EventsFunctionsContainer void Init(const gd::EventsFunctionsContainer& other) { return SerializableWithNameList::Init(other); }; + +private: + FunctionOwner owner; }; } // namespace gd diff --git a/Core/GDCore/Project/EventsFunctionsExtension.cpp b/Core/GDCore/Project/EventsFunctionsExtension.cpp index cfe1d26fa8..508fb9e60b 100644 --- a/Core/GDCore/Project/EventsFunctionsExtension.cpp +++ b/Core/GDCore/Project/EventsFunctionsExtension.cpp @@ -13,10 +13,14 @@ namespace gd { -EventsFunctionsExtension::EventsFunctionsExtension() {} +EventsFunctionsExtension::EventsFunctionsExtension() : + gd::EventsFunctionsContainer( + gd::EventsFunctionsContainer::FunctionOwner::Extension) {} EventsFunctionsExtension::EventsFunctionsExtension( - const EventsFunctionsExtension& other) { + const EventsFunctionsExtension& other) : + gd::EventsFunctionsContainer( + gd::EventsFunctionsContainer::FunctionOwner::Extension) { Init(other); } diff --git a/Core/GDCore/Project/EventsFunctionsExtension.h b/Core/GDCore/Project/EventsFunctionsExtension.h index acdff4151d..1a7623f69b 100644 --- a/Core/GDCore/Project/EventsFunctionsExtension.h +++ b/Core/GDCore/Project/EventsFunctionsExtension.h @@ -20,7 +20,9 @@ class Project; } // namespace gd namespace gd { - +// TODO Remove the EventsFunctionsContainer inheritance and make it an attribute. +// This will allow to get EventsFunctionsContainer the same way for extensions, +// objects and behaviors. /** * \brief Hold a list of Events Functions (gd::EventsFunction) and Events Based * Behaviors. diff --git a/Core/tests/EventsFunctionsContainer.cpp b/Core/tests/EventsFunctionsContainer.cpp index e96e5d9925..35ff5af50c 100644 --- a/Core/tests/EventsFunctionsContainer.cpp +++ b/Core/tests/EventsFunctionsContainer.cpp @@ -10,7 +10,8 @@ TEST_CASE("EventsFunctionsContainer", "[common]") { SECTION("Sanity checks") { - gd::EventsFunctionsContainer eventsFunctionContainer; + gd::EventsFunctionsContainer eventsFunctionContainer( + gd::EventsFunctionsContainer::FunctionOwner::Extension); eventsFunctionContainer.InsertNewEventsFunction("Function1", 0); eventsFunctionContainer.InsertNewEventsFunction("Function2", 1); eventsFunctionContainer.InsertNewEventsFunction("Function3", 2); @@ -62,7 +63,8 @@ TEST_CASE("EventsFunctionsContainer", "[common]") { } SECTION("Serialization") { gd::Project project; - gd::EventsFunctionsContainer eventsFunctionContainer; + gd::EventsFunctionsContainer eventsFunctionContainer( + gd::EventsFunctionsContainer::FunctionOwner::Extension); eventsFunctionContainer.InsertNewEventsFunction("Function1", 0); eventsFunctionContainer.InsertNewEventsFunction("Function2", 1); eventsFunctionContainer.InsertNewEventsFunction("Function3", 2); @@ -72,7 +74,8 @@ TEST_CASE("EventsFunctionsContainer", "[common]") { eventsFunctionContainer.RemoveEventsFunction("Function2"); - gd::EventsFunctionsContainer eventsFunctionContainer2; + gd::EventsFunctionsContainer eventsFunctionContainer2( + gd::EventsFunctionsContainer::FunctionOwner::Extension); eventsFunctionContainer2.UnserializeEventsFunctionsFrom(project, element); REQUIRE(eventsFunctionContainer.GetEventsFunctionsCount() == 2); REQUIRE(eventsFunctionContainer.GetEventsFunction(0).GetName() == diff --git a/Core/tests/WholeProjectRefactorer.cpp b/Core/tests/WholeProjectRefactorer.cpp index 07203762e6..e84c8c1d9f 100644 --- a/Core/tests/WholeProjectRefactorer.cpp +++ b/Core/tests/WholeProjectRefactorer.cpp @@ -70,6 +70,9 @@ const gd::String &GetEventFirstActionType(const gd::BaseEvent &event) { enum TestEvent { FreeFunctionAction, FreeFunctionWithExpression, + FreeConditionFromExpressionAndCondition, + FreeExpressionFromExpressionAndCondition, + FreeActionWithOperator, FreeFunctionWithObjects, FreeFunctionWithObjectExpression, @@ -81,6 +84,9 @@ enum TestEvent { IllNamedBehaviorExpression, NoParameterBehaviorExpression, NoParameterIllNamedBehaviorExpression, + BehaviorConditionFromExpressionAndCondition, + BehaviorExpressionFromExpressionAndCondition, + BehaviorActionWithOperator, ObjectAction, ObjectPropertyAction, @@ -90,6 +96,9 @@ enum TestEvent { IllNamedObjectExpression, NoParameterObjectExpression, NoParameterIllNamedObjectExpression, + ObjectConditionFromExpressionAndCondition, + ObjectExpressionFromExpressionAndCondition, + ObjectActionWithOperator, }; const std::vector GetEventsLists(gd::Project &project) { @@ -115,7 +124,7 @@ const void SetupEvents(gd::EventsList &eventList) { // Add some free functions usages in events { if (eventList.GetEventsCount() != FreeFunctionAction) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event referring to // MyEventsExtension::MyEventsFunction @@ -133,7 +142,7 @@ const void SetupEvents(gd::EventsList &eventList) { } if (eventList.GetEventsCount() != FreeFunctionWithExpression) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event referring to // MyEventsExtension::MyEventsFunctionExpression @@ -150,8 +159,86 @@ const void SetupEvents(gd::EventsList &eventList) { eventList.InsertEvent(event); } + if (eventList.GetEventsCount() != FreeConditionFromExpressionAndCondition) { + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); + } + // Create an event referring to + // MyEventsExtension::MyEventsFunctionExpressionAndCondition + // as a condition. + { + gd::StandardEvent event; + gd::Instruction condition; + condition.SetType("MyEventsExtension::MyEventsFunctionExpressionAndCondition"); + condition.SetParametersCount(5); + condition.SetParameter( + 0, + gd::Expression("scene")); + condition.SetParameter( + 1, + gd::Expression(">")); + condition.SetParameter( + 2, + gd::Expression("2")); + condition.SetParameter( + 3, + gd::Expression("111")); + condition.SetParameter( + 4, + gd::Expression("222")); + event.GetConditions().Insert(condition); + eventList.InsertEvent(event); + } + + if (eventList.GetEventsCount() != FreeExpressionFromExpressionAndCondition) { + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); + } + // Create an event referring to + // MyEventsExtension::MyEventsFunctionExpressionAndCondition + // as an expression. + { + gd::StandardEvent event; + gd::Instruction action; + action.SetType("MyExtension::DoSomething"); + action.SetParametersCount(1); + action.SetParameter( + 0, + gd::Expression( + "2 + MyEventsExtension::MyEventsFunctionExpressionAndCondition(111, 222)")); + event.GetActions().Insert(action); + eventList.InsertEvent(event); + } + + if (eventList.GetEventsCount() != FreeActionWithOperator) { + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); + } + // Create an event referring to + // MyEventsExtension::MyEventsFunctionActionWithOperator + { + gd::StandardEvent event; + gd::Instruction action; + action.SetType("MyEventsExtension::MyEventsFunctionActionWithOperator"); + action.SetParametersCount(5); + action.SetParameter( + 0, + gd::Expression("scene")); + action.SetParameter( + 1, + gd::Expression("+")); + action.SetParameter( + 2, + gd::Expression("2")); + action.SetParameter( + 3, + gd::Expression("111")); + action.SetParameter( + 4, + gd::Expression("222")); + event.GetActions().Insert(action); + eventList.InsertEvent(event); + } + if (eventList.GetEventsCount() != FreeFunctionWithObjects) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event referring to objects { @@ -166,7 +253,7 @@ const void SetupEvents(gd::EventsList &eventList) { } if (eventList.GetEventsCount() != FreeFunctionWithObjectExpression) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event referring to objects in an expression { @@ -186,7 +273,7 @@ const void SetupEvents(gd::EventsList &eventList) { // Add some events based behavior usages in events { if (eventList.GetEventsCount() != BehaviorAction) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event in the layout referring to // MyEventsExtension::MyEventsBasedBehavior::MyBehaviorEventsFunction @@ -206,7 +293,7 @@ const void SetupEvents(gd::EventsList &eventList) { } if (eventList.GetEventsCount() != BehaviorPropertyAction) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event in the layout using "MyProperty" action { @@ -220,7 +307,7 @@ const void SetupEvents(gd::EventsList &eventList) { } if (eventList.GetEventsCount() != BehaviorPropertyCondition) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event in the layout using "MyProperty" condition { @@ -234,7 +321,7 @@ const void SetupEvents(gd::EventsList &eventList) { } if (eventList.GetEventsCount() != BehaviorPropertyExpression) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event in the layout using "MyProperty" expression { @@ -252,7 +339,7 @@ const void SetupEvents(gd::EventsList &eventList) { } if (eventList.GetEventsCount() != BehaviorExpression) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event referring to // MyEventsExtension::MyEventsBasedBehavior::MyBehaviorEventsFunctionExpression @@ -271,7 +358,7 @@ const void SetupEvents(gd::EventsList &eventList) { } if (eventList.GetEventsCount() != IllNamedBehaviorExpression) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event **wrongly** referring to // MyEventsExtension::MyEventsBasedBehavior::MyBehaviorEventsFunctionExpression @@ -291,7 +378,7 @@ const void SetupEvents(gd::EventsList &eventList) { } if (eventList.GetEventsCount() != NoParameterBehaviorExpression) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event referring to // MyEventsExtension::MyEventsBasedBehavior::MyBehaviorEventsFunctionExpression @@ -310,7 +397,7 @@ const void SetupEvents(gd::EventsList &eventList) { } if (eventList.GetEventsCount() != NoParameterIllNamedBehaviorExpression) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event **wrongly** referring to // MyEventsExtension::MyEventsBasedBehavior::MyBehaviorEventsFunctionExpression @@ -327,12 +414,99 @@ const void SetupEvents(gd::EventsList &eventList) { event.GetActions().Insert(instruction); eventList.InsertEvent(event); } + + if (eventList.GetEventsCount() != BehaviorConditionFromExpressionAndCondition) { + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); + } + // Create an event referring to + // MyEventsExtension::MyEventsBasedBehavior::MyBehaviorEventsFunctionExpressionAndCondition + // as a condition. + { + gd::StandardEvent event; + gd::Instruction condition; + condition.SetType("MyEventsExtension::MyEventsBasedBehavior::" + "MyBehaviorEventsFunctionExpressionAndCondition"); + condition.SetParametersCount(6); + condition.SetParameter( + 0, + gd::Expression("ObjectWithMyBehavior")); + condition.SetParameter( + 1, + gd::Expression("MyBehavior")); + condition.SetParameter( + 2, + gd::Expression(">")); + condition.SetParameter( + 3, + gd::Expression("5")); + condition.SetParameter( + 4, + gd::Expression("111")); + condition.SetParameter( + 5, + gd::Expression("222")); + event.GetConditions().Insert(condition); + eventList.InsertEvent(event); + } + + if (eventList.GetEventsCount() != BehaviorExpressionFromExpressionAndCondition) { + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); + } + // Create an event referring to + // MyEventsExtension::MyEventsBasedBehavior::MyBehaviorEventsFunctionExpressionAndCondition + // as an expression. + { + gd::StandardEvent event; + gd::Instruction action; + action.SetType("MyExtension::DoSomething"); + action.SetParametersCount(1); + action.SetParameter( + 0, + gd::Expression("5 + " + "ObjectWithMyBehavior.MyBehavior::" + "MyBehaviorEventsFunctionExpressionAndCondition(111, 222)")); + event.GetActions().Insert(action); + eventList.InsertEvent(event); + } + + if (eventList.GetEventsCount() != BehaviorActionWithOperator) { + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); + } + // Create an event referring to + // MyEventsExtension::MyEventsBasedBehavior::MyBehaviorEventsFunctionActionWithOperator + { + gd::StandardEvent event; + gd::Instruction action; + action.SetType("MyEventsExtension::MyEventsBasedBehavior::" + "MyBehaviorEventsFunctionActionWithOperator"); + action.SetParametersCount(6); + action.SetParameter( + 0, + gd::Expression("ObjectWithMyBehavior")); + action.SetParameter( + 1, + gd::Expression("MyBehavior")); + action.SetParameter( + 2, + gd::Expression("+")); + action.SetParameter( + 3, + gd::Expression("5")); + action.SetParameter( + 4, + gd::Expression("111")); + action.SetParameter( + 5, + gd::Expression("222")); + event.GetActions().Insert(action); + eventList.InsertEvent(event); + } } // Add some events based object usages in events { if (eventList.GetEventsCount() != ObjectAction) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event in the layout referring to // MyEventsExtension::MyEventsBasedObject::MyObjectEventsFunction @@ -351,7 +525,7 @@ const void SetupEvents(gd::EventsList &eventList) { } if (eventList.GetEventsCount() != ObjectPropertyAction) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event in the layout using "MyProperty" action { @@ -365,7 +539,7 @@ const void SetupEvents(gd::EventsList &eventList) { } if (eventList.GetEventsCount() != ObjectPropertyCondition) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event in the layout using "MyProperty" condition { @@ -379,7 +553,7 @@ const void SetupEvents(gd::EventsList &eventList) { } if (eventList.GetEventsCount() != ObjectPropertyExpression) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event in the layout using "MyProperty" expression { @@ -397,7 +571,7 @@ const void SetupEvents(gd::EventsList &eventList) { } if (eventList.GetEventsCount() != ObjectExpression) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event referring to // MyEventsExtension::MyEventsBasedObject::MyObjectEventsFunctionExpression @@ -415,7 +589,7 @@ const void SetupEvents(gd::EventsList &eventList) { } if (eventList.GetEventsCount() != IllNamedObjectExpression) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event **wrongly** referring to // MyEventsExtension::MyEventsBasedObject::MyObjectEventsFunctionExpression @@ -434,7 +608,7 @@ const void SetupEvents(gd::EventsList &eventList) { } if (eventList.GetEventsCount() != NoParameterObjectExpression) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event referring to // MyEventsExtension::MyEventsBasedObject::MyObjectEventsFunctionExpression @@ -453,7 +627,7 @@ const void SetupEvents(gd::EventsList &eventList) { } if (eventList.GetEventsCount() != NoParameterIllNamedObjectExpression) { - throw std::logic_error("Invalid events setup"); + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); } // Create an event **wrongly** referring to // MyEventsExtension::MyEventsBasedObject::MyObjectEventsFunctionExpression @@ -470,6 +644,87 @@ const void SetupEvents(gd::EventsList &eventList) { event.GetActions().Insert(instruction); eventList.InsertEvent(event); } + + if (eventList.GetEventsCount() != ObjectConditionFromExpressionAndCondition) { + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); + } + // Create an event referring to + // MyEventsExtension::MyEventsBasedObject::MyObjectEventsFunctionExpressionAndCondition + // as a condition. + { + gd::StandardEvent event; + gd::Instruction condition; + condition.SetType("MyEventsExtension::MyEventsBasedObject::" + "MyObjectEventsFunctionExpressionAndCondition"); + condition.SetParametersCount(5); + condition.SetParameter( + 0, + gd::Expression("MyCustomObject")); + condition.SetParameter( + 1, + gd::Expression(">")); + condition.SetParameter( + 2, + gd::Expression("5")); + condition.SetParameter( + 3, + gd::Expression("111")); + condition.SetParameter( + 4, + gd::Expression("222")); + event.GetConditions().Insert(condition); + eventList.InsertEvent(event); + } + + if (eventList.GetEventsCount() != ObjectExpressionFromExpressionAndCondition) { + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); + } + // Create an event referring to + // MyEventsExtension::MyEventsBasedObject::MyObjectEventsFunctionExpressionAndCondition + // as an expression. + { + gd::StandardEvent event; + gd::Instruction action; + action.SetType("MyExtension::DoSomething"); + action.SetParametersCount(1); + action.SetParameter( + 0, + gd::Expression("5 + " + "MyCustomObject." + "MyObjectEventsFunctionExpressionAndCondition(111, 222)")); + event.GetActions().Insert(action); + eventList.InsertEvent(event); + } + + if (eventList.GetEventsCount() != ObjectActionWithOperator) { + throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount())); + } + // Create an event referring to + // MyEventsExtension::MyEventsBasedObject::MyObjectEventsFunctionActionWithOperator + { + gd::StandardEvent event; + gd::Instruction action; + action.SetType("MyEventsExtension::MyEventsBasedObject::" + "MyObjectEventsFunctionActionWithOperator"); + action.SetParametersCount(5); + action.SetParameter( + 0, + gd::Expression("MyCustomObject")); + action.SetParameter( + 1, + gd::Expression("+")); + action.SetParameter( + 2, + gd::Expression("5")); + action.SetParameter( + 3, + gd::Expression("111")); + action.SetParameter( + 4, + gd::Expression("222")); + event.GetActions().Insert(action); + eventList.InsertEvent(event); + } } } @@ -527,6 +782,31 @@ SetupProjectWithEventsFunctionExtension(gd::Project &project) { .SetType("behavior") .SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior")); + auto &behaviorExpressionAndCondition = + behaviorEventsFunctions + .InsertNewEventsFunction("MyBehaviorEventsFunctionExpressionAndCondition", 2) + .SetFunctionType(gd::EventsFunction::ExpressionAndCondition); + behaviorExpressionAndCondition.GetParameters().push_back( + gd::ParameterMetadata().SetName("Object").SetType("object")); + behaviorExpressionAndCondition.GetParameters().push_back( + gd::ParameterMetadata() + .SetName("Behavior") + .SetType("behavior") + .SetExtraInfo("MyExtension::MyEventsBasedBehavior")); + behaviorExpressionAndCondition.GetParameters().push_back( + gd::ParameterMetadata() + .SetName("Value1") + .SetType("expression")); + behaviorExpressionAndCondition.GetParameters().push_back( + gd::ParameterMetadata() + .SetName("Value2") + .SetType("expression")); + + behaviorEventsFunctions + .InsertNewEventsFunction("MyBehaviorEventsFunctionActionWithOperator", 2) + .SetFunctionType(gd::EventsFunction::ActionWithOperator) + .SetGetterName("MyBehaviorEventsFunctionExpressionAndCondition"); + // Add property eventsBasedBehavior.GetPropertyDescriptors() .InsertNew("MyProperty", 0) @@ -570,6 +850,26 @@ SetupProjectWithEventsFunctionExtension(gd::Project &project) { .SetType("object") .SetExtraInfo("MyEventsExtension::MyEventsBasedObject")); + auto &objectExpressionAndCondition = + objectEventsFunctions + .InsertNewEventsFunction("MyObjectEventsFunctionExpressionAndCondition", 2) + .SetFunctionType(gd::EventsFunction::ExpressionAndCondition); + objectExpressionAndCondition.GetParameters().push_back( + gd::ParameterMetadata().SetName("Object").SetType("object")); + objectExpressionAndCondition.GetParameters().push_back( + gd::ParameterMetadata() + .SetName("Value1") + .SetType("expression")); + objectExpressionAndCondition.GetParameters().push_back( + gd::ParameterMetadata() + .SetName("Value2") + .SetType("expression")); + + objectEventsFunctions + .InsertNewEventsFunction("MyObjectEventsFunctionActionWithOperator", 2) + .SetFunctionType(gd::EventsFunction::ActionWithOperator) + .SetGetterName("MyObjectEventsFunctionExpressionAndCondition"); + // Add a property eventsBasedObject.GetPropertyDescriptors() .InsertNew("MyProperty", 0) @@ -622,6 +922,7 @@ SetupProjectWithEventsFunctionExtension(gd::Project &project) { .SetName("Behavior") .SetType("behavior") .SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior")); + auto &expression = eventsExtension.InsertNewEventsFunction("MyEventsFunctionExpression", 1) .SetFunctionType(gd::EventsFunction::Expression); @@ -629,6 +930,21 @@ SetupProjectWithEventsFunctionExtension(gd::Project &project) { .SetName("currentScene") .SetType("") .SetCodeOnly(true)); + + auto &freeExpressionAndCondition = eventsExtension.InsertNewEventsFunction("MyEventsFunctionExpressionAndCondition", 2) + .SetFunctionType(gd::EventsFunction::ExpressionAndCondition); + freeExpressionAndCondition.GetParameters().push_back( + gd::ParameterMetadata() + .SetName("Value1") + .SetType("expression")); + freeExpressionAndCondition.GetParameters().push_back( + gd::ParameterMetadata() + .SetName("Value2") + .SetType("expression")); + + eventsExtension.InsertNewEventsFunction("MyEventsFunctionActionWithOperator", 2) + .SetFunctionType(gd::EventsFunction::ActionWithOperator) + .SetGetterName("MyEventsFunctionExpressionAndCondition"); } // Add some usages in events @@ -1048,6 +1364,21 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { eventsList->GetEvent(FreeFunctionWithExpression)) == "1 + MyRenamedExtension::MyEventsFunctionExpression(123, 456)"); + // Check that events function calls from an ExpressionAndCondition have + // been renamed. + REQUIRE(GetEventFirstConditionType( + eventsList->GetEvent(FreeConditionFromExpressionAndCondition)) == + "MyRenamedExtension::MyEventsFunctionExpressionAndCondition"); + REQUIRE(GetEventFirstActionFirstParameterString( + eventsList->GetEvent(FreeExpressionFromExpressionAndCondition)) == + "2 + MyRenamedExtension::MyEventsFunctionExpressionAndCondition(111, 222)"); + + // Check that events function calls from an ActionWithOperator has + // been renamed. + REQUIRE(GetEventFirstActionType( + eventsList->GetEvent(FreeActionWithOperator)) == + "MyRenamedExtension::MyEventsFunctionActionWithOperator"); + // Check that the type of the behavior was changed in the behaviors of // objects. Name is *not* changed. REQUIRE(project.GetLayout("Scene") @@ -1084,7 +1415,17 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { REQUIRE(GetEventFirstActionType(eventsList->GetEvent(BehaviorAction)) == "MyRenamedExtension::MyEventsBasedBehavior::" "MyBehaviorEventsFunction"); - + REQUIRE( + GetEventFirstConditionType( + eventsList->GetEvent(BehaviorConditionFromExpressionAndCondition)) == + "MyRenamedExtension::MyEventsBasedBehavior::" + "MyBehaviorEventsFunctionExpressionAndCondition"); + REQUIRE( + GetEventFirstActionType( + eventsList->GetEvent(BehaviorActionWithOperator)) == + "MyRenamedExtension::MyEventsBasedBehavior::" + "MyBehaviorEventsFunctionActionWithOperator"); + // Check if events-based behaviors properties have been renamed in // instructions REQUIRE(GetEventFirstActionType( @@ -1096,16 +1437,19 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { // expressions REQUIRE(GetEventFirstActionFirstParameterString( eventsList->GetEvent(BehaviorExpression)) == - "1 + " - "ObjectWithMyBehavior.MyBehavior::" + "1 + ObjectWithMyBehavior.MyBehavior::" "MyBehaviorEventsFunctionExpression(123, 456, 789)"); REQUIRE(GetEventFirstActionFirstParameterString( eventsList->GetEvent(NoParameterBehaviorExpression)) == - "3 + " - "ObjectWithMyBehavior.MyBehavior::" + "3 + ObjectWithMyBehavior.MyBehavior::" "MyBehaviorEventsFunctionExpression"); + REQUIRE(GetEventFirstActionFirstParameterString( + eventsList->GetEvent(BehaviorExpressionFromExpressionAndCondition)) == + "5 + ObjectWithMyBehavior.MyBehavior::" + "MyBehaviorEventsFunctionExpressionAndCondition(111, 222)"); + // Check that the type of the object was changed in the custom // objects. Name is *not* changed. REQUIRE( @@ -1119,6 +1463,16 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { REQUIRE( GetEventFirstActionType(eventsList->GetEvent(ObjectAction)) == "MyRenamedExtension::MyEventsBasedObject::MyObjectEventsFunction"); + REQUIRE( + GetEventFirstConditionType( + eventsList->GetEvent(ObjectConditionFromExpressionAndCondition)) == + "MyRenamedExtension::MyEventsBasedObject::" + "MyObjectEventsFunctionExpressionAndCondition"); + REQUIRE( + GetEventFirstActionType( + eventsList->GetEvent(ObjectActionWithOperator)) == + "MyRenamedExtension::MyEventsBasedObject::" + "MyObjectEventsFunctionActionWithOperator"); // Check if events-based object properties have been renamed in // instructions @@ -1130,14 +1484,17 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { // expressions REQUIRE(GetEventFirstActionFirstParameterString( eventsList->GetEvent(ObjectExpression)) == - "1 + " - "MyCustomObject." + "1 + MyCustomObject." "MyObjectEventsFunctionExpression(123, 456, 789)"); REQUIRE(GetEventFirstActionFirstParameterString( eventsList->GetEvent(NoParameterObjectExpression)) == - "3 + " - "MyCustomObject.MyObjectEventsFunctionExpression"); + "3 + MyCustomObject.MyObjectEventsFunctionExpression"); + + REQUIRE(GetEventFirstActionFirstParameterString( + eventsList->GetEvent(ObjectExpressionFromExpressionAndCondition)) == + "5 + MyCustomObject." + "MyObjectEventsFunctionExpressionAndCondition(111, 222)"); } } @@ -1239,6 +1596,35 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { } } + SECTION("(Free) events expression and condition renamed") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + + gd::WholeProjectRefactorer::RenameEventsFunction( + project, + eventsExtension, + "MyEventsFunctionExpressionAndCondition", + "MyRenamedFunctionExpressionAndCondition"); + + for (auto *eventsList : GetEventsLists(project)) { + // Check that events function calls in expressions have been renamed + REQUIRE(GetEventFirstActionFirstParameterString( + eventsList->GetEvent(FreeExpressionFromExpressionAndCondition)) == + "2 + MyEventsExtension::MyRenamedFunctionExpressionAndCondition(111, 222)"); + + // Check that events function calls in instructions have been renamed + REQUIRE(GetEventFirstConditionType( + eventsList->GetEvent(FreeConditionFromExpressionAndCondition)) == + "MyEventsExtension::MyRenamedFunctionExpressionAndCondition"); + + // Check that the action still refer to the right ExpressionAndCondition. + REQUIRE(eventsExtension.GetEventsFunction("MyEventsFunctionActionWithOperator") + .GetGetterName() == "MyRenamedFunctionExpressionAndCondition"); + } + } + SECTION("(Free) events action parameter moved") { gd::Project project; gd::Platform platform; @@ -1279,6 +1665,35 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { } } + SECTION("(Free) events expression and condition parameter moved") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + + // The index 0 is reserved for the RuntimeScene. + gd::WholeProjectRefactorer::MoveEventsFunctionParameter( + project, eventsExtension, "MyEventsFunctionExpressionAndCondition", 1, 2); + + for (auto *eventsList : GetEventsLists(project)) { + // Check that events function calls in expressions have been updated + REQUIRE(GetEventFirstActionFirstParameterString( + eventsList->GetEvent(FreeExpressionFromExpressionAndCondition)) == + "2 + MyEventsExtension::MyEventsFunctionExpressionAndCondition(222, 111)"); + + // Check that events function calls in instructions have been updated + auto &condition = static_cast( + eventsList->GetEvent(FreeConditionFromExpressionAndCondition)) + .GetConditions() + .Get(0); + REQUIRE(condition.GetParameter(0).GetPlainString() == "scene"); + REQUIRE(condition.GetParameter(1).GetPlainString() == ">"); + REQUIRE(condition.GetParameter(2).GetPlainString() == "2"); + REQUIRE(condition.GetParameter(3).GetPlainString() == "222"); + REQUIRE(condition.GetParameter(4).GetPlainString() == "111"); + } + } + SECTION("Events based behavior renamed (instructions update)") { gd::Project project; gd::Platform platform; @@ -1316,6 +1731,14 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { REQUIRE(GetEventFirstActionType(eventsList->GetEvent(BehaviorAction)) == "MyEventsExtension::MyRenamedEventsBasedBehavior::" "MyBehaviorEventsFunction"); + REQUIRE(GetEventFirstConditionType( + eventsList->GetEvent(BehaviorConditionFromExpressionAndCondition)) == + "MyEventsExtension::MyRenamedEventsBasedBehavior::" + "MyBehaviorEventsFunctionExpressionAndCondition"); + REQUIRE(GetEventFirstActionType( + eventsList->GetEvent(BehaviorActionWithOperator)) == + "MyEventsExtension::MyRenamedEventsBasedBehavior::" + "MyBehaviorEventsFunctionActionWithOperator"); // Check if events-based behaviors properties have been renamed in // instructions @@ -1328,9 +1751,12 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { // expressions REQUIRE(GetEventFirstActionFirstParameterString( eventsList->GetEvent(BehaviorExpression)) == - "1 + " - "ObjectWithMyBehavior.MyBehavior::" + "1 + ObjectWithMyBehavior.MyBehavior::" "MyBehaviorEventsFunctionExpression(123, 456, 789)"); + REQUIRE(GetEventFirstActionFirstParameterString( + eventsList->GetEvent(BehaviorExpressionFromExpressionAndCondition)) == + "5 + ObjectWithMyBehavior.MyBehavior::" + "MyBehaviorEventsFunctionExpressionAndCondition(111, 222)"); } } @@ -1407,6 +1833,14 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { REQUIRE(GetEventFirstActionType(eventsList->GetEvent(ObjectAction)) == "MyEventsExtension::MyRenamedEventsBasedObject::" "MyObjectEventsFunction"); + REQUIRE(GetEventFirstConditionType( + eventsList->GetEvent(ObjectConditionFromExpressionAndCondition)) == + "MyEventsExtension::MyRenamedEventsBasedObject::" + "MyObjectEventsFunctionExpressionAndCondition"); + REQUIRE(GetEventFirstActionType( + eventsList->GetEvent(ObjectActionWithOperator)) == + "MyEventsExtension::MyRenamedEventsBasedObject::" + "MyObjectEventsFunctionActionWithOperator"); // Check if events-based object properties have been renamed in // instructions @@ -1419,9 +1853,12 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { // expressions REQUIRE(GetEventFirstActionFirstParameterString( eventsList->GetEvent(ObjectExpression)) == - "1 + " - "MyCustomObject." + "1 + MyCustomObject." "MyObjectEventsFunctionExpression(123, 456, 789)"); + REQUIRE(GetEventFirstActionFirstParameterString( + eventsList->GetEvent(ObjectExpressionFromExpressionAndCondition)) == + "5 + MyCustomObject." + "MyObjectEventsFunctionExpressionAndCondition(111, 222)"); } } @@ -1623,6 +2060,80 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { } } + SECTION("(Events based behavior) events expression and condition renamed") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + auto &eventsBasedBehavior = + eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior"); + + gd::WholeProjectRefactorer::RenameBehaviorEventsFunction( + project, + eventsExtension, + eventsBasedBehavior, + "MyBehaviorEventsFunctionExpressionAndCondition", + "MyRenamedBehaviorEventsFunctionExpressionAndCondition"); + + for (auto *eventsList : GetEventsLists(project)) { + // Check events-based behavior methods have been renamed in + // expressions + REQUIRE(GetEventFirstActionFirstParameterString( + eventsList->GetEvent(BehaviorExpressionFromExpressionAndCondition)) == + "5 + ObjectWithMyBehavior.MyBehavior::" + "MyRenamedBehaviorEventsFunctionExpressionAndCondition(111, 222)"); + + // Check if events-based behavior methods have been renamed in + // instructions + REQUIRE(GetEventFirstConditionType( + eventsList->GetEvent(BehaviorConditionFromExpressionAndCondition)) == + "MyEventsExtension::MyEventsBasedBehavior::" + "MyRenamedBehaviorEventsFunctionExpressionAndCondition"); + + // Check that the action still refer to the right ExpressionAndCondition. + REQUIRE(eventsBasedBehavior.GetEventsFunctions() + .GetEventsFunction("MyBehaviorEventsFunctionActionWithOperator") + .GetGetterName() == "MyRenamedBehaviorEventsFunctionExpressionAndCondition"); + } + } + + SECTION("(Events based object) events expression and condition renamed") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + auto &eventsBasedObject = + eventsExtension.GetEventsBasedObjects().Get("MyEventsBasedObject"); + + gd::WholeProjectRefactorer::RenameObjectEventsFunction( + project, + eventsExtension, + eventsBasedObject, + "MyObjectEventsFunctionExpressionAndCondition", + "MyRenamedObjectEventsFunctionExpressionAndCondition"); + + for (auto *eventsList : GetEventsLists(project)) { + // Check events-based behavior methods have been renamed in + // expressions + REQUIRE(GetEventFirstActionFirstParameterString( + eventsList->GetEvent(ObjectExpressionFromExpressionAndCondition)) == + "5 + MyCustomObject." + "MyRenamedObjectEventsFunctionExpressionAndCondition(111, 222)"); + + // Check if events-based behavior methods have been renamed in + // instructions + REQUIRE(GetEventFirstConditionType( + eventsList->GetEvent(ObjectConditionFromExpressionAndCondition)) == + "MyEventsExtension::MyEventsBasedObject::" + "MyRenamedObjectEventsFunctionExpressionAndCondition"); + + // Check that the action still refer to the right ExpressionAndCondition. + REQUIRE(eventsBasedObject.GetEventsFunctions() + .GetEventsFunction("MyObjectEventsFunctionActionWithOperator") + .GetGetterName() == "MyRenamedObjectEventsFunctionExpressionAndCondition"); + } + } + SECTION("(Events based behavior) events action parameter moved") { gd::Project project; gd::Platform platform; @@ -1739,6 +2250,83 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { } } + SECTION("(Events based behavior) events expression and condition parameter moved") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + auto &eventsBasedBehavior = + eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior"); + + // The first 2 parameters are reserved for the object and behavior. + gd::WholeProjectRefactorer::MoveBehaviorEventsFunctionParameter( + project, + eventsExtension, + eventsBasedBehavior, + "MyBehaviorEventsFunctionExpressionAndCondition", + 2, + 3); + + for (auto *eventsList : GetEventsLists(project)) { + // Check parameters of events-based behavior methods have been moved in + // expressions + REQUIRE(GetEventFirstActionFirstParameterString( + eventsList->GetEvent(BehaviorExpressionFromExpressionAndCondition)) == + "5 + ObjectWithMyBehavior.MyBehavior::" + "MyBehaviorEventsFunctionExpressionAndCondition(222, 111)"); + // Check if parameters of events-based behavior methods have been moved in + // instructions + auto &action = static_cast( + eventsList->GetEvent(BehaviorConditionFromExpressionAndCondition)) + .GetConditions() + .Get(0); + REQUIRE(action.GetParameter(0).GetPlainString() == "ObjectWithMyBehavior"); + REQUIRE(action.GetParameter(1).GetPlainString() == "MyBehavior"); + REQUIRE(action.GetParameter(2).GetPlainString() == ">"); + REQUIRE(action.GetParameter(3).GetPlainString() == "5"); + REQUIRE(action.GetParameter(4).GetPlainString() == "222"); + REQUIRE(action.GetParameter(5).GetPlainString() == "111"); + } + } + + SECTION("(Events based object) events expression and condition parameter moved") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + auto &eventsBasedObject = + eventsExtension.GetEventsBasedObjects().Get("MyEventsBasedObject"); + + // The first 2 parameters are reserved for the object and behavior. + gd::WholeProjectRefactorer::MoveObjectEventsFunctionParameter( + project, + eventsExtension, + eventsBasedObject, + "MyObjectEventsFunctionExpressionAndCondition", + 1, + 2); + + for (auto *eventsList : GetEventsLists(project)) { + // Check parameters of events-based behavior methods have been moved in + // expressions + REQUIRE(GetEventFirstActionFirstParameterString( + eventsList->GetEvent(ObjectExpressionFromExpressionAndCondition)) == + "5 + MyCustomObject." + "MyObjectEventsFunctionExpressionAndCondition(222, 111)"); + // Check if parameters of events-based behavior methods have been moved in + // instructions + auto &action = static_cast( + eventsList->GetEvent(ObjectConditionFromExpressionAndCondition)) + .GetConditions() + .Get(0); + REQUIRE(action.GetParameter(0).GetPlainString() == "MyCustomObject"); + REQUIRE(action.GetParameter(1).GetPlainString() == ">"); + REQUIRE(action.GetParameter(2).GetPlainString() == "5"); + REQUIRE(action.GetParameter(3).GetPlainString() == "222"); + REQUIRE(action.GetParameter(4).GetPlainString() == "111"); + } + } + SECTION( "(Events based behavior) property renamed (not a required behavior)") { gd::Project project; diff --git a/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp b/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp index 30d9ca3cae..df3d9c8807 100644 --- a/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp +++ b/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp @@ -116,6 +116,7 @@ gd::String EventsCodeGenerator::GenerateLayoutCode( gd::String EventsCodeGenerator::GenerateEventsFunctionCode( gd::Project& project, + const gd::EventsFunctionsContainer& functionsContainer, const gd::EventsFunction& eventsFunction, const gd::String& codeNamespace, std::set& includeFiles, @@ -123,7 +124,7 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionCode( gd::ObjectsContainer globalObjectsAndGroups; gd::ObjectsContainer objectsAndGroups; gd::EventsFunctionTools::FreeEventsFunctionToObjectsContainer( - project, eventsFunction, globalObjectsAndGroups, objectsAndGroups); + project, functionsContainer, eventsFunction, globalObjectsAndGroups, objectsAndGroups); EventsCodeGenerator codeGenerator(globalObjectsAndGroups, objectsAndGroups); codeGenerator.SetCodeNamespace(codeNamespace); @@ -133,9 +134,9 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionCode( codeGenerator, codeGenerator.GetCodeNamespaceAccessor() + "func", codeGenerator.GenerateEventsFunctionParameterDeclarationsList( - eventsFunction.GetParameters(), 0, true), + eventsFunction.GetParametersForEvents(functionsContainer), 0, true), codeGenerator.GenerateFreeEventsFunctionContext( - eventsFunction.GetParameters(), "runtimeScene.getOnceTriggers()"), + eventsFunction.GetParametersForEvents(functionsContainer), "runtimeScene.getOnceTriggers()"), eventsFunction.GetEvents(), "", codeGenerator.GenerateEventsFunctionReturn(eventsFunction)); @@ -185,7 +186,8 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode( "var Behavior = this.name;\n" + codeGenerator.GenerateBehaviorEventsFunctionContext( eventsBasedBehavior, - eventsFunction.GetParameters(), + eventsFunction.GetParametersForEvents( + eventsBasedBehavior.GetEventsFunctions()), onceTriggersVariable, // Pass the names of the parameters considered as the current // object and behavior parameters: @@ -196,7 +198,8 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode( codeGenerator, fullyQualifiedFunctionName, codeGenerator.GenerateEventsFunctionParameterDeclarationsList( - eventsFunction.GetParameters(), 2, false), + eventsFunction.GetParametersForEvents( + eventsBasedBehavior.GetEventsFunctions()), 2, false), fullPreludeCode, eventsFunction.GetEvents(), "", @@ -258,7 +261,8 @@ gd::String EventsCodeGenerator::GenerateObjectEventsFunctionCode( fullPreludeCode += codeGenerator.GenerateObjectEventsFunctionContext( eventsBasedObject, - eventsFunction.GetParameters(), + eventsFunction.GetParametersForEvents( + eventsBasedObject.GetEventsFunctions()), onceTriggersVariable, // Pass the names of the parameters considered as the current // object and behavior parameters: @@ -269,7 +273,8 @@ gd::String EventsCodeGenerator::GenerateObjectEventsFunctionCode( fullyQualifiedFunctionName, codeGenerator.GenerateEventsFunctionParameterDeclarationsList( // TODO EBO use constants for firstParameterIndex - eventsFunction.GetParameters(), 1, false), + eventsFunction.GetParametersForEvents( + eventsBasedObject.GetEventsFunctions()), 1, false), fullPreludeCode, eventsFunction.GetEvents(), endingCode, @@ -557,16 +562,20 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext( gd::String EventsCodeGenerator::GenerateEventsFunctionReturn( const gd::EventsFunction& eventsFunction) { + // We don't use IsCondition because ExpressionAndCondition event functions + // don't need a boolean function. They use the expression function with a + // relational operator. if (eventsFunction.GetFunctionType() == gd::EventsFunction::Condition) { return "return !!eventsFunctionContext.returnValue;"; - } else if (eventsFunction.GetFunctionType() == - gd::EventsFunction::Expression) { - return "return Number(eventsFunctionContext.returnValue) || 0;"; - } else if (eventsFunction.GetFunctionType() == - gd::EventsFunction::StringExpression) { - return "return \"\" + eventsFunctionContext.returnValue;"; + } else if (eventsFunction.IsExpression()) { + if (eventsFunction.GetExpressionType().IsNumber()) { + return "return Number(eventsFunctionContext.returnValue) || 0;"; + } else { + // Default on string because it's more likely that future expression + // types are strings. + return "return \"\" + eventsFunctionContext.returnValue;"; + } } - return "return;"; } diff --git a/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.h b/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.h index 38eefd35f9..5537630dd3 100644 --- a/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.h +++ b/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.h @@ -14,6 +14,7 @@ #include "GDCore/Events/InstructionsList.h" namespace gd { class ObjectsContainer; +class EventsFunctionsContainer; class EventsFunction; class EventsBasedBehavior; class EventsBasedObject; @@ -55,6 +56,7 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator { * Generate JavaScript for executing events of an events based function. * * \param project Project used. + * \param functionsContainer The container of the compiled event function. * \param eventsFunction The events function to be compiled. * \param codeNamespace Where to store the context used by the function. * \param includeFiles Will be filled with the necessary include files. @@ -65,6 +67,7 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator { */ static gd::String GenerateEventsFunctionCode( gd::Project& project, + const gd::EventsFunctionsContainer& functionsContainer, const gd::EventsFunction& eventsFunction, const gd::String& codeNamespace, std::set& includeFiles, diff --git a/GDJS/GDJS/Events/CodeGeneration/EventsFunctionsExtensionCodeGenerator.cpp b/GDJS/GDJS/Events/CodeGeneration/EventsFunctionsExtensionCodeGenerator.cpp index b90101fbe6..b12a293f48 100644 --- a/GDJS/GDJS/Events/CodeGeneration/EventsFunctionsExtensionCodeGenerator.cpp +++ b/GDJS/GDJS/Events/CodeGeneration/EventsFunctionsExtensionCodeGenerator.cpp @@ -11,6 +11,7 @@ namespace gdjs { gd::String EventsFunctionsExtensionCodeGenerator::GenerateFreeEventsFunctionCompleteCode( + const gd::EventsFunctionsExtension& extension, const gd::EventsFunction& eventsFunction, const gd::String& codeNamespace, std::set& includeFiles, @@ -27,6 +28,7 @@ if (typeof CODE_NAMESPACE !== "undefined") { gd::String eventsFunctionCode = EventsCodeGenerator::GenerateEventsFunctionCode(project, + extension, eventsFunction, codeNamespace, includeFiles, diff --git a/GDJS/GDJS/Events/CodeGeneration/EventsFunctionsExtensionCodeGenerator.h b/GDJS/GDJS/Events/CodeGeneration/EventsFunctionsExtensionCodeGenerator.h index ab5a9e47d6..798f4bd12c 100644 --- a/GDJS/GDJS/Events/CodeGeneration/EventsFunctionsExtensionCodeGenerator.h +++ b/GDJS/GDJS/Events/CodeGeneration/EventsFunctionsExtensionCodeGenerator.h @@ -29,6 +29,7 @@ class EventsFunctionsExtensionCodeGenerator { * \brief Generate the complete code for the specified events function. */ gd::String GenerateFreeEventsFunctionCompleteCode( + const gd::EventsFunctionsExtension& extension, const gd::EventsFunction& eventsFunction, const gd::String& codeNamespace, std::set& includeFiles, diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index 9424553345..f6a9afddf2 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -1200,8 +1200,8 @@ interface InstructionMetadata { [Ref] InstructionMetadata SetParameterLongDescription([Const] DOMString longDescription); [Ref] InstructionMetadata SetParameterExtraInfo([Const] DOMString extraInfo); - [Ref] InstructionMetadata UseStandardOperatorParameters([Const] DOMString type); - [Ref] InstructionMetadata UseStandardRelationalOperatorParameters([Const] DOMString type); + [Ref] InstructionMetadata UseStandardOperatorParameters([Const] DOMString type, [Const] optional DOMString typeExtraInfo = ""); + [Ref] InstructionMetadata UseStandardRelationalOperatorParameters([Const] DOMString type, [Const] optional DOMString typeExtraInfo = ""); [Ref] InstructionMetadata SetRequiresBaseObjectCapability([Const] DOMString capability); [Const, Ref] DOMString GetRequiredBaseObjectCapability(); @@ -1211,6 +1211,12 @@ interface InstructionMetadata { [Ref] InstructionMetadata MarkAsComplex(); [Ref] ExtraInformation GetCodeExtraInformation(); + + [Ref] ExtraInformation SetFunctionName([Const] DOMString functionName); + + [Ref] InstructionMetadata SetIncludeFile([Const] DOMString includeFile); + [Ref] InstructionMetadata AddIncludeFile([Const] DOMString includeFile); + [Const, Ref] VectorString GetIncludeFiles(); }; interface ExpressionMetadata { @@ -1246,11 +1252,18 @@ interface ExpressionMetadata { [Const] DOMString type, [Const] DOMString supplementaryInformation); [Ref] ExpressionMetadata SetDefaultValue([Const] DOMString defaultValue); [Ref] ExpressionMetadata SetParameterLongDescription([Const] DOMString longDescription); + [Ref] ExpressionMetadata SetParameterExtraInfo([Const] DOMString extraInfo); [Ref] ExpressionMetadata SetRequiresBaseObjectCapability([Const] DOMString capability); [Const, Ref] DOMString GetRequiredBaseObjectCapability(); [Ref] ExpressionCodeGenerationInformation GetCodeExtraInformation(); + + [Ref] ExpressionCodeGenerationInformation SetFunctionName([Const] DOMString functionName); + + [Ref] ExpressionMetadata SetIncludeFile([Const] DOMString includeFile); + [Ref] ExpressionMetadata AddIncludeFile([Const] DOMString includeFile); + [Const, Ref] VectorString GetIncludeFiles(); }; interface MultipleInstructionMetadata { @@ -1262,8 +1275,9 @@ interface MultipleInstructionMetadata { [Const] DOMString type, [Const] DOMString supplementaryInformation); [Ref] MultipleInstructionMetadata SetDefaultValue([Const] DOMString defaultValue); [Ref] MultipleInstructionMetadata SetParameterLongDescription([Const] DOMString longDescription); + [Ref] MultipleInstructionMetadata SetParameterExtraInfo([Const] DOMString extraInfo); - [Ref] MultipleInstructionMetadata UseStandardParameters([Const] DOMString type); + [Ref] MultipleInstructionMetadata UseStandardParameters([Const] DOMString type, [Const] optional DOMString typeExtraInfo = ""); [Ref] MultipleInstructionMetadata SetHidden(); @@ -1272,10 +1286,12 @@ interface MultipleInstructionMetadata { [Ref] MultipleInstructionMetadata SetIncludeFile([Const] DOMString includeFile); [Ref] MultipleInstructionMetadata AddIncludeFile([Const] DOMString includeFile); + [Const, Ref] VectorString GetIncludeFiles(); [Ref] MultipleInstructionMetadata MarkAsSimple(); [Ref] MultipleInstructionMetadata MarkAsAdvanced(); [Ref] MultipleInstructionMetadata MarkAsComplex(); + [Ref] MultipleInstructionMetadata SetPrivate(); }; interface DependencyMetadata { @@ -1317,6 +1333,10 @@ interface ParameterMetadata { [Ref] ParameterMetadata SetCodeOnly(boolean codeOnly_); [Const, Ref] DOMString GetDefaultValue(); [Ref] ParameterMetadata SetDefaultValue([Const] DOMString defaultValue_); + + [Ref] ParameterMetadata SetValueTypeMetadata([Const, Ref] ValueTypeMetadata type); + [Const, Ref] ValueTypeMetadata GetValueTypeMetadata(); + boolean STATIC_IsObject([Const] DOMString param); boolean STATIC_IsBehavior([Const] DOMString param); boolean STATIC_IsExpression([Const] DOMString type_, [Const] DOMString parameterType); @@ -1325,6 +1345,33 @@ interface ParameterMetadata { void UnserializeFrom([Const, Ref] SerializerElement element); }; +interface ValueTypeMetadata { + void ValueTypeMetadata(); + + [Const, Ref] DOMString GetName(); + [Ref] ValueTypeMetadata SetName([Const] DOMString name_); + [Const, Ref] DOMString GetExtraInfo(); + [Ref] ValueTypeMetadata SetExtraInfo([Const] DOMString extraInfo_); + boolean IsOptional(); + [Ref] ValueTypeMetadata SetOptional(boolean optional_); + [Const, Ref] DOMString GetDefaultValue(); + [Ref] ValueTypeMetadata SetDefaultValue([Const] DOMString defaultValue_); + + boolean IsObject(); + boolean IsBehavior(); + boolean IsNumber(); + boolean IsString(); + boolean IsVariable(); + + boolean STATIC_IsTypeObject([Const] DOMString parameterType); + boolean STATIC_IsTypeBehavior([Const] DOMString parameterType); + boolean STATIC_IsTypeExpression([Const] DOMString type, [Const] DOMString parameterType); + [Const, Ref] DOMString STATIC_GetPrimitiveValueType([Const] DOMString parameterType); + + void SerializeTo([Ref] SerializerElement element); + void UnserializeFrom([Const, Ref] SerializerElement element); +}; + interface VectorParameterMetadata { void VectorParameterMetadata(); @@ -1343,7 +1390,7 @@ interface ParameterMetadataTools { }; interface EventsFunctionTools { - void STATIC_FreeEventsFunctionToObjectsContainer([Ref] Project project, [Const, Ref] EventsFunction eventsFunction, [Ref] ObjectsContainer outputGlobalObjectsContainer, [Ref] ObjectsContainer outputObjectsContainer); + void STATIC_FreeEventsFunctionToObjectsContainer([Ref] Project project, [Const, Ref] EventsFunctionsContainer functionsContainer, [Const, Ref] EventsFunction eventsFunction, [Ref] ObjectsContainer outputGlobalObjectsContainer, [Ref] ObjectsContainer outputObjectsContainer); void STATIC_BehaviorEventsFunctionToObjectsContainer([Ref] Project project, [Const, Ref] EventsBasedBehavior eventsBasedBehavior, [Const, Ref] EventsFunction eventsFunction, [Ref] ObjectsContainer outputGlobalObjectsContainer, [Ref] ObjectsContainer outputObjectsContainer); void STATIC_ObjectEventsFunctionToObjectsContainer([Ref] Project project, [Const, Ref] EventsBasedObject eventsBasedObject, [Const, Ref] EventsFunction eventsFunction, [Ref] ObjectsContainer outputGlobalObjectsContainer, [Ref] ObjectsContainer outputObjectsContainer); }; @@ -2243,7 +2290,8 @@ enum EventsFunction_FunctionType { "EventsFunction::Action", "EventsFunction::Condition", "EventsFunction::Expression", - "EventsFunction::StringExpression" + "EventsFunction::ExpressionAndCondition", + "EventsFunction::ActionWithOperator" }; interface EventsFunction { @@ -2260,13 +2308,21 @@ interface EventsFunction { [Const, Ref] DOMString GetSentence(); [Ref] EventsFunction SetGroup([Const] DOMString group); [Const, Ref] DOMString GetGroup(); + [Ref] EventsFunction SetGetterName([Const] DOMString group); + [Const, Ref] DOMString GetGetterName(); + [Ref] EventsFunction SetExpressionType([Const, Ref] ValueTypeMetadata type); + [Const, Ref] ValueTypeMetadata GetExpressionType(); [Ref] EventsFunction SetPrivate(boolean isPrivate); boolean IsPrivate(); + boolean IsAction(); + boolean IsExpression(); + boolean IsCondition(); [Ref] EventsFunction SetFunctionType(EventsFunction_FunctionType type); EventsFunction_FunctionType GetFunctionType(); [Ref] EventsList GetEvents(); [Ref] VectorParameterMetadata GetParameters(); + [Const, Ref] VectorParameterMetadata GetParametersForEvents([Const, Ref] EventsFunctionsContainer functionsContainer); [Ref] ObjectGroupsContainer GetObjectGroups(); void SerializeTo([Ref] SerializerElement element); @@ -2947,7 +3003,7 @@ interface ObjectCodeGenerator { [Prefix="gdjs::"] interface EventsFunctionsExtensionCodeGenerator { void EventsFunctionsExtensionCodeGenerator([Ref] Project project); - [Const, Value] DOMString GenerateFreeEventsFunctionCompleteCode([Const, Ref] EventsFunction eventsFunction, [Const] DOMString codeNamespac, [Ref] SetString includes, boolean compilationForRuntime); + [Const, Value] DOMString GenerateFreeEventsFunctionCompleteCode([Const, Ref] EventsFunctionsExtension extension, [Const, Ref] EventsFunction eventsFunction, [Const] DOMString codeNamespac, [Ref] SetString includes, boolean compilationForRuntime); }; [Prefix="gdjs::"] diff --git a/GDevelop.js/Bindings/Wrapper.cpp b/GDevelop.js/Bindings/Wrapper.cpp index 1ccdd057fd..a5295b0753 100644 --- a/GDevelop.js/Bindings/Wrapper.cpp +++ b/GDevelop.js/Bindings/Wrapper.cpp @@ -511,6 +511,11 @@ typedef ExtensionAndMetadata ExtensionAndExpressionMetadata; #define STATIC_IsObject IsObject #define STATIC_IsBehavior IsBehavior #define STATIC_IsExpression IsExpression +#define STATIC_IsTypeObject IsTypeObject +#define STATIC_IsTypeBehavior IsTypeBehavior +#define STATIC_IsTypeExpression IsTypeExpression +#define STATIC_GetExpressionValueType GetExpressionValueType +#define STATIC_GetPrimitiveValueType GetPrimitiveValueType #define STATIC_Get Get #define STATIC_GetAllUseless GetAllUseless #define STATIC_RemoveAllUseless RemoveAllUseless diff --git a/GDevelop.js/TestUtils/CodeGenerationHelpers.js b/GDevelop.js/TestUtils/CodeGenerationHelpers.js index 3adf50a6b1..f21f5d0a6a 100644 --- a/GDevelop.js/TestUtils/CodeGenerationHelpers.js +++ b/GDevelop.js/TestUtils/CodeGenerationHelpers.js @@ -17,8 +17,10 @@ function generateCompiledEventsForEventsFunction( new gd.EventsFunctionsExtensionCodeGenerator(project); const includeFiles = new gd.SetString(); + const extension = new gd.EventsFunctionsExtension(); const code = eventsFunctionsExtensionCodeGenerator.generateFreeEventsFunctionCompleteCode( + extension, eventsFunction, namespace, includeFiles, @@ -26,6 +28,7 @@ function generateCompiledEventsForEventsFunction( ); eventsFunctionsExtensionCodeGenerator.delete(); + extension.delete(); includeFiles.delete(); if (logCode) console.log(code); diff --git a/GDevelop.js/__tests__/Core.js b/GDevelop.js/__tests__/Core.js index c909a4efc0..69061fe249 100644 --- a/GDevelop.js/__tests__/Core.js +++ b/GDevelop.js/__tests__/Core.js @@ -2468,8 +2468,11 @@ describe('libGD.js', function () { expect(parametersLister.getParametersAndTypes().get('MyObject')).toBe( 'object' ); + // There are a lot of parameter definitions with 'expression' instead + // of 'number'. They both means the same thing but 'expression' is + // deprecated. expect(parametersLister.getParametersAndTypes().get('300')).toBe( - 'expression' + 'number' ); project.delete(); diff --git a/GDevelop.js/__tests__/GDJS.js b/GDevelop.js/__tests__/GDJS.js index 7925e32f86..ab1311fd92 100644 --- a/GDevelop.js/__tests__/GDJS.js +++ b/GDevelop.js/__tests__/GDJS.js @@ -475,10 +475,12 @@ describe('libGD.js - GDJS related tests', function () { gd.asRepeatEvent(evt).getActions().insert(action2, 1); const namespace = 'gdjs.eventsFunction.myTest'; + const extension = new gd.EventsFunctionsExtension(); const eventsFunctionsExtensionCodeGenerator = new gd.EventsFunctionsExtensionCodeGenerator(project); const code = eventsFunctionsExtensionCodeGenerator.generateFreeEventsFunctionCompleteCode( + extension, eventsFunction, namespace, includeFiles, @@ -531,6 +533,7 @@ describe('libGD.js - GDJS related tests', function () { condition.delete(); action.delete(); + extension.delete(); }); it('can generate code for an events function, with groups', function () { @@ -583,6 +586,7 @@ describe('libGD.js - GDJS related tests', function () { new gd.EventsFunctionsExtensionCodeGenerator(project); const code = eventsFunctionsExtensionCodeGenerator.generateFreeEventsFunctionCompleteCode( + new gd.EventsFunctionsExtension(), eventsFunction, namespace, includeFiles, diff --git a/GDevelop.js/scripts/generate-types.js b/GDevelop.js/scripts/generate-types.js index 929ec4dade..babaecc813 100644 --- a/GDevelop.js/scripts/generate-types.js +++ b/GDevelop.js/scripts/generate-types.js @@ -82,7 +82,7 @@ declare type gdEmscriptenObject = { fs.writeFileSync( 'types/eventsfunction_functiontype.js', `// Automatically generated by GDevelop.js/scripts/generate-types.js -type EventsFunction_FunctionType = 0 | 1 | 2 | 3` +type EventsFunction_FunctionType = 0 | 1 | 2 | 3 | 4` ); shell.sed( '-i', @@ -92,7 +92,8 @@ type EventsFunction_FunctionType = 0 | 1 | 2 | 3` ' static Action: 0;', ' static Condition: 1;', ' static Expression: 2;', - ' static StringExpression: 3;', + ' static ExpressionAndCondition: 3;', + ' static ActionWithOperator: 4;', ].join('\n'), 'types/gdeventsfunction.js' ); diff --git a/GDevelop.js/types/eventsfunction_functiontype.js b/GDevelop.js/types/eventsfunction_functiontype.js index 065e3ca89b..5249f7f0b7 100644 --- a/GDevelop.js/types/eventsfunction_functiontype.js +++ b/GDevelop.js/types/eventsfunction_functiontype.js @@ -1,2 +1,2 @@ // Automatically generated by GDevelop.js/scripts/generate-types.js -type EventsFunction_FunctionType = 0 | 1 | 2 | 3 \ No newline at end of file +type EventsFunction_FunctionType = 0 | 1 | 2 | 3 | 4 \ No newline at end of file diff --git a/GDevelop.js/types/gdeventsfunction.js b/GDevelop.js/types/gdeventsfunction.js index bbac079573..39c5b8f07f 100644 --- a/GDevelop.js/types/gdeventsfunction.js +++ b/GDevelop.js/types/gdeventsfunction.js @@ -3,7 +3,8 @@ declare class gdEventsFunction { static Action: 0; static Condition: 1; static Expression: 2; - static StringExpression: 3; + static ExpressionAndCondition: 3; + static ActionWithOperator: 4; constructor(): void; clone(): gdEventsFunction; setDescription(description: string): gdEventsFunction; @@ -16,12 +17,20 @@ declare class gdEventsFunction { getSentence(): string; setGroup(group: string): gdEventsFunction; getGroup(): string; + setGetterName(group: string): gdEventsFunction; + getGetterName(): string; + setExpressionType(type: gdValueTypeMetadata): gdEventsFunction; + getExpressionType(): gdValueTypeMetadata; setPrivate(isPrivate: boolean): gdEventsFunction; isPrivate(): boolean; + isAction(): boolean; + isExpression(): boolean; + isCondition(): boolean; setFunctionType(type: EventsFunction_FunctionType): gdEventsFunction; getFunctionType(): EventsFunction_FunctionType; getEvents(): gdEventsList; getParameters(): gdVectorParameterMetadata; + getParametersForEvents(functionsContainer: gdEventsFunctionsContainer): gdVectorParameterMetadata; getObjectGroups(): gdObjectGroupsContainer; serializeTo(element: gdSerializerElement): void; unserializeFrom(project: gdProject, element: gdSerializerElement): void; diff --git a/GDevelop.js/types/gdeventsfunctionsextensioncodegenerator.js b/GDevelop.js/types/gdeventsfunctionsextensioncodegenerator.js index b04cdb1101..db24b4f750 100644 --- a/GDevelop.js/types/gdeventsfunctionsextensioncodegenerator.js +++ b/GDevelop.js/types/gdeventsfunctionsextensioncodegenerator.js @@ -1,7 +1,7 @@ // Automatically generated by GDevelop.js/scripts/generate-types.js declare class gdEventsFunctionsExtensionCodeGenerator { constructor(project: gdProject): void; - generateFreeEventsFunctionCompleteCode(eventsFunction: gdEventsFunction, codeNamespac: string, includes: gdSetString, compilationForRuntime: boolean): string; + generateFreeEventsFunctionCompleteCode(extension: gdEventsFunctionsExtension, eventsFunction: gdEventsFunction, codeNamespac: string, includes: gdSetString, compilationForRuntime: boolean): string; delete(): void; ptr: number; }; \ No newline at end of file diff --git a/GDevelop.js/types/gdeventsfunctiontools.js b/GDevelop.js/types/gdeventsfunctiontools.js index 863cd25436..3022a57904 100644 --- a/GDevelop.js/types/gdeventsfunctiontools.js +++ b/GDevelop.js/types/gdeventsfunctiontools.js @@ -1,6 +1,6 @@ // Automatically generated by GDevelop.js/scripts/generate-types.js declare class gdEventsFunctionTools { - static freeEventsFunctionToObjectsContainer(project: gdProject, eventsFunction: gdEventsFunction, outputGlobalObjectsContainer: gdObjectsContainer, outputObjectsContainer: gdObjectsContainer): void; + static freeEventsFunctionToObjectsContainer(project: gdProject, functionsContainer: gdEventsFunctionsContainer, eventsFunction: gdEventsFunction, outputGlobalObjectsContainer: gdObjectsContainer, outputObjectsContainer: gdObjectsContainer): void; static behaviorEventsFunctionToObjectsContainer(project: gdProject, eventsBasedBehavior: gdEventsBasedBehavior, eventsFunction: gdEventsFunction, outputGlobalObjectsContainer: gdObjectsContainer, outputObjectsContainer: gdObjectsContainer): void; static objectEventsFunctionToObjectsContainer(project: gdProject, eventsBasedObject: gdEventsBasedObject, eventsFunction: gdEventsFunction, outputGlobalObjectsContainer: gdObjectsContainer, outputObjectsContainer: gdObjectsContainer): void; delete(): void; diff --git a/GDevelop.js/types/gdexpressionmetadata.js b/GDevelop.js/types/gdexpressionmetadata.js index a9a8fd3126..a09067e434 100644 --- a/GDevelop.js/types/gdexpressionmetadata.js +++ b/GDevelop.js/types/gdexpressionmetadata.js @@ -18,9 +18,14 @@ declare class gdExpressionMetadata { addCodeOnlyParameter(type: string, supplementaryInformation: string): gdExpressionMetadata; setDefaultValue(defaultValue: string): gdExpressionMetadata; setParameterLongDescription(longDescription: string): gdExpressionMetadata; + setParameterExtraInfo(extraInfo: string): gdExpressionMetadata; setRequiresBaseObjectCapability(capability: string): gdExpressionMetadata; getRequiredBaseObjectCapability(): string; getCodeExtraInformation(): gdExpressionCodeGenerationInformation; + setFunctionName(functionName: string): gdExpressionCodeGenerationInformation; + setIncludeFile(includeFile: string): gdExpressionMetadata; + addIncludeFile(includeFile: string): gdExpressionMetadata; + getIncludeFiles(): gdVectorString; delete(): void; ptr: number; }; \ No newline at end of file diff --git a/GDevelop.js/types/gdinstructionmetadata.js b/GDevelop.js/types/gdinstructionmetadata.js index 4b6c1fd2d6..487d7c006c 100644 --- a/GDevelop.js/types/gdinstructionmetadata.js +++ b/GDevelop.js/types/gdinstructionmetadata.js @@ -26,14 +26,18 @@ declare class gdInstructionMetadata { setDefaultValue(defaultValue: string): gdInstructionMetadata; setParameterLongDescription(longDescription: string): gdInstructionMetadata; setParameterExtraInfo(extraInfo: string): gdInstructionMetadata; - useStandardOperatorParameters(type: string): gdInstructionMetadata; - useStandardRelationalOperatorParameters(type: string): gdInstructionMetadata; + useStandardOperatorParameters(type: string, typeExtraInfo?: string): gdInstructionMetadata; + useStandardRelationalOperatorParameters(type: string, typeExtraInfo?: string): gdInstructionMetadata; setRequiresBaseObjectCapability(capability: string): gdInstructionMetadata; getRequiredBaseObjectCapability(): string; markAsSimple(): gdInstructionMetadata; markAsAdvanced(): gdInstructionMetadata; markAsComplex(): gdInstructionMetadata; getCodeExtraInformation(): gdExtraInformation; + setFunctionName(functionName: string): gdExtraInformation; + setIncludeFile(includeFile: string): gdInstructionMetadata; + addIncludeFile(includeFile: string): gdInstructionMetadata; + getIncludeFiles(): gdVectorString; delete(): void; ptr: number; }; \ No newline at end of file diff --git a/GDevelop.js/types/gdmultipleinstructionmetadata.js b/GDevelop.js/types/gdmultipleinstructionmetadata.js index 5e44ecf524..94e6dd26ae 100644 --- a/GDevelop.js/types/gdmultipleinstructionmetadata.js +++ b/GDevelop.js/types/gdmultipleinstructionmetadata.js @@ -4,15 +4,18 @@ declare class gdMultipleInstructionMetadata { addCodeOnlyParameter(type: string, supplementaryInformation: string): gdMultipleInstructionMetadata; setDefaultValue(defaultValue: string): gdMultipleInstructionMetadata; setParameterLongDescription(longDescription: string): gdMultipleInstructionMetadata; - useStandardParameters(type: string): gdMultipleInstructionMetadata; + setParameterExtraInfo(extraInfo: string): gdMultipleInstructionMetadata; + useStandardParameters(type: string, typeExtraInfo?: string): gdMultipleInstructionMetadata; setHidden(): gdMultipleInstructionMetadata; setFunctionName(functionName: string): gdMultipleInstructionMetadata; setGetter(getter: string): gdMultipleInstructionMetadata; setIncludeFile(includeFile: string): gdMultipleInstructionMetadata; addIncludeFile(includeFile: string): gdMultipleInstructionMetadata; + getIncludeFiles(): gdVectorString; markAsSimple(): gdMultipleInstructionMetadata; markAsAdvanced(): gdMultipleInstructionMetadata; markAsComplex(): gdMultipleInstructionMetadata; + setPrivate(): gdMultipleInstructionMetadata; delete(): void; ptr: number; }; \ No newline at end of file diff --git a/GDevelop.js/types/gdparametermetadata.js b/GDevelop.js/types/gdparametermetadata.js index 4bb43f628b..93cab19b49 100644 --- a/GDevelop.js/types/gdparametermetadata.js +++ b/GDevelop.js/types/gdparametermetadata.js @@ -17,6 +17,8 @@ declare class gdParameterMetadata { setCodeOnly(codeOnly_: boolean): gdParameterMetadata; getDefaultValue(): string; setDefaultValue(defaultValue_: string): gdParameterMetadata; + setValueTypeMetadata(type: gdValueTypeMetadata): gdParameterMetadata; + getValueTypeMetadata(): gdValueTypeMetadata; static isObject(param: string): boolean; static isBehavior(param: string): boolean; static isExpression(type_: string, parameterType: string): boolean; diff --git a/GDevelop.js/types/gdvaluetypemetadata.js b/GDevelop.js/types/gdvaluetypemetadata.js new file mode 100644 index 0000000000..2a8f74e7fc --- /dev/null +++ b/GDevelop.js/types/gdvaluetypemetadata.js @@ -0,0 +1,25 @@ +// Automatically generated by GDevelop.js/scripts/generate-types.js +declare class gdValueTypeMetadata { + constructor(): void; + getName(): string; + setName(name_: string): gdValueTypeMetadata; + getExtraInfo(): string; + setExtraInfo(extraInfo_: string): gdValueTypeMetadata; + isOptional(): boolean; + setOptional(optional_: boolean): gdValueTypeMetadata; + getDefaultValue(): string; + setDefaultValue(defaultValue_: string): gdValueTypeMetadata; + isObject(): boolean; + isBehavior(): boolean; + isNumber(): boolean; + isString(): boolean; + isVariable(): boolean; + static isTypeObject(parameterType: string): boolean; + static isTypeBehavior(parameterType: string): boolean; + static isTypeExpression(type: string, parameterType: string): boolean; + static getPrimitiveValueType(parameterType: string): string; + serializeTo(element: gdSerializerElement): void; + unserializeFrom(element: gdSerializerElement): void; + 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 5ec256f337..16ac5f02e5 100644 --- a/GDevelop.js/types/libgdevelop.js +++ b/GDevelop.js/types/libgdevelop.js @@ -119,6 +119,7 @@ declare class libGDevelop { MultipleInstructionMetadata: Class; DependencyMetadata: Class; ParameterMetadata: Class; + ValueTypeMetadata: Class; VectorParameterMetadata: Class; ParameterMetadataTools: Class; EventsFunctionTools: Class; diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionParametersEditor.js b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionParametersEditor.js index 642e2462f0..16d42a2a0b 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionParametersEditor.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionParametersEditor.js @@ -5,8 +5,6 @@ import { I18n } from '@lingui/react'; import { type I18n as I18nType } from '@lingui/core'; import * as React from 'react'; import { Column, Line, Spacer } from '../../UI/Grid'; -import SelectField from '../../UI/SelectField'; -import SelectOption from '../../UI/SelectOption'; import { mapVector } from '../../Utils/MapFor'; import RaisedButton from '../../UI/RaisedButton'; import IconButton from '../../UI/IconButton'; @@ -17,8 +15,6 @@ import HelpButton from '../../UI/HelpButton'; import SemiControlledTextField from '../../UI/SemiControlledTextField'; import MiniToolbar, { MiniToolbarText } from '../../UI/MiniToolbar'; import { showWarningBox } from '../../UI/Messages/MessageBox'; -import ObjectTypeSelector from '../../ObjectTypeSelector'; -import BehaviorTypeSelector from '../../BehaviorTypeSelector'; import { isBehaviorLifecycleEventsFunction, isExtensionLifecycleEventsFunction, @@ -26,10 +22,10 @@ import { import { ParametersIndexOffsets } from '../../EventsFunctionsExtensionsLoader'; import Add from '@material-ui/icons/Add'; import DismissableAlertMessage from '../../UI/DismissableAlertMessage'; -import { ColumnStackLayout, ResponsiveLineStackLayout } from '../../UI/Layout'; +import { ColumnStackLayout } from '../../UI/Layout'; import { getLastObjectParameterObjectType } from '../../EventsSheet/ParameterFields/ParameterMetadataTools'; -import StringArrayEditor from '../../StringArrayEditor'; import newNameGenerator from '../../Utils/NewNameGenerator'; +import ValueTypeEditor from './ValueTypeEditor'; const gd: libGDevelop = global.gd; @@ -38,6 +34,7 @@ type Props = {| eventsFunction: gdEventsFunction, eventsBasedBehavior: ?gdEventsBasedBehavior, eventsBasedObject: ?gdEventsBasedObject, + eventsFunctionsContainer: ?gdEventsFunctionsContainer, onParametersUpdated: () => void, helpPagePath?: string, freezeParameters?: boolean, @@ -97,27 +94,6 @@ const validateParameterName = (i18n: I18nType, newName: string) => { return true; }; -const getExtraInfoArray = (parameter: gdParameterMetadata) => { - const extraInfoJson = parameter.getExtraInfo(); - let array = []; - try { - if (extraInfoJson !== '') array = JSON.parse(extraInfoJson); - if (!Array.isArray(array)) array = []; - } catch (e) { - console.error('Cannot parse parameter extraInfo: ', e); - } - - 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 @@ -244,11 +220,11 @@ export default class EventsFunctionParametersEditor extends React.Component< eventsFunction, eventsBasedBehavior, eventsBasedObject, + eventsFunctionsContainer, freezeParameters, helpPagePath, } = this.props; - const parameters = eventsFunction.getParameters(); const isABehaviorLifecycleEventsFunction = !!eventsBasedBehavior && isBehaviorLifecycleEventsFunction(eventsFunction.getName()); @@ -292,29 +268,48 @@ export default class EventsFunctionParametersEditor extends React.Component< ); } + const parameters = + eventsFunctionsContainer && + eventsFunction.getFunctionType() === gd.EventsFunction.ActionWithOperator + ? eventsFunction.getParametersForEvents(eventsFunctionsContainer) + : eventsFunction.getParameters(); + + const firstParameterIndex = eventsBasedBehavior + ? 2 + : eventsBasedObject + ? 1 + : 0; const isParameterDisabled = index => { return ( - !!freezeParameters || - (!!eventsBasedBehavior && index < 2) || - (!!eventsBasedObject && index < 1) + eventsFunction.getFunctionType() === + gd.EventsFunction.ActionWithOperator || + freezeParameters || + index < firstParameterIndex ); }; - const isParameterDescriptionAndTypeShown = index => { - // The first two parameters of a behavior method should not be changed at all, - // so we even hide their description and type to avoid cluttering the interface. - // Same thing for an object which has mandatory Object parameter. - return ( - (!eventsBasedBehavior && !eventsBasedObject) || - (!!eventsBasedBehavior && index >= 2) || - (!!eventsBasedObject && index >= 1) - ); + // The first two parameters of a behavior method should not be changed at all, + // so we even hide their description and type to avoid cluttering the interface. + // Same thing for an object which has mandatory Object parameter. + const typeShownFirstIndex = firstParameterIndex; + const isParameterTypeShown = index => { + return index >= typeShownFirstIndex; + }; + // The first two parameters of a behavior method should not be changed at all, + // so we even hide their description and type to avoid cluttering the interface. + // Same thing for an object which has mandatory Object parameter. + const labelShownFirstIndex = + firstParameterIndex + + (eventsFunction.getFunctionType() === gd.EventsFunction.ActionWithOperator + ? 1 + : 0); + const isParameterDescriptionShown = index => { + return index >= labelShownFirstIndex; }; const isParameterLongDescriptionShown = (parameter, index): boolean => { - if (!isParameterDescriptionAndTypeShown(index)) return false; - return ( - !!parameter.getLongDescription() || - !!this.state.longDescriptionShownIndexes[index] + isParameterDescriptionShown(index) && + (!!parameter.getLongDescription() || + !!this.state.longDescriptionShownIndexes[index]) ); }; const parametersIndexOffset = eventsBasedBehavior @@ -406,221 +401,19 @@ export default class EventsFunctionParametersEditor extends React.Component< - - {isParameterDescriptionAndTypeShown(i) && ( - Type} - value={parameter.getType()} - onChange={(e, i, value: string) => { - parameter.setType(value); - parameter.setOptional(false); - parameter.setDefaultValue(''); - this.forceUpdate(); - this.props.onParametersUpdated(); - }} - disabled={isParameterDisabled(i)} - fullWidth - > - - - - - - - - - - - - - - - - - )} - {gd.ParameterMetadata.isObject( - parameter.getType() - ) && ( - { - parameter.setExtraInfo(value); - this.forceUpdate(); - this.props.onParametersUpdated(); - }} - disabled={isParameterDisabled(i)} - /> - )} - {parameter.getType() === 'behavior' && ( - { - parameter.setExtraInfo(value); - this.forceUpdate(); - this.props.onParametersUpdated(); - }} - disabled={isParameterDisabled(i)} - /> - )} - {parameter.getType() === 'yesorno' && ( - Default value} - value={ - parameter.getDefaultValue() === 'yes' - ? 'yes' - : 'no' - } - onChange={(e, i, value) => { - parameter.setOptional(true); - parameter.setDefaultValue(value); - this.forceUpdate(); - this.props.onParametersUpdated(); - }} - fullWidth - > - - - - )} - {parameter.getType() === 'trueorfalse' && ( - Default value} - value={ - parameter.getDefaultValue() === 'True' - ? 'True' - : 'False' - } - onChange={(e, i, value) => { - parameter.setOptional(true); - parameter.setDefaultValue(value); - this.forceUpdate(); - this.props.onParametersUpdated(); - }} - fullWidth - > - - - - )} - {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' && ( - - )} - {isParameterDescriptionAndTypeShown(i) && ( + + this.props.onParametersUpdated() + } + getLastObjectParameterObjectType={() => + getLastObjectParameterObjectType(parameters, i) + } + /> + {isParameterDescriptionShown(i) && ( Label} @@ -632,7 +425,8 @@ export default class EventsFunctionParametersEditor extends React.Component< }} fullWidth disabled={ - false /* Label, if shown, can always be changed */ + /* When parameter are freezed, long description (if shown) can always be changed */ + isParameterDisabled(i) && !freezeParameters } /> )} @@ -651,7 +445,8 @@ export default class EventsFunctionParametersEditor extends React.Component< multiline fullWidth disabled={ - false /* Long description, if shown, can always be changed */ + /* When parameter are freezed, long description (if shown) can always be changed */ + isParameterDisabled(i) && !freezeParameters } /> )} @@ -673,6 +468,10 @@ export default class EventsFunctionParametersEditor extends React.Component< label={Add a parameter} onClick={this._addParameter} icon={} + disabled={ + eventsFunction.getFunctionType() === + gd.EventsFunction.ActionWithOperator + } /> )} diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionPropertiesEditor.js b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionPropertiesEditor.js index e4c0e432e6..900f921a1a 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionPropertiesEditor.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionPropertiesEditor.js @@ -8,7 +8,7 @@ import * as React from 'react'; import { Column, Line, Spacer } from '../../UI/Grid'; import SelectField from '../../UI/SelectField'; import SelectOption from '../../UI/SelectOption'; -import { mapVector } from '../../Utils/MapFor'; +import { mapVector, mapFor } from '../../Utils/MapFor'; import HelpButton from '../../UI/HelpButton'; import SemiControlledTextField from '../../UI/SemiControlledTextField'; import { @@ -22,13 +22,16 @@ import { type MessageDescriptor } from '../../Utils/i18n/MessageDescriptor.flow' import { ResponsiveLineStackLayout, ColumnStackLayout } from '../../UI/Layout'; import DismissableAlertMessage from '../../UI/DismissableAlertMessage'; import SemiControlledAutoComplete from '../../UI/SemiControlledAutoComplete'; +import ValueTypeEditor from './ValueTypeEditor'; const gd: libGDevelop = global.gd; type Props = {| + project: gdProject, eventsFunction: gdEventsFunction, eventsBasedBehavior: ?gdEventsBasedBehavior, eventsBasedObject: ?gdEventsBasedObject, + eventsFunctionsContainer: ?gdEventsFunctionsContainer, helpPagePath?: string, onConfigurationUpdated?: (whatChanged?: 'type') => void, renderConfigurationHeader?: () => React.Node, @@ -56,11 +59,19 @@ export const getSentenceErrorText = ( ? ParametersIndexOffsets.ObjectFunction : ParametersIndexOffsets.FreeFunction; + const type = eventsFunction.getFunctionType(); + const param0isImplicit = + (eventsBasedBehavior || eventsBasedObject) && + type === gd.EventsFunction.ExpressionAndCondition; const missingParameters = mapVector( eventsFunction.getParameters(), (parameter, index) => { - if (gd.ParameterMetadata.isBehavior(parameter.getType())) { - return null; // Behaviors are usually not shown in sentences. + if (parameter.getValueTypeMetadata().isBehavior()) { + // Behaviors are usually not shown in sentences. + return null; + } + if (index === 0 && param0isImplicit) { + return null; } const expectedString = `_PARAM${index + parametersIndexOffset}_`; @@ -103,27 +114,32 @@ export const getSentenceErrorText = ( return undefined; }; -const getFullNameHintText = (type: any): MessageDescriptor => { +const getFullNameHintText = ( + type: EventsFunction_FunctionType, + expressionType: gdValueTypeMetadata +): MessageDescriptor => { if (type === gd.EventsFunction.Condition) { return t`Example: Is flashing`; } else if (type === gd.EventsFunction.Expression) { - return t`Example: Remaining life`; - } else if (type === gd.EventsFunction.StringExpression) { - return t`Example: Equipped shield name`; + return expressionType.isNumber() + ? t`Example: Remaining life` + : t`Example: Equipped shield name`; } return t`Example: Flash the object`; }; -const getDescriptionHintText = (type: any): MessageDescriptor => { +const getDescriptionHintText = ( + type: EventsFunction_FunctionType, + expressionType: gdValueTypeMetadata +): MessageDescriptor => { if (type === gd.EventsFunction.Condition) { return t`Example: Check if the object is flashing.`; } else if (type === gd.EventsFunction.Expression) { - return t`Example: Return the number of remaining lives for the player.`; - } else if (type === gd.EventsFunction.StringExpression) { - return t`Example: Return the name of the shield equipped by the player.`; + return expressionType.isNumber() + ? t`Example: Return the number of remaining lives for the player.` + : t`Example: Return the name of the shield equipped by the player.`; } - return t`Example: Make the object flash for 5 seconds.`; }; @@ -133,6 +149,7 @@ export default class EventsFunctionPropertiesEditor extends React.Component< > { render() { const { + project, eventsFunction, freezeEventsFunctionType, onConfigurationUpdated, @@ -141,6 +158,7 @@ export default class EventsFunctionPropertiesEditor extends React.Component< eventsBasedBehavior, eventsBasedObject, getFunctionGroupNames, + eventsFunctionsContainer, } = this.props; const type = eventsFunction.getFunctionType(); @@ -205,6 +223,17 @@ export default class EventsFunctionPropertiesEditor extends React.Component< ); } + const getterFunction = + eventsFunctionsContainer && + type === gd.EventsFunction.ActionWithOperator && + eventsFunctionsContainer.hasEventsFunctionNamed( + eventsFunction.getGetterName() + ) + ? eventsFunctionsContainer.getEventsFunction( + eventsFunction.getGetterName() + ) + : null; + return ( {({ i18n }) => ( @@ -217,8 +246,9 @@ export default class EventsFunctionPropertiesEditor extends React.Component< floatingLabelText={Function type} fullWidth disabled={!!freezeEventsFunctionType} - onChange={(e, i, value: string) => { + onChange={(e, i, valueString: string) => { // $FlowFixMe + const value: EventsFunction_FunctionType = valueString; eventsFunction.setFunctionType(value); if (onConfigurationUpdated) onConfigurationUpdated('type'); this.forceUpdate(); @@ -237,86 +267,218 @@ export default class EventsFunctionPropertiesEditor extends React.Component< primaryText={t`Expression`} /> + - Full name displayed in editor} - translatableHintText={getFullNameHintText(type)} - value={eventsFunction.getFullName()} - onChange={text => { - eventsFunction.setFullName(text); - if (onConfigurationUpdated) onConfigurationUpdated(); - this.forceUpdate(); - }} - fullWidth - /> + + {type === gd.EventsFunction.ActionWithOperator ? ( + Related action and expression + } + fullWidth + onChange={(e, i, value: string) => { + eventsFunction.setGetterName(value); + if (onConfigurationUpdated) onConfigurationUpdated(); + this.forceUpdate(); + }} + > + {eventsFunctionsContainer + ? mapFor( + 0, + eventsFunctionsContainer.getEventsFunctionsCount(), + i => { + const eventsFunction = eventsFunctionsContainer.getEventsFunctionAt( + i + ); + + return ( + eventsFunction.getFunctionType() === + gd.EventsFunction.ExpressionAndCondition && ( + + ) + ); + } + ) + : []} + + ) : ( + Full name displayed in editor + } + translatableHintText={getFullNameHintText( + type, + eventsFunction.getExpressionType() + )} + value={eventsFunction.getFullName()} + onChange={text => { + eventsFunction.setFullName(text); + if (onConfigurationUpdated) onConfigurationUpdated(); + this.forceUpdate(); + }} + fullWidth + /> + )} + + + {type === gd.EventsFunction.ActionWithOperator ? ( + Group name} + fullWidth + value={getterFunction ? getterFunction.getGroup() : ''} + onChange={text => {}} + /> + ) : ( + Group name} + hintText={t`Leave it empty to use the default group for this extension.`} + fullWidth + value={eventsFunction.getGroup()} + onChange={text => { + eventsFunction.setGroup(text); + if (onConfigurationUpdated) onConfigurationUpdated(); + this.forceUpdate(); + }} + dataSource={ + getFunctionGroupNames + ? getFunctionGroupNames().map(name => ({ + text: name, + value: name, + })) + : [] + } + openOnFocus={true} + /> + )} + - Group name} - hintText={t`Leave it empty to use the default group for this extension.`} - fullWidth - value={eventsFunction.getGroup()} - onChange={text => { - eventsFunction.setGroup(text); - if (onConfigurationUpdated) onConfigurationUpdated(); - this.forceUpdate(); - }} - dataSource={ - getFunctionGroupNames - ? getFunctionGroupNames().map(name => ({ - text: name, - value: name, - })) - : [] - } - openOnFocus={true} - /> - - - Description, displayed in editor - } - translatableHintText={getDescriptionHintText(type)} - fullWidth - multiline - value={eventsFunction.getDescription()} - onChange={text => { - eventsFunction.setDescription(text); - if (onConfigurationUpdated) onConfigurationUpdated(); - this.forceUpdate(); - }} - /> - - - {type === gd.EventsFunction.Action || - type === gd.EventsFunction.Condition ? ( + {type === gd.EventsFunction.ActionWithOperator ? ( + Description, displayed in editor + } + fullWidth + multiline + value={ + getterFunction + ? 'Change ' + getterFunction.getDescription() + : '' + } + onChange={text => {}} + /> + ) : ( Sentence in Events Sheet} - translatableHintText={t`Note: write _PARAMx_ for parameters, e.g: Flash _PARAM1_ for 5 seconds`} + floatingLabelText={ + type === gd.EventsFunction.ExpressionAndCondition ? ( + + Description, displayed in editor (automatically prefixed + by "Compare" or "Return") + + ) : ( + Description, displayed in editor + ) + } + translatableHintText={getDescriptionHintText( + type, + eventsFunction.getExpressionType() + )} fullWidth - value={eventsFunction.getSentence()} + multiline + value={eventsFunction.getDescription()} onChange={text => { - eventsFunction.setSentence(text); + eventsFunction.setDescription(text); if (onConfigurationUpdated) onConfigurationUpdated(); this.forceUpdate(); }} - errorText={getSentenceErrorText( - i18n, - eventsBasedBehavior, - eventsBasedObject, - eventsFunction - )} /> - ) : null} + )} + {type === gd.EventsFunction.ActionWithOperator ? ( + + Sentence in Events Sheet} + fullWidth + value={ + getterFunction + ? 'Change ' + + getterFunction.getSentence() + + ' of _PARAM0_' + : '' + } + onChange={text => {}} + /> + + ) : ( + (type === gd.EventsFunction.Action || + type === gd.EventsFunction.Condition || + type === gd.EventsFunction.ExpressionAndCondition) && ( + + + Sentence in Events Sheet (automatically suffixed by + "of _PARAM0_") + + ) : ( + Sentence in Events Sheet + ) + } + translatableHintText={t`Note: write _PARAMx_ for parameters, e.g: Flash _PARAM1_ for 5 seconds`} + fullWidth + value={eventsFunction.getSentence()} + onChange={text => { + eventsFunction.setSentence(text); + if (onConfigurationUpdated) onConfigurationUpdated(); + this.forceUpdate(); + }} + errorText={getSentenceErrorText( + i18n, + eventsBasedBehavior, + eventsBasedObject, + eventsFunction + )} + /> + + ) + )} + {eventsFunction.isExpression() && ( + { + if (onConfigurationUpdated) onConfigurationUpdated(); + }} + getLastObjectParameterObjectType={() => ''} + /> + )} {helpPagePath ? ( diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/ValueTypeEditor.js b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/ValueTypeEditor.js new file mode 100644 index 0000000000..8f24eb6e42 --- /dev/null +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/ValueTypeEditor.js @@ -0,0 +1,244 @@ +// @flow +import { Trans } from '@lingui/macro'; +import { t } from '@lingui/macro'; +import { I18n } from '@lingui/react'; +import * as React from 'react'; +import SelectField from '../../UI/SelectField'; +import SelectOption from '../../UI/SelectOption'; +import SemiControlledTextField from '../../UI/SemiControlledTextField'; +import ObjectTypeSelector from '../../ObjectTypeSelector'; +import BehaviorTypeSelector from '../../BehaviorTypeSelector'; +import { ColumnStackLayout, ResponsiveLineStackLayout } from '../../UI/Layout'; +import StringArrayEditor from '../../StringArrayEditor'; +import useForceUpdate from '../../Utils/UseForceUpdate'; + +type Props = {| + project: gdProject, + valueTypeMetadata: gdValueTypeMetadata, + onTypeUpdated: () => void, + disabled?: boolean, + isTypeSelectorShown: boolean, + isExpressionType?: boolean, + getLastObjectParameterObjectType: () => string, +|}; + +const getExtraInfoArray = (type: gdValueTypeMetadata) => { + const extraInfoJson = type.getExtraInfo(); + let array = []; + try { + if (extraInfoJson !== '') array = JSON.parse(extraInfoJson); + if (!Array.isArray(array)) array = []; + } catch (e) { + console.error('Cannot parse parameter extraInfo: ', e); + } + 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 function ValueTypeEditor({ + project, + valueTypeMetadata, + disabled, + isTypeSelectorShown, + onTypeUpdated, + getLastObjectParameterObjectType, + isExpressionType, +}: Props) { + const forceUpdate = useForceUpdate(); + + return ( + + {({ i18n }) => ( + + + {isTypeSelectorShown && ( + Type} + value={valueTypeMetadata.getName()} + onChange={(e, i, value: string) => { + valueTypeMetadata.setName(value); + valueTypeMetadata.setOptional(false); + valueTypeMetadata.setDefaultValue(''); + forceUpdate(); + onTypeUpdated(); + }} + disabled={disabled} + fullWidth + > + {!isExpressionType && ( + + )} + {!isExpressionType && ( + + )} + + + + + + + + + {!isExpressionType && ( + + )} + {!isExpressionType && ( + + )} + + + + + )} + {valueTypeMetadata.isObject() && ( + { + valueTypeMetadata.setExtraInfo(value); + forceUpdate(); + onTypeUpdated(); + }} + disabled={disabled} + /> + )} + {valueTypeMetadata.isBehavior() && ( + { + valueTypeMetadata.setExtraInfo(value); + forceUpdate(); + onTypeUpdated(); + }} + disabled={disabled} + /> + )} + {valueTypeMetadata.getName() === 'yesorno' && ( + Default value} + value={ + valueTypeMetadata.getDefaultValue() === 'yes' ? 'yes' : 'no' + } + onChange={(e, i, value) => { + valueTypeMetadata.setOptional(true); + valueTypeMetadata.setDefaultValue(value); + forceUpdate(); + onTypeUpdated(); + }} + fullWidth + > + + + + )} + {valueTypeMetadata.getName() === 'trueorfalse' && ( + Default value} + value={ + valueTypeMetadata.getDefaultValue() === 'True' + ? 'True' + : 'False' + } + onChange={(e, i, value) => { + valueTypeMetadata.setOptional(true); + valueTypeMetadata.setDefaultValue(value); + forceUpdate(); + onTypeUpdated(); + }} + fullWidth + > + + + + )} + {valueTypeMetadata.getName() === 'identifier' && ( + Scope} + value={getIdentifierScope(valueTypeMetadata.getExtraInfo())} + onChange={(e, i, value) => { + const identifierName = getIdentifierName( + valueTypeMetadata.getExtraInfo() + ); + valueTypeMetadata.setExtraInfo(value + identifierName); + forceUpdate(); + onTypeUpdated(); + }} + fullWidth + > + + + + )} + {valueTypeMetadata.getName() === 'identifier' && ( + Identifier name} + floatingLabelFixed + value={getIdentifierName(valueTypeMetadata.getExtraInfo())} + onChange={value => { + const scope = getIdentifierScope( + valueTypeMetadata.getExtraInfo() + ); + valueTypeMetadata.setExtraInfo(scope + value); + forceUpdate(); + onTypeUpdated(); + }} + fullWidth + /> + )} + + {valueTypeMetadata.getName() === 'stringWithSelector' && ( + ) => { + valueTypeMetadata.setExtraInfo(JSON.stringify(newExtraInfo)); + forceUpdate(); + onTypeUpdated(); + }} + /> + )} + + )} + + ); +} diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/index.js b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/index.js index e505f1b374..8d455d10c7 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/index.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/index.js @@ -22,6 +22,7 @@ type Props = {| eventsFunction: gdEventsFunction, eventsBasedBehavior: ?gdEventsBasedBehavior, eventsBasedObject: ?gdEventsBasedObject, + eventsFunctionsContainer: gdEventsFunctionsContainer, onParametersOrGroupsUpdated: () => void, helpPagePath?: string, onConfigurationUpdated?: (whatChanged?: 'type') => void, @@ -173,6 +174,7 @@ export default class EventsFunctionConfigurationEditor extends React.Component< onMoveBehaviorEventsParameter, onMoveObjectEventsParameter, getFunctionGroupNames, + eventsFunctionsContainer, } = this.props; return ( @@ -195,9 +197,11 @@ export default class EventsFunctionConfigurationEditor extends React.Component< { // Initialize this "context" of objects with the function // (as done during code generation). @@ -189,13 +190,18 @@ export default class EventsFunctionsExtensionEditor extends React.Component< this._globalObjectsContainer, this._objectsContainer ); - } else { + } else if (eventsFunctionsExtension) { gd.EventsFunctionTools.freeEventsFunctionToObjectsContainer( project, + eventsFunctionsExtension, eventsFunction, this._globalObjectsContainer, this._objectsContainer ); + } else { + throw new Error( + 'No extension, behavior or object was specified when loading a function' + ); } }; @@ -258,7 +264,8 @@ export default class EventsFunctionsExtensionEditor extends React.Component< this.props.project, selectedEventsFunction, selectedEventsBasedBehavior, - selectedEventsBasedObject + selectedEventsBasedObject, + this.props.eventsFunctionsExtension ); this.setState( { @@ -592,7 +599,9 @@ export default class EventsFunctionsExtensionEditor extends React.Component< this._loadEventsFunctionFrom( this.props.project, this.state.selectedEventsFunction, - this.state.selectedEventsBasedBehavior + this.state.selectedEventsBasedBehavior, + this.state.selectedEventsBasedObject, + this.props.eventsFunctionsExtension ); } }; @@ -611,7 +620,8 @@ export default class EventsFunctionsExtensionEditor extends React.Component< this.props.project, this.state.selectedEventsFunction, this.state.selectedEventsBasedBehavior, - this.state.selectedEventsBasedObject + this.state.selectedEventsBasedObject, + this.props.eventsFunctionsExtension ); } }; @@ -825,7 +835,8 @@ export default class EventsFunctionsExtensionEditor extends React.Component< this.props.project, this.state.selectedEventsFunction, this.state.selectedEventsBasedBehavior, - this.state.selectedEventsBasedObject + this.state.selectedEventsBasedObject, + this.props.eventsFunctionsExtension ); } } @@ -867,7 +878,8 @@ export default class EventsFunctionsExtensionEditor extends React.Component< this.props.project, this.state.selectedEventsFunction, this.state.selectedEventsBasedBehavior, - this.state.selectedEventsBasedObject + this.state.selectedEventsBasedObject, + this.props.eventsFunctionsExtension ); } } @@ -1000,6 +1012,9 @@ export default class EventsFunctionsExtensionEditor extends React.Component< editedEventsBasedObject, } = this.state; + const selectedEventsBasedEntity = + selectedEventsBasedBehavior || selectedEventsBasedObject; + const editors = { 'choose-editor': { type: 'primary', @@ -1029,6 +1044,11 @@ export default class EventsFunctionsExtensionEditor extends React.Component< eventsFunction={selectedEventsFunction} eventsBasedBehavior={selectedEventsBasedBehavior} eventsBasedObject={selectedEventsBasedObject} + eventsFunctionsContainer={ + (selectedEventsBasedEntity && + selectedEventsBasedEntity.getEventsFunctions()) || + eventsFunctionsExtension + } globalObjectsContainer={this._globalObjectsContainer} objectsContainer={this._objectsContainer} onConfigurationUpdated={this._onConfigurationUpdated} @@ -1045,7 +1065,8 @@ export default class EventsFunctionsExtensionEditor extends React.Component< project, selectedEventsFunction, selectedEventsBasedBehavior, - selectedEventsBasedObject + selectedEventsBasedObject, + this.props.eventsFunctionsExtension ); this.forceUpdate(); }} diff --git a/newIDE/app/src/EventsFunctionsExtensionsLoader/MetadataDeclarationHelpers.js b/newIDE/app/src/EventsFunctionsExtensionsLoader/MetadataDeclarationHelpers.js index b53a944c60..dd90f8a694 100644 --- a/newIDE/app/src/EventsFunctionsExtensionsLoader/MetadataDeclarationHelpers.js +++ b/newIDE/app/src/EventsFunctionsExtensionsLoader/MetadataDeclarationHelpers.js @@ -2,6 +2,7 @@ import { type I18n as I18nType } from '@lingui/core'; import { t } from '@lingui/macro'; import { mapVector } from '../Utils/MapFor'; +import { getFreeFunctionCodeName } from '.'; const gd: libGDevelop = global.gd; // This file contains the logic to declare extension metadata from @@ -301,6 +302,12 @@ export const isExtensionLifecycleEventsFunction = (functionName: string) => { ); }; +const removeTrailingDot = (description: string): string => { + return description.endsWith('.') + ? description.slice(0, description.length - 1) + : description; +}; + /** * Declare the instruction (action/condition) or expression for the given * (free) events function. @@ -309,24 +316,80 @@ export const declareInstructionOrExpressionMetadata = ( extension: gdPlatformExtension, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsFunction: gdEventsFunction -): gdInstructionMetadata | gdExpressionMetadata => { +): + | gdInstructionMetadata + | gdExpressionMetadata + | gdMultipleInstructionMetadata => { const functionType = eventsFunction.getFunctionType(); if (functionType === gd.EventsFunction.Expression) { - return extension.addExpression( + if (eventsFunction.getExpressionType().isNumber()) { + return extension.addExpression( + eventsFunction.getName(), + eventsFunction.getFullName() || eventsFunction.getName(), + eventsFunction.getDescription() || eventsFunction.getFullName(), + eventsFunction.getGroup() || '', + getExtensionIconUrl(extension) + ); + } else { + return extension.addStrExpression( + eventsFunction.getName(), + eventsFunction.getFullName() || eventsFunction.getName(), + eventsFunction.getDescription() || eventsFunction.getFullName(), + eventsFunction.getGroup() || '', + getExtensionIconUrl(extension) + ); + } + } else if (functionType === gd.EventsFunction.ExpressionAndCondition) { + return extension.addExpressionAndCondition( + gd.ValueTypeMetadata.getPrimitiveValueType( + eventsFunction.getExpressionType().getName() + ), eventsFunction.getName(), eventsFunction.getFullName() || eventsFunction.getName(), - eventsFunction.getDescription() || eventsFunction.getFullName(), + removeTrailingDot(eventsFunction.getDescription()) || + eventsFunction.getFullName(), + // An operator and an operand are inserted before user parameters. + shiftSentenceParamIndexes(eventsFunction.getSentence(), 2), eventsFunction.getGroup() || '', getExtensionIconUrl(extension) ); - } else if (functionType === gd.EventsFunction.StringExpression) { - return extension.addStrExpression( + } else if (functionType === gd.EventsFunction.ActionWithOperator) { + const getterFunction = eventsFunctionsExtension.hasEventsFunctionNamed( + eventsFunction.getGetterName() + ) + ? eventsFunctionsExtension.getEventsFunction( + eventsFunction.getGetterName() + ) + : null; + const action = extension.addAction( eventsFunction.getName(), - eventsFunction.getFullName() || eventsFunction.getName(), - eventsFunction.getDescription() || eventsFunction.getFullName(), - eventsFunction.getGroup() || '', + (getterFunction && getterFunction.getFullName()) || + eventsFunction.getName(), + 'Change ' + + ((getterFunction && getterFunction.getDescription()) || + eventsFunction.getFullName()), + // An operator and an operand are inserted before user parameters. + getterFunction + ? shiftSentenceParamIndexes(getterFunction.getSentence(), 2) + : '', + (getterFunction && getterFunction.getGroup()) || '', + getExtensionIconUrl(extension), getExtensionIconUrl(extension) ); + if (getterFunction) { + action + .getCodeExtraInformation() + .setManipulatedType( + gd.ValueTypeMetadata.getPrimitiveValueType( + getterFunction.getExpressionType().getName() + ) + ) + // TODO Use an helper method + .setGetter( + getFreeFunctionCodeName(eventsFunctionsExtension, getterFunction) + ); + } + return action; } else if (functionType === gd.EventsFunction.Condition) { return extension.addCondition( eventsFunction.getName(), @@ -350,6 +413,34 @@ export const declareInstructionOrExpressionMetadata = ( } }; +export const shiftSentenceParamIndexes = ( + sentence: string, + offset: number +): string => { + const parameterIndexesStrings = sentence.match(/(?<=_PARAM)(\d+)(?=_)/g); + if (!parameterIndexesStrings) { + return sentence; + } + const parameterIndexes = parameterIndexesStrings.map(indexString => + Number.parseInt(indexString) + ); + const sentenceElements = sentence.split(/_PARAM\d+_/); + let shiftedSentence = ''; + for (let index = 0; index < parameterIndexes.length; index++) { + shiftedSentence += + sentenceElements[index] + + '_PARAM' + + (parameterIndexes[index] + offset) + + '_'; + } + const sentenceIsEndingWithAnElement = + sentenceElements.length > parameterIndexes.length; + if (sentenceIsEndingWithAnElement) { + shiftedSentence += sentenceElements[sentenceElements.length - 1]; + } + return shiftedSentence; +}; + /** * Declare the instruction (action/condition) or expression for the given * behavior events function. @@ -359,28 +450,84 @@ export const declareBehaviorInstructionOrExpressionMetadata = ( behaviorMetadata: gdBehaviorMetadata, eventsBasedBehavior: gdEventsBasedBehavior, eventsFunction: gdEventsFunction -): gdInstructionMetadata | gdExpressionMetadata => { +): + | gdInstructionMetadata + | gdExpressionMetadata + | gdMultipleInstructionMetadata => { const functionType = eventsFunction.getFunctionType(); if (functionType === gd.EventsFunction.Expression) { - return behaviorMetadata.addExpression( + if (eventsFunction.getExpressionType().isNumber()) { + return behaviorMetadata.addExpression( + eventsFunction.getName(), + eventsFunction.getFullName() || eventsFunction.getName(), + eventsFunction.getDescription() || eventsFunction.getFullName(), + eventsFunction.getGroup() || + eventsBasedBehavior.getFullName() || + eventsBasedBehavior.getName(), + getExtensionIconUrl(extension) + ); + } else { + return behaviorMetadata.addStrExpression( + eventsFunction.getName(), + eventsFunction.getFullName() || eventsFunction.getName(), + eventsFunction.getDescription() || eventsFunction.getFullName(), + eventsFunction.getGroup() || + eventsBasedBehavior.getFullName() || + eventsBasedBehavior.getName(), + getExtensionIconUrl(extension) + ); + } + } else if (functionType === gd.EventsFunction.ExpressionAndCondition) { + return behaviorMetadata.addExpressionAndCondition( + gd.ValueTypeMetadata.getPrimitiveValueType( + eventsFunction.getExpressionType().getName() + ), eventsFunction.getName(), eventsFunction.getFullName() || eventsFunction.getName(), - eventsFunction.getDescription() || eventsFunction.getFullName(), - eventsFunction.getGroup() || - eventsBasedBehavior.getFullName() || - eventsBasedBehavior.getName(), + removeTrailingDot(eventsFunction.getDescription()) || + eventsFunction.getFullName(), + // An operator and an operand are inserted before user parameters. + shiftSentenceParamIndexes(eventsFunction.getSentence(), 2), + eventsFunction.getGroup() || '', getExtensionIconUrl(extension) ); - } else if (functionType === gd.EventsFunction.StringExpression) { - return behaviorMetadata.addStrExpression( + } else if (functionType === gd.EventsFunction.ActionWithOperator) { + const eventsFunctionsContainer = eventsBasedBehavior.getEventsFunctions(); + const getterFunction = eventsFunctionsContainer.hasEventsFunctionNamed( + eventsFunction.getGetterName() + ) + ? eventsFunctionsContainer.getEventsFunction( + eventsFunction.getGetterName() + ) + : null; + const action = behaviorMetadata.addScopedAction( eventsFunction.getName(), - eventsFunction.getFullName() || eventsFunction.getName(), - eventsFunction.getDescription() || eventsFunction.getFullName(), - eventsFunction.getGroup() || + (getterFunction && getterFunction.getFullName()) || + eventsFunction.getName(), + 'Change ' + + ((getterFunction && getterFunction.getDescription()) || + eventsFunction.getFullName()), + // An operator and an operand are inserted before user parameters. + getterFunction + ? shiftSentenceParamIndexes(getterFunction.getSentence(), 2) + : '', + (getterFunction && getterFunction.getGroup()) || eventsBasedBehavior.getFullName() || eventsBasedBehavior.getName(), + getExtensionIconUrl(extension), getExtensionIconUrl(extension) ); + if (getterFunction) { + action + .getCodeExtraInformation() + .setManipulatedType( + gd.ValueTypeMetadata.getPrimitiveValueType( + getterFunction.getExpressionType().getName() + ) + ) + .setGetter(getterFunction.getName()); + } + return action; } else if (functionType === gd.EventsFunction.Condition) { // Use the new "scoped" way to declare an instruction, because // we want to prevent any conflict between free functions and @@ -423,28 +570,84 @@ export const declareObjectInstructionOrExpressionMetadata = ( objectMetadata: gdObjectMetadata, eventsBasedObject: gdEventsBasedObject, eventsFunction: gdEventsFunction -): gdInstructionMetadata | gdExpressionMetadata => { +): + | gdInstructionMetadata + | gdExpressionMetadata + | gdMultipleInstructionMetadata => { const functionType = eventsFunction.getFunctionType(); if (functionType === gd.EventsFunction.Expression) { - return objectMetadata.addExpression( + if (eventsFunction.getExpressionType().isNumber()) { + return objectMetadata.addExpression( + eventsFunction.getName(), + eventsFunction.getFullName() || eventsFunction.getName(), + eventsFunction.getDescription() || eventsFunction.getFullName(), + eventsFunction.getGroup() || + eventsBasedObject.getFullName() || + eventsBasedObject.getName(), + getExtensionIconUrl(extension) + ); + } else { + return objectMetadata.addStrExpression( + eventsFunction.getName(), + eventsFunction.getFullName() || eventsFunction.getName(), + eventsFunction.getDescription() || eventsFunction.getFullName(), + eventsFunction.getGroup() || + eventsBasedObject.getFullName() || + eventsBasedObject.getName(), + getExtensionIconUrl(extension) + ); + } + } else if (functionType === gd.EventsFunction.ExpressionAndCondition) { + return objectMetadata.addExpressionAndCondition( + gd.ValueTypeMetadata.getPrimitiveValueType( + eventsFunction.getExpressionType().getName() + ), eventsFunction.getName(), eventsFunction.getFullName() || eventsFunction.getName(), - eventsFunction.getDescription() || eventsFunction.getFullName(), - eventsFunction.getGroup() || - eventsBasedObject.getFullName() || - eventsBasedObject.getName(), + removeTrailingDot(eventsFunction.getDescription()) || + eventsFunction.getFullName(), + // An operator and an operand are inserted before user parameters. + shiftSentenceParamIndexes(eventsFunction.getSentence(), 2), + eventsFunction.getGroup() || '', getExtensionIconUrl(extension) ); - } else if (functionType === gd.EventsFunction.StringExpression) { - return objectMetadata.addStrExpression( + } else if (functionType === gd.EventsFunction.ActionWithOperator) { + const eventsFunctionsContainer = eventsBasedObject.getEventsFunctions(); + const getterFunction = eventsFunctionsContainer.hasEventsFunctionNamed( + eventsFunction.getGetterName() + ) + ? eventsFunctionsContainer.getEventsFunction( + eventsFunction.getGetterName() + ) + : null; + const action = objectMetadata.addScopedAction( eventsFunction.getName(), - eventsFunction.getFullName() || eventsFunction.getName(), - eventsFunction.getDescription() || eventsFunction.getFullName(), - eventsFunction.getGroup() || + (getterFunction && getterFunction.getFullName()) || + eventsFunction.getName(), + 'Change ' + + ((getterFunction && getterFunction.getDescription()) || + eventsFunction.getFullName()), + // An operator and an operand are inserted before user parameters. + getterFunction + ? shiftSentenceParamIndexes(getterFunction.getSentence(), 2) + : '', + (getterFunction && getterFunction.getGroup()) || eventsBasedObject.getFullName() || eventsBasedObject.getName(), + getExtensionIconUrl(extension), getExtensionIconUrl(extension) ); + if (getterFunction) { + action + .getCodeExtraInformation() + .setManipulatedType( + gd.ValueTypeMetadata.getPrimitiveValueType( + getterFunction.getExpressionType().getName() + ) + ) + .setGetter(getterFunction.getName()); + } + return action; } else if (functionType === gd.EventsFunction.Condition) { // Use the new "scoped" way to declare an instruction, because // we want to prevent any conflict between free functions and @@ -498,19 +701,17 @@ export const declareBehaviorPropertiesInstructionAndExpressions = ( instructionOrExpression: T ): T => { // By convention, first parameter is always the object: - instructionOrExpression.addParameter( - 'object', - 'Object', - '', // See below for adding the extra information - false - ); - - // Manually add the "extra info" without relying on addParameter - // as this method is prefixing the value passed with the extension namespace (this - // was done to ease extension declarations when dealing with object). instructionOrExpression - .getParameter(instructionOrExpression.getParametersCount() - 1) - .setExtraInfo(eventsBasedBehavior.getObjectType()); + .addParameter( + 'object', + 'Object', + '', // See below for adding the extra information + false + ) + // Manually add the "extra info" without relying on addParameter + // as this method is prefixing the value passed with the extension namespace (this + // was done to ease extension declarations when dealing with object). + .setParameterExtraInfo(eventsBasedBehavior.getObjectType()); // By convention, second parameter is always the behavior: instructionOrExpression.addParameter( @@ -912,36 +1113,75 @@ export const declareObjectPropertiesInstructionAndExpressions = ( * expected by the events function. */ export const declareEventsFunctionParameters = ( + eventsFunctionsContainer: gdEventsFunctionsContainer, eventsFunction: gdEventsFunction, - instructionOrExpression: gdInstructionMetadata | gdExpressionMetadata + instructionOrExpression: + | gdInstructionMetadata + | gdExpressionMetadata + | gdMultipleInstructionMetadata, + userDefinedFirstParameterIndex: number ) => { - mapVector( - eventsFunction.getParameters(), - (parameter: gdParameterMetadata) => { - if (!parameter.isCodeOnly()) { - instructionOrExpression.addParameter( + const addParameter = (parameter: gdParameterMetadata) => { + if (!parameter.isCodeOnly()) { + instructionOrExpression + .addParameter( parameter.getType(), parameter.getDescription(), '', // See below for adding the extra information parameter.isOptional() - ); - instructionOrExpression.setParameterLongDescription( - parameter.getLongDescription() - ); - instructionOrExpression.setDefaultValue(parameter.getDefaultValue()); - } else { - instructionOrExpression.addCodeOnlyParameter( - parameter.getType(), - '' // See below for adding the extra information - ); - } - // Manually add the "extra info" without relying on addParameter (or addCodeOnlyParameter) - // as these methods are prefixing the value passed with the extension namespace (this - // was done to ease extension declarations when dealing with object). - instructionOrExpression - .getParameter(instructionOrExpression.getParametersCount() - 1) - .setExtraInfo(parameter.getExtraInfo()); + ) + // Manually add the "extra info" without relying on addParameter (or addCodeOnlyParameter) + // as these methods are prefixing the value passed with the extension namespace (this + // was done to ease extension declarations when dealing with object). + .setParameterExtraInfo(parameter.getExtraInfo()); + instructionOrExpression.setParameterLongDescription( + parameter.getLongDescription() + ); + instructionOrExpression.setDefaultValue(parameter.getDefaultValue()); + } else { + instructionOrExpression.addCodeOnlyParameter( + parameter.getType(), + parameter.getExtraInfo() + ); } + }; + + const functionType = eventsFunction.getFunctionType(); + const getterFunction = eventsFunctionsContainer.hasEventsFunctionNamed( + eventsFunction.getGetterName() + ) + ? eventsFunctionsContainer.getEventsFunction(eventsFunction.getGetterName()) + : null; + // This is used instead of getParametersForEvents because the Value parameter + // is already add by useStandardOperatorParameters. + const parameters = (functionType === gd.EventsFunction.ActionWithOperator && + getterFunction + ? getterFunction + : eventsFunction + ).getParameters(); + + mapVector( + parameters, + (parameter: gdParameterMetadata, index: number) => + index < userDefinedFirstParameterIndex && addParameter(parameter) + ); + + if (functionType === gd.EventsFunction.ExpressionAndCondition) { + ((instructionOrExpression: any): gdMultipleInstructionMetadata).useStandardParameters( + eventsFunction ? eventsFunction.getExpressionType().getName() : 'string', + eventsFunction ? eventsFunction.getExpressionType().getExtraInfo() : '' + ); + } else if (functionType === gd.EventsFunction.ActionWithOperator) { + ((instructionOrExpression: any): gdInstructionMetadata).useStandardOperatorParameters( + getterFunction ? getterFunction.getExpressionType().getName() : 'string', + getterFunction ? getterFunction.getExpressionType().getExtraInfo() : '' + ); + } + + mapVector( + parameters, + (parameter: gdParameterMetadata, index: number) => + index >= userDefinedFirstParameterIndex && addParameter(parameter) ); // By convention, latest parameter is always the eventsFunctionContext of the calling function diff --git a/newIDE/app/src/EventsFunctionsExtensionsLoader/MetadataDeclarationHelpers.spec.js b/newIDE/app/src/EventsFunctionsExtensionsLoader/MetadataDeclarationHelpers.spec.js new file mode 100644 index 0000000000..023b31404a --- /dev/null +++ b/newIDE/app/src/EventsFunctionsExtensionsLoader/MetadataDeclarationHelpers.spec.js @@ -0,0 +1,91 @@ +// @flow +import { shiftSentenceParamIndexes } from './MetadataDeclarationHelpers'; + +describe('shiftSentenceParamIndexes', () => { + it('give back the sentence when there is no parameters', () => { + expect(shiftSentenceParamIndexes('Make an action', 2)).toBe( + 'Make an action' + ); + }); + it('can shift a parameter at the end', () => { + expect(shiftSentenceParamIndexes('Change the speed to _PARAM2_', 2)).toBe( + 'Change the speed to _PARAM4_' + ); + }); + it('can shift a parameter at the beginning', () => { + expect(shiftSentenceParamIndexes('_PARAM2_ is moving', 2)).toBe( + '_PARAM4_ is moving' + ); + }); + it('can shift a parameter alone', () => { + expect(shiftSentenceParamIndexes('_PARAM2_', 2)).toBe('_PARAM4_'); + }); + it("won't shift an ill-formed parameter", () => { + expect( + shiftSentenceParamIndexes( + 'The speed is greater than PARAM2_ pixels per second', + 2 + ) + ).toBe('The speed is greater than PARAM2_ pixels per second'); + expect( + shiftSentenceParamIndexes( + 'The speed is greater than _PARAM2 pixels per second', + 2 + ) + ).toBe('The speed is greater than _PARAM2 pixels per second'); + expect( + shiftSentenceParamIndexes( + 'The speed is greater than PARAM2 pixels per second', + 2 + ) + ).toBe('The speed is greater than PARAM2 pixels per second'); + expect( + shiftSentenceParamIndexes( + 'The speed is greater than _param2_ pixels per second', + 2 + ) + ).toBe('The speed is greater than _param2_ pixels per second'); + expect( + shiftSentenceParamIndexes( + 'The speed is greater than 2 pixels per second', + 2 + ) + ).toBe('The speed is greater than 2 pixels per second'); + }); + [2, 0, -2].forEach(indexOffset => { + it(`can shift 1 parameter by ${indexOffset}`, () => { + expect( + shiftSentenceParamIndexes( + 'The speed is greater than _PARAM2_ pixels per second', + indexOffset + ) + ).toBe( + 'The speed is greater than _PARAM' + + (2 + indexOffset) + + '_ pixels per second' + ); + }); + it(`can shift 2 parameters by ${indexOffset}`, () => { + expect( + shiftSentenceParamIndexes( + 'The speed is between _PARAM1_ and _PARAM2_ pixels per second', + indexOffset + ) + ).toBe( + `The speed is between _PARAM${1 + indexOffset}_ and _PARAM${2 + + indexOffset}_ pixels per second` + ); + }); + it(`can shift 2 parameters with jumbled indexes by ${indexOffset}`, () => { + expect( + shiftSentenceParamIndexes( + 'The speed is between _PARAM3_ and _PARAM2_ pixels per second', + indexOffset + ) + ).toBe( + `The speed is between _PARAM${3 + indexOffset}_ and _PARAM${2 + + indexOffset}_ pixels per second` + ); + }); + }); +}); diff --git a/newIDE/app/src/EventsFunctionsExtensionsLoader/index.js b/newIDE/app/src/EventsFunctionsExtensionsLoader/index.js index 6e00436f3e..03b64a3635 100644 --- a/newIDE/app/src/EventsFunctionsExtensionsLoader/index.js +++ b/newIDE/app/src/EventsFunctionsExtensionsLoader/index.js @@ -50,6 +50,12 @@ const mangleName = (name: string) => { return caseSensitiveSlug(name, '_', []); }; +const getExtensionCodeNamespacePrefix = ( + eventsFunctionsExtension: gdEventsFunctionsExtension +) => { + return 'gdjs.evtsExt__' + mangleName(eventsFunctionsExtension.getName()); +}; + /** Generate the namespace for a free function. */ const getFreeFunctionCodeNamespace = ( eventsFunction: gdEventsFunction, @@ -58,6 +64,18 @@ const getFreeFunctionCodeNamespace = ( return codeNamespacePrefix + '__' + mangleName(eventsFunction.getName()); }; +export const getFreeFunctionCodeName = ( + eventsFunctionsExtension: gdEventsFunctionsExtension, + eventsFunction: gdEventsFunction +) => { + return ( + getFreeFunctionCodeNamespace( + eventsFunction, + getExtensionCodeNamespacePrefix(eventsFunctionsExtension) + ) + '.func' + ); +}; + /** Generate the namespace for a behavior function. */ const getBehaviorFunctionCodeNamespace = ( eventsBasedBehavior: gdEventsBasedBehavior, @@ -248,7 +266,10 @@ const generateFreeFunction = ( codeGenerationContext: CodeGenerationContext ): Promise<{ functionFile: string, - functionMetadata: gdInstructionMetadata | gdExpressionMetadata, + functionMetadata: + | gdInstructionMetadata + | gdExpressionMetadata + | gdMultipleInstructionMetadata, }> => { const instructionOrExpression = declareInstructionOrExpressionMetadata( extension, @@ -257,7 +278,12 @@ const generateFreeFunction = ( ); // By convention, first parameter is always the Runtime Scene. instructionOrExpression.addCodeOnlyParameter('currentScene', ''); - declareEventsFunctionParameters(eventsFunction, instructionOrExpression); + declareEventsFunctionParameters( + eventsFunctionsExtension, + eventsFunction, + instructionOrExpression, + 0 + ); // Hide "lifecycle" functions as they are called automatically by // the game engine. @@ -272,17 +298,16 @@ const generateFreeFunction = ( ); const functionName = codeNamespace + '.func'; - const codeExtraInformation = instructionOrExpression.getCodeExtraInformation(); const functionFile = options.eventsFunctionCodeWriter.getIncludeFileFor( functionName ); - codeExtraInformation + instructionOrExpression .setIncludeFile(functionFile) .setFunctionName(functionName); // Always include the extension include files when using a free function. codeGenerationContext.extensionIncludeFiles.forEach(includeFile => { - codeExtraInformation.addIncludeFile(includeFile); + instructionOrExpression.addIncludeFile(includeFile); }); if (!options.skipCodeGeneration) { @@ -291,6 +316,7 @@ const generateFreeFunction = ( project ); const code = eventsFunctionsExtensionCodeGenerator.generateFreeEventsFunctionCompleteCode( + eventsFunctionsExtension, eventsFunction, codeNamespace, includeFiles, @@ -307,7 +333,7 @@ const generateFreeFunction = ( .toNewVectorString() .toJSArray() .forEach((includeFile: string) => { - codeExtraInformation.addIncludeFile(includeFile); + instructionOrExpression.addIncludeFile(includeFile); }); includeFiles.delete(); @@ -338,7 +364,10 @@ const generateFreeFunction = ( const applyFunctionIncludeFilesDependencyTransitivity = ( functionInfos: Array<{ functionFile: string, - functionMetadata: gdInstructionMetadata | gdExpressionMetadata, + functionMetadata: + | gdInstructionMetadata + | gdExpressionMetadata + | gdMultipleInstructionMetadata, }> ): void => { // Note that the iteration order doesn't matter, for instance for: @@ -362,12 +391,7 @@ const applyFunctionIncludeFilesDependencyTransitivity = ( // c -> d const includeFileSets = functionInfos.map( functionInfo => - new Set( - functionInfo.functionMetadata - .getCodeExtraInformation() - .getIncludeFiles() - .toJSArray() - ) + new Set(functionInfo.functionMetadata.getIncludeFiles().toJSArray()) ); // For any function A of the extension... for (let index = 0; index < functionInfos.length; index++) { @@ -376,9 +400,7 @@ const applyFunctionIncludeFilesDependencyTransitivity = ( // ...and any function B of the extension... for (let otherIndex = 0; otherIndex < functionInfos.length; otherIndex++) { - const otherCodeExtraInformation = functionInfos[ - otherIndex - ].functionMetadata.getCodeExtraInformation(); + const otherFunctionMetadata = functionInfos[otherIndex].functionMetadata; const otherIncludeFileSet = includeFileSets[otherIndex]; // ...where function B depends on function A... if (otherIncludeFileSet.has(functionIncludeFile)) { @@ -386,7 +408,7 @@ const applyFunctionIncludeFilesDependencyTransitivity = ( includeFiles.forEach(includeFile => { if (!otherIncludeFileSet.has(includeFile)) { otherIncludeFileSet.add(includeFile); - otherCodeExtraInformation.addIncludeFile(includeFile); + otherFunctionMetadata.addIncludeFile(includeFile); } }); } @@ -450,7 +472,12 @@ function generateBehavior( eventsBasedBehavior, eventsFunction ); - declareEventsFunctionParameters(eventsFunction, instructionOrExpression); + declareEventsFunctionParameters( + eventsFunctionsContainer, + eventsFunction, + instructionOrExpression, + 2 + ); // Hide "lifecycle" methods as they are called automatically by // the game engine. @@ -460,8 +487,7 @@ function generateBehavior( if (eventsFunction.isPrivate()) instructionOrExpression.setPrivate(); - const codeExtraInformation = instructionOrExpression.getCodeExtraInformation(); - codeExtraInformation + instructionOrExpression .setIncludeFile(includeFile) .setFunctionName(eventsFunctionMangledName); }); @@ -567,7 +593,12 @@ function generateObject( eventsBasedObject, eventsFunction ); - declareEventsFunctionParameters(eventsFunction, instructionOrExpression); + declareEventsFunctionParameters( + eventsFunctionsContainer, + eventsFunction, + instructionOrExpression, + 1 + ); // Hide "lifecycle" methods as they are called automatically by // the game engine. @@ -577,8 +608,7 @@ function generateObject( if (eventsFunction.isPrivate()) instructionOrExpression.setPrivate(); - const codeExtraInformation = instructionOrExpression.getCodeExtraInformation(); - codeExtraInformation + instructionOrExpression .setIncludeFile(includeFile) .setFunctionName(eventsFunctionMangledName); }); diff --git a/newIDE/app/src/EventsFunctionsList/index.js b/newIDE/app/src/EventsFunctionsList/index.js index 9c62187d99..4524ae5cb2 100644 --- a/newIDE/app/src/EventsFunctionsList/index.js +++ b/newIDE/app/src/EventsFunctionsList/index.js @@ -145,6 +145,7 @@ export default class EventsFunctionsList extends React.Component { default: return 'res/functions/function.svg'; case gd.EventsFunction.Action: + case gd.EventsFunction.ActionWithOperator: switch (eventsFunction.getName()) { default: return 'res/functions/action.svg'; @@ -178,8 +179,7 @@ export default class EventsFunctionsList extends React.Component { case gd.EventsFunction.Condition: return 'res/functions/condition.svg'; case gd.EventsFunction.Expression: - return 'res/functions/expression.svg'; - case gd.EventsFunction.StringExpression: + case gd.EventsFunction.ExpressionAndCondition: return 'res/functions/expression.svg'; } }; diff --git a/newIDE/app/src/EventsSheet/EventsFunctionExtractor/EventsFunctionExtractorDialog.js b/newIDE/app/src/EventsSheet/EventsFunctionExtractor/EventsFunctionExtractorDialog.js index 515add699d..de91039a33 100644 --- a/newIDE/app/src/EventsSheet/EventsFunctionExtractor/EventsFunctionExtractorDialog.js +++ b/newIDE/app/src/EventsSheet/EventsFunctionExtractor/EventsFunctionExtractorDialog.js @@ -286,9 +286,11 @@ export default class EventsFunctionExtractorDialog extends React.Component< ) : null} { // Force re-running logic to see if Create button is disabled. this.forceUpdate(); @@ -302,6 +304,7 @@ export default class EventsFunctionExtractorDialog extends React.Component< eventsFunction={eventsFunction} eventsBasedBehavior={null} eventsBasedObject={null} + eventsFunctionsContainer={null} onParametersUpdated={() => { // Force the dialog to adapt its size this.forceUpdate(); diff --git a/newIDE/app/src/EventsSheet/ParameterFields/EnumerateFunctionParameters.js b/newIDE/app/src/EventsSheet/ParameterFields/EnumerateFunctionParameters.js index d6f8b09ecb..2399c9c3bf 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/EnumerateFunctionParameters.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/EnumerateFunctionParameters.js @@ -3,13 +3,16 @@ import { mapVector } from '../../Utils/MapFor'; const gd: libGDevelop = global.gd; export const enumerateParametersUsableInExpressions = ( + eventsFunctionsContainer: gdEventsFunctionsContainer, eventsFunction: gdEventsFunction ): Array => { - return mapVector(eventsFunction.getParameters(), parameterMetadata => - parameterMetadata.isCodeOnly() || - gd.ParameterMetadata.isObject(parameterMetadata.getType()) || - gd.ParameterMetadata.isBehavior(parameterMetadata.getType()) - ? null - : parameterMetadata + return mapVector( + eventsFunction.getParametersForEvents(eventsFunctionsContainer), + parameterMetadata => + parameterMetadata.isCodeOnly() || + gd.ParameterMetadata.isObject(parameterMetadata.getType()) || + gd.ParameterMetadata.isBehavior(parameterMetadata.getType()) + ? null + : parameterMetadata ).filter(Boolean); }; diff --git a/newIDE/app/src/EventsSheet/ParameterFields/FunctionParameterNameField.js b/newIDE/app/src/EventsSheet/ParameterFields/FunctionParameterNameField.js index ff2fd74314..cab7c63f5f 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/FunctionParameterNameField.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/FunctionParameterNameField.js @@ -16,15 +16,22 @@ export default class FunctionParameterNameField extends Component< } render() { - const parameterNames: Array = this.props.scope - .eventsFunction - ? enumerateParametersUsableInExpressions( - this.props.scope.eventsFunction - ).map(parameterMetadata => ({ - kind: 'Text', - completion: `"${parameterMetadata.getName()}"`, - })) - : []; + const eventsBasedEntity = + this.props.scope.eventsBasedBehavior || + this.props.scope.eventsBasedObject; + const functionsContainer = eventsBasedEntity + ? eventsBasedEntity.getEventsFunctions() + : this.props.scope.eventsFunctionsExtension; + const parameterNames: Array = + this.props.scope.eventsFunction && functionsContainer + ? enumerateParametersUsableInExpressions( + functionsContainer, + this.props.scope.eventsFunction + ).map(parameterMetadata => ({ + kind: 'Text', + completion: `"${parameterMetadata.getName()}"`, + })) + : []; return ( `"${parameterMetadata.getName()}"`); } diff --git a/newIDE/app/src/StringArrayEditor/index.js b/newIDE/app/src/StringArrayEditor/index.js index ba1b5f93c3..b4252531a4 100644 --- a/newIDE/app/src/StringArrayEditor/index.js +++ b/newIDE/app/src/StringArrayEditor/index.js @@ -13,11 +13,13 @@ import Add from '@material-ui/icons/Add'; type StringArrayEditorProps = {| extraInfo: Array, setExtraInfo: (Array) => void, + disabled?: boolean, |}; const StringArrayEditor = ({ extraInfo, setExtraInfo, + disabled, }: StringArrayEditorProps) => { const updateExtraInfo = () => setExtraInfo(extraInfo); @@ -27,6 +29,7 @@ const StringArrayEditor = ({ {extraInfo.map((item, index) => ( { @@ -36,6 +39,7 @@ const StringArrayEditor = ({ fullWidth /> { extraInfo.splice(index, 1); @@ -49,6 +53,7 @@ const StringArrayEditor = ({ { extraInfo.push('New Option'); diff --git a/newIDE/app/src/Utils/MapFor.js b/newIDE/app/src/Utils/MapFor.js index 6b6454fbd4..c00650ca2d 100644 --- a/newIDE/app/src/Utils/MapFor.js +++ b/newIDE/app/src/Utils/MapFor.js @@ -1,11 +1,11 @@ // @flow // Note: this file does not use export/imports and use Flow comments to allow its usage from Node.js -const mapFor = /*:: */ ( +const mapFor = /*:: */ ( start /*: number */, end /*: number */, func /*: (number) => T */ -) /*:Array */ => { +) /*: Array */ => { const result = []; for (let i = start; i < end; i++) { result.push(func(i)); @@ -13,11 +13,11 @@ const mapFor = /*:: */ ( return result; }; -const mapReverseFor = /*:: */ ( +const mapReverseFor = /*:: */ ( start /*: number */, end /*: number */, func /*: (number) => T */ -) /*:Array */ => { +) /*: Array */ => { const result = []; for (let i = end - 1; i >= start; i--) { result.push(func(i)); @@ -32,10 +32,10 @@ type CppVector = { } */ -const mapVector = /*:: */ ( +const mapVector = /*:: */ ( cppVector /*: CppVector */, func /*: (T, number) => U */ -) /*:Array */ => { +) /*: Array */ => { return mapFor(0, cppVector.size(), i => func(cppVector.at(i), i)); }; diff --git a/newIDE/app/src/stories/componentStories/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor.stories.js b/newIDE/app/src/stories/componentStories/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor.stories.js index 5d4e51229f..fdf508fb2b 100644 --- a/newIDE/app/src/stories/componentStories/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor.stories.js +++ b/newIDE/app/src/stories/componentStories/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor.stories.js @@ -27,6 +27,7 @@ export const DefaultFreeFunction = () => ( eventsFunction={testProject.testEventsFunction} eventsBasedBehavior={null} eventsBasedObject={null} + eventsFunctionsContainer={testProject.testEventsFunctionsExtension} onParametersOrGroupsUpdated={action('Parameters or groups were updated')} /> @@ -42,6 +43,7 @@ export const DefaultBehaviorFunction = () => ( eventsFunction={testProject.testBehaviorEventsFunction} eventsBasedBehavior={testProject.testEventsBasedBehavior} eventsBasedObject={null} + eventsFunctionsContainer={testProject.testEventsBasedBehavior.getEventsFunctions()} onParametersOrGroupsUpdated={action('Parameters or groups were updated')} /> @@ -57,6 +59,7 @@ export const DefaultBehaviorLifecycleFunction = () => ( eventsFunction={testProject.testBehaviorLifecycleEventsFunction} eventsBasedBehavior={testProject.testEventsBasedBehavior} eventsBasedObject={null} + eventsFunctionsContainer={testProject.testEventsBasedBehavior.getEventsFunctions()} onParametersOrGroupsUpdated={action('Parameters or groups were updated')} /> @@ -72,6 +75,7 @@ export const DefaultObjectFunction = () => ( eventsFunction={testProject.testObjectEventsFunction} eventsBasedBehavior={null} eventsBasedObject={testProject.testEventsBasedObject} + eventsFunctionsContainer={testProject.testEventsBasedObject.getEventsFunctions()} onParametersOrGroupsUpdated={action('Parameters or groups were updated')} /> diff --git a/newIDE/app/src/stories/index.js b/newIDE/app/src/stories/index.js index 3254ec33e6..3f39d145ee 100644 --- a/newIDE/app/src/stories/index.js +++ b/newIDE/app/src/stories/index.js @@ -88,21 +88,14 @@ import PlaceholderLoader from '../UI/PlaceholderLoader'; import ColorField from '../UI/ColorField'; import EmptyMessage from '../UI/EmptyMessage'; import BackgroundText from '../UI/BackgroundText'; -import EventsFunctionConfigurationEditor from '../EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor'; -import EventsFunctionsList from '../EventsFunctionsList'; -import EventsFunctionsExtensionEditor from '../EventsFunctionsExtensionEditor'; -import OptionsEditorDialog from '../EventsFunctionsExtensionEditor/OptionsEditorDialog'; import ProjectManager from '../ProjectManager'; import AlertMessage from '../UI/AlertMessage'; import ChangelogRenderer from '../MainFrame/Changelog/ChangelogRenderer'; import ChangelogDialog from '../MainFrame/Changelog/ChangelogDialog'; import EventsFunctionExtractorDialog from '../EventsSheet/EventsFunctionExtractor/EventsFunctionExtractorDialog'; import FixedHeightFlexContainer from './FixedHeightFlexContainer'; -import EventsBasedBehaviorEditor from '../EventsBasedBehaviorEditor'; -import EventsBasedBehaviorEditorDialog from '../EventsBasedBehaviorEditor/EventsBasedBehaviorEditorDialog'; import BehaviorTypeSelector from '../BehaviorTypeSelector'; import ObjectTypeSelector from '../ObjectTypeSelector'; -import EventsFunctionsExtensionsProvider from '../EventsFunctionsExtensionsLoader/EventsFunctionsExtensionsProvider'; import SemiControlledTextField from '../UI/SemiControlledTextField'; import SemiControlledAutoComplete from '../UI/SemiControlledAutoComplete'; import SemiControlledMultiAutoComplete from '../UI/SemiControlledMultiAutoComplete'; @@ -121,7 +114,6 @@ import EditorMosaic from '../UI/EditorMosaic'; import FlatButton from '../UI/FlatButton'; import EditorMosaicPlayground from './EditorMosaicPlayground'; import EditorNavigator from '../UI/EditorMosaic/EditorNavigator'; -import ChooseEventsFunctionsExtensionEditor from '../EventsFunctionsExtensionEditor/ChooseEventsFunctionsExtensionEditor'; import PropertiesEditor from '../PropertiesEditor'; import { OpenConfirmDialog } from '../ProjectsStorage/OpenConfirmDialog'; import BrowserPreviewErrorDialog from '../Export/BrowserExporters/BrowserS3PreviewLauncher/BrowserPreviewErrorDialog';