Merge pull request #742 from 4ian/feature/font-resource

Rework fonts loading
This commit is contained in:
Florian Rival
2018-11-13 23:33:18 +00:00
committed by GitHub
40 changed files with 654 additions and 234 deletions

View File

@@ -370,8 +370,8 @@
<Unit filename="GDCore/IDE/Events/ExpressionsCorrectnessTesting.h" />
<Unit filename="GDCore/IDE/ExtensionsLoader.cpp" />
<Unit filename="GDCore/IDE/ExtensionsLoader.h" />
<Unit filename="GDCore/IDE/Project/ImagesUsedInventorizer.cpp" />
<Unit filename="GDCore/IDE/Project/ImagesUsedInventorizer.h" />
<Unit filename="GDCore/IDE/Project/ResourcesInUseHelper.cpp" />
<Unit filename="GDCore/IDE/Project/ResourcesInUseHelper.h" />
<Unit filename="GDCore/Extensions/Metadata/MetadataProvider.cpp" />
<Unit filename="GDCore/Extensions/Metadata/MetadataProvider.h" />
<Unit filename="GDCore/IDE/PlatformLoader.cpp" />

View File

@@ -41,7 +41,7 @@
#include "GDCore/IDE/Dialogs/DndResourcesEditor.h"
#include "GDCore/IDE/Dialogs/PropertyDescriptor.h"
#include "GDCore/IDE/Dialogs/ResourceLibraryDialog.h"
#include "GDCore/IDE/Project/ImagesUsedInventorizer.h"
#include "GDCore/IDE/Project/ResourcesInUseHelper.h"
#include "GDCore/IDE/Project/ProjectResourcesAdder.h"
#include "GDCore/IDE/wxTools/FileProperty.h"
#include "GDCore/IDE/wxTools/SkinHelper.h"
@@ -1332,7 +1332,7 @@ void ResourcesEditor::Refresh() {
void ResourcesEditor::OnDeleteUnusedFiles(wxCommandEvent& event) {
std::vector<gd::String> unusedImages =
gd::ProjectResourcesAdder::GetAllUselessImages(project);
gd::ProjectResourcesAdder::GetAllUseless(project, "image");
// Construct corresponding wxArrayString with unused images
wxArrayString imagesNotUsed;

View File

@@ -21,7 +21,7 @@ using namespace std;
namespace gd {
void ArbitraryResourceWorker::ExposeImage(gd::String& imageName){
// Nothing to do, the image is a referece to a resource that
// Nothing to do, the image is a reference to a resource that
// is already exposed.
};
@@ -40,6 +40,21 @@ void ArbitraryResourceWorker::ExposeAudio(gd::String& audioName) {
ExposeFile(audioName);
};
void ArbitraryResourceWorker::ExposeFont(gd::String& fontName) {
for (auto resources : GetResources()) {
if (!resources) continue;
if (resources->HasResource(fontName) &&
resources->GetResource(fontName).GetKind() == "font") {
// Nothing to do, the font is a reference to a resource that
// is already exposed.
return;
}
}
ExposeFile(fontName);
};
void ArbitraryResourceWorker::ExposeResources(
gd::ResourcesManager* resourcesManager) {
if (!resourcesManager) return;

View File

@@ -34,7 +34,7 @@ namespace gd {
* sometimes update them.
*
* \see ResourcesMergingHelper
* \see gd::ImagesUsedInventorizer
* \see gd::ResourcesInUseHelper
*
* \see gd::LaunchResourceWorkerOnEvents
*
@@ -64,6 +64,12 @@ class GD_CORE_API ArbitraryResourceWorker {
*/
virtual void ExposeAudio(gd::String &audioName);
/**
* \brief Expose a font, which is either a reference to a "font" resource,
* or a filename if no resource with this name exists.
*/
virtual void ExposeFont(gd::String &fontName);
/**
* \brief Expose a shader.
* \warn Currently unsupported.

View File

@@ -1,52 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#ifndef IMAGESUSEDINVENTORIZER_H
#define IMAGESUSEDINVENTORIZER_H
#include <set>
#include <vector>
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
#include "GDCore/String.h"
namespace gd {
/**
* \brief Class used to track all images used in a game.
*
* Usage example:
\code
gd::ImagesUsedInventorizer inventorizer;
project.ExposeResources(inventorizer);
//Get a set with the name of all images in the project:
std::set<gd::String> & usedImages = inventorizer.GetAllUsedImages();
\endcode
*
* \ingroup IDE
*/
class ImagesUsedInventorizer : public gd::ArbitraryResourceWorker {
public:
ImagesUsedInventorizer() : gd::ArbitraryResourceWorker(){};
virtual ~ImagesUsedInventorizer(){};
std::set<gd::String>& GetAllUsedImages() { return allUsedImages; };
virtual void ExposeFile(gd::String& resource){
/*Don't care, we just list images*/};
virtual void ExposeImage(gd::String& imageName) {
allUsedImages.insert(imageName);
};
protected:
std::set<gd::String> allUsedImages;
};
} // namespace gd
#endif // IMAGESUSEDINVENTORIZER_H
#endif

View File

@@ -5,7 +5,7 @@
*/
#include "ProjectResourcesAdder.h"
#include "GDCore/CommonTools.h"
#include "GDCore/IDE/Project/ImagesUsedInventorizer.h"
#include "GDCore/IDE/Project/ResourcesInUseHelper.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Tools/Log.h"
@@ -14,51 +14,52 @@ using namespace std;
namespace gd {
bool ProjectResourcesAdder::AddAllMissingImages(gd::Project& project) {
gd::ImagesUsedInventorizer inventorizer;
project.ExposeResources(inventorizer);
std::set<gd::String>& allImages = inventorizer.GetAllUsedImages();
bool ProjectResourcesAdder::AddAllMissing(gd::Project& project,
const gd::String& resourceType) {
// Search for resources used in the project
gd::ResourcesInUseHelper resourcesInUse;
project.ExposeResources(resourcesInUse);
ResourcesManager& resourcesManager = project.GetResourcesManager();
for (std::set<gd::String>::const_iterator it = allImages.begin();
it != allImages.end();
++it) {
if (!resourcesManager.HasResource(*it)) {
std::cout << "Adding missing resource \"" << *it << "\"to the project.";
resourcesManager.AddResource(*it, /*filename=*/*it, "image");
for (auto& resourceName : resourcesInUse.GetAll(resourceType)) {
if (!resourcesManager.HasResource(resourceName)) {
std::cout << "Adding missing resource \"" << resourceName
<< "\"to the project." << std::endl;
resourcesManager.AddResource(
resourceName, /*filename=*/resourceName, resourceType);
}
}
return true;
}
std::vector<gd::String> ProjectResourcesAdder::GetAllUselessImages(
gd::Project& project) {
std::vector<gd::String> ProjectResourcesAdder::GetAllUseless(
gd::Project& project, const gd::String& resourceType) {
std::vector<gd::String> unusedResources;
// Search for resources used in the project
gd::ResourcesInUseHelper resourcesInUse;
project.ExposeResources(resourcesInUse);
std::set<gd::String>& usedResources = resourcesInUse.GetAll(resourceType);
// Search for used images
gd::ImagesUsedInventorizer inventorizer;
project.ExposeResources(inventorizer);
std::set<gd::String>& usedImages = inventorizer.GetAllUsedImages();
// Search all images resources not used
// Search all resources not used
std::vector<gd::String> resources =
project.GetResourcesManager().GetAllResourceNames();
for (std::size_t i = 0; i < resources.size(); i++) {
if (project.GetResourcesManager().GetResource(resources[i]).GetKind() !=
"image")
resourceType)
continue;
if (usedImages.find(resources[i]) == usedImages.end())
if (usedResources.find(resources[i]) == usedResources.end())
unusedResources.push_back(resources[i]);
}
return unusedResources;
}
void ProjectResourcesAdder::RemoveAllUselessImages(gd::Project& project) {
std::vector<gd::String> unusedResources = GetAllUselessImages(project);
void ProjectResourcesAdder::RemoveAllUseless(gd::Project& project,
const gd::String& resourceType) {
std::vector<gd::String> unusedResources =
GetAllUseless(project, resourceType);
for (std::size_t i = 0; i < unusedResources.size(); ++i) {
project.GetResourcesManager().RemoveResource(unusedResources[i]);

View File

@@ -21,38 +21,35 @@ namespace gd {
class GD_CORE_API ProjectResourcesAdder {
public:
/**
* \brief Update the project so that all missing images are added, with an
* \brief Update the project so that all missing resources are added, with an
* filename that is equal to the missing resource name.
*
* \param project The project to be updated.
* \param resourceType The type of the resource the be searched
*
* \return true if no error happened
*/
static bool AddAllMissingImages(gd::Project& project);
static bool AddAllMissing(gd::Project& project, const gd::String & resourceType);
/**
* \brief Find all resources that are
* not used in the project.
*
* \note For now, only images resources can be tracked and marked
* as not used.
* \brief Find all resources of the specified kind that are
* not used by the project.
*
* \param project The project to be crawled.
* \param resourceType The type of the resource the be searched
*
* \return A vector containing the name of all unused resources
*/
static std::vector<gd::String> GetAllUselessImages(gd::Project& project);
static std::vector<gd::String> GetAllUseless(gd::Project& project, const gd::String & resourceType);
/**
* \brief Remove all resources that are not used
* in the project.
*
* \note For now, only images resources can be tracked and marked
* as not used.
* \brief Remove all resources of the specified kind that are not used
* by the project.
*
* \param project The project to be crawled.
* \param resourceType The type of the resource the be searched
*/
static void RemoveAllUselessImages(gd::Project& project);
static void RemoveAllUseless(gd::Project& project, const gd::String & resourceType);
};
} // namespace gd

View File

@@ -0,0 +1,72 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#ifndef IMAGESUSEDINVENTORIZER_H
#define IMAGESUSEDINVENTORIZER_H
#include <set>
#include <vector>
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
#include "GDCore/String.h"
namespace gd {
/**
* \brief Class used to track all resources used by a game,
* or a part of it (like a gd::Object).
*
* Usage example:
\code
gd::ResourcesInUseHelper resourcesInUse;
project.ExposeResources(resourcesInUse);
//Get a set with the name of all images in the project:
std::set<gd::String> & usedImages = resourcesInUse.GetAllImages();
\endcode
*
* \ingroup IDE
*/
class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
public:
ResourcesInUseHelper() : gd::ArbitraryResourceWorker(){};
virtual ~ResourcesInUseHelper(){};
std::set<gd::String>& GetAllImages() { return GetAll("image"); };
std::set<gd::String>& GetAllFonts() { return GetAll("font"); };
std::set<gd::String>& GetAllAudios() { return GetAll("audio"); };
std::set<gd::String>& GetAll(const gd::String& resourceType) {
return resourceType == "image"
? allImages
: (resourceType == "audio"
? allAudios
: (resourceType == "font") ? allFonts : emptyResources);
};
virtual void ExposeFile(gd::String& resource) override{
/*Don't care, we just list resource names*/
};
virtual void ExposeImage(gd::String& imageResourceName) override {
allImages.insert(imageResourceName);
};
virtual void ExposeAudio(gd::String& audioResourceName) override {
allAudios.insert(audioResourceName);
};
virtual void ExposeFont(gd::String& fontResourceName) override {
allFonts.insert(fontResourceName);
};
protected:
std::set<gd::String> allImages;
std::set<gd::String> allAudios;
std::set<gd::String> allFonts;
std::set<gd::String> emptyResources;
};
} // namespace gd
#endif // IMAGESUSEDINVENTORIZER_H
#endif

View File

@@ -79,6 +79,8 @@ std::shared_ptr<Resource> ResourcesManager::CreateResource(
return std::make_shared<ImageResource>();
else if (kind == "audio")
return std::make_shared<AudioResource>();
else if (kind == "font")
return std::make_shared<FontResource>();
std::cout << "Bad resource created (type: " << kind << ")" << std::endl;
return std::make_shared<Resource>();
@@ -92,7 +94,7 @@ bool ResourcesManager::HasResource(const gd::String& name) const {
return false;
}
std::vector<gd::String> ResourcesManager::GetAllResourceNames() {
std::vector<gd::String> ResourcesManager::GetAllResourceNames() const {
std::vector<gd::String> allResources;
for (std::size_t i = 0; i < resources.size(); ++i)
allResources.push_back(resources[i]->GetName());
@@ -134,19 +136,11 @@ bool ImageResource::UpdateProperty(const gd::String& name,
bool ResourcesManager::AddResource(const gd::Resource& resource) {
if (HasResource(resource.GetName())) return false;
try {
const Resource& castedResource = dynamic_cast<const Resource&>(resource);
std::shared_ptr<Resource> newResource =
std::shared_ptr<Resource>(castedResource.Clone());
if (newResource == std::shared_ptr<Resource>()) return false;
resources.push_back(newResource);
} catch (...) {
std::cout << "WARNING: Tried to add a resource which is not a GD C++ "
"Platform Resource to a GD C++ Platform project";
std::cout << char(7);
}
std::shared_ptr<Resource> newResource =
std::shared_ptr<Resource>(resource.Clone());
if (newResource == std::shared_ptr<Resource>()) return false;
resources.push_back(newResource);
return true;
}
@@ -408,16 +402,8 @@ bool ResourceFolder::HasResource(const gd::String& name) const {
void ResourceFolder::AddResource(const gd::String& name,
gd::ResourcesManager& parentManager) {
try {
ResourcesManager& manager = dynamic_cast<ResourcesManager&>(parentManager);
std::shared_ptr<Resource> resource =
std::dynamic_pointer_cast<Resource>(manager.GetResourceSPtr(name));
if (resource != std::shared_ptr<Resource>()) resources.push_back(resource);
} catch (...) {
std::cout << "Warning: A resources manager which is not part of GD C++ "
"Platform was used during call to AddResource"
<< std::endl;
}
std::shared_ptr<Resource> resource = parentManager.GetResourceSPtr(name);
if (resource != std::shared_ptr<Resource>()) resources.push_back(resource);
}
void ResourcesManager::RenameResource(const gd::String& oldName,
@@ -580,7 +566,31 @@ void AudioResource::SerializeTo(SerializerElement& element) const {
"file", GetFile()); // Keep the resource path in the current locale (but
// save it in UTF8 for compatibility on other OSes)
}
#endif
void FontResource::SetFile(const gd::String& newFile) {
file = newFile;
// Convert all backslash to slashs.
while (file.find('\\') != gd::String::npos)
file.replace(file.find('\\'), 1, "/");
}
void FontResource::UnserializeFrom(const SerializerElement& element) {
SetUserAdded(element.GetBoolAttribute("userAdded"));
SetFile(element.GetStringAttribute("file"));
}
#if defined(GD_IDE_ONLY)
void FontResource::SerializeTo(SerializerElement& element) const {
element.SetAttribute("userAdded", IsUserAdded());
element.SetAttribute(
"file", GetFile()); // Keep the resource path in the current locale (but
// save it in UTF8 for compatibility on other OSes)
}
#endif
#if defined(GD_IDE_ONLY)
ResourceFolder::ResourceFolder(const ResourceFolder& other) { Init(other); }
ResourceFolder& ResourceFolder::operator=(const ResourceFolder& other) {

View File

@@ -262,6 +262,34 @@ class GD_CORE_API AudioResource : public Resource {
gd::String file;
};
/**
* \brief Describe a font file used by a project.
*
* \see Resource
* \ingroup ResourcesManagement
*/
class GD_CORE_API FontResource : public Resource {
public:
FontResource() : Resource() { SetKind("font"); };
virtual ~FontResource(){};
virtual FontResource* Clone() const override {
return new FontResource(*this);
}
virtual const gd::String& GetFile() const override { return file; };
virtual void SetFile(const gd::String& newFile) override;
#if defined(GD_IDE_ONLY)
virtual bool UseFile() override { return true; }
void SerializeTo(SerializerElement& element) const override;
#endif
void UnserializeFrom(const SerializerElement& element) override;
private:
gd::String file;
};
/**
* \brief Inventory all resources used by a project
*
@@ -298,7 +326,7 @@ class GD_CORE_API ResourcesManager {
/**
* \brief Get a list containing the names of all resources.
*/
std::vector<gd::String> GetAllResourceNames();
std::vector<gd::String> GetAllResourceNames() const;
#if defined(GD_IDE_ONLY)
/**

View File

@@ -87,11 +87,11 @@ TEST_CASE("Resources", "[common][resources]") {
SECTION("ProjectResourcesAdder") {
std::vector<gd::String> uselessResources =
gd::ProjectResourcesAdder::GetAllUselessImages(project);
gd::ProjectResourcesAdder::GetAllUseless(project, "image");
REQUIRE(uselessResources.size() == 2);
gd::ProjectResourcesAdder::RemoveAllUselessImages(project);
gd::ProjectResourcesAdder::RemoveAllUseless(project, "image");
std::vector<gd::String> remainingResources =
project.GetResourcesManager().GetAllResourceNames();
REQUIRE(remainingResources.size() == 2);

View File

@@ -35,7 +35,7 @@ TextObjectEditor::TextObjectEditor(wxWindow* parent,
// Update from the text object
m_textCtrl->SetValue(object.GetString());
m_fontTextCtrl->SetValue(object.GetFontFilename());
m_fontTextCtrl->SetValue(object.GetFontName());
m_sizeCombobox->SetValue(gd::String::From<float>(object.GetCharacterSize()));
textColor =
wxColour(object.GetColorR(), object.GetColorG(), object.GetColorB());
@@ -56,7 +56,7 @@ TextObjectEditor::~TextObjectEditor() {}
void TextObjectEditor::OnOkBtClicked(wxCommandEvent& event) {
// Update the text object
object.SetString(m_textCtrl->GetValue());
object.SetFontFilename(m_fontTextCtrl->GetValue());
object.SetFontName(m_fontTextCtrl->GetValue());
object.SetCharacterSize(gd::String(m_sizeCombobox->GetValue()).To<float>());
object.SetColor(textColor.Red(), textColor.Green(), textColor.Blue());
object.SetBold(m_toolbar->GetToolToggled(BOLD_TOOL_ID));

View File

@@ -54,7 +54,7 @@ TextObject::~TextObject(){};
void TextObject::DoUnserializeFrom(gd::Project& project,
const gd::SerializerElement& element) {
SetString(element.GetChild("string", 0, "String").GetValue().GetString());
SetFontFilename(element.GetChild("font", 0, "Font").GetValue().GetString());
SetFontName(element.GetChild("font", 0, "Font").GetValue().GetString());
SetCharacterSize(element.GetChild("characterSize", 0, "CharacterSize")
.GetValue()
.GetInt());
@@ -119,7 +119,7 @@ void TextObject::LoadResources(gd::Project& project, gd::Layout& layout) {
void TextObject::DoSerializeTo(gd::SerializerElement& element) const {
element.AddChild("string").SetValue(GetString());
element.AddChild("font").SetValue(GetFontFilename());
element.AddChild("font").SetValue(GetFontName());
element.AddChild("characterSize").SetValue(GetCharacterSize());
element.AddChild("color")
.SetAttribute("r", (int)GetColorR())
@@ -133,7 +133,7 @@ void TextObject::DoSerializeTo(gd::SerializerElement& element) const {
}
void TextObject::ExposeResources(gd::ArbitraryResourceWorker& worker) {
worker.ExposeFile(fontName);
worker.ExposeFont(fontName);
}
bool TextObject::GenerateThumbnail(const gd::Project& project,
@@ -157,19 +157,12 @@ void TextObject::EditObject(wxWindow* parent,
}
#endif
void TextObject::SetFontFilename(const gd::String& fontFilename) {
fontName = fontFilename;
#if defined(GD_IDE_ONLY)
fontName = gd::AbstractFileSystem::NormalizeSeparator(fontName);
#endif
};
/* RuntimeTextObject : */
RuntimeTextObject::RuntimeTextObject(RuntimeScene& scene,
const TextObject& textObject)
: RuntimeObject(scene, textObject), opacity(255), angle(0) {
ChangeFont(textObject.GetFontFilename());
ChangeFont(textObject.GetFontName());
SetSmooth(textObject.IsSmoothed());
SetColor(
textObject.GetColorR(), textObject.GetColorG(), textObject.GetColorB());
@@ -332,7 +325,7 @@ void RuntimeTextObject::GetPropertyForDebugger(std::size_t propertyNb,
value = GetString();
} else if (propertyNb == 1) {
name = _("Font");
value = GetFontFilename();
value = GetFontName();
} else if (propertyNb == 2) {
name = _("Font Size");
value = gd::String::From(GetCharacterSize());

View File

@@ -15,26 +15,17 @@ This project is released under the MIT License.
class ImageManager;
class RuntimeScene;
namespace gd {
class Project;
class Object;
}
namespace gd {
class ImageManager;
}
namespace gd {
class InitialInstance;
}
#if defined(GD_IDE_ONLY)
class wxBitmap;
namespace gd {
class Project;
}
class wxWindow;
namespace gd {
class MainFrameWrapper;
}
namespace gd {
class ResourcesMergingHelper;
}
#endif
/**
@@ -82,13 +73,13 @@ class GD_EXTENSION_API TextObject : public gd::Object {
*/
inline float GetCharacterSize() const { return characterSize; };
/** \brief Return the font filename.
/** \brief Return the name of the font resource used for the text.
*/
inline const gd::String& GetFontFilename() const { return fontName; };
inline const gd::String& GetFontName() const { return fontName; };
/** \brief Change the font filename.
/** \brief Change the font resource used for the text.
*/
void SetFontFilename(const gd::String& fontFilename);
void SetFontName(const gd::String& resourceName) { fontName = resourceName; };
bool IsBold() const { return bold; };
void SetBold(bool enable) { bold = enable; };
@@ -170,9 +161,9 @@ class GD_EXTENSION_API RuntimeTextObject : public RuntimeObject {
*/
void ChangeFont(const gd::String& fontFilename);
/** \brief Return the font file name.
/** \brief Return the font resource name.
*/
inline gd::String GetFontFilename() const { return fontName; };
inline gd::String GetFontName() const { return fontName; };
void SetFontStyle(int style);
int GetFontStyle();

View File

@@ -1,6 +1,7 @@
gdjs.TextRuntimeObjectCocosRenderer = function(runtimeObject, runtimeScene)
{
this._object = runtimeObject;
this._fontManager = runtimeScene.getGame().getFontManager();
this._text = new cc.LabelTTF(" ", "Arial", 38);
this._text.disableStroke();
@@ -33,8 +34,8 @@ gdjs.TextRuntimeObjectCocosRenderer.prototype.updateStyle = function() {
'Arial' :
(
gdjs.CocosTools.isHTML5() ?
'gdjs_font_' + this._object._fontName :
'res/' + this._object._fontName
this._fontManager.getFontFamily(this._object._fontName) :
'res/' + this._fontManager.getFontFile(this._object._fontName)
);
this._text.setFontName(fontName);
};

View File

@@ -1,6 +1,7 @@
gdjs.TextRuntimeObjectPixiRenderer = function(runtimeObject, runtimeScene)
{
this._object = runtimeObject;
this._fontManager = runtimeScene.getGame().getFontManager();
if ( this._text === undefined ) this._text = new PIXI.Text(" ", {align:"left"});
this._text.anchor.x = 0.5;
@@ -28,7 +29,7 @@ gdjs.TextRuntimeObjectPixiRenderer.prototype.ensureUpToDate = function() {
};
gdjs.TextRuntimeObjectPixiRenderer.prototype.updateStyle = function() {
var fontName = this._object._fontName ? "\"gdjs_font_" + this._object._fontName + "\"" : 'Arial';
var fontName = "\"" + this._fontManager.getFontFamily(this._object._fontName) + "\"";
var style = this._text.style;
style.fontStyle = this._object._italic ? 'italic' : 'normal';

View File

@@ -172,7 +172,8 @@ bool Exporter::ExportWholePixiProject(
else if (exportForFacebookInstantGames)
source = gdjsRoot + "/Runtime/FacebookInstantGames/index.html";
if (!helper.ExportPixiIndexFile(source, exportDir, includesFiles, "")) {
if (!helper.ExportPixiIndexFile(
exportedProject, source, exportDir, includesFiles, "")) {
gd::LogError(_("Error during export:\n") + lastError);
return false;
}
@@ -248,7 +249,8 @@ bool Exporter::ExportWholeCocos2dProject(gd::Project& project,
helper.AddLibsInclude(false, true, false, includesFiles);
// Export events
if (!helper.ExportEventsCode(exportedProject, codeOutputDir, includesFiles, false)) {
if (!helper.ExportEventsCode(
exportedProject, codeOutputDir, includesFiles, false)) {
gd::LogError(_("Error during exporting! Unable to export events:\n") +
lastError);
return false;

View File

@@ -44,27 +44,56 @@ static void InsertUnique(std::vector<gd::String> &container, gd::String str) {
container.push_back(str);
}
static void GenerateFontsDeclaration(gd::AbstractFileSystem &fs,
const gd::String &outputDir,
gd::String &css,
gd::String &html,
gd::String urlPrefix = "") {
std::vector<gd::String> ttfFiles = fs.ReadDir(outputDir, ".TTF");
for (std::size_t i = 0; i < ttfFiles.size(); ++i) {
gd::String relativeFile = ttfFiles[i];
fs.MakeRelative(relativeFile, outputDir);
static void GenerateFontsDeclaration(
const gd::ResourcesManager &resourcesManager,
gd::AbstractFileSystem &fs,
const gd::String &outputDir,
gd::String &css,
gd::String &html,
gd::String urlPrefix = "") {
std::set<gd::String> files;
auto makeCSSDeclarationFor = [&urlPrefix](gd::String relativeFile) {
gd::String css;
css += "@font-face{ font-family : \"gdjs_font_";
css += relativeFile;
css += "\"; src : url('";
css += urlPrefix + relativeFile;
css += "') format('truetype'); }";
css += "') format('truetype'); }\n";
// Use the font for a dummy text to trigger immediate load of the font at
// game startup
return css;
};
for (auto &resourceName : resourcesManager.GetAllResourceNames()) {
const gd::Resource &resource = resourcesManager.GetResource(resourceName);
if (resource.GetKind() != "font") continue;
gd::String relativeFile = resource.GetFile();
css += makeCSSDeclarationFor(relativeFile);
files.insert(relativeFile);
}
// Compatibility with GD <= 5.0-beta56
// Before, fonts were detected by scanning the export folder for .TTF files.
// Text Object (or anything using a font) was just declaring the font filename
// as a file (using ArbitraryResourceWorker::ExposeFile) for export.
// We still support this, the time everything is migrated to using font
// resources.
std::vector<gd::String> ttfFiles = fs.ReadDir(outputDir, ".TTF");
for (std::size_t i = 0; i < ttfFiles.size(); ++i) {
gd::String relativeFile = ttfFiles[i];
fs.MakeRelative(relativeFile, outputDir);
// Skip font files already in resources
if (files.find(relativeFile) != files.end()) continue;
css += makeCSSDeclarationFor(relativeFile);
// This is needed to trigger the loading of the fonts.
html += "<div style=\"font-family: 'gdjs_font_";
html += relativeFile;
html += "'; color: black;\">.</div>";
}
// end of compatibility code
}
ExporterHelper::ExporterHelper(gd::AbstractFileSystem &fileSystem,
@@ -119,7 +148,8 @@ bool ExporterHelper::ExportLayoutForPixiPreview(gd::Project &project,
ExportIncludesAndLibs(includesFiles, exportDir, false);
// Create the index file
if (!ExportPixiIndexFile(gdjsRoot + "/Runtime/index.html",
if (!ExportPixiIndexFile(exportedProject,
gdjsRoot + "/Runtime/index.html",
exportDir,
includesFiles,
additionalSpec))
@@ -148,6 +178,7 @@ gd::String ExporterHelper::ExportToJSON(gd::AbstractFileSystem &fs,
}
bool ExporterHelper::ExportPixiIndexFile(
const gd::Project &project,
gd::String source,
gd::String exportDir,
const std::vector<gd::String> &includesFiles,
@@ -156,8 +187,14 @@ bool ExporterHelper::ExportPixiIndexFile(
// Generate custom declarations for font resources
gd::String customCss;
gd::String customHtml;
GenerateFontsDeclaration(fs, exportDir, customCss, customHtml);
gd::String customHtml; // Custom HTML is only needed for the deprecated way
// of loading fonts
GenerateFontsDeclaration(project.GetResourcesManager(),
fs, // File system is only needed for the deprecated
// way of loading fonts
exportDir,
customCss,
customHtml);
// Generate the file
if (!CompleteIndexFile(
@@ -229,10 +266,13 @@ bool ExporterHelper::ExportCordovaConfigFile(const gd::Project &project,
.FindAndReplace("GDJS_ICON_IOS_100",
getIconFilename("ios", "icon-100"));
if(!project.GetAdMobAppId().empty()){
str = str.FindAndReplace("<!-- GDJS_ADMOB_PLUGIN_AND_APPLICATION_ID -->",
"<plugin name=\"cordova-plugin-admob-free\" spec=\"~0.21.0\">\n"
"\t\t<variable name=\"ADMOB_APP_ID\" value=\"" + project.GetAdMobAppId() + "\" />\n"
if (!project.GetAdMobAppId().empty()) {
str = str.FindAndReplace(
"<!-- GDJS_ADMOB_PLUGIN_AND_APPLICATION_ID -->",
"<plugin name=\"cordova-plugin-admob-free\" spec=\"~0.21.0\">\n"
"\t\t<variable name=\"ADMOB_APP_ID\" value=\"" +
project.GetAdMobAppId() +
"\" />\n"
"\t</plugin>");
}
@@ -267,8 +307,12 @@ bool ExporterHelper::ExportCocos2dFiles(
// Generate custom declarations for font resources
gd::String customCss;
gd::String customHtml;
GenerateFontsDeclaration(
fs, exportDir + "/res", customCss, customHtml, "res/");
GenerateFontsDeclaration(project.GetResourcesManager(),
fs,
exportDir + "/res",
customCss,
customHtml,
"res/");
// Generate the file
std::vector<gd::String> noIncludesInThisFile;
@@ -476,6 +520,8 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
"pixi-renderers/loadingscreen-pixi-renderer.js");
InsertUnique(includesFiles, "howler-sound-manager/howler.min.js");
InsertUnique(includesFiles, "howler-sound-manager/howler-sound-manager.js");
InsertUnique(includesFiles, "fontfaceobserver-font-manager/fontfaceobserver.js");
InsertUnique(includesFiles, "fontfaceobserver-font-manager/fontfaceobserver-font-manager.js");
}
if (cocosRenderers) {
@@ -492,6 +538,8 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
InsertUnique(includesFiles,
"cocos-renderers/spriteruntimeobject-cocos-renderer.js");
InsertUnique(includesFiles, "cocos-sound-manager/cocos-sound-manager.js");
InsertUnique(includesFiles, "fontfaceobserver-font-manager/fontfaceobserver.js");
InsertUnique(includesFiles, "fontfaceobserver-font-manager/fontfaceobserver-font-manager.js");
}
}

View File

@@ -156,7 +156,8 @@ class ExporterHelper {
* important. \param additionalSpec JSON string that will be passed to the
* gdjs.RuntimeGame object.
*/
bool ExportPixiIndexFile(gd::String source,
bool ExportPixiIndexFile(const gd::Project &project,
gd::String source,
gd::String exportDir,
const std::vector<gd::String> &includesFiles,
gd::String additionalSpec = "");

View File

@@ -17,7 +17,9 @@ gdjs.CocosImageManager = function(resources)
var that = this;
resources.forEach(function(res) {
that._resources[res.name] = res;
if ( res.file && res.kind === "image" ) {
that._resources[res.name] = res;
}
})
};
@@ -67,6 +69,6 @@ gdjs.CocosImageManager.prototype.loadTextures = function(onProgress, onComplete)
});
cc.LoaderScene.preload(files, function () {
onComplete();
onComplete(files.length);
});
}

View File

@@ -152,5 +152,5 @@ gdjs.CocosSoundManager.prototype.preloadAudio = function(onProgress, onComplete,
}
//TODO: sound preloading
onComplete();
onComplete(files.length);
}

View File

@@ -0,0 +1,128 @@
/*
* GDevelop JS Platform
* Copyright 2013-present Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
/**
* FontFaceObserverFontManager loads fonts (using fontfaceobserver library)
* from the game resources (see `loadFonts`), and allow to access to
* the font families of the loaded fonts during the game (see `getFontFamily`).
*
* @font-face declarations must be have been added separately in the index.html
* (or any CSS file).
*
* @class FontFaceObserverFontManager
* @memberof gdjs
* @param {Object} resources The resources data of the game.
*/
gdjs.FontFaceObserverFontManager = function(resources)
{
this._resources = resources;
this._loadedFontFamily = {}; // Associate font resource names to the loaded font family
this._loadedFonts = {}; // Associate font resource names to the resources, for faster access
};
gdjs.FontManager = gdjs.FontFaceObserverFontManager; //Register the class to let the engine use it.
/**
* Return the font family associated to the specified font resource name.
* The font resource must have been loaded before. If that's not the case,
* a font family will be returned but without guarantee of it being loaded (to
* keep compatibility with GDevelop 5.0-beta56 and previous).
*
* @param {string} resourceName The name of the resource to get.
* @returns {string} The font family to be used for this font resource,
* or "Arial" if `resourceName` is empty.
*/
gdjs.FontFaceObserverFontManager.prototype.getFontFamily = function(resourceName) {
if (this._loadedFontFamily[resourceName]) {
return this._loadedFontFamily[resourceName];
}
return resourceName ?
gdjs.FontFaceObserverFontManager._getFontFamilyFromFilename(resourceName) :
'Arial';
}
/**
* Return the font file associated to the specified font resource name.
* The font resource must have been loaded before. If that's not the case,
* the resource name will be returned (to
* keep compatibility with GDevelop 5.0-beta56 and previous).
*
* Should only be useful for renderers running on a non HTML5/non browser environment.
*
* @param {string} resourceName The name of the resource to get.
* @returns {string} The file of the font resource.
*/
gdjs.FontFaceObserverFontManager.prototype.getFontFile = function(resourceName) {
if (this._loadedFonts[resourceName]) {
return this._loadedFonts[resourceName].file || '';
}
return resourceName;
}
/**
* Return the font family for a given filename.
* Should be kept in sync with the declaration of "@font-face" during exports.
*
* @private
* @param {string} filename The filename of the font
* @returns {string} The font family to be used for this font resource.
*/
gdjs.FontFaceObserverFontManager._getFontFamilyFromFilename = function(filename) {
return "gdjs_font_" + filename;
}
/**
* Load the specified resources, so that fonts are loaded and can then be
* used by using the font family returned by getFontFamily.
* @param onProgress Callback called each time a new file is loaded.
* @param onComplete Callback called when loading is done.
* @param resources The resources to be loaded. If not specified, will load the resources
* specified in the FontFaceObserverFontManager constructor.
*/
gdjs.FontFaceObserverFontManager.prototype.loadFonts = function(onProgress, onComplete, resources) {
resources = resources || this._resources;
//Construct the list of files to be loaded.
//For one loaded file, it can have one or more resources
//that use it.
var filesResources = {};
for(var i = 0, len = resources.length;i<len;++i) {
var res = resources[i];
if ( res.file && res.kind === "font" ) {
filesResources[res.file] = filesResources[res.file] ? filesResources[res.file].concat(res) : [res];
}
}
var totalCount = Object.keys(filesResources).length;
if (totalCount === 0)
return onComplete(totalCount); //Nothing to load.
var loadingCount = 0;
var that = this;
var onFontLoaded = function(fontFamily, resources) {
resources.forEach(function(resource) {
that._loadedFontFamily[resource.name] = fontFamily;
that._loadedFonts[resource.name] = resource;
});
loadingCount++;
onProgress(loadingCount, totalCount);
if (loadingCount === totalCount) onComplete(totalCount);
}
Object.keys(filesResources).forEach(function(file) {
var fontFamily = gdjs.FontFaceObserverFontManager._getFontFamilyFromFilename(file);
var resources = filesResources[file];
new FontFaceObserver(fontFamily).load().then(function() {
onFontLoaded(fontFamily, resources);
}, function() {
console.error("Error loading font resource \"" + resources[0].name + "\" (file: " + file + ")");
onFontLoaded(fontFamily, resources);
});
});
}

View File

@@ -0,0 +1,16 @@
(function() {
var module; //Define an undefined module variable to avoid fontfaceobserver thinking it's used in an environment using require.
/* Font Face Observer v2.0.13 - © Bram Stein. License: BSD-3-Clause */(function(){'use strict';var f,g=[];function l(a){g.push(a);1==g.length&&f()}function m(){for(;g.length;)g[0](),g.shift()}f=function(){setTimeout(m)};function n(a){this.a=p;this.b=void 0;this.f=[];var b=this;try{a(function(a){q(b,a)},function(a){r(b,a)})}catch(c){r(b,c)}}var p=2;function t(a){return new n(function(b,c){c(a)})}function u(a){return new n(function(b){b(a)})}function q(a,b){if(a.a==p){if(b==a)throw new TypeError;var c=!1;try{var d=b&&b.then;if(null!=b&&"object"==typeof b&&"function"==typeof d){d.call(b,function(b){c||q(a,b);c=!0},function(b){c||r(a,b);c=!0});return}}catch(e){c||r(a,e);return}a.a=0;a.b=b;v(a)}}
function r(a,b){if(a.a==p){if(b==a)throw new TypeError;a.a=1;a.b=b;v(a)}}function v(a){l(function(){if(a.a!=p)for(;a.f.length;){var b=a.f.shift(),c=b[0],d=b[1],e=b[2],b=b[3];try{0==a.a?"function"==typeof c?e(c.call(void 0,a.b)):e(a.b):1==a.a&&("function"==typeof d?e(d.call(void 0,a.b)):b(a.b))}catch(h){b(h)}}})}n.prototype.g=function(a){return this.c(void 0,a)};n.prototype.c=function(a,b){var c=this;return new n(function(d,e){c.f.push([a,b,d,e]);v(c)})};
function w(a){return new n(function(b,c){function d(c){return function(d){h[c]=d;e+=1;e==a.length&&b(h)}}var e=0,h=[];0==a.length&&b(h);for(var k=0;k<a.length;k+=1)u(a[k]).c(d(k),c)})}function x(a){return new n(function(b,c){for(var d=0;d<a.length;d+=1)u(a[d]).c(b,c)})};window.Promise||(window.Promise=n,window.Promise.resolve=u,window.Promise.reject=t,window.Promise.race=x,window.Promise.all=w,window.Promise.prototype.then=n.prototype.c,window.Promise.prototype["catch"]=n.prototype.g);}());
(function(){function l(a,b){document.addEventListener?a.addEventListener("scroll",b,!1):a.attachEvent("scroll",b)}function m(a){document.body?a():document.addEventListener?document.addEventListener("DOMContentLoaded",function c(){document.removeEventListener("DOMContentLoaded",c);a()}):document.attachEvent("onreadystatechange",function k(){if("interactive"==document.readyState||"complete"==document.readyState)document.detachEvent("onreadystatechange",k),a()})};function r(a){this.a=document.createElement("div");this.a.setAttribute("aria-hidden","true");this.a.appendChild(document.createTextNode(a));this.b=document.createElement("span");this.c=document.createElement("span");this.h=document.createElement("span");this.f=document.createElement("span");this.g=-1;this.b.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";this.c.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";
this.f.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";this.h.style.cssText="display:inline-block;width:200%;height:200%;font-size:16px;max-width:none;";this.b.appendChild(this.h);this.c.appendChild(this.f);this.a.appendChild(this.b);this.a.appendChild(this.c)}
function t(a,b){a.a.style.cssText="max-width:none;min-width:20px;min-height:20px;display:inline-block;overflow:hidden;position:absolute;width:auto;margin:0;padding:0;top:-999px;white-space:nowrap;font-synthesis:none;font:"+b+";"}function y(a){var b=a.a.offsetWidth,c=b+100;a.f.style.width=c+"px";a.c.scrollLeft=c;a.b.scrollLeft=a.b.scrollWidth+100;return a.g!==b?(a.g=b,!0):!1}function z(a,b){function c(){var a=k;y(a)&&a.a.parentNode&&b(a.g)}var k=a;l(a.b,c);l(a.c,c);y(a)};function A(a,b){var c=b||{};this.family=a;this.style=c.style||"normal";this.weight=c.weight||"normal";this.stretch=c.stretch||"normal"}var B=null,C=null,E=null,F=null;function G(){if(null===C)if(J()&&/Apple/.test(window.navigator.vendor)){var a=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))(?:\.([0-9]+))/.exec(window.navigator.userAgent);C=!!a&&603>parseInt(a[1],10)}else C=!1;return C}function J(){null===F&&(F=!!document.fonts);return F}
function K(){if(null===E){var a=document.createElement("div");try{a.style.font="condensed 100px sans-serif"}catch(b){}E=""!==a.style.font}return E}function L(a,b){return[a.style,a.weight,K()?a.stretch:"","100px",b].join(" ")}
A.prototype.load=function(a,b){var c=this,k=a||"BESbswy",q=0,D=b||3E3,H=(new Date).getTime();return new Promise(function(a,b){if(J()&&!G()){var M=new Promise(function(a,b){function e(){(new Date).getTime()-H>=D?b():document.fonts.load(L(c,'"'+c.family+'"'),k).then(function(c){1<=c.length?a():setTimeout(e,25)},function(){b()})}e()}),N=new Promise(function(a,c){q=setTimeout(c,D)});Promise.race([N,M]).then(function(){clearTimeout(q);a(c)},function(){b(c)})}else m(function(){function u(){var b;if(b=-1!=
f&&-1!=g||-1!=f&&-1!=h||-1!=g&&-1!=h)(b=f!=g&&f!=h&&g!=h)||(null===B&&(b=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))/.exec(window.navigator.userAgent),B=!!b&&(536>parseInt(b[1],10)||536===parseInt(b[1],10)&&11>=parseInt(b[2],10))),b=B&&(f==v&&g==v&&h==v||f==w&&g==w&&h==w||f==x&&g==x&&h==x)),b=!b;b&&(d.parentNode&&d.parentNode.removeChild(d),clearTimeout(q),a(c))}function I(){if((new Date).getTime()-H>=D)d.parentNode&&d.parentNode.removeChild(d),b(c);else{var a=document.hidden;if(!0===a||void 0===a)f=e.a.offsetWidth,
g=n.a.offsetWidth,h=p.a.offsetWidth,u();q=setTimeout(I,50)}}var e=new r(k),n=new r(k),p=new r(k),f=-1,g=-1,h=-1,v=-1,w=-1,x=-1,d=document.createElement("div");d.dir="ltr";t(e,L(c,"sans-serif"));t(n,L(c,"serif"));t(p,L(c,"monospace"));d.appendChild(e.a);d.appendChild(n.a);d.appendChild(p.a);document.body.appendChild(d);v=e.a.offsetWidth;w=n.a.offsetWidth;x=p.a.offsetWidth;I();z(e,function(a){f=a;u()});t(e,L(c,'"'+c.family+'",sans-serif'));z(n,function(a){g=a;u()});t(n,L(c,'"'+c.family+'",serif'));
z(p,function(a){h=a;u()});t(p,L(c,'"'+c.family+'",monospace'))})})};"object"===typeof module?module.exports=A:(window.FontFaceObserver=A,window.FontFaceObserver.prototype.load=A.prototype.load);}());
})()

View File

@@ -308,6 +308,10 @@ gdjs.HowlerSoundManager.prototype.clearAll = function() {
gdjs.HowlerSoundManager.prototype.preloadAudio = function(onProgress, onComplete, resources) {
resources = resources || this._resources;
//Construct the list of files to be loaded.
//For one loaded file, it can have one or more resources
//that use it.
var files = [];
for(var i = 0, len = resources.length;i<len;++i) {
var res = resources[i];
@@ -320,13 +324,13 @@ gdjs.HowlerSoundManager.prototype.preloadAudio = function(onProgress, onComplete
}
}
if (files.length === 0) return onComplete();
if (files.length === 0) return onComplete(files.length);
var loaded = 0;
function onLoad(audioFile) {
loaded++;
if (loaded === files.length) {
return onComplete();
return onComplete(files.length);
}
onProgress(loaded, files.length);

View File

@@ -96,7 +96,7 @@ gdjs.PixiImageManager.prototype.loadTextures = function(onProgress, onComplete,
var totalCount = Object.keys(files).length;
if (totalCount === 0)
return onComplete(); //Nothing to load.
return onComplete(totalCount); //Nothing to load.
var loadingCount = 0;
var loader = PIXI.loader;
@@ -117,7 +117,7 @@ gdjs.PixiImageManager.prototype.loadTextures = function(onProgress, onComplete,
}
}
onComplete();
onComplete(totalCount);
});
loader.on('progress', function() {
loadingCount++;

View File

@@ -23,6 +23,9 @@ gdjs.RuntimeGame = function(data, spec) {
this._soundManager = new gdjs.SoundManager(
data.resources ? data.resources.resources : undefined
);
this._fontManager = new gdjs.FontManager(
data.resources ? data.resources.resources : undefined
);
this._minFPS = data ? parseInt(data.properties.minFPS, 10) : 15;
this._defaultWidth = data.properties.windowWidth; //Default size for scenes cameras
@@ -81,6 +84,14 @@ gdjs.RuntimeGame.prototype.getImageManager = function() {
return this._imageManager;
};
/**
* Get the gdjs.FontManager of the RuntimeGame.
* @return {gdjs.FontManager} The font manager.
*/
gdjs.RuntimeGame.prototype.getFontManager = function() {
return this._fontManager;
};
/**
* Get the input manager of the game, storing mouse, keyboard
* and touches states.
@@ -238,12 +249,15 @@ gdjs.RuntimeGame.prototype.getMinimalFramerate = function() {
*/
gdjs.RuntimeGame.prototype.pause = function(enable) {
this._paused = enable;
}
};
/**
* Load all assets, displaying progress in renderer.
*/
gdjs.RuntimeGame.prototype.loadAllAssets = function(callback, progressCallback) {
gdjs.RuntimeGame.prototype.loadAllAssets = function(
callback,
progressCallback
) {
var loadingScreen = new gdjs.LoadingScreenRenderer(
this.getRenderer(),
this._data.properties.loadingScreen
@@ -253,20 +267,33 @@ gdjs.RuntimeGame.prototype.loadAllAssets = function(callback, progressCallback)
var that = this;
this._imageManager.loadTextures(
function(count, total) {
var percent = Math.floor(count / allAssetsTotal * 100);
var percent = Math.floor((count / allAssetsTotal) * 100);
loadingScreen.render(percent);
if (progressCallback) progressCallback(percent);
},
function() {
function(texturesTotalCount) {
that._soundManager.preloadAudio(
function(count, total) {
loadingScreen.render(
Math.floor((allAssetsTotal - total + count) / allAssetsTotal * 100)
Math.floor(((texturesTotalCount + count) / allAssetsTotal) * 100)
);
},
function() {
loadingScreen.unload();
callback();
function(audioTotalCount) {
that._fontManager.loadFonts(
function(count, total) {
loadingScreen.render(
Math.floor(
((texturesTotalCount + audioTotalCount + count) /
allAssetsTotal) *
100
)
);
},
function() {
loadingScreen.unload();
callback();
}
);
}
);
}
@@ -329,10 +356,15 @@ gdjs.RuntimeGame.prototype.startGameLoop = function() {
* @param {?string} mode `adaptWidth` to change the width, `adaptHeight` to change the height. If not defined, will use the game "sizeOnStartupMode" .
*/
gdjs.RuntimeGame.prototype.adaptRendererSizeToFillScreen = function(mode) {
if (!gdjs.RuntimeGameRenderer || !gdjs.RuntimeGameRenderer.getScreenWidth || !gdjs.RuntimeGameRenderer.getScreenHeight)
if (
!gdjs.RuntimeGameRenderer ||
!gdjs.RuntimeGameRenderer.getScreenWidth ||
!gdjs.RuntimeGameRenderer.getScreenHeight
)
return;
newMode = mode !== undefined ? mode : (this._data.properties.sizeOnStartupMode || '');
newMode =
mode !== undefined ? mode : this._data.properties.sizeOnStartupMode || '';
var screenWidth = gdjs.RuntimeGameRenderer.getScreenWidth();
var screenHeight = gdjs.RuntimeGameRenderer.getScreenHeight();
@@ -341,10 +373,10 @@ gdjs.RuntimeGame.prototype.adaptRendererSizeToFillScreen = function(mode) {
var renderer = this.getRenderer();
var width = renderer.getCurrentWidth();
var height = renderer.getCurrentHeight();
if (newMode === "adaptWidth") {
width = height * screenWidth / screenHeight;
} else if (newMode === "adaptHeight") {
height = width * screenHeight / screenWidth;
if (newMode === 'adaptWidth') {
width = (height * screenWidth) / screenHeight;
} else if (newMode === 'adaptHeight') {
height = (width * screenHeight) / screenWidth;
}
// Update the renderer size, and also the default size of the game so that
@@ -352,19 +384,21 @@ gdjs.RuntimeGame.prototype.adaptRendererSizeToFillScreen = function(mode) {
renderer.setSize(width, height);
this.setDefaultWidth(width);
this.setDefaultHeight(height);
}
};
/**
* Start a profiler for the currently running scene.
* @param {Function} onProfilerStopped Function to be called when the profiler is stopped. Will be passed the profiler as argument.
*/
gdjs.RuntimeGame.prototype.startCurrentSceneProfiler = function(onProfilerStopped) {
gdjs.RuntimeGame.prototype.startCurrentSceneProfiler = function(
onProfilerStopped
) {
var currentScene = this._sceneStack.getCurrentScene();
if (!currentScene) return false;
currentScene.startProfiler(onProfilerStopped);
return true;
}
};
/**
* Stop the profiler for the currently running scene.
@@ -374,4 +408,4 @@ gdjs.RuntimeGame.prototype.stopCurrentSceneProfiler = function() {
if (!currentScene) return null;
currentScene.stopProfiler();
}
};

View File

@@ -15,6 +15,8 @@ module.exports = function(config) {
'../Runtime/pixi-renderers/*.js',
'../Runtime/howler-sound-manager/howler.min.js',
'../Runtime/howler-sound-manager/howler-sound-manager.js',
'../Runtime/fontfaceobserver-font-manager/fontfaceobserver.js',
'../Runtime/fontfaceobserver-font-manager/fontfaceobserver-font-manager.js',
'../Runtime/timemanager.js',
'../Runtime/runtimeobject.js',
'../Runtime/runtimescene.js',

View File

@@ -35,6 +35,7 @@ export default class PanelSpriteEditor extends React.Component<
panelSpriteObject.setTexture(resourceName);
this.forceUpdate();
}}
floatingLabelText="Select an image"
/>
</Line>
<Line>

View File

@@ -116,6 +116,7 @@ export default class ParticleEmitterEditor extends React.Component<
particleEmitterObject.setParticleTexture(resourceName);
this.forceUpdate();
}}
floatingLabelText="Select an image"
/>
</Line>
<Line>

View File

@@ -5,6 +5,8 @@ import TextField from 'material-ui/TextField';
import { Line, Column } from '../../UI/Grid';
import ColorPicker from '../../UI/ColorField/ColorPicker';
import MiniToolbar, { MiniToolbarText } from '../../UI/MiniToolbar';
import ResourceSelector from '../../ResourcesList/ResourceSelector';
import ResourcesLoader from '../../ResourcesLoader';
import { type EditorProps } from './EditorProps.flow';
const gd = global.gd;
@@ -26,7 +28,13 @@ const styles = {
export default class TextEditor extends React.Component<EditorProps, void> {
render() {
const { object } = this.props;
const {
object,
project,
resourceSources,
onChooseResource,
resourceExternalEditors,
} = this.props;
const textObject = gd.asTextObject(object);
return (
@@ -75,14 +83,20 @@ export default class TextEditor extends React.Component<EditorProps, void> {
}}
style={styles.checkbox}
/>
<TextField
hintText="Font"
<ResourceSelector
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourcesLoader={ResourcesLoader}
resourceKind="font"
fullWidth
value={textObject.getFontFilename()}
onChange={(e, value) => {
textObject.setFontFilename(value);
initialResourceName={textObject.getFontName()}
onChange={resourceName => {
textObject.setFontName(resourceName);
this.forceUpdate();
}}
hintText="Choose a font"
/>
</MiniToolbar>
<Line noMargin>

View File

@@ -34,6 +34,7 @@ export default class TiledSpriteEditor extends React.Component<
tiledSpriteObject.setTexture(resourceName);
this.forceUpdate();
}}
floatingLabelText="Select an image"
/>
</Line>
<Line>

View File

@@ -119,37 +119,58 @@ export default class PixiResourcesLoader {
*/
static loadFontFamily(
project: gdProject,
fontFilename: string
resourceName: string
): Promise<string> {
// Avoid reloading a font if it's already cached
if (loadedFontFamilies[fontFilename]) {
return Promise.resolve(loadedFontFamilies[fontFilename]);
if (loadedFontFamilies[resourceName]) {
return Promise.resolve(loadedFontFamilies[resourceName]);
}
const fontFamily = slugs(resourceName);
let fullFilename = null;
if (project.getResourcesManager().hasResource(resourceName)) {
const resource = project.getResourcesManager().getResource(resourceName);
if (resource.getKind() === 'font') {
fullFilename = ResourcesLoader.getResourceFullUrl(
project,
resourceName
);
}
} else {
// Compatibility with GD <= 5.0-beta56
// Assume resourceName is just the filename to the font
fullFilename = ResourcesLoader.getFullUrl(project, resourceName);
// end of compatibility code
}
if (!fullFilename) {
// If no resource is found/resource is not a font, default to Arial,
// as done by the game engine too.
return Promise.resolve('Arial');
}
const fontFamily = slugs(fontFilename);
const fullFilename = ResourcesLoader.getFullUrl(project, fontFilename);
return loadFontFace(
fontFamily,
`url("${fullFilename}")`,
{}
).then(loadedFace => {
loadedFontFamilies[fontFilename] = fontFamily;
loadedFontFamilies[resourceName] = fontFamily;
return fontFamily;
});
}
/**
* Get the font family name for the given font from its url/filename.
* Get the font family name for the given font resource.
* The font won't be loaded.
* @returns The font-family to be used to render a text with the font.
*/
static getFontFamily(project: gdProject, fontFilename: string) {
if (loadedFontFamilies[fontFilename]) {
return loadedFontFamilies[fontFilename];
static getFontFamily(project: gdProject, resourceName: string) {
if (loadedFontFamilies[resourceName]) {
return loadedFontFamilies[resourceName];
}
const fontFamily = slugs(fontFilename);
const fontFamily = slugs(resourceName);
return fontFamily;
}

View File

@@ -36,7 +36,7 @@ function RenderedTextInstance(
this._styleFontDirty = true;
this._fontFamily = this._pixiResourcesLoader.getFontFamily(
this._project,
textObject.getFontFilename()
textObject.getFontName()
);
this.update();
}
@@ -69,11 +69,11 @@ RenderedTextInstance.prototype.update = function() {
this._styleFontDirty = true;
}
if (this._fontFilename !== textObject.getFontFilename()) {
if (this._fontName !== textObject.getFontName()) {
//Avoid calling loadFontFamily if the font didn't changed.
this._fontFilename = textObject.getFontFilename();
this._fontName = textObject.getFontName();
this._pixiResourcesLoader
.loadFontFamily(this._project, textObject.getFontFilename())
.loadFontFamily(this._project, textObject.getFontName())
.then(fontFamily => {
// Once the font is loaded, we can use the given fontFamily.
this._fontFamily = fontFamily;

View File

@@ -86,6 +86,14 @@ const publicAudioUrls = [
'https://df5lqcdudryde.cloudfront.net/examples/space-shooter/sfx_lose.ogg',
];
const publicFontUrls = [
// Platformer fonts (see platformer.json in fixtures)
'https://df5lqcdudryde.cloudfront.net/examples/platformer/Bimbo_JVE.ttf',
// Space shooter fonts (see space-shooter.json in fixtures)
'https://df5lqcdudryde.cloudfront.net/examples/space-shooter/kenvector_future.ttf',
];
const nameFromUrl = (url: string): string => {
const urlParts = url.split('/');
return urlParts[urlParts.length - 1]
@@ -225,7 +233,7 @@ export default [
name: 'publicImageUrlChooser',
displayName: 'Choose an image from library',
kind: 'image',
component: class AudioResourceChooser extends React.Component {
component: class ImageResourceChooser extends React.Component {
chooseResources = () => {
if (this._chooser) return this._chooser.chooseResources();
};
@@ -243,4 +251,26 @@ export default [
}
},
},
{
name: 'publicFontUrlChooser',
displayName: 'Choose a font from library',
kind: 'font',
component: class FontResourceChooser extends React.Component {
chooseResources = () => {
if (this._chooser) return this._chooser.chooseResources();
};
render() {
return (
<GenericResourcesChooser
{...this.props}
urls={publicFontUrls}
urlsAreImages={false}
createNewResource={() => new gd.FontResource()}
title="Choose a font from the library"
ref={chooser => (this._chooser = chooser)}
/>
);
}
},
},
];

View File

@@ -95,6 +95,53 @@ export default [
});
};
render() {
return null;
}
},
},
{
name: 'localFontFileOpener',
displayName: 'Choose a new font file',
kind: 'font',
component: class LocalFontFileOpener extends Component {
chooseResources = (
project,
multiSelections = true
): Promise<Array<any>> => {
return new Promise((resolve, reject) => {
if (!dialog) return reject('Not supported');
const properties = ['openFile'];
if (multiSelections) properties.push('multiSelections');
const projectPath = path.dirname(project.getProjectFile());
const browserWindow = electron.remote.getCurrentWindow();
dialog.showOpenDialog(
browserWindow,
{
title: 'Choose a font file',
properties,
filters: [{ name: 'Font files', extensions: ['ttf'] }],
defaultPath: projectPath,
},
paths => {
if (!paths) return resolve([]);
const resources = paths.map(resourcePath => {
const fontResource = new gd.FontResource();
const projectPath = path.dirname(project.getProjectFile());
fontResource.setFile(path.relative(projectPath, resourcePath));
fontResource.setName(path.relative(projectPath, resourcePath));
return fontResource;
});
return resolve(resources);
}
);
});
};
render() {
return null;
}

View File

@@ -27,6 +27,7 @@ type Props = {|
initialResourceName: string,
onChange: string => void,
floatingLabelText?: string,
hintText?: string,
|};
type State = {|
@@ -236,7 +237,8 @@ export default class ResourceSelector extends React.Component<Props, State> {
<div style={styles.container}>
<AutoComplete
{...defaultAutocompleteProps}
floatingLabelText={this.props.floatingLabelText || 'Select an image'}
floatingLabelText={this.props.floatingLabelText}
hintText={this.props.hintText}
openOnFocus
dataSource={this.autoCompleteData || []}
onUpdateInput={this._onUpdate}

View File

@@ -19,6 +19,7 @@ type Props = {|
resourceName: string,
onChange: (string) => void,
floatingLabelText?: string,
hintText?: string,
|};
const ResourceSelectorWithThumbnail = ({
@@ -30,6 +31,7 @@ const ResourceSelectorWithThumbnail = ({
resourceName,
onChange,
floatingLabelText,
hintText,
}: Props) => {
return (
<div style={{ flex: 1, display: 'flex', alignItems: 'flex-end' }}>
@@ -45,6 +47,7 @@ const ResourceSelectorWithThumbnail = ({
initialResourceName={resourceName}
onChange={onChange}
floatingLabelText={floatingLabelText}
hintText={hintText}
/>
</div>
<ResourceThumbnail

View File

@@ -1,7 +1,7 @@
// @flow
import * as React from 'react';
export type ResourceKind = 'image' | 'audio';
export type ResourceKind = 'image' | 'audio' | 'font';
export type ResourceSource = {
name: string,

View File

@@ -109,12 +109,12 @@ export default class ResourcesList extends React.Component<Props, State> {
_removeAllUnusedImages = () => {
const { project } = this.props;
gd.ProjectResourcesAdder
.getAllUselessImages(project)
.getAllUseless(project, 'image')
.toJSArray()
.forEach(imageName => {
console.info(`Removing unused image resource: ${imageName}`);
});
gd.ProjectResourcesAdder.removeAllUselessImages(project);
gd.ProjectResourcesAdder.removeAllUseless(project, 'image');
this.forceUpdate();
};

View File

@@ -804,10 +804,10 @@ export default class SceneEditor extends React.Component<Props, State> {
reloadResourcesFor = (object: gdObject) => {
const { project } = this.props;
const imagesUsedInventorizer = new gd.ImagesUsedInventorizer();
object.exposeResources(imagesUsedInventorizer);
const objectResourceNames = imagesUsedInventorizer
.getAllUsedImages()
const resourcesInUse = new gd.ResourcesInUseHelper();
object.exposeResources(resourcesInUse);
const objectResourceNames = resourcesInUse
.getAllImages()
.toNewVectorString()
.toJSArray();