Add animation autocompletion for object groups (#6817)

This commit is contained in:
D8H
2024-07-31 10:19:31 +02:00
committed by GitHub
parent 48dd91043a
commit 4b70653a0b
20 changed files with 704 additions and 246 deletions

View File

@@ -116,6 +116,13 @@ void SpriteAnimationList::ExposeResources(gd::ArbitraryResourceWorker& worker) {
}
}
bool SpriteAnimationList::HasAnimationNamed(const gd::String &name) const {
return !name.empty() && (find_if(animations.begin(), animations.end(),
[&name](const Animation &animation) {
return animation.GetName() == name;
}) != animations.end());
}
const Animation& SpriteAnimationList::GetAnimation(std::size_t nb) const {
if (nb >= animations.size()) return badAnimation;

View File

@@ -51,6 +51,11 @@ class GD_CORE_API SpriteAnimationList {
*/
std::size_t GetAnimationsCount() const { return animations.size(); };
/**
* \brief Return true if an animation exists for a given name.
*/
bool HasAnimationNamed(const gd::String &name) const;
/**
* \brief Add an animation at the end of the existing ones.
*/

View File

@@ -87,6 +87,19 @@ bool SpriteObject::UpdateInitialInstanceProperty(
return true;
}
size_t SpriteObject::GetAnimationsCount() const {
return animations.GetAnimationsCount();
}
const gd::String &SpriteObject::GetAnimationName(size_t index) const {
return animations.GetAnimation(index).GetName();
}
bool SpriteObject::HasAnimationNamed(
const gd::String &name) const {
return animations.HasAnimationNamed(name);
}
const SpriteAnimationList& SpriteObject::GetAnimations() const {
return animations;
}

View File

@@ -52,6 +52,12 @@ class GD_CORE_API SpriteObject : public gd::ObjectConfiguration {
const gd::String& name,
const gd::String& value) override;
size_t GetAnimationsCount() const override;
const gd::String &GetAnimationName(size_t index) const override;
bool HasAnimationNamed(const gd::String &animationName) const override;
/**
* \brief Return the animation configuration.
*/

View File

@@ -224,6 +224,20 @@ void CustomObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker& wor
}
}
std::size_t CustomObjectConfiguration::GetAnimationsCount() const {
return animations.GetAnimationsCount();
}
const gd::String &
CustomObjectConfiguration::GetAnimationName(size_t index) const {
return animations.GetAnimation(index).GetName();
}
bool CustomObjectConfiguration::HasAnimationNamed(
const gd::String &name) const {
return animations.HasAnimationNamed(name);
}
const SpriteAnimationList& CustomObjectConfiguration::GetAnimations() const {
return animations;
}

View File

@@ -67,6 +67,12 @@ class CustomObjectConfiguration : public gd::ObjectConfiguration {
gd::ObjectConfiguration &GetChildObjectConfiguration(const gd::String& objectName);
std::size_t GetAnimationsCount() const override;
const gd::String &GetAnimationName(size_t index) const override;
bool HasAnimationNamed(const gd::String &animationName) const override;
/**
* \brief Return the animation configuration for Animatable custom objects.
*/

View File

@@ -14,6 +14,7 @@
#include "GDCore/Tools/Log.h"
namespace gd {
gd::String ObjectConfiguration::badAnimationName;
ObjectConfiguration::~ObjectConfiguration() {}

View File

@@ -165,7 +165,36 @@ class GD_CORE_API ObjectConfiguration {
void UnserializeFrom(gd::Project& project, const SerializerElement& element);
///@}
protected:
/** \name Animations
* Members functions related to object animations
*/
///@{
/**
* \brief Return the number of animations declared in this object
* configuration.
*/
virtual size_t GetAnimationsCount() const {
return 0;
};
/**
* \brief Return the name of an animation declared in this object
* configuration.
*/
virtual const gd::String &GetAnimationName(size_t index) const {
return badAnimationName;
}
/**
* \brief Return true if an animation is declared in this object
* configuration for a given name.
*/
virtual bool HasAnimationNamed(const gd::String &animationName) const {
return false;
}
///@}
protected:
gd::String type; ///< Which type of object is represented by this
///< configuration.
@@ -181,6 +210,9 @@ class GD_CORE_API ObjectConfiguration {
* custom attributes.
*/
virtual void DoSerializeTo(SerializerElement& element) const {};
private:
static gd::String badAnimationName;
};
} // namespace gd

View File

@@ -73,6 +73,15 @@ bool ObjectsContainersList::HasObjectNamed(const gd::String& name) const {
return false;
}
const gd::Object* ObjectsContainersList::GetObject(const gd::String& name) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(name)) return &(*it)->GetObject(name);
}
return nullptr;
}
ObjectsContainersList::VariableExistence
ObjectsContainersList::HasObjectOrGroupWithVariableNamed(
const gd::String& objectOrGroupName, const gd::String& variableName) const {
@@ -368,7 +377,7 @@ std::vector<gd::String> ObjectsContainersList::ExpandObjectName(
}
// Ensure that all returned objects actually exists (i.e: if some groups have
// names refering to non existing objects, don't return them).
// names referring to non existing objects, don't return them).
for (std::size_t i = 0; i < realObjects.size();) {
if (!HasObjectNamed(realObjects[i]))
realObjects.erase(realObjects.begin() + i);
@@ -521,4 +530,63 @@ std::vector<gd::String> ObjectsContainersList::GetBehaviorsOfObject(
*objectsContainers[0], *objectsContainers[1], objectName, searchInGroups);
}
std::vector<gd::String> ObjectsContainersList::GetAnimationNamesOfObject(
const gd::String &objectOrGroupName) const {
std::vector<gd::String> animationNames;
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
const auto &objectConfiguration =
(*it)->GetObject(objectOrGroupName).GetConfiguration();
for (size_t index = 0; index < objectConfiguration.GetAnimationsCount();
index++) {
animationNames.push_back(objectConfiguration.GetAnimationName(index));
}
return animationNames;
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
const auto &objectGroup = (*it)->GetObjectGroups().Get(objectOrGroupName);
const auto &objectNames = objectGroup.GetAllObjectsNames();
std::size_t objectIndex = 0;
bool isFirstObjectFound = false;
for (; objectIndex < objectNames.size() && !isFirstObjectFound;
objectIndex++) {
const gd::String &objectName = objectNames[objectIndex];
if (!HasObjectNamed(objectName)) {
continue;
}
isFirstObjectFound = true;
const auto &objectConfiguration =
GetObject(objectName)->GetConfiguration();
for (size_t index = 0; index < objectConfiguration.GetAnimationsCount();
index++) {
animationNames.push_back(objectConfiguration.GetAnimationName(index));
}
}
for (; objectIndex < objectNames.size(); objectIndex++) {
const gd::String &objectName = objectNames[objectIndex];
if (!HasObjectNamed(objectName)) {
continue;
}
const auto &objectConfiguration =
GetObject(objectName)->GetConfiguration();
for (size_t animationIndex = 0; animationIndex < animationNames.size();
animationIndex++) {
if (!objectConfiguration.HasAnimationNamed(
animationNames[animationIndex])) {
animationNames.erase(animationNames.begin() + animationIndex);
animationIndex--;
}
}
}
return animationNames;
}
}
return animationNames;
}
} // namespace gd

View File

@@ -129,7 +129,17 @@ class GD_CORE_API ObjectsContainersList {
const gd::String& objectName, bool searchInGroups = true) const;
/**
* \brief Return a list containing all objects refered to by the group.
* \brief Get the animation names of an object/group.
* \note The animation names of a group are the animation names common to
* every object of the group.
*
* @return The names of animations
*/
std::vector<gd::String>
GetAnimationNamesOfObject(const gd::String &objectOrGroupName) const;
/**
* \brief Return a list containing all objects referred to by the group.
* If an object name is passed, then only this object name is returned.
*
* If \a onlyObjectToSelectIfPresent is set and present in the group(s),
@@ -170,6 +180,8 @@ class GD_CORE_API ObjectsContainersList {
private:
bool HasObjectNamed(const gd::String& name) const;
const gd::Object* GetObject(const gd::String& name) const;
bool HasObjectWithVariableNamed(const gd::String& objectName,
const gd::String& variableName) const;

View File

@@ -1,136 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
/**
* @file Tests covering layout content helper methods.
*/
#include "GDCore/Project/Layout.h"
#include "DummyPlatform.h"
#include "GDCore/CommonTools.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/Project.h"
#include "catch.hpp"
using namespace gd;
TEST_CASE("Layout", "[common]") {
SECTION("Find the type of a behavior in a object") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
REQUIRE(GetTypeOfBehaviorInObjectOrGroup(
project.GetObjects(), layout.GetObjects(),
"MyObject", "MyBehavior", true) == "MyExtension::MyBehavior");
}
SECTION("Give an empty type for an object that doesn't have the behavior") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
REQUIRE(GetTypeOfBehaviorInObjectOrGroup(
project.GetObjects(), layout.GetObjects(),
"MyObject", "MyBehavior", true) == "");
}
SECTION("Find the type of a behavior in a group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
object2.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto &group =
layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
REQUIRE(GetTypeOfBehaviorInObjectOrGroup(
project.GetObjects(), layout.GetObjects(),
"MyGroup", "MyBehavior", true) == "MyExtension::MyBehavior");
}
SECTION(
"Give an empty type for a group with an object missing the behavior") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
// object2 doesn't have the behavior.
auto &group =
layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
REQUIRE(GetTypeOfBehaviorInObjectOrGroup(
project.GetObjects(), layout.GetObjects(),
"MyGroup", "MyBehavior", true) == "");
}
SECTION("Give an empty type for a group with behaviors of same name but "
"different types") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
object2.AddNewBehavior(project, "MyExtension::MyOtherBehavior",
"MyBehavior");
auto &group =
layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
REQUIRE(GetTypeOfBehaviorInObjectOrGroup(
project.GetObjects(), layout.GetObjects(),
"MyGroup", "MyBehavior", true) == "");
}
SECTION("Give an empty type for an empty group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
auto &group =
layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
REQUIRE(GetTypeOfBehaviorInObjectOrGroup(
project.GetObjects(), layout.GetObjects(),
"MyGroup", "MyBehavior", true) == "");
}
}

View File

@@ -0,0 +1,488 @@
/*
* GDevelop Core
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
/**
* @file Tests covering layout content helper methods.
*/
#include "GDCore/Project/ObjectsContainersList.h"
#include "DummyPlatform.h"
#include "GDCore/CommonTools.h"
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteObject.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Tools/MakeUnique.h"
#include "catch.hpp"
#include <algorithm>
#include <vector>
using namespace gd;
TEST_CASE("ObjectContainersList (HasObjectOrGroupNamed)", "[common]") {
SECTION("Can check an object exists") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.HasObjectOrGroupNamed("MyObject"));
REQUIRE(!objectsContainersList.HasObjectOrGroupNamed("MyWrongObject"));
}
SECTION("Can check a group exists") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.HasObjectOrGroupNamed("MyGroup"));
}
}
TEST_CASE("ObjectContainersList (HasObjectOrGroupWithVariableNamed)", "[common]") {
SECTION("Can check a variable exists in an object") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
object.GetVariables().InsertNew("MyVariable", 0);
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.HasObjectOrGroupWithVariableNamed("MyObject", "MyVariable"));
REQUIRE(!objectsContainersList.HasObjectOrGroupWithVariableNamed("MyObject", "MyWrongVariable"));
}
SECTION("Can check a variable exists in a group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.GetVariables().InsertNew("MyVariable", 0);
// This variable is only in one of the 2 objects.
object1.GetVariables().InsertNew("MyOtherVariable", 0);
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
object1.GetVariables().InsertNew("MyVariable", 0);
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.HasObjectOrGroupWithVariableNamed("MyGroup", "MyVariable"));
REQUIRE(!objectsContainersList.HasObjectOrGroupWithVariableNamed("MyGroup", "MyWrongVariable"));
}
}
TEST_CASE("ObjectContainersList (GetTypeOfObject)", "[common]") {
SECTION("Find the type of an object") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfObject("MyObject") == "MyExtension::Sprite");
}
SECTION("Find the object type of a group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfObject("MyGroup") == "MyExtension::Sprite");
}
SECTION("Give an empty type for groups with mixed object types") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "FakeObjectWithDefaultBehavior", "MyObject2", 0);
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfObject("MyGroup") == "");
}
SECTION("Give an empty type for an empty group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfObject(
"MyGroup") == "");
}
}
TEST_CASE("ObjectContainersList (GetTypeOfBehaviorInObjectOrGroup)",
"[common]") {
SECTION("Find the type of a behavior in an object") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfBehaviorInObjectOrGroup(
"MyObject", "MyBehavior", true) == "MyExtension::MyBehavior");
}
SECTION("Give an empty type for an object that doesn't have the behavior") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfBehaviorInObjectOrGroup(
"MyObject", "MyBehavior", true) == "");
}
SECTION("Find the type of a behavior in a group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
object2.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfBehaviorInObjectOrGroup(
"MyGroup", "MyBehavior", true) == "MyExtension::MyBehavior");
}
SECTION(
"Give an empty type for a group with an object missing the behavior") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
// object2 doesn't have the behavior.
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfBehaviorInObjectOrGroup(
"MyGroup", "MyBehavior", true) == "");
}
SECTION("Give an empty type for a group with behaviors of same name but different types") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
object2.AddNewBehavior(project, "MyExtension::MyOtherBehavior",
"MyBehavior");
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfBehaviorInObjectOrGroup(
"MyGroup", "MyBehavior", true) == "");
}
SECTION("Give an empty type for an empty group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfBehaviorInObjectOrGroup(
"MyGroup", "MyBehavior", true) == "");
}
}
TEST_CASE("ObjectContainersList (HasBehaviorInObjectOrGroup)", "[common]") {
SECTION("Can check a behavior exists in an object") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.HasBehaviorInObjectOrGroup("MyObject", "MyBehavior"));
REQUIRE(!objectsContainersList.HasBehaviorInObjectOrGroup("MyObject", "MyWrongBehavior"));
}
SECTION("Can check a behavior exists in a group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
// This behavior is only in one of the 2 objects.
object1.AddNewBehavior(project, "MyExtension::MyOtherBehavior",
"MyOtherBehavior");
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
object2.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.HasBehaviorInObjectOrGroup("MyGroup", "MyBehavior"));
REQUIRE(!objectsContainersList.HasBehaviorInObjectOrGroup("MyGroup", "MyOtherBehavior"));
}
}
TEST_CASE("ObjectContainersList (GetBehaviorsOfObject)", "[common]") {
SECTION("Find the behaviors in an object") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
const auto behaviors =
objectsContainersList.GetBehaviorsOfObject("MyObject", true);
REQUIRE(behaviors.size() == 1);
REQUIRE(behaviors[0] == "MyBehavior");
}
SECTION("Find the behaviors in a group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
// This behavior is only in one of the 2 objects.
object1.AddNewBehavior(project, "MyExtension::MyOtherBehavior",
"MyOtherBehavior");
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
object2.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
const auto behaviors =
objectsContainersList.GetBehaviorsOfObject("MyGroup", true);
REQUIRE(behaviors.size() == 1);
REQUIRE(behaviors[0] == "MyBehavior");
}
}
namespace {
gd::SpriteObject BuildSpriteWithAnimations(gd::String animationName1 = "!",
gd::String animationName2 = "!",
gd::String animationName3 = "!") {
gd::SpriteObject configuration;
gd::SpriteAnimationList &animations = configuration.GetAnimations();
if (animationName1 != "!") {
gd::Animation animation;
animation.SetName(animationName1);
animations.AddAnimation(animation);
if (animationName2 != "!") {
gd::Animation animation;
animation.SetName(animationName2);
animations.AddAnimation(animation);
}
if (animationName3 != "!") {
gd::Animation animation;
animation.SetName(animationName3);
animations.AddAnimation(animation);
}
}
return configuration;
}
bool Contains(const std::vector<gd::String> &vector, const gd::String &value) {
return std::find(vector.begin(), vector.end(), value) !=
vector.end();
}
} // namespace
TEST_CASE("ObjectContainersList (GetAnimationNamesOfObject)", "[common]") {
SECTION("Find the animation names in a sprite") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object object("MyObject", "Sprite",
gd::make_unique<gd::SpriteObject>(
BuildSpriteWithAnimations("Idle", "Run")));
layout.GetObjects().InsertObject(object, 0);
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
const auto animationNames =
objectsContainersList.GetAnimationNamesOfObject("MyObject");
REQUIRE(Contains(animationNames, "Idle"));
REQUIRE(Contains(animationNames, "Run"));
REQUIRE(animationNames.size() == 2);
}
SECTION("Find the animation names in a group of sprite") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object object1("MyObject1", "Sprite",
gd::make_unique<gd::SpriteObject>(
BuildSpriteWithAnimations("Idle", "Jump", "Run")));
layout.GetObjects().InsertObject(object1, 0);
gd::Object object2("MyObject2", "Sprite",
gd::make_unique<gd::SpriteObject>(
BuildSpriteWithAnimations("Run", "Idle", "Climb")));
layout.GetObjects().InsertObject(object2, 0);
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
const auto animationNames =
objectsContainersList.GetAnimationNamesOfObject("MyGroup");
REQUIRE(Contains(animationNames, "Idle"));
REQUIRE(Contains(animationNames, "Run"));
REQUIRE(animationNames.size() == 2);
}
}

View File

@@ -235,6 +235,19 @@ void Model3DObjectConfiguration::ExposeResources(
worker.ExposeModel3D(modelResourceName);
}
const gd::String &
Model3DObjectConfiguration::GetAnimationName(size_t index) const {
return GetAnimation(index).GetName();
}
bool Model3DObjectConfiguration::HasAnimationNamed(
const gd::String &name) const {
return !name.empty() && (find_if(animations.begin(), animations.end(),
[&name](const Model3DAnimation &animation) {
return animation.GetName() == name;
}) != animations.end());
}
Model3DAnimation Model3DObjectConfiguration::badAnimation;
const Model3DAnimation &
@@ -252,14 +265,6 @@ Model3DAnimation &Model3DObjectConfiguration::GetAnimation(std::size_t nb) {
return animations[nb];
}
bool Model3DObjectConfiguration::HasAnimationNamed(
const gd::String &name) const {
return !name.empty() && (find_if(animations.begin(), animations.end(),
[&name](const Model3DAnimation &animation) {
return animation.GetName() == name;
}) != animations.end());
}
void Model3DObjectConfiguration::AddAnimation(
const Model3DAnimation &animation) {
animations.push_back(animation);

View File

@@ -85,6 +85,12 @@ public:
* Methods related to animations management
*/
///@{
std::size_t GetAnimationsCount() const override { return animations.size(); };
const gd::String &GetAnimationName(size_t index) const override;
bool HasAnimationNamed(const gd::String &animationName) const override;
/**
* \brief Return the animation at the specified index.
* If the index is out of bound, a "bad animation" object is returned.
@@ -97,16 +103,6 @@ public:
*/
Model3DAnimation &GetAnimation(std::size_t nb);
/**
* \brief Return the number of animations this object has.
*/
std::size_t GetAnimationsCount() const { return animations.size(); };
/**
* \brief Return true if the animation called "name" exists.
*/
bool HasAnimationNamed(const gd::String& name) const;
/**
* \brief Add an animation at the end of the existing ones.
*/

View File

@@ -116,6 +116,18 @@ void SpineObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker &work
worker.ExposeEmbeddeds(spineResourceName);
}
const gd::String &
SpineObjectConfiguration::GetAnimationName(size_t index) const {
return GetAnimation(index).GetName();
}
bool SpineObjectConfiguration::HasAnimationNamed(const gd::String &name) const {
return !name.empty() && (find_if(animations.begin(), animations.end(),
[&name](const SpineAnimation &animation) {
return animation.GetName() == name;
}) != animations.end());
}
const SpineAnimation &
SpineObjectConfiguration::GetAnimation(std::size_t nb) const {
if (nb >= animations.size()) return badAnimation;
@@ -129,13 +141,6 @@ SpineAnimation &SpineObjectConfiguration::GetAnimation(std::size_t nb) {
return animations[nb];
}
bool SpineObjectConfiguration::HasAnimationNamed(const gd::String &name) const {
return !name.empty() && (find_if(animations.begin(), animations.end(),
[&name](const SpineAnimation &animation) {
return animation.GetName() == name;
}) != animations.end());
}
void SpineObjectConfiguration::AddAnimation(const SpineAnimation &animation) {
animations.push_back(animation);
}

View File

@@ -93,15 +93,11 @@ public:
*/
SpineAnimation &GetAnimation(std::size_t nb);
/**
* \brief Return the number of animations this object has.
*/
std::size_t GetAnimationsCount() const { return animations.size(); };
std::size_t GetAnimationsCount() const override { return animations.size(); };
/**
* \brief Return true if the animation called "name" exists.
*/
bool HasAnimationNamed(const gd::String& name) const;
const gd::String &GetAnimationName(size_t index) const override;
bool HasAnimationNamed(const gd::String &animationName) const override;
/**
* \brief Add an animation at the end of the existing ones.

View File

@@ -659,6 +659,7 @@ interface ObjectsContainersList {
[Const, Value] DOMString GetTypeOfObject([Const] DOMString objectName);
[Const, Value] DOMString GetTypeOfBehavior([Const] DOMString name, boolean searchInGroups);
[Value] VectorString GetBehaviorsOfObject([Const] DOMString name, boolean searchInGroups);
[Value] VectorString GetAnimationNamesOfObject([Const] DOMString name);
[Const, Value] DOMString GetTypeOfBehaviorInObjectOrGroup([Const] DOMString objectOrGroupName, [Const] DOMString behaviorName, boolean searchInGroups);
boolean HasObjectOrGroupNamed([Const] DOMString name);

View File

@@ -590,6 +590,7 @@ export class ObjectsContainersList extends EmscriptenObject {
getTypeOfObject(objectName: string): string;
getTypeOfBehavior(name: string, searchInGroups: boolean): string;
getBehaviorsOfObject(name: string, searchInGroups: boolean): VectorString;
getAnimationNamesOfObject(name: string): VectorString;
getTypeOfBehaviorInObjectOrGroup(objectOrGroupName: string, behaviorName: string, searchInGroups: boolean): string;
hasObjectOrGroupNamed(name: string): boolean;
hasObjectOrGroupWithVariableNamed(objectName: string, variableName: string): ObjectsContainersList_VariableExistence;

View File

@@ -10,6 +10,7 @@ declare class gdObjectsContainersList {
getTypeOfObject(objectName: string): string;
getTypeOfBehavior(name: string, searchInGroups: boolean): string;
getBehaviorsOfObject(name: string, searchInGroups: boolean): gdVectorString;
getAnimationNamesOfObject(name: string): gdVectorString;
getTypeOfBehaviorInObjectOrGroup(objectOrGroupName: string, behaviorName: string, searchInGroups: boolean): string;
hasObjectOrGroupNamed(name: string): boolean;
hasObjectOrGroupWithVariableNamed(objectName: string, variableName: string): ObjectsContainersList_VariableExistence;

View File

@@ -34,7 +34,7 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
}));
const {
project,
projectScopedContainersAccessor,
scope,
instructionMetadata,
instruction,
@@ -43,7 +43,7 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
parameterIndex,
} = props;
const { layout, eventsFunctionsExtension, eventsBasedObject } = scope;
const { eventsFunctionsExtension, eventsBasedObject } = scope;
// We don't memo/callback this, as we want to recompute it every time something changes.
// Because of the function getLastObjectParameterValue.
@@ -55,84 +55,21 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
expression,
parameterIndex,
});
if (!objectName || !project) {
return [];
}
const object = getObjectByName(
project.getObjects(),
layout ? layout.getObjects() : null,
objectName
);
if (!object) {
return [];
}
if (object.getType() === 'Sprite') {
const spriteConfiguration = gd.asSpriteConfiguration(
object.getConfiguration()
);
const animations = spriteConfiguration.getAnimations();
return mapFor(0, animations.getAnimationsCount(), index => {
const animationName = animations.getAnimation(index).getName();
return animationName.length > 0 ? animationName : null;
}).filter(Boolean);
} else if (object.getType() === 'Scene3D::Model3DObject') {
const model3DConfiguration = gd.asModel3DConfiguration(
object.getConfiguration()
);
return mapFor(0, model3DConfiguration.getAnimationsCount(), index => {
const animationName = model3DConfiguration
.getAnimation(index)
.getName();
return animationName.length > 0 ? animationName : null;
})
.filter(Boolean)
.sort();
} else if (object.getType() === 'SpineObject::SpineObject') {
const spineConfiguration = gd.asSpineConfiguration(
object.getConfiguration()
);
return mapFor(0, spineConfiguration.getAnimationsCount(), index => {
const animationName = spineConfiguration
.getAnimation(index)
.getName();
return animationName.length > 0 ? animationName : null;
})
.filter(Boolean)
.sort();
} else if (project.hasEventsBasedObject(object.getType())) {
const eventsBasedObject = project.getEventsBasedObject(
object.getType()
);
if (eventsBasedObject.isAnimatable()) {
const customObjectConfiguration = gd.asCustomObjectConfiguration(
object.getConfiguration()
);
const animations = customObjectConfiguration.getAnimations();
return mapFor(0, animations.getAnimationsCount(), index => {
const animationName = animations.getAnimation(index).getName();
return animationName.length > 0 ? animationName : null;
}).filter(Boolean);
}
}
return [];
return objectName
? projectScopedContainersAccessor
.get()
.getObjectsContainersList()
.getAnimationNamesOfObject(objectName)
.toJSArray()
: [];
};
const animationNames = getAnimationNames();
const canAutocomplete = !eventsFunctionsExtension || eventsBasedObject;
const animationNames = canAutocomplete ? getAnimationNames() : [];
const isCurrentValueInAnimationNamesList = !!animationNames.find(
animationName => `"${animationName}"` === props.value
);
const canAutocomplete = !eventsFunctionsExtension || eventsBasedObject;
// If the current value is not in the list of animation names, display an expression field.
const [isExpressionField, setIsExpressionField] = React.useState(
(!!props.value && !isCurrentValueInAnimationNamesList) || !canAutocomplete