Compare commits

..

32 Commits

Author SHA1 Message Date
Florian Rival
fcfb71132c Bump GDCore version 2018-04-03 21:21:32 +02:00
Florian Rival
2017d34197 Make explicit to install InnoSetup Unicode to generate GD4 Windows installer 2018-04-03 18:54:20 +02:00
Florian Rival
662c7bd397 Add missing header files 2018-04-03 18:49:05 +02:00
Florian Rival
cf5011c149 Merge pull request #457 from 4ian/feature/copy-paste-delete-variables-list
Copy/paste/delete variables for GDevelop 5
2018-04-03 18:31:31 +02:00
Florian Rival
954520ae3b Add comment about compiler version for compiling GDCore/GDCpp/IDE 2018-04-03 18:31:06 +02:00
Florian Rival
6ffcd6dfb0 Merge branch 'master' of https://github.com/4ian/GD 2018-04-02 21:53:41 +02:00
Florian Rival
80e7a6010a Add missing std::endl in Light_Manager sometimes creating conflicts with
FileStream.

* Other files should be audited for missing std::endl.
* FileStream should be protected against this.
2018-04-02 21:49:29 +02:00
Florian Rival
0dc023ba89 Improve renaming of pasted objects in newIDE 2018-04-02 12:25:17 +02:00
Florian Rival
e70021d0dd Refactor Variable/VariablesContainer and add copy/paste/delete to VariablesList in newIDE 2018-04-01 23:42:52 +02:00
Florian Rival
eb63bda7d2 [WIP] Change VariablesContainer to keep references to variables valid after a removal/move/swap 2018-04-01 20:19:54 +02:00
Florian Rival
10833aa45d [WIP] Add copy/paste/delete in VariablesList in newIDE 2018-04-01 19:44:22 +02:00
Florian Rival
eed844357e Add WindowBorder color to layout background 2018-04-01 16:56:29 +02:00
Florian Rival
6d91676dab Bump version 2018-04-01 01:44:32 +02:00
Florian Rival
69410d62ea Fix theming of objects parameters conflicting between default and dark theme 2018-04-01 01:11:12 +02:00
Florian Rival
977425e700 Add explanation about how to compile libGD.js manually 2018-03-31 13:19:57 +02:00
Florian Rival
49d409260a Add TODO about a performance improvement for Sprite collision mask in native game engine 2018-03-31 00:20:41 +02:00
Florian Rival
a50b62a2d8 Update links in README 2018-03-30 23:53:17 +02:00
Florian Rival
6b21ebcc9b Merge pull request #455 from 4ian/feature/collision-mask-editor
Collision mask editor for GDevelop 5
2018-03-30 23:46:51 +02:00
Florian Rival
3c8aa4a249 Display circle for each vertex of collision mask polygons 2018-03-30 23:12:49 +02:00
Florian Rival
a8e9fa5895 Ensure polygon vertices are not put outside the sprite bounding box 2018-03-30 23:05:11 +02:00
Florian Rival
c9f8b4a8ed [WIP] Add preview of default bounding box collision mask 2018-03-30 22:52:22 +02:00
Florian Rival
6b38479166 [WIP] Factor selectors of PointsEditor and CollisionMasksEditor into SpriteSelector 2018-03-30 22:44:19 +02:00
Florian Rival
4ccbc1b958 [WIP] Add preview of collision mask polygons 2018-03-30 22:01:21 +02:00
Florian Rival
54d7d284c8 [WIP] Add CollisionMasksEditor in newIDE (preview not yet done) 2018-03-30 18:06:21 +02:00
Florian Rival
58ed74e020 Add help button about Play Store when one-click Android packaging is finished 2018-03-30 00:00:15 +02:00
Florian Rival
e8ce83b162 Bump version 2018-03-29 22:34:06 +02:00
Florian Rival
9b91f06011 Add menu item to set an object as global 2018-03-29 22:09:23 +02:00
Florian Rival
17247cbbf1 Ensure coordinates of objects moved with the mouse are round 2018-03-29 22:05:42 +02:00
Florian Rival
10b81dd232 Fix StartPage snapshot test 2018-03-25 20:19:48 +02:00
Florian Rival
3f3a5dbd3b Remove warnings and add Flow on StartPage 2018-03-25 20:05:35 +02:00
Florian Rival
6ff8ee749d Fix changes wrongly discarded in ObjectField 2018-03-17 17:21:40 +01:00
Florian Rival
db5f146818 Fix errors with some parameter fields 2018-03-17 17:18:03 +01:00
71 changed files with 1847 additions and 539 deletions

View File

@@ -24,9 +24,9 @@
"__WXMAC__",
"__WXOSX__",
"__WXOSX_COCOA__",
"GD_CORE_API=\/* Macro used to export classes on Windows, please ignore *\/",
"GD_API=\/* Macro used to export classes on Windows, please ignore *\/",
"GD_EXTENSION_API=\/* Macro used to export classes on Windows, please ignore *\/",
"GD_CORE_API=/* Macro used to export classes on Windows, please ignore */",
"GD_API=/* Macro used to export classes on Windows, please ignore */",
"GD_EXTENSION_API=/* Macro used to export classes on Windows, please ignore */",
"WXUSINGDLL"
],
"intelliSenseMode": "clang-x64",
@@ -45,7 +45,10 @@
"macFrameworkPath": [
"/System/Library/Frameworks",
"/Library/Frameworks"
]
],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17"
},
{
"name": "Linux",
@@ -66,9 +69,9 @@
"__WXMAC__",
"__WXOSX__",
"__WXOSX_COCOA__",
"GD_CORE_API=\/* Macro used to export classes on Windows, please ignore *\/",
"GD_API=\/* Macro used to export classes on Windows, please ignore *\/",
"GD_EXTENSION_API=\/* Macro used to export classes on Windows, please ignore *\/",
"GD_CORE_API=/* Macro used to export classes on Windows, please ignore */",
"GD_API=/* Macro used to export classes on Windows, please ignore */",
"GD_EXTENSION_API=/* Macro used to export classes on Windows, please ignore */",
"WXUSINGDLL"
],
"intelliSenseMode": "clang-x64",
@@ -102,9 +105,9 @@
"__WXMAC__",
"__WXOSX__",
"__WXOSX_COCOA__",
"GD_CORE_API=\/* Macro used to export classes on Windows, please ignore *\/",
"GD_API=\/* Macro used to export classes on Windows, please ignore *\/",
"GD_EXTENSION_API=\/* Macro used to export classes on Windows, please ignore *\/",
"GD_CORE_API=/* Macro used to export classes on Windows, please ignore */",
"GD_API=/* Macro used to export classes on Windows, please ignore */",
"GD_EXTENSION_API=/* Macro used to export classes on Windows, please ignore */",
"WXUSINGDLL"
],
"intelliSenseMode": "msvc-x64",
@@ -119,4 +122,4 @@
}
],
"version": 3
}
}

View File

@@ -89,5 +89,6 @@
"newIDE/electron-app/app/www": true
},
// Support for Flowtype:
"javascript.validate.enable": false
"javascript.validate.enable": false,
"flow.useNPMPackagedFlow": true
}

View File

@@ -11,7 +11,7 @@ IF NOT EXIST "%INNOSETUP_EXE%" set INNOSETUP_EXE=C:\Program Files\Inno Setup 5\I
IF EXIST "%INNOSETUP_EXE%" (
echo "Note: InnoSetup found at %INNOSETUP_EXE%"
) ELSE (
echo Warning: InnoSetup not found! Skipping installer creation.
echo Warning: InnoSetup Unicode not found! Skipping installer creation.
SET SKIPINSTALLER=1
)

View File

@@ -13,17 +13,17 @@ AllowNoIcons=yes
LicenseFile=..\Output\Release_Windows\License-en.rtf
InfoBeforeFile=..\Output\Release_Windows\Informations-en.rtf
OutputDir=.\
OutputBaseFilename=gd4096
OutputBaseFilename=gd4097
Compression=lzma
SolidCompression=yes
SetupIconFile=..\Output\Release_Windows\res\icon.ico
VersionInfoVersion=4.0
WizardImageFile=Setup bitmap\wizbmp.bmp
WizardSmallImageFile=Setup bitmap/smallicon.bmp
AppCopyright=2008-2017 Florian Rival
AppCopyright=2008-2018 Florian Rival
VersionInfoCompany=Florian Rival
VersionInfoDescription=GDevelop setup
VersionInfoCopyright=2008-2016 Florian Rival
VersionInfoCopyright=2008-2018 Florian Rival
VersionInfoProductName=GDevelop
VersionInfoProductVersion=4.0

View File

@@ -78,6 +78,7 @@
* The installation is fairly simple :<br>
* <br>
* - Launch the installer.<br>
* - Uncheck "Check for updated files on the TDM-GCC server" (otherwise you won't get TDM-GCC 4.9.2 but a more recent version that won't be compatible with wxWidgets pre-compiled binaries)
* - Choose Create.<br>
\image html compilerInstall1.png

View File

@@ -26,14 +26,24 @@ public:
mutable std::vector<sf::Vector2f> edges; ///< Edges. Can be computed from vertices using ComputeEdges()
/**
* Moves each vertices from the given amount.
* \brief Get the vertices composing the polygon.
*/
std::vector<sf::Vector2f> & GetVertices() { return vertices; }
/**
* \brief Get the vertices composing the polygon.
*/
const std::vector<sf::Vector2f> & GetVertices() const { return vertices; }
/**
* \brief Moves each vertices from the given amount.
*
* \note Edges are updated, there is no need to call ComputeEdges after calling Move.
*/
void Move(float x, float y);
/**
* Rotate the polygon.
* \brief Rotate the polygon.
* \param angle Angle in radians
*
* \warning Rotation is made clockwise
@@ -42,18 +52,18 @@ public:
void Rotate(float angle);
/**
* Automatically fill edges vector using vertices.
* \brief Automatically fill edges vector using vertices.
*/
void ComputeEdges() const;
/**
* Check if the polygon is convex.
* \brief Check if the polygon is convex.
* \return true if the polygon is convex
*/
bool IsConvex() const;
/**
* Return the position of the center of the polygon
* \brief Return the position of the center of the polygon
*/
sf::Vector2f ComputeCenter() const;
@@ -62,7 +72,7 @@ public:
*/
///@{
/**
* Create a rectangle
* \brief Create a rectangle
*/
static Polygon2d CreateRectangle(float width, float height);
///@}

View File

@@ -103,6 +103,7 @@ bool Sprite::SetDefaultCenterPoint(bool enabled)
std::vector<Polygon2d> Sprite::GetCollisionMask() const
{
//TODO(perf): Cache to avoid re-creating a mask at every call
#if !defined(EMSCRIPTEN)
if ( automaticCollisionMask )
{

View File

@@ -56,6 +56,16 @@ public:
*/
std::vector<Polygon2d> GetCollisionMask() const;
/**
* \brief Get the custom collision mask.
*/
std::vector<Polygon2d> & GetCustomCollisionMask() { return customCollisionMask; };
/**
* \brief Get the custom collision mask.
*/
const std::vector<Polygon2d> & GetCustomCollisionMask() const { return customCollisionMask; };
/**
* \brief Set the custom collision mask.
* Call then `SetCollisionMaskAutomatic(false)` to use it.

View File

@@ -225,13 +225,13 @@ void ChooseVariableDialog::RefreshVariable(wxTreeListItem item, const gd::String
variablesList->SetItemText(item, 1, "(Structure)");
//Add/update children
const std::map<gd::String, gd::Variable> & children = variable.GetAllChildren();
const auto & children = variable.GetAllChildren();
wxTreeListItem currentChildItem = variablesList->GetFirstChild(item);
wxTreeListItem lastChildItem;
for(std::map<gd::String, gd::Variable>::const_iterator it = children.begin();it != children.end();++it)
for(auto it = children.begin();it != children.end();++it)
{
if ( !currentChildItem.IsOk() ) currentChildItem = variablesList->AppendItem(item, it->first);
RefreshVariable(currentChildItem, it->first, it->second);
RefreshVariable(currentChildItem, it->first, *it->second);
lastChildItem = currentChildItem;
currentChildItem = variablesList->GetNextSibling(currentChildItem);
@@ -255,10 +255,11 @@ void ChooseVariableDialog::RefreshAll()
for (std::size_t i = 0;i<temporaryContainer->Count();++i)
{
const std::pair<gd::String, gd::Variable> & variable = temporaryContainer->Get(i);
const gd::String & name = temporaryContainer->GetNameAt(i);
const auto & variable = temporaryContainer->Get(i);
wxTreeListItem item = variablesList->AppendItem(variablesList->GetRootItem(), variable.first);
RefreshVariable(item, variable.first, variable.second);
wxTreeListItem item = variablesList->AppendItem(variablesList->GetRootItem(), name);
RefreshVariable(item, name, variable);
variablesList->Expand(item);
}
@@ -330,20 +331,11 @@ void ChooseVariableDialog::OnAddVarSelected(wxCommandEvent& event)
void ChooseVariableDialog::OnMoveUpVarSelected(wxCommandEvent& event)
{
UpdateSelectedAndParentVariable();
for (std::size_t i = 1;i<temporaryContainer->Count();++i)
{
const std::pair<gd::String, gd::Variable> & currentVar = temporaryContainer->Get(i);
if ( currentVar.first == selectedVariableName)
{
const std::pair<gd::String, gd::Variable> & prevVar = temporaryContainer->Get(i-1);
temporaryContainer->Swap(i, i-1);
RefreshAll();
modificationCount++;
return;
}
}
auto position = temporaryContainer->GetPosition(selectedVariableName);
temporaryContainer->Move(position, position-1);
RefreshAll();
modificationCount++;
}
/**
@@ -352,21 +344,11 @@ void ChooseVariableDialog::OnMoveUpVarSelected(wxCommandEvent& event)
void ChooseVariableDialog::OnMoveDownVarSelected(wxCommandEvent& event)
{
UpdateSelectedAndParentVariable();
for (std::size_t i = 0;i<temporaryContainer->Count()-1;++i)
{
const std::pair<gd::String, gd::Variable> & currentVar = temporaryContainer->Get(i);
if ( currentVar.first == selectedVariableName)
{
const std::pair<gd::String, gd::Variable> & nextVar = temporaryContainer->Get(i+1);
temporaryContainer->Swap(i, i+1);
RefreshAll();
modificationCount++;
return;
}
}
auto position = temporaryContainer->GetPosition(selectedVariableName);
temporaryContainer->Move(position, position+1);
RefreshAll();
modificationCount++;
}
/**

View File

@@ -5,24 +5,23 @@
*/
#include "GDCore/Project/Variable.h"
#include "GDCore/String.h"
#include <sstream>
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
#include "GDCore/TinyXml/tinyxml.h"
#include <sstream>
using namespace std;
namespace gd
{
namespace gd {
/**
* Get value as a double
*/
double Variable::GetValue() const
{
if (!isNumber)
{
stringstream ss; ss << str;
if (!isNumber) {
stringstream ss;
ss << str;
ss >> value;
isNumber = true;
}
@@ -30,11 +29,11 @@ double Variable::GetValue() const
return value;
}
const gd::String & Variable::GetString() const
const gd::String& Variable::GetString() const
{
if (isNumber)
{
stringstream s; s << (value);
if (isNumber) {
stringstream s;
s << (value);
str = s.str();
isNumber = false;
}
@@ -42,7 +41,7 @@ const gd::String & Variable::GetString() const
return str;
}
bool Variable::HasChild(const gd::String & name) const
bool Variable::HasChild(const gd::String& name) const
{
return isStructure && children.find(name) != children.end();
}
@@ -53,15 +52,15 @@ bool Variable::HasChild(const gd::String & name) const
* If the variable is not a structure or has not
* the specified child, an empty variable is returned.
*/
Variable & Variable::GetChild(const gd::String & name)
Variable& Variable::GetChild(const gd::String& name)
{
std::map<gd::String, Variable>::iterator it = children.find(name);
if ( it != children.end() ) return it->second;
auto it = children.find(name);
if (it != children.end())
return *it->second;
isStructure = true;
Variable newEmptyVariable;
children[name] = newEmptyVariable;
return children[name];
children[name] = std::make_shared<gd::Variable>();
return *children[name];
}
/**
@@ -70,26 +69,28 @@ Variable & Variable::GetChild(const gd::String & name)
* If the variable is not a structure or has not
* the specified child, an empty variable is returned.
*/
const Variable & Variable::GetChild(const gd::String & name) const
const Variable& Variable::GetChild(const gd::String& name) const
{
std::map<gd::String, Variable>::iterator it = children.find(name);
if ( it != children.end() ) return it->second;
auto it = children.find(name);
if (it != children.end())
return *it->second;
isStructure = true;
Variable newEmptyVariable;
children[name] = newEmptyVariable;
return children[name];
children[name] = std::make_shared<gd::Variable>();
return *children[name];
}
void Variable::RemoveChild(const gd::String & name)
void Variable::RemoveChild(const gd::String& name)
{
if ( !isStructure ) return;
if (!isStructure)
return;
children.erase(name);
}
bool Variable::RenameChild(const gd::String & oldName, const gd::String & newName)
bool Variable::RenameChild(const gd::String& oldName, const gd::String& newName)
{
if ( !isStructure || !HasChild(oldName)|| HasChild(newName) ) return false;
if (!isStructure || !HasChild(oldName) || HasChild(newName))
return false;
children[newName] = children[oldName];
children.erase(oldName);
@@ -99,91 +100,114 @@ bool Variable::RenameChild(const gd::String & oldName, const gd::String & newNam
void Variable::ClearChildren()
{
if ( !isStructure ) return;
if (!isStructure)
return;
children.clear();
}
void Variable::SerializeTo(SerializerElement & element) const
void Variable::SerializeTo(SerializerElement& element) const
{
if (!isStructure)
element.SetAttribute("value", GetString());
else
{
SerializerElement & childrenElement = element.AddChild("children");
else {
SerializerElement& childrenElement = element.AddChild("children");
childrenElement.ConsiderAsArrayOf("variable");
for (std::map<gd::String, gd::Variable>::iterator i = children.begin(); i != children.end(); ++i)
{
SerializerElement & variableElement = childrenElement.AddChild("variable");
for (auto i = children.begin(); i != children.end(); ++i) {
SerializerElement& variableElement = childrenElement.AddChild("variable");
variableElement.SetAttribute("name", i->first);
i->second.SerializeTo(variableElement);
i->second->SerializeTo(variableElement);
}
}
}
void Variable::UnserializeFrom(const SerializerElement & element)
void Variable::UnserializeFrom(const SerializerElement& element)
{
isStructure = element.HasChild("children", "Children");
if (isStructure)
{
const SerializerElement & childrenElement = element.GetChild("children", 0, "Children");
if (isStructure) {
const SerializerElement& childrenElement = element.GetChild("children", 0, "Children");
childrenElement.ConsiderAsArrayOf("variable", "Variable");
for (int i = 0; i < childrenElement.GetChildrenCount(); ++i)
{
const SerializerElement & childElement = childrenElement.GetChild(i);
for (int i = 0; i < childrenElement.GetChildrenCount(); ++i) {
const SerializerElement& childElement = childrenElement.GetChild(i);
gd::String name = childElement.GetStringAttribute("name", "", "Name");
gd::Variable childVariable;
childVariable.UnserializeFrom(childElement);
children[name] = childVariable;
children[name] = std::make_shared<gd::Variable>();
children[name]->UnserializeFrom(childElement);
}
}
else
} else
SetString(element.GetStringAttribute("value", "", "Value"));
}
void Variable::SaveToXml(TiXmlElement * element) const
void Variable::SaveToXml(TiXmlElement* element) const
{
if (!element) return;
if (!element)
return;
if ( !isStructure )
if (!isStructure)
element->SetAttribute("Value", GetString().c_str());
else
{
TiXmlElement * childrenElem = new TiXmlElement( "Children" );
element->LinkEndChild( childrenElem );
for (std::map<gd::String, gd::Variable>::iterator i = children.begin(); i != children.end(); ++i)
{
TiXmlElement * variable = new TiXmlElement( "Variable" );
childrenElem->LinkEndChild( variable );
else {
TiXmlElement* childrenElem = new TiXmlElement("Children");
element->LinkEndChild(childrenElem);
for (auto i = children.begin(); i != children.end(); ++i) {
TiXmlElement* variable = new TiXmlElement("Variable");
childrenElem->LinkEndChild(variable);
variable->SetAttribute("Name", i->first.c_str());
i->second.SaveToXml(variable);
i->second->SaveToXml(variable);
}
}
}
void Variable::LoadFromXml(const TiXmlElement * element)
void Variable::LoadFromXml(const TiXmlElement* element)
{
if (!element) return;
if (!element)
return;
isStructure = element->FirstChildElement("Children") != NULL;
if ( isStructure )
{
const TiXmlElement * child = element->FirstChildElement("Children")->FirstChildElement();
while ( child )
{
if (isStructure) {
const TiXmlElement* child = element->FirstChildElement("Children")->FirstChildElement();
while (child) {
gd::String name = child->Attribute("Name") ? child->Attribute("Name") : "";
gd::Variable childVariable;
childVariable.LoadFromXml(child);
children[name] = childVariable;
children[name] = std::make_shared<gd::Variable>();
children[name]->LoadFromXml(child);
child = child->NextSiblingElement();
}
}
else if (element->Attribute("Value"))
} else if (element->Attribute("Value"))
SetString(element->Attribute("Value"));
}
std::vector<gd::String> Variable::GetAllChildrenNames() const
{
std::vector<gd::String> names;
for (auto& it : children) {
names.push_back(it.first);
}
return names;
}
bool Variable::Contains(const gd::Variable& variableToSearch, bool recursive) const
{
for (auto& it : children) {
if (it.second.get() == &variableToSearch)
return true;
if (recursive && it.second->Contains(variableToSearch, true))
return true;
}
return false;
}
void Variable::RemoveRecursively(const gd::Variable& variableToRemove)
{
for (auto it = children.begin(); it != children.end();) {
if (it->second.get() == &variableToRemove) {
it = children.erase(it);
} else {
it->second->RemoveRecursively(variableToRemove);
it++;
}
}
}
}

View File

@@ -8,6 +8,7 @@
#define GDCORE_VARIABLE_H
#include "GDCore/String.h"
#include <map>
#include <memory>
namespace gd { class SerializerElement; }
class TiXmlElement;
@@ -148,11 +149,30 @@ public:
*/
void ClearChildren();
/**
* \brief Get the count of children that the variable has.
*/
size_t GetChildrenCount() const { return children.size(); };
/**
* \brief Get the names of all children
*/
std::vector<gd::String> GetAllChildrenNames() const;
/**
* \brief Get the map containing all the children.
*/
const std::map<gd::String, Variable> & GetAllChildren() const { return children; }
const std::map<gd::String, std::shared_ptr<Variable>> & GetAllChildren() const { return children; }
/**
* \brief Search if a variable is part of the children, optionally recursively
*/
bool Contains(const gd::Variable & variableToSearch, bool recursive) const;
/**
* \brief Remove the specified variable if it can be found in the children
*/
void RemoveRecursively(const gd::Variable & variableToRemove);
///@}
/** \name Serialization
@@ -186,7 +206,7 @@ private:
mutable gd::String str;
mutable bool isNumber; ///< True if the type of the variable is a number.
mutable bool isStructure; ///< False when the variable is a primitive ( i.e: Number or String ), true when it is a structure and has may have children.
mutable std::map<gd::String, Variable> children; ///<Children, when the variable is considered as a structure.
mutable std::map<gd::String, std::shared_ptr<Variable>> children; ///<Children, when the variable is considered as a structure.
};
}

View File

@@ -3,177 +3,192 @@
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
#include <iostream>
#include "GDCore/String.h"
#include <algorithm>
#include "GDCore/Project/Variable.h"
#include "GDCore/TinyXml/tinyxml.h"
#include "GDCore/Project/VariablesContainer.h"
#include "GDCore/Project/Variable.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
#include "GDCore/TinyXml/tinyxml.h"
#include <algorithm>
#include <iostream>
namespace gd
{
namespace gd {
std::pair<gd::String, Variable> VariablesContainer::badVariable;
gd::Variable VariablesContainer::badVariable;
gd::String VariablesContainer::badName;
namespace {
//Tool functor used below
class VariableHasName
{
public:
VariableHasName(gd::String const& name_) : name(name_) { }
//Tool functor used below
class VariableHasName {
public:
VariableHasName(gd::String const& name_)
: name(name_)
{
}
bool operator () (const std::pair<gd::String, gd::Variable> & p)
{
return (p.first == name);
}
gd::String name;
};
bool operator()(const std::pair<gd::String, std::shared_ptr<gd::Variable>>& p)
{
return (p.first == name);
}
gd::String name;
};
}
VariablesContainer::VariablesContainer()
{
}
bool VariablesContainer::Has(const gd::String & name) const
bool VariablesContainer::Has(const gd::String& name) const
{
std::vector < std::pair<gd::String, gd::Variable> >::const_iterator i =
std::find_if(variables.begin(), variables.end(), VariableHasName(name));
auto i = std::find_if(variables.begin(), variables.end(), VariableHasName(name));
return (i != variables.end());
}
std::pair<gd::String, gd::Variable> & VariablesContainer::Get(std::size_t index)
Variable& VariablesContainer::Get(const gd::String& name)
{
if ( index < variables.size() )
return variables[index];
auto i = std::find_if(variables.begin(), variables.end(), VariableHasName(name));
if (i != variables.end())
return *i->second;
return badVariable;
}
const std::pair<gd::String, gd::Variable> & VariablesContainer::Get(std::size_t index) const
const Variable& VariablesContainer::Get(const gd::String& name) const
{
if ( index < variables.size() )
return variables[index];
auto i = std::find_if(variables.begin(), variables.end(), VariableHasName(name));
if (i != variables.end())
return *i->second;
return badVariable;
}
Variable & VariablesContainer::Get(const gd::String & name)
Variable & VariablesContainer::Get(std::size_t index)
{
std::vector < std::pair<gd::String, gd::Variable> >::iterator i =
std::find_if(variables.begin(), variables.end(), VariableHasName(name));
if (i != variables.end())
return i->second;
if (index < variables.size())
return *variables[index].second;
return badVariable.second;
return badVariable;
}
const Variable & VariablesContainer::Get(const gd::String & name) const
const Variable & VariablesContainer::Get(std::size_t index) const
{
std::vector < std::pair<gd::String, gd::Variable> >::const_iterator i =
std::find_if(variables.begin(), variables.end(), VariableHasName(name));
if (i != variables.end())
return i->second;
if (index < variables.size())
return *variables[index].second;
return badVariable.second;
return badVariable;
}
Variable & VariablesContainer::Insert(const gd::String & name, const gd::Variable & variable, std::size_t position)
const gd::String & VariablesContainer::GetNameAt(std::size_t index) const
{
if (position<variables.size())
{
variables.insert(variables.begin()+position, std::make_pair(name, variable));
return variables[position].second;
}
else
{
variables.push_back(std::make_pair(name, variable));
return variables.back().second;
if (index < variables.size())
return variables[index].first;
return badName;
}
Variable& VariablesContainer::Insert(const gd::String& name, const gd::Variable& variable, std::size_t position)
{
auto newVariable = std::make_shared<gd::Variable>(variable);
if (position < variables.size()) {
variables.insert(variables.begin() + position, std::make_pair(name, newVariable));
return *variables[position].second;
} else {
variables.push_back(std::make_pair(name, newVariable));
return *variables.back().second;
}
}
#if defined(GD_IDE_ONLY)
void VariablesContainer::Remove(const gd::String & varName)
void VariablesContainer::Remove(const gd::String& varName)
{
variables.erase(std::remove_if(variables.begin(), variables.end(),
VariableHasName(varName)), variables.end() );
VariableHasName(varName)),
variables.end());
}
std::size_t VariablesContainer::GetPosition(const gd::String & name) const
void VariablesContainer::RemoveRecursively(const gd::Variable& variableToRemove)
{
for(std::size_t i = 0;i<variables.size();++i)
{
if ( variables[i].first == name )
variables.erase(std::remove_if(variables.begin(), variables.end(),
[&variableToRemove](const std::pair<gd::String, std::shared_ptr<gd::Variable>>& nameAndVariable) {
return &variableToRemove == nameAndVariable.second.get();
}),
variables.end());
for (auto& it : variables) {
it.second->RemoveRecursively(variableToRemove);
}
}
std::size_t VariablesContainer::GetPosition(const gd::String& name) const
{
for (std::size_t i = 0; i < variables.size(); ++i) {
if (variables[i].first == name)
return i;
}
return gd::String::npos;
}
Variable & VariablesContainer::InsertNew(const gd::String & name, std::size_t position)
Variable& VariablesContainer::InsertNew(const gd::String& name, std::size_t position)
{
Variable newVariable;
return Insert(name, newVariable, position);
}
bool VariablesContainer::Rename(const gd::String & oldName, const gd::String & newName)
bool VariablesContainer::Rename(const gd::String& oldName, const gd::String& newName)
{
if (Has(newName)) return false;
if (Has(newName))
return false;
std::vector < std::pair<gd::String, gd::Variable> >::iterator i =
std::find_if(variables.begin(), variables.end(), VariableHasName(oldName));
if (i != variables.end()) i->first = newName;
auto i = std::find_if(variables.begin(), variables.end(), VariableHasName(oldName));
if (i != variables.end())
i->first = newName;
return true;
}
void VariablesContainer::Swap(std::size_t firstVariableIndex, std::size_t secondVariableIndex)
{
if ( firstVariableIndex >= variables.size() || secondVariableIndex >= variables.size() )
if (firstVariableIndex >= variables.size() || secondVariableIndex >= variables.size())
return;
std::pair<gd::String, gd::Variable> temp = variables[firstVariableIndex];
auto temp = variables[firstVariableIndex];
variables[firstVariableIndex] = variables[secondVariableIndex];
variables[secondVariableIndex] = temp;
}
void VariablesContainer::Move(std::size_t oldIndex, std::size_t newIndex)
{
if ( oldIndex >= variables.size() || newIndex >= variables.size() )
if (oldIndex >= variables.size() || newIndex >= variables.size())
return;
auto nameAndVariable = variables[oldIndex];
variables.erase(variables.begin() + oldIndex);
Insert(nameAndVariable.first, nameAndVariable.second, newIndex);
variables.insert(variables.begin() + newIndex, nameAndVariable);
}
#endif
void VariablesContainer::SerializeTo(SerializerElement & element) const
void VariablesContainer::SerializeTo(SerializerElement& element) const
{
element.ConsiderAsArrayOf("variable");
for ( std::size_t j = 0;j < variables.size();j++ )
{
SerializerElement & variableElement = element.AddChild("variable");
for (std::size_t j = 0; j < variables.size(); j++) {
SerializerElement& variableElement = element.AddChild("variable");
variableElement.SetAttribute("name", variables[j].first);
variables[j].second.SerializeTo(variableElement);
variables[j].second->SerializeTo(variableElement);
}
}
void VariablesContainer::UnserializeFrom(const SerializerElement & element)
void VariablesContainer::UnserializeFrom(const SerializerElement& element)
{
Clear();
element.ConsiderAsArrayOf("variable", "Variable");
for ( std::size_t j = 0;j < element.GetChildrenCount();j++ )
{
const SerializerElement & variableElement = element.GetChild(j);
for (std::size_t j = 0; j < element.GetChildrenCount(); j++) {
const SerializerElement& variableElement = element.GetChild(j);
Variable variable;
variable.UnserializeFrom(variableElement);
Insert(variableElement.GetStringAttribute("name", "", "Name" ), variable, -1);
Insert(variableElement.GetStringAttribute("name", "", "Name"), variable, -1);
}
}
}

View File

@@ -8,6 +8,7 @@
#define GDCORE_VARIABLESCONTAINER_H
#include "GDCore/String.h"
#include <vector>
#include <memory>
#include "GDCore/Project/Variable.h"
namespace gd { class SerializerElement; }
class TiXmlElement;
@@ -52,18 +53,14 @@ public:
const Variable & Get(const gd::String & name) const;
/**
* \brief Return a pair containing the name and the variable at position \index in the container.
*
* \note If index is invalid, an empty variable is returned.
* \brief Return a reference to the variable at the specified position in the list.
*/
std::pair<gd::String, gd::Variable> & Get(std::size_t index);
Variable & Get(std::size_t index);
/**
* \brief Return a pair containing the name and the variable at position \index in the container.
*
* \note If index is invalid, an empty variable is returned.
* \brief Return a reference to the variable at the specified position in the list.
*/
const std::pair<gd::String, gd::Variable> & Get(std::size_t index) const;
const Variable & Get(std::size_t index) const;
/**
* Must add a new variable constructed from the variable passed as parameter.
@@ -79,6 +76,11 @@ public:
*/
std::size_t Count() const { return variables.size(); };
/**
* \brief Return the name of the variable at a position
*/
const gd::String & GetNameAt(std::size_t index) const;
#if defined(GD_IDE_ONLY)
/**
* \brief return the position of the variable called "name" in the variable list
@@ -94,10 +96,16 @@ public:
Variable & InsertNew(const gd::String & name, std::size_t position = -1);
/**
* \brief Remove the specified variable from the container.
* \brief Remove the variable with the specified name from the container.
* \note This operation is not recursive on variable children
*/
void Remove(const gd::String & name);
/**
* \brief Remove the specified variable from the container.
*/
void RemoveRecursively(const gd::Variable & variable);
/**
* \brief Rename a variable.
* \return true if the variable was renamed, false otherwise.
@@ -138,8 +146,9 @@ public:
private:
std::vector < std::pair<gd::String, gd::Variable> > variables;
static std::pair<gd::String, Variable> badVariable;
std::vector < std::pair<gd::String, std::shared_ptr<gd::Variable>> > variables;
static gd::Variable badVariable;
static gd::String badName;
};
}

View File

@@ -66,11 +66,11 @@ public:
for(auto & child : variable.GetAllChildren())
{
const gd::String & name = child.first;
const gd::Variable & serializedItem = child.second;
inventory.SetMaximum(name, serializedItem.GetChild("maxCount").GetValue());
inventory.SetUnlimited(name, serializedItem.GetChild("unlimited").GetString() == "true");
inventory.SetCount(name, serializedItem.GetChild("count").GetValue());
inventory.Equip(name, serializedItem.GetChild("equipped").GetString() == "true");
const auto & serializedItem = child.second;
inventory.SetMaximum(name, serializedItem->GetChild("maxCount").GetValue());
inventory.SetUnlimited(name, serializedItem->GetChild("unlimited").GetString() == "true");
inventory.SetCount(name, serializedItem->GetChild("count").GetValue());
inventory.Equip(name, serializedItem->GetChild("equipped").GetString() == "true");
}
}

View File

@@ -4,11 +4,11 @@
Light_Manager::Light_Manager() :
commonBlurEffectLoaded(false)
{
std::cout << "Creating Light Manager";
std::cout << "Creating Light Manager" << std::endl;
}
Light_Manager::~Light_Manager()
{
std::cout << "Destroying Light Manager";
std::cout << "Destroying Light Manager" << std::endl;
}

View File

@@ -245,11 +245,10 @@ gd::String GD_API VariableStructureToJSON(const gd::Variable & variable)
gd::String str = "{";
bool firstChild = true;
for(std::map<gd::String, gd::Variable>::const_iterator i = variable.GetAllChildren().begin();
i != variable.GetAllChildren().end();++i)
for(auto i = variable.GetAllChildren().begin(); i != variable.GetAllChildren().end();++i)
{
if ( !firstChild ) str += ",";
str += gd::String::FromUTF8(StringToQuotedJSONString(i->first.c_str()))+": "+VariableStructureToJSON(i->second);
str += gd::String::FromUTF8(StringToQuotedJSONString(i->first.c_str()))+": "+VariableStructureToJSON(*i->second);
firstChild = false;
}

View File

@@ -287,7 +287,7 @@ unsigned int GD_API GetVariableChildCount(gd::Variable & variable)
{
if (variable.IsStructure() == false) return 0;
return variable.GetAllChildren().size();
return variable.GetChildrenCount();
}
double GD_API GetVariableValue(const gd::Variable & variable)

View File

@@ -403,7 +403,7 @@ RaycastResult RuntimeObject::RaycastTest(float x, float y, float angle, float di
if ( sqrt(diffX*diffX + diffY*diffY) > boundingRadius + dist )
return result;
float endX = x + dist*cos(angle*3.14159/180.0);
float endY = y + dist*sin(angle*3.14159/180.0);
float testSqDist = closest ? dist*dist : 0.0f;
@@ -412,7 +412,7 @@ RaycastResult RuntimeObject::RaycastTest(float x, float y, float angle, float di
for (std::size_t i = 0; i < hitboxes.size(); ++i)
{
RaycastResult res = PolygonRaycastTest(hitboxes[i], x, y, endX, endY);
if ( res.collision ) {
if ( closest && (res.closeSqDist < testSqDist) ) {
testSqDist = res.closeSqDist;
@@ -424,7 +424,7 @@ RaycastResult RuntimeObject::RaycastTest(float x, float y, float angle, float di
}
}
}
return result;
}
@@ -752,5 +752,5 @@ void RuntimeObject::VariableClearChildren(gd::Variable & variable)
unsigned int RuntimeObject::GetVariableChildCount(gd::Variable & variable)
{
if (variable.IsStructure() == false) return 0;
return variable.GetAllChildren().size();
return variable.GetChildrenCount();
}

View File

@@ -38,15 +38,16 @@ void RuntimeVariablesContainer::Merge(const gd::VariablesContainer & container)
{
for ( std::size_t i = 0; i<container.Count();++i)
{
const std::pair<gd::String, gd::Variable> & variable = container.Get(i);
const gd::String & name = container.GetNameAt(i);
const gd::Variable & variable = container.Get(i);
if ( Has(variable.first) )
Get(variable.first) = variable.second;
if ( Has(name) )
Get(name) = variable;
else
{
gd::Variable * newVariable = new gd::Variable(variable.second);
gd::Variable * newVariable = new gd::Variable(variable);
variablesArray.push_back(newVariable);
variables[variable.first] = newVariable;
variables[name] = newVariable;
}
}
}

View File

@@ -9,7 +9,7 @@ Getting started [![Build Status](https://semaphoreci.com/api/v1/4ian/gd/branches
| ❔ I want to... | 🚀 What to do |
| --- | --- |
| Download GDevelop to make games | Go on [GDevelop website](http://compilgames.net) to download GD! |
| Download GDevelop to make games | Go on [GDevelop website](https://gdevelop-app.com) to download GD! |
| Contribute to the new editor | Download [Node.js] and follow this [README](newIDE/README.md). |
| Contribute to the old C++ editor | Download and launch [CMake], choose this directory as the source, generate the Makefile and launch it. Be sure to have [required development libraries](http://4ian.github.io/GD-Documentation/GDCore%20Documentation/setup_dev_env.html) installed. <br><br> Fully detailed instructions are [available here](http://4ian.github.io/GD-Documentation). |
| Help to translate GDevelop | Go on the [GDevelop project on Crowdin](https://crowdin.com/project/gdevelop). |
@@ -36,7 +36,7 @@ Links
### Community
* [GDevelop forums](http://forum.compilgames.net)
* [GDevelop homepage](http://www.compilgames.net) ([open source](https://github.com/4ian/GDevelop-website))
* [GDevelop homepage](https://gdevelop-app.com) ([open-source](https://github.com/4ian/GDevelop-website))
* [GDevelop wiki](http://wiki.compilgames.net)
* Help translate GD in your language: [GDevelop project on Crowdin](https://crowdin.com/project/gdevelop).
* [GDevelop Nightly Builds](http://nightlies.gd.victorlevasseur.com/) (powered by a [Buildbot](https://github.com/victorlevasseur/GD-Buildbot)) ([open source](https://github.com/victorlevasseur/GD-Nightlies-Website))

View File

@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.6)
cmake_policy(SET CMP0015 NEW)
project(GDVersion)
set(GD_VERSION_STR "4.0.96")
set(GD_VERSION_STR "4.0.97-0-release")
if(FULL_VERSION_NUMBER)
set(GENERATE_VERSION_SCRIPT ${PROJECT_SOURCE_DIR}/GenerateVersionFull.cmake)

View File

@@ -23,7 +23,8 @@ yarn start #or npm start
This will open the app in your web browser.
Images resources, GDJS Runtime, extensions will be copied in resources, and [libGD.js](https://github.com/4ian/GDevelop.js) will be downloaded automatically.
Images resources, GDJS Runtime, extensions will be copied in resources, and [libGD.js](https://github.com/4ian/GDevelop.js) will be downloaded automatically. If you wish, you can
[build libGD.js by yourself](https://github.com/4ian/GDevelop.js) (useful if you modified GDevelop native code like extensions).
### Development of the standalone app
@@ -97,7 +98,6 @@ yarn deploy #or npm run deploy
This new editor is still in development and is missing some features:
- [ ] Support for translations (See an [example of a component that can be translated](https://github.com/4ian/GD/blob/master/newIDE/app/src/MainFrame/Toolbar.js#L44))
- [ ] [Collision mask editor](https://trello.com/c/2Kzwj61r/47-collision-masks-editors-for-sprite-objects-in-the-new-ide)
- [ ] Support for native games
- [ ] More [documentation](http://wiki.compilgames.net/doku.php/gdevelop5/start) about how to package for iOS/Android with Cordova/PhoneGap Build or Cocos2d-JS.
- [ ] Search in events

View File

@@ -24,5 +24,9 @@ declare type gdParameterMetadata = EmscriptenObject;
declare type gdVariable = EmscriptenObject;
declare type gdVariablesContainer = EmscriptenObject;
declare type gdVectorPolygon2d = EmscriptenObject;
declare type gdSpriteObject = EmscriptenObject;
//Represents all objects that have serializeTo and unserializeFrom methods.
declare type gdSerializable = EmscriptenObject;

View File

@@ -38,7 +38,7 @@ if (shell.test('-f', sourceFile)) {
var file = fs.createWriteStream('../public/libGD.js');
https.get(
'https://github.com/4ian/GDevelop.js/releases/download/4.0.96/libGD.js',
'https://github.com/4ian/GDevelop.js/releases/download/5.0.0-beta28/libGD.js',
function(response) {
if (response.statusCode !== 200) {
shell.echo(

View File

@@ -87,11 +87,20 @@ export default class InstructionParametersEditor extends Component {
<div key={type} style={styles.parametersContainer}>
{mapFor(0, instructionMetadata.getParametersCount(), i => {
const parameterMetadata = instructionMetadata.getParameter(i);
const parameterMetadataType = parameterMetadata.getType();
const ParameterComponent = ParameterRenderingService.getParameterComponent(
parameterMetadata.getType()
parameterMetadataType
);
if (parameterMetadata.isCodeOnly()) return null;
if (!ParameterComponent) {
console.warn(
'Missing parameter component for',
parameterMetadataType
);
return null;
}
return (
<ParameterComponent
parameterMetadata={parameterMetadata}

View File

@@ -17,8 +17,7 @@ export const enumerateVariables = (
if (!variable.isStructure()) return names;
variable
.getAllChildren()
.keys()
.getAllChildrenNames()
.toJSArray()
.forEach(childName => {
enumerateVariableAndChildrenNames(
@@ -36,10 +35,9 @@ export const enumerateVariables = (
mapFor(0, variablesContainer.count(), i => {
if (!variablesContainer) return [];
const variableAndName = variablesContainer.getAt(i);
return enumerateVariableAndChildrenNames(
variableAndName.getName(),
variableAndName.getVariable()
variablesContainer.getNameAt(i),
variablesContainer.getAt(i)
);
})
);

View File

@@ -1,6 +1,4 @@
// @flow
import ParameterRenderingService from '../ParameterRenderingService';
export type ParameterFieldProps = {|
parameterMetadata?: gdParameterMetadata,
project: gdProject,
@@ -8,5 +6,8 @@ export type ParameterFieldProps = {|
onChange: string => void,
value: string,
isInline?: boolean,
parameterRenderingService?: typeof ParameterRenderingService,
parameterRenderingService?: {
components: any,
getParameterComponent: (type: string) => any,
},
|};

View File

@@ -5,6 +5,7 @@ import CircularProgress from 'material-ui/CircularProgress';
import LinearProgress from 'material-ui/LinearProgress';
import FlatButton from 'material-ui/FlatButton';
import { Line, Spacer } from '../../../UI/Grid';
import HelpButton from '../../../UI/HelpButton';
export default ({
exportStep,
@@ -16,6 +17,7 @@ export default ({
buildMax,
buildProgress,
errored,
onPlayStore,
}) => (
<Stepper
activeStep={
@@ -69,9 +71,7 @@ export default ({
<StepLabel>Build</StepLabel>
<StepContent>
{errored ? (
<p>
Something wrong happened :(
</p>
<p>Something wrong happened :(</p>
) : exportStep === 'waiting-for-build' ? (
<Line alignItems="center">
<CircularProgress size={20} />
@@ -95,6 +95,10 @@ export default ({
<StepContent>
<Line>
<RaisedButton label="Download" primary onClick={onDownload} />
<HelpButton
label="Upload to Play Store"
helpPagePath="/publishing/android_and_ios/play-store"
/>
<FlatButton label="See logs" onClick={onDownloadLogs} />
</Line>
<Line expand>

View File

@@ -12,7 +12,7 @@ export default class InstancesResizer {
}
_roundXPosition(x) {
if (!this.options.snap || !this.options.grid) return x;
if (!this.options.snap || !this.options.grid) return Math.round(x);
return (
Math.round((x - this.options.gridOffsetX) / this.options.gridWidth) *
@@ -22,7 +22,7 @@ export default class InstancesResizer {
}
_roundYPosition(y) {
if (!this.options.snap || !this.options.grid) return y;
if (!this.options.snap || !this.options.grid) return Math.round(y);
return (
Math.round((y - this.options.gridOffsetY) / this.options.gridHeight) *

View File

@@ -1,9 +1,11 @@
import PIXI from 'pixi.js';
import transformRect from '../Utils/TransformRect';
import { rgbToHexNumber } from '../Utils/ColorTransformer';
export default class WindowBorder {
constructor({ project, toCanvasCoordinates }) {
constructor({ project, layout, toCanvasCoordinates }) {
this.project = project;
this.layout = layout;
this.toCanvasCoordinates = toCanvasCoordinates;
this.pixiRectangle = new PIXI.Graphics();
@@ -28,7 +30,15 @@ export default class WindowBorder {
this.pixiRectangle.clear();
this.pixiRectangle.beginFill(0x000000);
this.pixiRectangle.lineStyle(1, 0x000000, 1);
this.pixiRectangle.lineStyle(
1,
rgbToHexNumber(
(128 + this.layout.getBackgroundColorRed() % 256),
(128 + this.layout.getBackgroundColorBlue() % 256),
(128 + this.layout.getBackgroundColorGreen() % 256)
),
1
);
this.pixiRectangle.alpha = 1;
this.pixiRectangle.fillAlpha = 0;
this.pixiRectangle.drawRect(

View File

@@ -212,6 +212,7 @@ export default class InstancesEditorContainer extends Component {
});
this.windowBorder = new WindowBorder({
project: props.project,
layout: props.layout,
toCanvasCoordinates: this.viewPosition.toCanvasCoordinates,
});
this.windowMask = new WindowMask({

View File

@@ -8,7 +8,7 @@ export default class BaseEditor extends React.Component<*,*> {
setToolbar: () => {},
};
getProject(): gdProject {
getProject(): ?gdProject {
return this.props.project;
}

View File

@@ -0,0 +1,16 @@
// @flow
import * as React from 'react';
import muiThemeable from 'material-ui/styles/muiThemeable';
const styles = {
logo: {
width: '100%',
},
};
const ThemableGDevelopLogo = ({ muiTheme }) => (
<img src={muiTheme.logo.src} alt="" style={styles.logo} />
);
const GDevelopLogo = muiThemeable()(ThemableGDevelopLogo);
export default GDevelopLogo;

View File

@@ -0,0 +1,25 @@
// @flow
import * as React from 'react';
import muiThemeable from 'material-ui/styles/muiThemeable';
const styles = {
scrollContainer: {
flex: 1,
display: 'flex',
overflowY: 'scroll',
},
};
const ThemableScrollBackground = ({ muiTheme, children }) => (
<div
style={{
...styles.scrollContainer,
backgroundColor: muiTheme.palette.canvasColor,
}}
>
{children}
</div>
);
const ScrollBackground = muiThemeable()(ThemableScrollBackground);
export default ScrollBackground;

View File

@@ -2,8 +2,8 @@
import React from 'react';
import renderer from 'react-test-renderer';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import defaultTheme from '../../UI/Theme/DefaultTheme';
import StartPage from './StartPage';
import defaultTheme from '../../../UI/Theme/DefaultTheme';
import StartPage from '.';
const gd = global.gd;
describe('StartPage', () => {

View File

@@ -48,7 +48,7 @@ exports[`StartPage renders the start page with a project 1`] = `
style={
Object {
"WebkitTapHighlightColor": "rgba(0,0,0,0)",
"backgroundColor": "#f7f7f7",
"backgroundColor": "#f0f0f0",
"borderRadius": 2,
"boxShadow": "0 1px 6px rgba(0, 0, 0, 0.12),
0 1px 4px rgba(0, 0, 0, 0.12)",
@@ -634,7 +634,7 @@ exports[`StartPage renders the start page with no project opened 1`] = `
style={
Object {
"WebkitTapHighlightColor": "rgba(0,0,0,0)",
"backgroundColor": "#f7f7f7",
"backgroundColor": "#f0f0f0",
"borderRadius": 2,
"boxShadow": "0 1px 6px rgba(0, 0, 0, 0.12),
0 1px 4px rgba(0, 0, 0, 0.12)",

View File

@@ -1,18 +1,15 @@
// @flow
import React from 'react';
import FlatButton from 'material-ui/FlatButton';
import Paper from 'material-ui/Paper';
import IconButton from 'material-ui/IconButton';
import muiThemeable from 'material-ui/styles/muiThemeable';
import BaseEditor from './BaseEditor';
import Window from '../../Utils/Window';
import { Line } from '../../UI/Grid';
import BaseEditor from '../BaseEditor';
import Window from '../../../Utils/Window';
import { Line } from '../../../UI/Grid';
import GDevelopLogo from './GDevelopLogo';
import ScrollBackground from './ScrollBackground';
const styles = {
scrollContainer: {
flex: 1,
display: 'flex',
overflowY: 'scroll',
},
innerContainer: {
display: 'flex',
flexDirection: 'column',
@@ -35,14 +32,11 @@ const styles = {
buttonsPaper: {
width: '100%',
},
logo: {
width: '100%',
},
};
class ThemableStartPage extends BaseEditor {
constructor(props) {
super(props);
class StartPage extends BaseEditor {
constructor() {
super();
this.state = {
aboutDialogOpen: false,
@@ -65,28 +59,21 @@ class ThemableStartPage extends BaseEditor {
onCreate,
onOpenProjectManager,
onCloseProject,
muiTheme,
onOpenAboutDialog,
} = this.props;
return (
<div
style={{
backgroundColor: muiTheme.palette.canvasColor,
...styles.scrollContainer,
}}
>
<ScrollBackground>
<div style={styles.innerContainer}>
<Line expand justifyContent="center">
<div style={styles.centerContainer}>
<Paper
zDepth={1}
style={{
backgroundColor: muiTheme.startPage.backgroundColor,
...styles.logoPaper,
}}
>
<img src={muiTheme.logo.src} alt="" style={styles.logo} />
<GDevelopLogo />
<p>
GDevelop is an easy-to-use game creator with no programming
language to learn.
@@ -160,10 +147,9 @@ class ThemableStartPage extends BaseEditor {
</div>
</Line>
</div>
</div>
</ScrollBackground>
);
}
}
const StartPage = muiThemeable()(ThemableStartPage);
export default StartPage;

View File

@@ -0,0 +1,21 @@
import React from 'react';
import { TableRow, TableRowColumn } from 'material-ui/Table';
import Add from 'material-ui/svg-icons/content/add';
import IconButton from 'material-ui/IconButton';
import styles from './styles';
const AddPolygonRow = ({ onAdd }) => (
<TableRow>
<TableRowColumn style={styles.handleColumn} />
<TableRowColumn />
<TableRowColumn style={styles.coordinateColumn} />
<TableRowColumn style={styles.coordinateColumn} />
<TableRowColumn style={styles.toolColumn}>
<IconButton onClick={onAdd}>
<Add />
</IconButton>
</TableRowColumn>
</TableRow>
);
export default AddPolygonRow;

View File

@@ -0,0 +1,21 @@
import React from 'react';
import { TableRow, TableRowColumn } from 'material-ui/Table';
import AddCircle from 'material-ui/svg-icons/content/add-circle';
import IconButton from 'material-ui/IconButton';
import styles from './styles';
const AddVerticeRow = ({ onAdd }) => (
<TableRow>
<TableRowColumn style={styles.handleColumn} />
<TableRowColumn />
<TableRowColumn style={styles.coordinateColumn} />
<TableRowColumn style={styles.coordinateColumn} />
<TableRowColumn style={styles.toolColumn}>
<IconButton onClick={onAdd}>
<AddCircle />
</IconButton>
</TableRowColumn>
</TableRow>
);
export default AddVerticeRow;

View File

@@ -0,0 +1,92 @@
// @flow
import * as React from 'react';
import { mapVector } from '../../../../Utils/MapFor';
const styles = {
container: {
position: 'relative',
},
svg: {
width: '100%',
height: '100%',
},
};
type Props = {|
polygons: gdVectorPolygon2d,
isDefaultBoundingBox: boolean,
imageWidth: number,
imageHeight: number,
|};
export default class CollisionMasksPreview extends React.Component<
Props,
void
> {
_renderBoundingBox() {
const { imageWidth, imageHeight } = this.props;
return (
<polygon
fill="rgba(255,0,0,0.2)"
stroke="rgba(255,0,0,0.5)"
strokeWidth={1}
fileRule="evenodd"
points={`0,0 ${imageWidth},0 ${imageWidth},${imageHeight} 0,${imageHeight}`}
/>
);
}
_renderPolygons() {
const { polygons } = this.props;
return (
<React.Fragment>
{mapVector(polygons, (polygon, i) => {
const vertices = polygon.getVertices();
return (
<polygon
key={`polygon-${i}`}
fill="rgba(255,0,0,0.2)"
stroke="rgba(255,0,0,0.5)"
strokeWidth={1}
fileRule="evenodd"
points={mapVector(
vertices,
(vertex, j) => `${vertex.get_x()},${vertex.get_y()}`
).join(' ')}
/>
);
})}
{mapVector(polygons, (polygon, i) => {
const vertices = polygon.getVertices();
return mapVector(vertices, (vertex, j) => (
<circle
key={`polygon-${i}-vertex-${j}`}
fill="rgba(255,0,0,0.5)"
strokeWidth={1}
cx={vertex.get_x()}
cy={vertex.get_y()}
r={3}
/>
));
})}
</React.Fragment>
);
}
render() {
const { isDefaultBoundingBox } = this.props;
return (
<svg
width={this.props.imageWidth}
height={this.props.imageHeight}
style={styles.svg}
>
{isDefaultBoundingBox && this._renderBoundingBox()}
{!isDefaultBoundingBox && this._renderPolygons()}
</svg>
);
}
}

View File

@@ -0,0 +1,40 @@
import React from 'react';
import { TableRow, TableRowColumn } from 'material-ui/Table';
import IconButton from 'material-ui/IconButton';
import Delete from 'material-ui/svg-icons/action/delete';
import muiThemeable from 'material-ui/styles/muiThemeable';
import styles from './styles';
const ThemablePolygonRow = ({ onRemove, isConvex, verticesCount, muiTheme }) => {
return (
<TableRow
style={{
backgroundColor: muiTheme.list.itemsBackgroundColor,
}}
>
<TableRowColumn style={styles.handleColumn}>
{/* <DragHandle /> Reordering polygons is not supported for now */}
</TableRowColumn>
{isConvex && (
<TableRowColumn>
{verticesCount === 3 && `Triangle`}
{verticesCount === 4 && `Quadrilateral`}
{verticesCount >= 5 && `Polygon with ${verticesCount} vertices`}
</TableRowColumn>
)}
{!isConvex && <TableRowColumn>Polygon is not convex!</TableRowColumn>}
<TableRowColumn style={styles.coordinateColumn} />
<TableRowColumn style={styles.coordinateColumn} />
<TableRowColumn style={styles.toolColumn}>
{!!onRemove && (
<IconButton onClick={onRemove}>
<Delete />
</IconButton>
)}
</TableRowColumn>
</TableRow>
);
};
const PointRow = muiThemeable()(ThemablePolygonRow);
export default PointRow;

View File

@@ -0,0 +1,182 @@
// @flow
import React, { Component } from 'react';
import flatten from 'lodash/flatten';
import {
Table,
TableBody,
TableHeader,
TableHeaderColumn,
TableRow,
TableRowColumn,
} from 'material-ui/Table';
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
import { mapVector } from '../../../../Utils/MapFor';
import styles from './styles';
import VerticeRow from './VerticeRow';
import PolygonRow from './PolygonRow';
import AddVerticeRow from './AddVerticeRow';
import AddPolygonRow from './AddPolygonRow';
const gd = global.gd;
const SortableAddPolygonRow = SortableElement(AddPolygonRow);
const SortableAddVerticeRow = SortableElement(AddVerticeRow);
const SortableVerticeRow = SortableElement(VerticeRow);
const SortablePolygonRow = SortableElement(PolygonRow);
type PolygonsListBodyProps = {|
polygons: gdVectorPolygon2d,
onPolygonsUpdated: () => void,
// Sprite size is useful to make sure polygon vertices
// are not put outside the sprite bounding box, which is not supported:
spriteWidth: number,
spriteHeight: number,
|};
class PolygonsListBody extends Component<PolygonsListBodyProps, void> {
_onPolygonUpdated() {
this.forceUpdate();
this.props.onPolygonsUpdated();
}
updateVerticeX = (vertice, newValue) => {
// Ensure vertice stays inside the sprite bounding box.
vertice.set_x(Math.min(this.props.spriteWidth, Math.max(newValue, 0)));
this._onPolygonUpdated();
};
updateVerticeY = (vertice, newValue) => {
// Ensure vertice stays inside the sprite bounding box.
vertice.set_y(Math.min(this.props.spriteHeight, Math.max(newValue, 0)));
this._onPolygonUpdated();
};
render() {
const { polygons } = this.props;
const polygonRows = flatten(
mapVector(polygons, (polygon, i) => {
const vertices = polygon.getVertices();
const isConvex = polygon.isConvex();
return [
<SortablePolygonRow
index={i}
disabled
key={'polygon-' + i}
polygon={polygon}
onRemove={() => {
gd.removeFromVectorPolygon2d(polygons, i);
this._onPolygonUpdated();
}}
isConvex={isConvex}
verticesCount={vertices.size()}
/>,
mapVector(vertices, (vertice, j) => (
<SortableVerticeRow
index={i}
disabled
key={`polygon-${i}-vertice-${j}`}
verticeX={vertice.get_x()}
verticeY={vertice.get_y()}
onChangeVerticeX={newValue =>
this.updateVerticeX(vertice, newValue)}
onChangeVerticeY={newValue =>
this.updateVerticeY(vertice, newValue)}
onRemove={() => {
gd.removeFromVectorVector2f(polygon.getVertices(), j);
this._onPolygonUpdated();
}}
canRemove={vertices.size() > 3}
hasWarning={!isConvex}
/>
)),
<SortableAddVerticeRow
index={0}
key={`polygon-${i}-add-vertice-row`}
disabled
onAdd={() => {
const newVertice = new gd.Vector2f();
polygon.getVertices().push_back(newVertice);
newVertice.delete();
this._onPolygonUpdated();
}}
/>,
];
})
);
const addRow = (
<SortableAddPolygonRow
index={0}
key={'add-polygon-row'}
disabled
onAdd={() => {
const newPolygon = gd.Polygon2d.createRectangle(32, 32);
newPolygon.move(
this.props.spriteWidth / 2,
this.props.spriteHeight / 2
);
polygons.push_back(newPolygon);
this._onPolygonUpdated();
}}
/>
);
return (
<TableBody
displayRowCheckbox={false}
deselectOnClickaway={true}
showRowHover={true}
>
{[...polygonRows, addRow]}
</TableBody>
);
}
}
const SortablePolygonsListBody = SortableContainer(PolygonsListBody);
SortablePolygonsListBody.muiName = 'TableBody';
type Props = {|
polygons: gdVectorPolygon2d,
onPolygonsUpdated: () => void,
spriteWidth: number,
spriteHeight: number,
|};
export default class PolygonsList extends Component<Props, void> {
render() {
return (
<Table selectable={false}>
<TableHeader displaySelectAll={false} adjustForCheckbox={false}>
<TableRow>
<TableHeaderColumn style={styles.handleColumn} />
<TableHeaderColumn>Polygon</TableHeaderColumn>
<TableHeaderColumn style={styles.coordinateColumn}>
X
</TableHeaderColumn>
<TableHeaderColumn style={styles.coordinateColumn}>
Y
</TableHeaderColumn>
<TableRowColumn style={styles.toolColumn} />
</TableRow>
</TableHeader>
<SortablePolygonsListBody
polygons={this.props.polygons}
onPolygonsUpdated={this.props.onPolygonsUpdated}
spriteWidth={this.props.spriteWidth}
spriteHeight={this.props.spriteHeight}
onSortEnd={({ oldIndex, newIndex }) => {
// Reordering polygons is not supported for now
}}
helperClass="sortable-helper"
useDragHandle
lockToContainerEdges
/>
</Table>
);
}
}

View File

@@ -0,0 +1,58 @@
import React from 'react';
import { TableRow, TableRowColumn } from 'material-ui/Table';
import IconButton from 'material-ui/IconButton';
import Delete from 'material-ui/svg-icons/action/delete';
import TextField from 'material-ui/TextField';
import Warning from 'material-ui/svg-icons/alert/warning';
import muiThemeable from 'material-ui/styles/muiThemeable';
import styles from './styles';
const ThemableVerticeRow = ({
hasWarning,
canRemove,
onRemove,
verticeX,
verticeY,
onChangeVerticeX,
onChangeVerticeY,
muiTheme,
}) => (
<TableRow style={{
backgroundColor: muiTheme.list.itemsBackgroundColor,
}}>
<TableRowColumn style={styles.handleColumn}>
{/* <DragHandle /> Reordering vertices is not supported for now */}
</TableRowColumn>
<TableRowColumn>
{hasWarning && <Warning />}
</TableRowColumn>
<TableRowColumn style={styles.coordinateColumn}>
<TextField
value={verticeX}
type="number"
id="vertice-x"
onChange={(e, value) => onChangeVerticeX(parseFloat(value || 0, 10))}
/>
</TableRowColumn>
<TableRowColumn style={styles.coordinateColumn}>
<TextField
value={verticeY}
type="number"
id="vertice-y"
onChange={(e, value) => onChangeVerticeY(parseFloat(value || 0, 10))}
/>
</TableRowColumn>
<TableRowColumn style={styles.toolColumn}>
{!!onRemove && (
<IconButton onClick={onRemove} disabled={!canRemove}>
<Delete />
</IconButton>
)}
</TableRowColumn>
</TableRow>
);
const PointRow = muiThemeable()(
ThemableVerticeRow
);
export default PointRow;

View File

@@ -0,0 +1,279 @@
import React, { Component } from 'react';
import FlatButton from 'material-ui/FlatButton';
import EmptyMessage from '../../../../UI/EmptyMessage';
import { Line, Column } from '../../../../UI/Grid';
import { mapFor } from '../../../../Utils/MapFor';
import PolygonsList from './PolygonsList';
import CollisionMasksPreview from './CollisionMasksPreview';
import ImagePreview from '../../../ImagePreview';
import {
getCurrentElements,
allSpritesHaveSameCollisionMasksAs,
copyAnimationsSpriteCollisionMasks,
} from '../Utils/SpriteObjectHelper';
import SpriteSelector from '../Utils/SpriteSelector';
import every from 'lodash/every';
const gd = global.gd;
export default class CollisionMasksEditor extends Component {
state = {
animationIndex: 0,
directionIndex: 0,
spriteIndex: 0,
sameCollisionMasksForAnimations: true,
sameCollisionMasksForSprites: true,
spriteWidth: 0,
spriteHeight: 0,
};
componentDidMount() {
this._updateSameCollisionMasksToggles();
}
_updateCollisionMasks = () => {
const { object } = this.props;
const { animationIndex, directionIndex, spriteIndex } = this.state;
const spriteObject = gd.asSpriteObject(object);
const { animation, sprite } = getCurrentElements(
spriteObject,
animationIndex,
directionIndex,
spriteIndex
);
if (animation && sprite) {
if (this.state.sameCollisionMasksForAnimations) {
mapFor(0, spriteObject.getAnimationsCount(), i => {
const otherAnimation = spriteObject.getAnimation(i);
copyAnimationsSpriteCollisionMasks(sprite, otherAnimation);
});
} else if (this.state.sameCollisionMasksForSprites) {
copyAnimationsSpriteCollisionMasks(sprite, animation);
}
}
this.forceUpdate(); // Refresh the preview
if (this.props.onCollisionMasksUpdated)
this.props.onCollisionMasksUpdated();
};
chooseAnimation = index => {
this.setState(
{
animationIndex: index,
directionIndex: 0,
spriteIndex: 0,
},
() => this._updateSameCollisionMasksToggles()
);
};
chooseDirection = index => {
this.setState({
directionIndex: index,
spriteIndex: 0,
});
};
chooseSprite = index => {
this.setState({
spriteIndex: index,
});
};
_updateSameCollisionMasksToggles = () => {
const { object } = this.props;
const { animationIndex, directionIndex, spriteIndex } = this.state;
const spriteObject = gd.asSpriteObject(object);
const { animation, sprite } = getCurrentElements(
spriteObject,
animationIndex,
directionIndex,
spriteIndex
);
if (!animation || !sprite) return;
this.setState({
sameCollisionMasksForAnimations: every(
mapFor(0, spriteObject.getAnimationsCount(), i => {
const otherAnimation = spriteObject.getAnimation(i);
return allSpritesHaveSameCollisionMasksAs(sprite, otherAnimation);
})
),
sameCollisionMasksForSprites: allSpritesHaveSameCollisionMasksAs(
sprite,
animation
),
});
};
_onSetCollisionMaskAutomatic = (automatic: boolean = true) => {
const { object } = this.props;
const { animationIndex, directionIndex, spriteIndex } = this.state;
const spriteObject = gd.asSpriteObject(object);
const { sprite } = getCurrentElements(
spriteObject,
animationIndex,
directionIndex,
spriteIndex
);
if (!sprite) return;
sprite.setCollisionMaskAutomatic(automatic);
this._updateCollisionMasks();
};
_setSameCollisionMasksForAllAnimations = enable => {
if (enable) {
// eslint-disable-next-line
const answer = confirm(
"Having the same collision masks for all animations will erase and reset all the other animations collision masks. This can't be undone. Are you sure you want to share these collision masks amongst all the animations of the object?"
);
if (!answer) return;
}
this.setState(
{
sameCollisionMasksForAnimations: enable,
sameCollisionMasksForSprites: enable
? true
: this.state.sameCollisionMasksForSprites,
},
() => {
this._updateCollisionMasks();
}
);
};
_setSameCollisionMasksForAllSprites = enable => {
if (enable) {
// eslint-disable-next-line
const answer = confirm(
"Having the same collision masks for all frames will erase and reset all the other frames collision masks. This can't be undone. Are you sure you want to share these collision masks amongst all the frames of the animation?"
);
if (!answer) return;
}
this.setState(
{
sameCollisionMasksForAnimations: enable
? this.state.sameCollisionMasksForAnimations
: false,
sameCollisionMasksForSprites: enable,
},
() => {
this._updateCollisionMasks();
}
);
};
_setCurrentSpriteSize = (spriteWidth: number, spriteHeight: number) => {
this.setState({
spriteWidth,
spriteHeight,
});
};
render() {
const { object, resourcesLoader, project } = this.props;
const {
sameCollisionMasksForAnimations,
sameCollisionMasksForSprites,
animationIndex,
directionIndex,
spriteIndex,
spriteWidth,
spriteHeight,
} = this.state;
const spriteObject = gd.asSpriteObject(object);
if (!object.getAnimationsCount()) return null;
const { hasValidSprite, sprite } = getCurrentElements(
spriteObject,
animationIndex,
directionIndex,
spriteIndex
);
return (
<div>
<ImagePreview
resourceName={hasValidSprite ? sprite.getImageName() : ''}
resourcesLoader={resourcesLoader}
project={project}
onImageLoaded={this._setCurrentSpriteSize}
>
{hasValidSprite && (
<CollisionMasksPreview
isDefaultBoundingBox={sprite.isCollisionMaskAutomatic()}
polygons={sprite.getCustomCollisionMask()}
/>
)}
</ImagePreview>
<Line>
<Column expand>
<SpriteSelector
spriteObject={spriteObject}
animationIndex={animationIndex}
directionIndex={directionIndex}
spriteIndex={spriteIndex}
chooseAnimation={this.chooseAnimation}
chooseDirection={this.chooseDirection}
chooseSprite={this.chooseSprite}
sameForAllAnimations={sameCollisionMasksForAnimations}
sameForAllSprites={sameCollisionMasksForSprites}
setSameForAllAnimations={
this._setSameCollisionMasksForAllAnimations
}
setSameForAllSprites={this._setSameCollisionMasksForAllSprites}
setSameForAllAnimationsLabel="Share same collision masks for all animations"
setSameForAllSpritesLabel="Share same collision masks for all sprites of this animation"
/>
</Column>
</Line>
{!!sprite &&
!sprite.isCollisionMaskAutomatic() && (
<React.Fragment>
<PolygonsList
polygons={sprite.getCustomCollisionMask()}
onPolygonsUpdated={this._updateCollisionMasks}
spriteWidth={spriteWidth}
spriteHeight={spriteHeight}
/>
<Line justifyContent="center">
<FlatButton
label="Restore the default collision mask"
primary={false}
onClick={() => this._onSetCollisionMaskAutomatic(true)}
/>
</Line>
</React.Fragment>
)}
{!!sprite &&
sprite.isCollisionMaskAutomatic() && (
<React.Fragment>
<EmptyMessage>
This sprite uses the default collision mask, a rectangle that is
as large as the sprite.
</EmptyMessage>
<Line justifyContent="center">
<FlatButton
label="Use a custom collision mask"
primary={false}
onClick={() => this._onSetCollisionMaskAutomatic(false)}
/>
</Line>
</React.Fragment>
)}
{!sprite && (
<EmptyMessage>
Choose an animation and frame to edit the collision masks
</EmptyMessage>
)}
</div>
);
}
}

View File

@@ -0,0 +1,14 @@
//TODO: Factor with styles.js from LayersList.
export default {
handleColumn: {
width: 24,
paddingLeft: 8,
paddingRight: 0,
},
coordinateColumn: {
width: 48,
},
toolColumn: {
width: 48,
},
};

View File

@@ -4,7 +4,7 @@ import Add from 'material-ui/svg-icons/content/add';
import IconButton from 'material-ui/IconButton';
import styles from './styles';
const AddLayerRow = ({ onAdd }) => (
const AddPointRow = ({ onAdd }) => (
<TableRow>
<TableRowColumn style={styles.handleColumn} />
<TableRowColumn />
@@ -18,4 +18,4 @@ const AddLayerRow = ({ onAdd }) => (
</TableRow>
);
export default AddLayerRow;
export default AddPointRow;

View File

@@ -19,12 +19,9 @@ const SortableAddPointRow = SortableElement(AddPointRow);
const SortablePointRow = SortableElement(PointRow);
class PointsListBody extends Component {
constructor() {
super();
this.state = {
nameErrors: {},
};
}
state = {
nameErrors: {},
};
_onPointsUpdated() {
this.forceUpdate();

View File

@@ -1,7 +1,4 @@
import React, { Component } from 'react';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import Toggle from 'material-ui/Toggle';
import EmptyMessage from '../../../../UI/EmptyMessage';
import { Line, Column } from '../../../../UI/Grid';
import { mapFor } from '../../../../Utils/MapFor';
@@ -13,6 +10,7 @@ import {
allSpritesHaveSamePointsAs,
copyAnimationsSpritePoints,
} from '../Utils/SpriteObjectHelper';
import SpriteSelector from '../Utils/SpriteSelector';
import every from 'lodash/every';
const gd = global.gd;
@@ -104,7 +102,7 @@ export default class PointsEditor extends Component {
});
};
_onToggleSamePointsForAnimation = enable => {
_setSamePointsForAllAnimations = enable => {
if (enable) {
// eslint-disable-next-line
const answer = confirm(
@@ -124,7 +122,7 @@ export default class PointsEditor extends Component {
);
};
_onToggleSamePointsForSprites = enable => {
_setSamePointsForAllSprites = enable => {
if (enable) {
// eslint-disable-next-line
const answer = confirm(
@@ -158,14 +156,7 @@ export default class PointsEditor extends Component {
const spriteObject = gd.asSpriteObject(object);
if (!object.getAnimationsCount()) return null;
const {
hasValidAnimation,
animation,
hasValidDirection,
direction,
hasValidSprite,
sprite,
} = getCurrentElements(
const { hasValidSprite, sprite } = getCurrentElements(
spriteObject,
animationIndex,
directionIndex,
@@ -183,76 +174,20 @@ export default class PointsEditor extends Component {
</ImagePreview>
<Line>
<Column expand>
<Toggle
label="Share same points for all animations"
labelPosition="right"
toggled={samePointsForAnimations}
onToggle={(e, checked) =>
this._onToggleSamePointsForAnimation(checked)}
/>
<Line>
{!samePointsForAnimations && (
<SelectField
floatingLabelText="Animation"
value={this.state.animationIndex}
onChange={(e, i, value) => this.chooseAnimation(value)}
>
{mapFor(0, spriteObject.getAnimationsCount(), i => {
const animation = spriteObject.getAnimation(i);
return (
<MenuItem
key={i}
value={i}
primaryText={`Animation #${i} ${animation.getName()}`}
/>
);
})}
</SelectField>
)}
{!samePointsForAnimations &&
hasValidAnimation &&
animation.getDirectionsCount() > 1 && (
<SelectField
floatingLabelText="Direction"
value={this.state.directionIndex}
onChange={(e, i, value) => this.chooseDirection(value)}
>
{mapFor(0, animation.getDirectionsCount(), i => {
return (
<MenuItem
value={i}
key={i}
primaryText={`Direction #${i}`}
/>
);
})}
</SelectField>
)}
{!samePointsForSprites &&
hasValidDirection && (
<SelectField
floatingLabelText="Frame"
value={this.state.spriteIndex}
onChange={(e, i, value) => this.chooseSprite(value)}
>
{mapFor(0, direction.getSpritesCount(), i => {
return (
<MenuItem
value={i}
key={i}
primaryText={`Frame #${i}`}
/>
);
})}
</SelectField>
)}
</Line>
<Toggle
label="Share same points for all sprites of the animation"
labelPosition="right"
toggled={samePointsForSprites}
onToggle={(e, checked) =>
this._onToggleSamePointsForSprites(checked)}
<SpriteSelector
spriteObject={spriteObject}
animationIndex={animationIndex}
directionIndex={directionIndex}
spriteIndex={spriteIndex}
chooseAnimation={this.chooseAnimation}
chooseDirection={this.chooseDirection}
chooseSprite={this.chooseSprite}
sameForAllAnimations={samePointsForAnimations}
sameForAllSprites={samePointsForSprites}
setSameForAllAnimations={this._setSamePointsForAllAnimations}
setSameForAllSprites={this._setSamePointsForAllSprites}
setSameForAllAnimationsLabel="Share same points for all animations"
setSameForAllSpritesLabel="Share same points for all sprites of this animation"
/>
</Column>
</Line>

View File

@@ -118,6 +118,85 @@ export const allSpritesHaveSamePointsAs = (originalSprite, animation) => {
);
};
export const copySpritePolygons = (originalSprite, destinationSprite) => {
if (originalSprite.ptr === destinationSprite.ptr) return;
destinationSprite.setCollisionMaskAutomatic(
originalSprite.isCollisionMaskAutomatic()
);
destinationSprite.getCustomCollisionMask().clear();
mapVector(originalSprite.getCustomCollisionMask(), originalPolygon => {
destinationSprite.getCustomCollisionMask().push_back(originalPolygon);
});
};
export const copyAnimationsSpriteCollisionMasks = (originalSprite, animation) => {
mapFor(0, animation.getDirectionsCount(), i => {
const direction = animation.getDirection(i);
mapFor(0, direction.getSpritesCount(), j => {
const sprite = direction.getSprite(j);
copySpritePolygons(originalSprite, sprite);
});
});
};
export const isSamePolygon = (polygon1, polygon2) => {
const polygon1Vertices = polygon1.getVertices();
const polygon2Vertices = polygon2.getVertices();
if (polygon1Vertices.size() !== polygon2Vertices.size()) return false;
return every(
mapVector(polygon1Vertices, (point1, index) => {
const point2 = polygon2Vertices.at(index);
return point1.get_x() === point2.get_x() && point1.get_y() === point2.get_y();
})
);
};
export const haveSameCollisionMasks = (sprite1, sprite2) => {
if (sprite1.isCollisionMaskAutomatic() !== sprite2.isCollisionMaskAutomatic())
return false;
if (
sprite1.isCollisionMaskAutomatic() &&
sprite2.isCollisionMaskAutomatic()
)
return true;
const sprite1CollisionMask = sprite1.getCustomCollisionMask();
const sprite2CollisionMask = sprite2.getCustomCollisionMask();
if (sprite1CollisionMask.size() !== sprite2CollisionMask.size()) return false;
return every(
mapVector(sprite1CollisionMask, (sprite1Polygon, index) => {
return isSamePolygon(sprite1Polygon, sprite2CollisionMask.at(index));
})
);
};
export const allSpritesHaveSameCollisionMasksAs = (
originalSprite,
animation
) => {
return every(
mapFor(0, animation.getDirectionsCount(), i => {
const direction = animation.getDirection(i);
return every(
mapFor(0, direction.getSpritesCount(), j => {
const sprite = direction.getSprite(j);
return haveSameCollisionMasks(sprite, originalSprite);
})
);
})
);
};
export const deleteSpritesFromAnimation = (animation, spritePtrs) => {
mapFor(0, animation.getDirectionsCount(), i => {
const direction = animation.getDirection(i);

View File

@@ -3,121 +3,179 @@ import {
allSpritesHaveSamePointsAs,
copyAnimationsSpritePoints,
deleteSpritesFromAnimation,
haveSameCollisionMasks,
} from './SpriteObjectHelper';
const gd = global.gd;
describe('History', () => {
it('can tell if two sprite have the exact same points', () => {
const sprite1 = new gd.Sprite();
const sprite2 = new gd.Sprite();
describe('SpriteObjectHelper', () => {
describe('Points related methods', () => {
it('can tell if two sprite have the exact same points', () => {
const sprite1 = new gd.Sprite();
const sprite2 = new gd.Sprite();
expect(haveSamePoints(sprite1, sprite2)).toBe(true);
expect(haveSamePoints(sprite2, sprite1)).toBe(true);
sprite1.getOrigin().setX(40);
expect(haveSamePoints(sprite1, sprite2)).toBe(false);
expect(haveSamePoints(sprite2, sprite1)).toBe(false);
sprite2.getOrigin().setX(40);
expect(haveSamePoints(sprite1, sprite2)).toBe(true);
expect(haveSamePoints(sprite2, sprite1)).toBe(true);
expect(haveSamePoints(sprite1, sprite2)).toBe(true);
expect(haveSamePoints(sprite2, sprite1)).toBe(true);
sprite1.getOrigin().setX(40);
expect(haveSamePoints(sprite1, sprite2)).toBe(false);
expect(haveSamePoints(sprite2, sprite1)).toBe(false);
sprite2.getOrigin().setX(40);
expect(haveSamePoints(sprite1, sprite2)).toBe(true);
expect(haveSamePoints(sprite2, sprite1)).toBe(true);
sprite1.setDefaultCenterPoint(false);
expect(haveSamePoints(sprite1, sprite2)).toBe(false);
expect(haveSamePoints(sprite2, sprite1)).toBe(false);
sprite2.setDefaultCenterPoint(false);
expect(haveSamePoints(sprite1, sprite2)).toBe(true);
expect(haveSamePoints(sprite2, sprite1)).toBe(true);
sprite1.setDefaultCenterPoint(false);
expect(haveSamePoints(sprite1, sprite2)).toBe(false);
expect(haveSamePoints(sprite2, sprite1)).toBe(false);
sprite2.setDefaultCenterPoint(false);
expect(haveSamePoints(sprite1, sprite2)).toBe(true);
expect(haveSamePoints(sprite2, sprite1)).toBe(true);
const customPoint1 = new gd.Point('CustomPoint');
sprite1.addPoint(customPoint1);
customPoint1.delete();
expect(haveSamePoints(sprite1, sprite2)).toBe(false);
expect(haveSamePoints(sprite2, sprite1)).toBe(false);
const customPoint2 = new gd.Point('CustomPoint');
sprite2.addPoint(customPoint2);
customPoint2.delete();
expect(haveSamePoints(sprite1, sprite2)).toBe(true);
expect(haveSamePoints(sprite2, sprite1)).toBe(true);
const customPoint1 = new gd.Point('CustomPoint');
sprite1.addPoint(customPoint1);
customPoint1.delete();
expect(haveSamePoints(sprite1, sprite2)).toBe(false);
expect(haveSamePoints(sprite2, sprite1)).toBe(false);
const customPoint2 = new gd.Point('CustomPoint');
sprite2.addPoint(customPoint2);
customPoint2.delete();
expect(haveSamePoints(sprite1, sprite2)).toBe(true);
expect(haveSamePoints(sprite2, sprite1)).toBe(true);
sprite1.getPoint('CustomPoint').setY(10);
expect(haveSamePoints(sprite1, sprite2)).toBe(false);
expect(haveSamePoints(sprite2, sprite1)).toBe(false);
sprite2.getPoint('CustomPoint').setY(10);
expect(haveSamePoints(sprite1, sprite2)).toBe(true);
expect(haveSamePoints(sprite2, sprite1)).toBe(true);
});
sprite1.getPoint('CustomPoint').setY(10);
expect(haveSamePoints(sprite1, sprite2)).toBe(false);
expect(haveSamePoints(sprite2, sprite1)).toBe(false);
sprite2.getPoint('CustomPoint').setY(10);
expect(haveSamePoints(sprite1, sprite2)).toBe(true);
expect(haveSamePoints(sprite2, sprite1)).toBe(true);
});
it('can tell if all sprites of animations have the exact same points', () => {
const originalSprite = new gd.Sprite();
it('can tell if all sprites of animations have the exact same points', () => {
const originalSprite = new gd.Sprite();
const animation1 = new gd.Animation();
animation1.setDirectionsCount(1);
const sprite1 = new gd.Sprite();
const sprite2 = new gd.Sprite();
const animation1 = new gd.Animation();
animation1.setDirectionsCount(1);
const sprite1 = new gd.Sprite();
const sprite2 = new gd.Sprite();
animation1.getDirection(0).addSprite(sprite1);
animation1.getDirection(0).addSprite(sprite2);
animation1.getDirection(0).addSprite(sprite1);
animation1.getDirection(0).addSprite(sprite2);
const animation2 = new gd.Animation();
animation2.setDirectionsCount(1);
const sprite3 = new gd.Sprite();
const sprite4 = new gd.Sprite();
sprite4.setDefaultCenterPoint(false);
sprite4.getCenter().setY(5);
const animation2 = new gd.Animation();
animation2.setDirectionsCount(1);
const sprite3 = new gd.Sprite();
const sprite4 = new gd.Sprite();
sprite4.setDefaultCenterPoint(false);
sprite4.getCenter().setY(5);
animation2.getDirection(0).addSprite(sprite3);
animation2.getDirection(0).addSprite(sprite4);
animation2.getDirection(0).addSprite(sprite3);
animation2.getDirection(0).addSprite(sprite4);
expect(allSpritesHaveSamePointsAs(originalSprite, animation1)).toBe(true);
expect(allSpritesHaveSamePointsAs(originalSprite, animation2)).toBe(false);
});
expect(allSpritesHaveSamePointsAs(originalSprite, animation1)).toBe(true);
expect(allSpritesHaveSamePointsAs(originalSprite, animation2)).toBe(
false
);
});
it('can copy points of a sprite in all sprites of an animation', () => {
const animation1 = new gd.Animation();
animation1.setDirectionsCount(1);
const emptySprite = new gd.Sprite();
const spriteWithCustomPoints = new gd.Sprite();
it('can copy points of a sprite in all sprites of an animation', () => {
const animation1 = new gd.Animation();
animation1.setDirectionsCount(1);
const emptySprite = new gd.Sprite();
const spriteWithCustomPoints = new gd.Sprite();
const point = new gd.Point('CustomPoint');
spriteWithCustomPoints.addPoint(point);
point.delete();
spriteWithCustomPoints.setDefaultCenterPoint(false);
spriteWithCustomPoints.getCenter().setY(5);
spriteWithCustomPoints.getPoint('CustomPoint').setX(1);
spriteWithCustomPoints.getPoint('CustomPoint').setY(2);
const point = new gd.Point('CustomPoint');
spriteWithCustomPoints.addPoint(point);
point.delete();
spriteWithCustomPoints.setDefaultCenterPoint(false);
spriteWithCustomPoints.getCenter().setY(5);
spriteWithCustomPoints.getPoint('CustomPoint').setX(1);
spriteWithCustomPoints.getPoint('CustomPoint').setY(2);
animation1.getDirection(0).addSprite(emptySprite);
animation1.getDirection(0).addSprite(spriteWithCustomPoints);
animation1.getDirection(0).addSprite(emptySprite);
animation1.getDirection(0).addSprite(emptySprite);
animation1.getDirection(0).addSprite(spriteWithCustomPoints);
animation1.getDirection(0).addSprite(emptySprite);
const animation2 = new gd.Animation();
animation2.getDirection(0).addSprite(emptySprite);
copyAnimationsSpritePoints(spriteWithCustomPoints, animation2);
expect(allSpritesHaveSamePointsAs(spriteWithCustomPoints, animation2)).toBe(
true
);
const animation2 = new gd.Animation();
animation2.getDirection(0).addSprite(emptySprite);
copyAnimationsSpritePoints(spriteWithCustomPoints, animation2);
expect(
allSpritesHaveSamePointsAs(spriteWithCustomPoints, animation2)
).toBe(true);
copyAnimationsSpritePoints(
animation1.getDirection(0).getSprite(1),
animation1
);
expect(
haveSamePoints(
animation1.getDirection(0).getSprite(0),
spriteWithCustomPoints
)
).toBe(true);
expect(
haveSamePoints(
copyAnimationsSpritePoints(
animation1.getDirection(0).getSprite(1),
spriteWithCustomPoints
)
).toBe(true);
expect(
haveSamePoints(
animation1.getDirection(0).getSprite(2),
spriteWithCustomPoints
)
).toBe(true);
animation1
);
expect(
haveSamePoints(
animation1.getDirection(0).getSprite(0),
spriteWithCustomPoints
)
).toBe(true);
expect(
haveSamePoints(
animation1.getDirection(0).getSprite(1),
spriteWithCustomPoints
)
).toBe(true);
expect(
haveSamePoints(
animation1.getDirection(0).getSprite(2),
spriteWithCustomPoints
)
).toBe(true);
});
});
describe('Collision masks related methods', () => {
it('can tell if two sprite have the exact same collision masks', () => {
const addVertice = (polygon, x, y) => {
const vertice = new gd.Vector2f();
vertice.x = x;
vertice.y = y;
polygon.getVertices().push_back(vertice);
vertice.delete();
};
const sprite1 = new gd.Sprite();
const sprite2 = new gd.Sprite();
expect(haveSameCollisionMasks(sprite1, sprite2)).toBe(true);
expect(haveSameCollisionMasks(sprite2, sprite1)).toBe(true);
sprite1.setCollisionMaskAutomatic(false);
expect(haveSameCollisionMasks(sprite1, sprite2)).toBe(false);
expect(haveSameCollisionMasks(sprite2, sprite1)).toBe(false);
{
const polygon1 = new gd.Polygon2d();
addVertice(polygon1, 0, 0);
addVertice(polygon1, 0, 10);
addVertice(polygon1, 10, 0);
sprite1.getCustomCollisionMask().push_back(polygon1);
polygon1.delete();
expect(haveSameCollisionMasks(sprite1, sprite2)).toBe(false);
expect(haveSameCollisionMasks(sprite2, sprite1)).toBe(false);
}
{
sprite2.setCollisionMaskAutomatic(false);
const polygon2 = new gd.Polygon2d();
addVertice(polygon2, 0, 0);
addVertice(polygon2, 0, 10);
addVertice(polygon2, 10, 0);
sprite2.getCustomCollisionMask().push_back(polygon2);
polygon2.delete();
expect(haveSameCollisionMasks(sprite1, sprite2)).toBe(true);
expect(haveSameCollisionMasks(sprite2, sprite1)).toBe(true);
sprite2.getCustomCollisionMask().at(0).getVertices().at(1).set_x(-20);
expect(haveSameCollisionMasks(sprite1, sprite2)).toBe(false);
expect(haveSameCollisionMasks(sprite2, sprite1)).toBe(false);
sprite1.getCustomCollisionMask().at(0).getVertices().at(1).set_x(-20);
expect(haveSameCollisionMasks(sprite1, sprite2)).toBe(true);
expect(haveSameCollisionMasks(sprite2, sprite1)).toBe(true);
}
});
});
it('can remove sprites using the sprites pointers', () => {

View File

@@ -0,0 +1,134 @@
// @flow
import * as React from 'react';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import Toggle from 'material-ui/Toggle';
import { Line } from '../../../../UI/Grid';
import { mapFor } from '../../../../Utils/MapFor';
import { getCurrentElements } from './SpriteObjectHelper';
type Props = {|
spriteObject: gdSpriteObject,
animationIndex: number,
directionIndex: number,
spriteIndex: number,
chooseAnimation: number => void,
chooseDirection: number => void,
chooseSprite: number => void,
sameForAllAnimations: boolean,
sameForAllSprites: boolean,
setSameForAllAnimations: boolean => void,
setSameForAllSprites: number => void,
setSameForAllAnimationsLabel: string,
setSameForAllSpritesLabel: string,
|};
/**
* A component that displays selector to browse the animations/directions/sprite
* of a Sprite object. Also have toggles so that the user can choose if the edited property
* (typically, the points or the collision masks of the sprite) should be shared between
* all sprites of an animation, or between all sprites of all animations of the object.
*/
export default class SpriteSelector extends React.Component<Props, void> {
render() {
const {
spriteObject,
animationIndex,
directionIndex,
spriteIndex,
sameForAllAnimations,
sameForAllSprites,
chooseAnimation,
chooseDirection,
chooseSprite,
setSameForAllAnimations,
setSameForAllSprites,
setSameForAllAnimationsLabel,
setSameForAllSpritesLabel,
} = this.props;
const {
hasValidAnimation,
animation,
hasValidDirection,
direction,
} = getCurrentElements(
spriteObject,
animationIndex,
directionIndex,
spriteIndex
);
return (
<React.Fragment>
<Line>
<SelectField
floatingLabelText="Animation"
value={this.props.animationIndex}
onChange={(e, i, value) => chooseAnimation(value)}
>
{mapFor(0, spriteObject.getAnimationsCount(), i => {
const animation = spriteObject.getAnimation(i);
return (
<MenuItem
key={i}
value={i}
primaryText={`Animation #${i} ${animation.getName()}`}
/>
);
})}
</SelectField>
{hasValidAnimation &&
animation.getDirectionsCount() > 1 && (
<SelectField
floatingLabelText="Direction"
value={this.props.directionIndex}
onChange={(e, i, value) => chooseDirection(value)}
>
{mapFor(0, animation.getDirectionsCount(), i => {
return (
<MenuItem
value={i}
key={i}
primaryText={`Direction #${i}`}
/>
);
})}
</SelectField>
)}
{hasValidDirection && (
<SelectField
floatingLabelText="Frame"
value={this.props.spriteIndex}
onChange={(e, i, value) => chooseSprite(value)}
>
{mapFor(0, direction.getSpritesCount(), i => {
return (
<MenuItem value={i} key={i} primaryText={`Frame #${i}`} />
);
})}
</SelectField>
)}
</Line>
<Toggle
label={setSameForAllAnimationsLabel}
labelPosition="right"
toggled={sameForAllAnimations}
onToggle={(e, checked) => setSameForAllAnimations(checked)}
/>
<Toggle
label={setSameForAllSpritesLabel}
labelPosition="right"
toggled={sameForAllSprites}
onToggle={(e, checked) => setSameForAllSprites(checked)}
/>
</React.Fragment>
);
}
}

View File

@@ -17,6 +17,7 @@ import ContextMenu from '../../../UI/Menu/ContextMenu';
import { showWarningBox } from '../../../UI/Messages/MessageBox';
import ResourcesLoader from '../../../ResourcesLoader';
import PointsEditor from './PointsEditor';
import CollisionMasksEditor from './CollisionMasksEditor';
import { deleteSpritesFromAnimation } from './Utils/SpriteObjectHelper';
const gd = global.gd;
@@ -283,6 +284,7 @@ class AnimationsListContainer extends Component {
export default class SpriteEditor extends Component {
state = {
pointsEditorOpen: false,
collisionMasksEditorOpen: false,
};
constructor(props) {
@@ -297,10 +299,10 @@ export default class SpriteEditor extends Component {
});
};
openHitboxesEditor = (open = true) => {
alert(
"Hitboxes editor is not ready yet! We're working on it and it will be available soon."
);
openCollisionMasksEditor = (open = true) => {
this.setState({
collisionMasksEditorOpen: open,
});
};
render() {
@@ -327,7 +329,7 @@ export default class SpriteEditor extends Component {
<RaisedButton
label="Edit hitboxes"
primary={false}
onClick={() => this.openHitboxesEditor(true)}
onClick={() => this.openCollisionMasksEditor(true)}
disabled={spriteObject.getAnimationsCount() === 0}
/>
<RaisedButton
@@ -359,7 +361,31 @@ export default class SpriteEditor extends Component {
resourcesLoader={this.resourcesLoader}
project={project}
onPointsUpdated={() =>
this.forceUpdate() /*Force update to ensure dialog is properly positionned*/}
this.forceUpdate() /*Force update to ensure dialog is properly positioned*/}
/>
</Dialog>
)}
{this.state.collisionMasksEditorOpen && (
<Dialog
actions={
<FlatButton
label="Close"
primary
onClick={() => this.openCollisionMasksEditor(false)}
/>
}
autoScrollBodyContent
noMargin
modal
onRequestClose={() => this.openCollisionMasksEditor(false)}
open={this.state.collisionMasksEditorOpen}
>
<CollisionMasksEditor
object={spriteObject}
resourcesLoader={this.resourcesLoader}
project={project}
onCollisionMasksUpdated={() =>
this.forceUpdate() /*Force update to ensure dialog is properly positioned*/}
/>
</Dialog>
)}

View File

@@ -37,6 +37,7 @@ type Props = {|
resourcesLoader: typeof ResourcesLoader,
children?: any,
style?: Object,
onImageLoaded?: (number, number) => void,
|};
type State = {|
@@ -71,8 +72,7 @@ export default class ImagePreview extends React.Component<Props, State> {
errored: false,
imageWidth: null,
imageHeight: null,
imageSource:
resourcesLoader.getResourceFullUrl(project, resourceName),
imageSource: resourcesLoader.getResourceFullUrl(project, resourceName),
};
}
@@ -92,10 +92,14 @@ export default class ImagePreview extends React.Component<Props, State> {
_handleImageLoaded = (e: any) => {
const imgElement = e.target;
const imageWidth = imgElement ? imgElement.clientWidth : 0;
const imageHeight = imgElement ? imgElement.clientHeight : 0;
this.setState({
imageWidth: imgElement ? imgElement.clientWidth : 0,
imageHeight: imgElement ? imgElement.clientHeight : 0,
imageWidth,
imageHeight,
});
if (this.props.onImageLoaded)
this.props.onImageLoaded(imageWidth, imageHeight);
};
render() {

View File

@@ -47,6 +47,11 @@ class ThemableObjectRow extends React.Component {
enabled: !!this.props.onEdit,
click: () => this.props.onEditName(),
},
{
label: 'Set as a global object',
enabled: !!this.props.onSetAsGlobalObject,
click: () => this.props.onSetAsGlobalObject(),
},
{
label: 'Delete',
enabled: !!this.props.onEdit,

View File

@@ -69,6 +69,7 @@ export default class ObjectSelector extends Component {
layout,
allowedObjectType,
noGroups,
onBlur,
...rest
} = this.props;
@@ -97,6 +98,8 @@ export default class ObjectSelector extends Component {
focused: false,
text: null,
});
if (onBlur) onBlur(event);
}}
onNewRequest={data => {
// Note that data may be a string or a {text, value} object.

View File

@@ -111,6 +111,11 @@ class ObjectsList extends Component<*, *> {
onPaste={() => this.props.onPaste(objectWithContext)}
onRename={newName =>
this.props.onRename(objectWithContext, newName)}
onSetAsGlobalObject={
objectWithContext.global
? undefined
: () => this.props.onSetAsGlobalObject(objectWithContext)
}
onAddNewObject={this.props.onAddNewObject}
editingName={nameBeingEdited}
getThumbnail={this.props.getThumbnail}
@@ -261,9 +266,10 @@ export default class ObjectsListContainer extends React.Component<
const { project, objectsContainer, onObjectPasted } = this.props;
const newName = newNameGenerator(
'CopyOf' + name,
name,
name =>
objectsContainer.hasObjectNamed(name) || project.hasObjectNamed(name)
objectsContainer.hasObjectNamed(name) || project.hasObjectNamed(name),
'CopyOf'
);
const newObject = global
@@ -355,6 +361,35 @@ export default class ObjectsListContainer extends React.Component<
this.forceUpdateList();
};
_setAsGlobalObject = (objectWithContext: ObjectWithContext) => {
const { object } = objectWithContext;
const { project, objectsContainer } = this.props;
const objectName = object.getName();
if (!objectsContainer.hasObjectNamed(objectName)) return;
if (project.hasObjectNamed(objectName)) {
showWarningBox(
'A global object with this name already exists. Please change the object name before setting it as a global object'
);
return;
}
//eslint-disable-next-line
const answer = confirm(
"This object will be loaded and available in all the scenes. This is only recommended for objects that you reuse a lot and can't be undone. Make this object global?"
);
if (!answer) return;
project.insertObject(
objectsContainer.getObject(objectName),
project.getObjectsCount()
);
objectsContainer.removeObject(objectName);
this.forceUpdateList();
};
forceUpdateList = () => {
this.forceUpdate();
this.sortableList.getWrappedInstance().forceUpdateGrid();
@@ -403,6 +438,7 @@ export default class ObjectsListContainer extends React.Component<
onEditObject={this.props.onEditObject}
onCopyObject={this._copyObject}
onCutObject={this._cutObject}
onSetAsGlobalObject={this._setAsGlobalObject}
onPaste={this._paste}
onAddNewObject={() =>
this.setState({ newObjectDialogOpen: true })}

View File

@@ -22,6 +22,7 @@ export default class SetupGridDialog extends Component {
const actions = [
<FlatButton
label="Cancel"
primary={false}
onClick={this.props.onCancel}
/>,
<FlatButton

View File

@@ -90,11 +90,10 @@
color: #E8DC59;
}
.gd-events-sheet-dark-theme
.instruction-parameter.object,
.instruction-parameter.objectPtr,
.instruction-parameter.objectList,
.instruction-parameter.objectListWithoutPicking {
.gd-events-sheet-dark-theme .instruction-parameter.object,
.gd-events-sheet-dark-theme .instruction-parameter.objectPtr,
.gd-events-sheet-dark-theme .instruction-parameter.objectList,
.gd-events-sheet-dark-theme .instruction-parameter.objectListWithoutPicking {
color: #B77CFF;
font-style: italic;
}

View File

@@ -95,9 +95,6 @@ const theme: Theme = {
logo: {
src: 'res/GD-logo.png',
},
startPage: {
backgroundColor,
},
mosaicRootClassName: 'mosaic-gd-dark-theme',
eventsSheetRootClassName: 'gd-events-sheet-dark-theme',
};

View File

@@ -83,11 +83,10 @@
color: rgb(27, 143, 1);
}
.gd-events-sheet-default-theme
.instruction-parameter.object,
.instruction-parameter.objectPtr,
.instruction-parameter.objectList,
.instruction-parameter.objectListWithoutPicking {
.gd-events-sheet-default-theme .instruction-parameter.object,
.gd-events-sheet-default-theme .instruction-parameter.objectPtr,
.gd-events-sheet-default-theme .instruction-parameter.objectList,
.gd-events-sheet-default-theme .instruction-parameter.objectListWithoutPicking {
color: #3c4698;
font-weight: bold;
}

View File

@@ -80,9 +80,6 @@ const theme = {
logo: {
src: 'res/GD-logo-big.png',
},
startPage: {
backgroundColor,
},
mosaicRootClassName: 'mosaic-gd-default-theme',
eventsSheetRootClassName: 'gd-events-sheet-default-theme',
};

View File

@@ -79,7 +79,6 @@ export default class Authentification {
getUserProfile = (cb: (any, ?Profile) => void) => {
if (!this.isAuthenticated()) return cb({ unauthenticated: true });
console.log('User found', this.user);
cb(null, this.user);
};

View File

@@ -0,0 +1,43 @@
// @flow
/**
* Export functions to manipulate a selection of objects.
*/
import values from 'lodash/values';
type ObjectType = { ptr: number };
type SelectionState<T> = {
[number]: ?T,
};
export const getInitialSelection = () => ({});
export const clearSelection = () => getInitialSelection();
export const getSelection = <T: ObjectType>(
selection: SelectionState<T>
): Array<T> => values(selection).filter(value => !!value);
export const addToSelection = <T: ObjectType>(
selection: SelectionState<T>,
object: T,
select: boolean = true
): SelectionState<T> => {
console.log(object, select);
return {
...selection,
[object.ptr]: select ? object : null,
};
};
export const isSelected = <T: ObjectType>(
selection: SelectionState<T>,
object: T
): boolean => !!selection[object.ptr];
export const hasSelection = <T: ObjectType>(
selection: SelectionState<T>
): boolean => {
return !!values(selection).filter(value => !!value).length;
};

View File

@@ -0,0 +1 @@
export const CLIPBOARD_KIND = 'Variables';

View File

@@ -1,8 +1,9 @@
import React from 'react';
// @flow
import * as React from 'react';
import { TreeTableRow, TreeTableCell } from '../UI/TreeTable';
import DragHandle from '../UI/DragHandle';
import SemiControlledTextField from '../UI/SemiControlledTextField';
import Delete from 'material-ui/svg-icons/action/delete';
import Checkbox from 'material-ui/Checkbox';
import AddCircle from 'material-ui/svg-icons/content/add-circle';
import SubdirectoryArrowRight from 'material-ui/svg-icons/navigation/subdirectory-arrow-right';
import TextField from 'material-ui/TextField';
@@ -16,6 +17,25 @@ const Indent = ({ width }) => (
</div>
);
const InlineCheckbox = props => <Checkbox {...props} style={{ width: 32 }} />;
type Props = {|
name: string,
variable: gdVariable,
depth: number,
errorText?: ?string,
onBlur: () => void,
onRemove: () => void,
onAddChild: () => void,
onChangeValue: string => void,
children?: React.Node,
muiTheme: Object,
showHandle: boolean,
showSelectionCheckbox: boolean,
isSelected: boolean,
onSelect: boolean => void,
|};
const ThemableVariableRow = ({
name,
variable,
@@ -27,7 +47,11 @@ const ThemableVariableRow = ({
onChangeValue,
children,
muiTheme,
}) => {
showHandle,
showSelectionCheckbox,
isSelected,
onSelect,
}: Props) => {
const isStructure = variable.isStructure();
const key = '' + depth + name;
@@ -36,7 +60,13 @@ const ThemableVariableRow = ({
{depth > 0 && (
<Indent width={(depth + 1) * styles.tableChildIndentation} />
)}
{depth === 0 && <DragHandle />}
{depth === 0 && showHandle && <DragHandle />}
{showSelectionCheckbox && (
<InlineCheckbox
checked={isSelected}
onCheck={(e, checked) => onSelect(checked)}
/>
)}
<TextField
fullWidth
name={key + 'name'}
@@ -64,9 +94,6 @@ const ThemableVariableRow = ({
}
columns.push(
<TreeTableCell key="tools" style={styles.toolColumn}>
<IconButton onClick={onRemove}>
<Delete />
</IconButton>
<IconButton onClick={onAddChild}>
<AddCircle />
</IconButton>

View File

@@ -1,11 +1,15 @@
// @flow
import React, { Component } from 'react';
import {
Table,
TableHeader,
TableHeaderColumn,
TableRow,
TableRowColumn,
} from 'material-ui/Table';
import IconButton from 'material-ui/IconButton';
import ContentCopy from 'material-ui/svg-icons/content/content-copy';
import ContentPaste from 'material-ui/svg-icons/content/content-paste';
import Delete from 'material-ui/svg-icons/action/delete';
import flatten from 'lodash/flatten';
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
import { mapFor } from '../Utils/MapFor';
@@ -14,12 +18,21 @@ import newNameGenerator from '../Utils/NewNameGenerator';
import VariableRow from './VariableRow';
import AddVariableRow from './AddVariableRow';
import styles from './styles';
import {
getInitialSelection,
hasSelection,
addToSelection,
getSelection,
} from '../Utils/SelectionHandler';
import { CLIPBOARD_KIND } from './ClipboardKind';
import Clipboard from '../Utils/Clipboard';
import { serializeToJSObject, unserializeFromJSObject } from '../Utils/Serializer';
const gd = global.gd;
const SortableVariableRow = SortableElement(VariableRow);
const SortableAddVariableRow = SortableElement(AddVariableRow);
class VariablesListBody extends Component {
class VariablesListBody extends Component<*, *> {
render() {
return <div>{this.props.children}</div>;
}
@@ -28,22 +41,97 @@ class VariablesListBody extends Component {
const SortableVariablesListBody = SortableContainer(VariablesListBody);
SortableVariablesListBody.muiName = 'TableBody';
export default class VariablesList extends Component {
constructor() {
super();
type VariableAndName = {| name: string, ptr: number, variable: gdVariable |};
this.state = {
nameErrors: {},
};
}
type Props = {|
variablesContainer: gdVariablesContainer,
emptyExplanationMessage?: string,
emptyExplanationSecondMessage?: string,
|};
type State = {|
nameErrors: { [string]: string },
selectedVariables: { [number]: ?VariableAndName },
mode: 'select' | 'move',
|};
_renderVariableChildren(name, parentVariable, depth) {
const children = parentVariable.getAllChildren();
const names = children.keys().toJSArray();
export default class VariablesList extends Component<Props, State> {
state = {
nameErrors: {},
selectedVariables: getInitialSelection(),
mode: 'select',
};
_selectVariable = (variableAndName: VariableAndName, select: boolean) => {
this.setState({
selectedVariables: addToSelection(
this.state.selectedVariables,
variableAndName,
select
),
});
};
copySelection = () => {
Clipboard.set(
CLIPBOARD_KIND,
getSelection(this.state.selectedVariables).map(({ name, variable }) => ({
name,
serializedVariable: serializeToJSObject(variable),
}))
);
};
paste = () => {
const { variablesContainer } = this.props;
if (!Clipboard.has(CLIPBOARD_KIND)) return;
const variables = Clipboard.get(CLIPBOARD_KIND);
variables.forEach(({ name, serializedVariable }) => {
const newName = newNameGenerator(name, (name) => variablesContainer.has(name), 'CopyOf');
const newVariable = new gd.Variable();
unserializeFromJSObject(newVariable, serializedVariable);
variablesContainer.insert(newName, newVariable, variablesContainer.count())
newVariable.delete();
});
this.forceUpdate();
};
deleteSelection = () => {
const { variablesContainer } = this.props;
const selection: Array<VariableAndName> = getSelection(
this.state.selectedVariables
);
// Only delete ancestor variables, as selection can be composed of variables
// that are contained inside others.
const ancestorOnlyVariables = selection.filter(({ variable }) => {
return selection.filter(
otherVariableAndName =>
variable !== otherVariableAndName &&
otherVariableAndName.variable.contains(variable)
);
});
// We don't want to ever manipulate/access to variables that have been deleted (by removeRecursively):
// that's why it's important to only delete ancestor variables.
ancestorOnlyVariables.forEach(({ variable }: VariableAndName) =>
variablesContainer.removeRecursively(variable)
);
this.setState({
selectedVariables: getInitialSelection(),
});
};
_renderVariableChildren(
name: string,
parentVariable: gdVariable,
depth: number
) {
const names = parentVariable.getAllChildrenNames().toJSArray();
return flatten(
names.map((name, index) => {
const variable = children.get(name);
const variable = parentVariable.getChild(name);
return this._renderVariableAndChildrenRows(
name,
variable,
@@ -55,7 +143,13 @@ export default class VariablesList extends Component {
);
}
_renderVariableAndChildrenRows(name, variable, depth, index, parentVariable) {
_renderVariableAndChildrenRows(
name: string,
variable: gdVariable,
depth: number,
index: number,
parentVariable: ?gdVariable
) {
const { variablesContainer } = this.props;
const isStructure = variable.isStructure();
@@ -119,6 +213,11 @@ export default class VariablesList extends Component {
? this._renderVariableChildren(name, variable, depth)
: null
}
showHandle={this.state.mode === 'move'}
showSelectionCheckbox={this.state.mode === 'select'}
isSelected={!!this.state.selectedVariables[variable.ptr]}
onSelect={select =>
this._selectVariable({ name, ptr: variable.ptr, variable }, select)}
/>
);
}
@@ -150,9 +249,8 @@ export default class VariablesList extends Component {
0,
variablesContainer.count(),
index => {
const nameAndVariable = variablesContainer.getAt(index);
const variable = nameAndVariable.getVariable();
const name = nameAndVariable.getName();
const variable = variablesContainer.getAt(index);
const name = variablesContainer.getNameAt(index);
return this._renderVariableAndChildrenRows(
name,
@@ -190,7 +288,26 @@ export default class VariablesList extends Component {
<TableRow>
<TableHeaderColumn>Name</TableHeaderColumn>
<TableHeaderColumn>Value</TableHeaderColumn>
<TableRowColumn />
<TableHeaderColumn style={styles.toolColumnHeader}>
<IconButton
onClick={this.copySelection}
disabled={!hasSelection(this.state.selectedVariables)}
>
<ContentCopy />
</IconButton>
<IconButton
onClick={this.paste}
disabled={!Clipboard.has(CLIPBOARD_KIND)}
>
<ContentPaste />
</IconButton>
<IconButton
onClick={this.deleteSelection}
disabled={!hasSelection(this.state.selectedVariables)}
>
<Delete />
</IconButton>
</TableHeaderColumn>
</TableRow>
</TableHeader>
</Table>

View File

@@ -1,6 +1,10 @@
export default {
toolColumnHeader: {
textAlign: 'right',
paddingRight: 8,
},
toolColumn: {
minWidth: 72,
minWidth: 48,
flex: 0,
justifyContent: 'flex-end',
},

View File

@@ -23,6 +23,7 @@ import TiledSpriteEditor from '../ObjectEditor/Editors/TiledSpriteEditor';
import PanelSpriteEditor from '../ObjectEditor/Editors/PanelSpriteEditor';
import SpriteEditor from '../ObjectEditor/Editors/SpriteEditor';
import PointsEditor from '../ObjectEditor/Editors/SpriteEditor/PointsEditor';
import CollisionMasksEditor from '../ObjectEditor/Editors/SpriteEditor/CollisionMasksEditor';
import EmptyEditor from '../ObjectEditor/Editors/EmptyEditor';
import ImageThumbnail from '../ObjectEditor/ImageThumbnail';
import ShapePainterEditor from '../ObjectEditor/Editors/ShapePainterEditor';
@@ -424,6 +425,15 @@ storiesOf('SpriteEditor and related editors', module)
resourcesLoader={ResourcesLoader}
/>
</SerializedObjectDisplay>
))
.add('CollisionMasksEditor', () => (
<SerializedObjectDisplay object={spriteObject}>
<CollisionMasksEditor
object={spriteObject}
project={project}
resourcesLoader={ResourcesLoader}
/>
</SerializedObjectDisplay>
));
storiesOf('ShapePainterEditor', module)

View File

@@ -1,6 +1,6 @@
{
"name": "gdevelop",
"version": "5.0.0-beta25",
"version": "5.0.0-beta28",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "gdevelop",
"productName": "GDevelop 5",
"description": "GDevelop 5 IDE running on the Electron runtime",
"version": "5.0.0-beta27",
"version": "5.0.0-beta28",
"author": "Florian Rival",
"license": "MIT",
"homepage": "http://gdevelop-app.com",

View File

@@ -23,8 +23,8 @@
"7zip-bin-win" "^2.1.1"
"@types/node@^7.0.18":
version "7.0.48"
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.48.tgz#24bfdc0aa82e8f6dbd017159c58094a2e06d0abb"
version "7.0.58"
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.58.tgz#ae852120137f40a29731a559e48003bd2d5d19f7"
ajv-keywords@^2.1.1:
version "2.1.1"