mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
51 Commits
fix/ai-mob
...
experiment
Author | SHA1 | Date | |
---|---|---|---|
![]() |
14a9ede540 | ||
![]() |
6d9fb72de5 | ||
![]() |
a6f359c788 | ||
![]() |
9d9ef7a305 | ||
![]() |
e34b3bab98 | ||
![]() |
fc054de5b3 | ||
![]() |
6533586fd5 | ||
![]() |
5c78760bd2 | ||
![]() |
57447a85a0 | ||
![]() |
43262b80e9 | ||
![]() |
73571dc250 | ||
![]() |
faa506cee7 | ||
![]() |
058de8d89e | ||
![]() |
b45acc7232 | ||
![]() |
1fdb1b4639 | ||
![]() |
d06490d224 | ||
![]() |
108aa83b5a | ||
![]() |
36994146a5 | ||
![]() |
07c58970d9 | ||
![]() |
b65c5ecbc0 | ||
![]() |
1f73056ab6 | ||
![]() |
a3a87b3b09 | ||
![]() |
2680536448 | ||
![]() |
cc080cd39c | ||
![]() |
a90fe9ae82 | ||
![]() |
f742b393e1 | ||
![]() |
4a7ab83f0e | ||
![]() |
0cfc8eccf8 | ||
![]() |
5b4dfd5c19 | ||
![]() |
5158d68157 | ||
![]() |
d69657f533 | ||
![]() |
6e4e5f6499 | ||
![]() |
d214c504b5 | ||
![]() |
639626d361 | ||
![]() |
3374823695 | ||
![]() |
3c8ee887b0 | ||
![]() |
2551778b21 | ||
![]() |
0e586038a3 | ||
![]() |
ac1adaba6c | ||
![]() |
6863360d3f | ||
![]() |
d56785eb5f | ||
![]() |
0b5feabaab | ||
![]() |
be74dc35e9 | ||
![]() |
74e8b4e544 | ||
![]() |
f8be8f7e14 | ||
![]() |
b59f30cd41 | ||
![]() |
581fa8d828 | ||
![]() |
1b4e5cdd4c | ||
![]() |
bdc4f443f8 | ||
![]() |
a479c43096 | ||
![]() |
969cde7bc3 |
@@ -33,6 +33,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
|
||||
"most elements of a game."),
|
||||
"CppPlatform/Extensions/spriteicon.png")
|
||||
.SetCategoryFullName(_("General"))
|
||||
.SetOpenFullEditorLabel(_("Edit animations"))
|
||||
.AddDefaultBehavior("EffectCapability::EffectBehavior")
|
||||
.AddDefaultBehavior("ResizableCapability::ResizableBehavior")
|
||||
.AddDefaultBehavior("ScalableCapability::ScalableBehavior")
|
||||
|
@@ -47,19 +47,11 @@ void SpriteObject::DoSerializeTo(gd::SerializerElement& element) const {
|
||||
std::map<gd::String, gd::PropertyDescriptor> SpriteObject::GetProperties()
|
||||
const {
|
||||
std::map<gd::String, gd::PropertyDescriptor> properties;
|
||||
properties[_("Animate even if hidden or far from the screen")]
|
||||
.SetValue(updateIfNotVisible ? "true" : "false")
|
||||
.SetType("Boolean");
|
||||
properties["PLEASE_ALSO_SHOW_EDIT_BUTTON_THANKS"].SetValue("");
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
bool SpriteObject::UpdateProperty(const gd::String& name,
|
||||
const gd::String& value) {
|
||||
if (name == _("Animate even if hidden or far from the screen"))
|
||||
updateIfNotVisible = value == "1";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -307,6 +307,15 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
|
||||
return *this;
|
||||
}
|
||||
|
||||
BehaviorMetadata &SetOpenFullEditorLabel(const gd::String& label) {
|
||||
openFullEditorLabel = label;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const gd::String& GetOpenFullEditorLabel() const {
|
||||
return openFullEditorLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Return the associated gd::Behavior, handling behavior contents.
|
||||
*
|
||||
@@ -384,6 +393,7 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
|
||||
mutable std::vector<gd::String> requiredBehaviors;
|
||||
bool isPrivate = false;
|
||||
bool isHidden = false;
|
||||
gd::String openFullEditorLabel;
|
||||
QuickCustomization::Visibility quickCustomizationVisibility;
|
||||
|
||||
// TODO: Nitpicking: convert these to std::unique_ptr to clarify ownership.
|
||||
|
@@ -323,6 +323,15 @@ class GD_CORE_API ObjectMetadata : public InstructionOrExpressionContainerMetada
|
||||
*/
|
||||
bool IsRenderedIn3D() const { return isRenderedIn3D; }
|
||||
|
||||
ObjectMetadata &SetOpenFullEditorLabel(const gd::String& label) {
|
||||
openFullEditorLabel = label;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const gd::String& GetOpenFullEditorLabel() const {
|
||||
return openFullEditorLabel;
|
||||
}
|
||||
|
||||
std::map<gd::String, gd::InstructionMetadata> conditionsInfos;
|
||||
std::map<gd::String, gd::InstructionMetadata> actionsInfos;
|
||||
std::map<gd::String, gd::ExpressionMetadata> expressionsInfos;
|
||||
@@ -344,6 +353,7 @@ class GD_CORE_API ObjectMetadata : public InstructionOrExpressionContainerMetada
|
||||
std::set<gd::String> defaultBehaviorTypes;
|
||||
bool hidden = false;
|
||||
bool isRenderedIn3D = false;
|
||||
gd::String openFullEditorLabel;
|
||||
|
||||
std::shared_ptr<gd::ObjectConfiguration>
|
||||
blueprintObject; ///< The "blueprint" object to be copied when a new
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Project/EventsBasedObject.h"
|
||||
@@ -109,6 +110,27 @@ class CustomObjectConfiguration : public gd::ObjectConfiguration {
|
||||
static const gd::CustomObjectConfiguration::EdgeAnchor
|
||||
GetEdgeAnchorFromString(const gd::String &value);
|
||||
|
||||
/**
|
||||
* Check if a child object properties must be displayed as folded in the editor.
|
||||
* This is only useful when the object can override its children configuration (which
|
||||
* is something being deprecated).
|
||||
*/
|
||||
bool IsChildObjectFolded(const gd::String& childName) const {
|
||||
return unfoldedChildren.find(childName) == unfoldedChildren.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if a child object properties must be displayed as folded in the editor.
|
||||
* This is only useful when the object can override its children configuration (which
|
||||
* is something being deprecated).
|
||||
*/
|
||||
void SetChildObjectFolded(const gd::String& childName, bool folded) {
|
||||
if (!folded)
|
||||
unfoldedChildren.insert(childName);
|
||||
else
|
||||
unfoldedChildren.erase(childName);
|
||||
}
|
||||
|
||||
protected:
|
||||
void DoSerializeTo(SerializerElement& element) const override;
|
||||
void DoUnserializeFrom(Project& project, const SerializerElement& element) override;
|
||||
@@ -121,7 +143,8 @@ protected:
|
||||
const Project* project; ///< The project is used to get the
|
||||
///< EventBasedObject from the fullType.
|
||||
gd::SerializerElement objectContent;
|
||||
|
||||
std::unordered_set<gd::String> unfoldedChildren;
|
||||
|
||||
bool isMarkedAsOverridingEventsBasedObjectChildrenConfiguration;
|
||||
mutable std::map<gd::String, std::unique_ptr<gd::ObjectConfiguration>> childObjectConfigurations;
|
||||
|
||||
|
@@ -12,6 +12,7 @@ namespace gd {
|
||||
void Effect::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("name", GetName());
|
||||
element.SetAttribute("effectType", GetEffectType());
|
||||
if (IsFolded()) element.SetBoolAttribute("folded", true);
|
||||
|
||||
SerializerElement& doubleParametersElement =
|
||||
element.AddChild("doubleParameters");
|
||||
@@ -41,6 +42,7 @@ void Effect::UnserializeFrom(const SerializerElement& element) {
|
||||
"effectName"
|
||||
// end of compatibility code
|
||||
));
|
||||
SetFolded(element.GetBoolAttribute("folded", false));
|
||||
|
||||
doubleParameters.clear();
|
||||
const SerializerElement& doubleParametersElement =
|
||||
|
@@ -194,6 +194,9 @@ void Object::UnserializeFrom(gd::Project& project,
|
||||
behavior->UnserializeFrom(behaviorElement);
|
||||
}
|
||||
|
||||
bool isFolded = behaviorElement.GetBoolAttribute("isFolded", false);
|
||||
behavior->SetFolded(isFolded);
|
||||
|
||||
// Handle Quick Customization info.
|
||||
if (behaviorElement.HasChild(
|
||||
"propertiesQuickCustomizationVisibilities")) {
|
||||
@@ -239,8 +242,10 @@ void Object::SerializeTo(SerializerElement& element) const {
|
||||
behaviorElement.RemoveChild("type"); // The content can contain type or
|
||||
// name properties, remove them.
|
||||
behaviorElement.RemoveChild("name");
|
||||
behaviorElement.RemoveChild("isFolded");
|
||||
behaviorElement.SetAttribute("type", behavior.GetTypeName());
|
||||
behaviorElement.SetAttribute("name", behavior.GetName());
|
||||
if (behavior.IsFolded()) behaviorElement.SetAttribute("isFolded", true);
|
||||
|
||||
// Handle Quick Customization info.
|
||||
behaviorElement.RemoveChild("propertiesQuickCustomizationVisibilities");
|
||||
|
@@ -803,11 +803,8 @@ module.exports = {
|
||||
}
|
||||
|
||||
const Cube3DObject = new gd.ObjectJsImplementation();
|
||||
Cube3DObject.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
Cube3DObject.updateProperty = function (propertyName, newValue) {
|
||||
const objectContent = this.content;
|
||||
if (
|
||||
propertyName === 'width' ||
|
||||
propertyName === 'height' ||
|
||||
@@ -851,8 +848,9 @@ module.exports = {
|
||||
|
||||
return false;
|
||||
};
|
||||
Cube3DObject.getProperties = function (objectContent) {
|
||||
Cube3DObject.getProperties = function () {
|
||||
const objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
const objectContent = this.content;
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('enableTextureTransparency')
|
||||
@@ -878,7 +876,8 @@ module.exports = {
|
||||
'The top of each image can touch the **top face** (Y) or the **front face** (Z).'
|
||||
)
|
||||
)
|
||||
.setGroup(_('Face orientation'));
|
||||
.setGroup(_('Face orientation'))
|
||||
.setAdvanced(true);
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('width')
|
||||
@@ -909,7 +908,7 @@ module.exports = {
|
||||
.setValue(objectContent.frontFaceResourceName || '')
|
||||
.setType('resource')
|
||||
.addExtraInfo('image')
|
||||
.setLabel(_('Front face image'))
|
||||
.setLabel(_('Front face'))
|
||||
.setGroup(_('Textures'));
|
||||
|
||||
objectProperties
|
||||
@@ -917,7 +916,7 @@ module.exports = {
|
||||
.setValue(objectContent.backFaceResourceName || '')
|
||||
.setType('resource')
|
||||
.addExtraInfo('image')
|
||||
.setLabel(_('Back face image'))
|
||||
.setLabel(_('Back face'))
|
||||
.setGroup(_('Textures'));
|
||||
|
||||
objectProperties
|
||||
@@ -932,14 +931,15 @@ module.exports = {
|
||||
'The top of the image can touch the **top face** (Y) or the **bottom face** (X).'
|
||||
)
|
||||
)
|
||||
.setGroup(_('Textures'));
|
||||
.setGroup(_('Face orientation'))
|
||||
.setAdvanced(true);
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('leftFaceResourceName')
|
||||
.setValue(objectContent.leftFaceResourceName || '')
|
||||
.setType('resource')
|
||||
.addExtraInfo('image')
|
||||
.setLabel(_('Left face image'))
|
||||
.setLabel(_('Left face'))
|
||||
.setGroup(_('Textures'));
|
||||
|
||||
objectProperties
|
||||
@@ -947,7 +947,7 @@ module.exports = {
|
||||
.setValue(objectContent.rightFaceResourceName || '')
|
||||
.setType('resource')
|
||||
.addExtraInfo('image')
|
||||
.setLabel(_('Right face image'))
|
||||
.setLabel(_('Right face'))
|
||||
.setGroup(_('Textures'));
|
||||
|
||||
objectProperties
|
||||
@@ -955,7 +955,7 @@ module.exports = {
|
||||
.setValue(objectContent.topFaceResourceName || '')
|
||||
.setType('resource')
|
||||
.addExtraInfo('image')
|
||||
.setLabel(_('Top face image'))
|
||||
.setLabel(_('Top face'))
|
||||
.setGroup(_('Textures'));
|
||||
|
||||
objectProperties
|
||||
@@ -963,92 +963,98 @@ module.exports = {
|
||||
.setValue(objectContent.bottomFaceResourceName || '')
|
||||
.setType('resource')
|
||||
.addExtraInfo('image')
|
||||
.setLabel(_('Bottom face image'))
|
||||
.setLabel(_('Bottom face'))
|
||||
.setGroup(_('Textures'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('frontFaceResourceRepeat')
|
||||
.setValue(objectContent.frontFaceResourceRepeat ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Tile front face image'))
|
||||
.setLabel(_('Tile'))
|
||||
.setGroup(_('Textures'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('backFaceResourceRepeat')
|
||||
.setValue(objectContent.backFaceResourceRepeat ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Tile back face image'))
|
||||
.setLabel(_('Tile'))
|
||||
.setGroup(_('Textures'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('leftFaceResourceRepeat')
|
||||
.setValue(objectContent.leftFaceResourceRepeat ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Tile left face image'))
|
||||
.setLabel(_('Tile'))
|
||||
.setGroup(_('Textures'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('rightFaceResourceRepeat')
|
||||
.setValue(objectContent.rightFaceResourceRepeat ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Tile right face image'))
|
||||
.setLabel(_('Tile'))
|
||||
.setGroup(_('Textures'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('topFaceResourceRepeat')
|
||||
.setValue(objectContent.topFaceResourceRepeat ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Tile top face image'))
|
||||
.setLabel(_('Tile'))
|
||||
.setGroup(_('Textures'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('bottomFaceResourceRepeat')
|
||||
.setValue(objectContent.bottomFaceResourceRepeat ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Tile bottom face image'))
|
||||
.setLabel(_('Tile'))
|
||||
.setGroup(_('Textures'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('frontFaceVisible')
|
||||
.setValue(objectContent.frontFaceVisible ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Show front face'))
|
||||
.setGroup(_('Face visibility'));
|
||||
.setLabel(_('Front face'))
|
||||
.setGroup(_('Face visibility'))
|
||||
.setAdvanced(true);
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('backFaceVisible')
|
||||
.setValue(objectContent.backFaceVisible ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Show back face'))
|
||||
.setGroup(_('Face visibility'));
|
||||
.setLabel(_('Back face'))
|
||||
.setGroup(_('Face visibility'))
|
||||
.setAdvanced(true);
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('leftFaceVisible')
|
||||
.setValue(objectContent.leftFaceVisible ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Show left face'))
|
||||
.setGroup(_('Face visibility'));
|
||||
.setLabel(_('Left face'))
|
||||
.setGroup(_('Face visibility'))
|
||||
.setAdvanced(true);
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('rightFaceVisible')
|
||||
.setValue(objectContent.rightFaceVisible ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Show right face'))
|
||||
.setGroup(_('Face visibility'));
|
||||
.setLabel(_('Right face'))
|
||||
.setGroup(_('Face visibility'))
|
||||
.setAdvanced(true);
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('topFaceVisible')
|
||||
.setValue(objectContent.topFaceVisible ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Show top face'))
|
||||
.setGroup(_('Face visibility'));
|
||||
.setLabel(_('Top face'))
|
||||
.setGroup(_('Face visibility'))
|
||||
.setAdvanced(true);
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('bottomFaceVisible')
|
||||
.setValue(objectContent.bottomFaceVisible ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Show bottom face'))
|
||||
.setGroup(_('Face visibility'));
|
||||
.setLabel(_('Bottom face'))
|
||||
.setGroup(_('Face visibility'))
|
||||
.setAdvanced(true);
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('materialType')
|
||||
@@ -1060,38 +1066,35 @@ module.exports = {
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
Cube3DObject.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
width: 100,
|
||||
height: 100,
|
||||
depth: 100,
|
||||
enableTextureTransparency: false,
|
||||
facesOrientation: 'Y',
|
||||
frontFaceResourceName: '',
|
||||
backFaceResourceName: '',
|
||||
backFaceUpThroughWhichAxisRotation: 'X',
|
||||
leftFaceResourceName: '',
|
||||
rightFaceResourceName: '',
|
||||
topFaceResourceName: '',
|
||||
bottomFaceResourceName: '',
|
||||
frontFaceVisible: true,
|
||||
backFaceVisible: false,
|
||||
leftFaceVisible: true,
|
||||
rightFaceVisible: true,
|
||||
topFaceVisible: true,
|
||||
bottomFaceVisible: true,
|
||||
frontFaceResourceRepeat: false,
|
||||
backFaceResourceRepeat: false,
|
||||
leftFaceResourceRepeat: false,
|
||||
rightFaceResourceRepeat: false,
|
||||
topFaceResourceRepeat: false,
|
||||
bottomFaceResourceRepeat: false,
|
||||
materialType: 'Basic',
|
||||
})
|
||||
);
|
||||
Cube3DObject.content = {
|
||||
width: 100,
|
||||
height: 100,
|
||||
depth: 100,
|
||||
enableTextureTransparency: false,
|
||||
facesOrientation: 'Y',
|
||||
frontFaceResourceName: '',
|
||||
backFaceResourceName: '',
|
||||
backFaceUpThroughWhichAxisRotation: 'X',
|
||||
leftFaceResourceName: '',
|
||||
rightFaceResourceName: '',
|
||||
topFaceResourceName: '',
|
||||
bottomFaceResourceName: '',
|
||||
frontFaceVisible: true,
|
||||
backFaceVisible: false,
|
||||
leftFaceVisible: true,
|
||||
rightFaceVisible: true,
|
||||
topFaceVisible: true,
|
||||
bottomFaceVisible: true,
|
||||
frontFaceResourceRepeat: false,
|
||||
backFaceResourceRepeat: false,
|
||||
leftFaceResourceRepeat: false,
|
||||
rightFaceResourceRepeat: false,
|
||||
topFaceResourceRepeat: false,
|
||||
bottomFaceResourceRepeat: false,
|
||||
materialType: 'Basic',
|
||||
};
|
||||
|
||||
Cube3DObject.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
propertyName,
|
||||
newValue
|
||||
@@ -1099,7 +1102,7 @@ module.exports = {
|
||||
return false;
|
||||
};
|
||||
|
||||
Cube3DObject.getInitialInstanceProperties = function (content, instance) {
|
||||
Cube3DObject.getInitialInstanceProperties = function (instance) {
|
||||
const instanceProperties = new gd.MapStringPropertyDescriptor();
|
||||
return instanceProperties;
|
||||
};
|
||||
@@ -2060,7 +2063,10 @@ module.exports = {
|
||||
};
|
||||
|
||||
const getFirstVisibleFaceResourceName = (objectConfiguration) => {
|
||||
const properties = objectConfiguration.getProperties();
|
||||
const object = gd.castObject(
|
||||
objectConfiguration,
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
|
||||
const orderedFaces = [
|
||||
['frontFaceVisible', 'frontFaceResourceName'],
|
||||
@@ -2075,10 +2081,8 @@ module.exports = {
|
||||
faceVisibleProperty,
|
||||
faceResourceNameProperty,
|
||||
] of orderedFaces) {
|
||||
if (properties.get(faceVisibleProperty).getValue() === 'true') {
|
||||
const textureResource = properties
|
||||
.get(faceResourceNameProperty)
|
||||
.getValue();
|
||||
if (object.content[faceVisibleProperty]) {
|
||||
const textureResource = object.content[faceResourceNameProperty];
|
||||
if (textureResource) return textureResource;
|
||||
}
|
||||
}
|
||||
@@ -2124,10 +2128,14 @@ module.exports = {
|
||||
// Name of the resource that is rendered.
|
||||
// If no face is visible, this will be null.
|
||||
this._renderedResourceName = undefined;
|
||||
const properties = associatedObjectConfiguration.getProperties();
|
||||
this._defaultWidth = parseFloat(properties.get('width').getValue());
|
||||
this._defaultHeight = parseFloat(properties.get('height').getValue());
|
||||
this._defaultDepth = parseFloat(properties.get('depth').getValue());
|
||||
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
this._defaultWidth = object.content.width;
|
||||
this._defaultHeight = object.content.height;
|
||||
this._defaultDepth = object.content.depth;
|
||||
|
||||
this._pixiObject = new PIXI.Container();
|
||||
this._pixiFallbackObject = new PIXI.Graphics();
|
||||
@@ -2315,70 +2323,67 @@ module.exports = {
|
||||
pixiResourcesLoader
|
||||
);
|
||||
|
||||
const properties = associatedObjectConfiguration.getProperties();
|
||||
this._defaultWidth = parseFloat(properties.get('width').getValue());
|
||||
this._defaultHeight = parseFloat(properties.get('height').getValue());
|
||||
this._defaultDepth = parseFloat(properties.get('depth').getValue());
|
||||
this._defaultWidth = 1;
|
||||
this._defaultHeight = 1;
|
||||
this._defaultDepth = 1;
|
||||
|
||||
this._pixiObject = new PIXI.Graphics();
|
||||
this._pixiContainer.addChild(this._pixiObject);
|
||||
|
||||
this._faceResourceNames = [
|
||||
properties.get('frontFaceResourceName').getValue(),
|
||||
properties.get('backFaceResourceName').getValue(),
|
||||
properties.get('leftFaceResourceName').getValue(),
|
||||
properties.get('rightFaceResourceName').getValue(),
|
||||
properties.get('topFaceResourceName').getValue(),
|
||||
properties.get('bottomFaceResourceName').getValue(),
|
||||
];
|
||||
this._faceVisibilities = [
|
||||
properties.get('frontFaceVisible').getValue() === 'true',
|
||||
properties.get('backFaceVisible').getValue() === 'true',
|
||||
properties.get('leftFaceVisible').getValue() === 'true',
|
||||
properties.get('rightFaceVisible').getValue() === 'true',
|
||||
properties.get('topFaceVisible').getValue() === 'true',
|
||||
properties.get('bottomFaceVisible').getValue() === 'true',
|
||||
];
|
||||
this._shouldRepeatTextureOnFace = [
|
||||
properties.get('frontFaceResourceRepeat').getValue() === 'true',
|
||||
properties.get('backFaceResourceRepeat').getValue() === 'true',
|
||||
properties.get('leftFaceResourceRepeat').getValue() === 'true',
|
||||
properties.get('rightFaceResourceRepeat').getValue() === 'true',
|
||||
properties.get('topFaceResourceRepeat').getValue() === 'true',
|
||||
properties.get('bottomFaceResourceRepeat').getValue() === 'true',
|
||||
];
|
||||
this._facesOrientation = properties.get('facesOrientation').getValue();
|
||||
this._backFaceUpThroughWhichAxisRotation = properties
|
||||
.get('backFaceUpThroughWhichAxisRotation')
|
||||
.getValue();
|
||||
this._shouldUseTransparentTexture =
|
||||
properties.get('enableTextureTransparency').getValue() === 'true';
|
||||
this._faceResourceNames = ['', '', '', '', '', ''];
|
||||
this._faceVisibilities = [true, true, true, true, true, true];
|
||||
this._shouldRepeatTextureOnFace = [true, true, true, true, true, true];
|
||||
this._facesOrientation = 'Y';
|
||||
this._backFaceUpThroughWhichAxisRotation = 'X';
|
||||
this._shouldUseTransparentTexture = false;
|
||||
|
||||
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
||||
const materials = [
|
||||
this._getFaceMaterial(project, materialIndexToFaceIndex[0]),
|
||||
this._getFaceMaterial(project, materialIndexToFaceIndex[1]),
|
||||
this._getFaceMaterial(project, materialIndexToFaceIndex[2]),
|
||||
this._getFaceMaterial(project, materialIndexToFaceIndex[3]),
|
||||
this._getFaceMaterial(project, materialIndexToFaceIndex[4]),
|
||||
this._getFaceMaterial(project, materialIndexToFaceIndex[5]),
|
||||
getTransparentMaterial(),
|
||||
getTransparentMaterial(),
|
||||
getTransparentMaterial(),
|
||||
getTransparentMaterial(),
|
||||
getTransparentMaterial(),
|
||||
getTransparentMaterial(),
|
||||
];
|
||||
this._threeObject = new THREE.Mesh(geometry, materials);
|
||||
this._threeObject.rotation.order = 'ZYX';
|
||||
|
||||
this._threeGroup.add(this._threeObject);
|
||||
|
||||
this.updateThreeObject();
|
||||
}
|
||||
|
||||
_getFaceMaterial(project, faceIndex) {
|
||||
if (!this._faceVisibilities[faceIndex]) return getTransparentMaterial();
|
||||
async _updateThreeObjectMaterials() {
|
||||
const getFaceMaterial = async (project, faceIndex) => {
|
||||
if (!this._faceVisibilities[faceIndex])
|
||||
return getTransparentMaterial();
|
||||
|
||||
return this._pixiResourcesLoader.getThreeMaterial(
|
||||
project,
|
||||
this._faceResourceNames[faceIndex],
|
||||
{
|
||||
useTransparentTexture: this._shouldUseTransparentTexture,
|
||||
}
|
||||
);
|
||||
return await this._pixiResourcesLoader.getThreeMaterial(
|
||||
project,
|
||||
this._faceResourceNames[faceIndex],
|
||||
{
|
||||
useTransparentTexture: this._shouldUseTransparentTexture,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const materials = await Promise.all([
|
||||
getFaceMaterial(this._project, materialIndexToFaceIndex[0]),
|
||||
getFaceMaterial(this._project, materialIndexToFaceIndex[1]),
|
||||
getFaceMaterial(this._project, materialIndexToFaceIndex[2]),
|
||||
getFaceMaterial(this._project, materialIndexToFaceIndex[3]),
|
||||
getFaceMaterial(this._project, materialIndexToFaceIndex[4]),
|
||||
getFaceMaterial(this._project, materialIndexToFaceIndex[5]),
|
||||
]);
|
||||
|
||||
this._threeObject.material[0] = materials[0];
|
||||
this._threeObject.material[1] = materials[1];
|
||||
this._threeObject.material[2] = materials[2];
|
||||
this._threeObject.material[3] = materials[3];
|
||||
this._threeObject.material[4] = materials[4];
|
||||
this._threeObject.material[5] = materials[5];
|
||||
|
||||
this._updateTextureUvMapping();
|
||||
}
|
||||
|
||||
static _getResourceNameToDisplay(objectConfiguration) {
|
||||
@@ -2386,6 +2391,15 @@ module.exports = {
|
||||
}
|
||||
|
||||
updateThreeObject() {
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
|
||||
this._defaultWidth = object.content.width;
|
||||
this._defaultHeight = object.content.height;
|
||||
this._defaultDepth = object.content.depth;
|
||||
|
||||
const width = this.getWidth();
|
||||
const height = this.getHeight();
|
||||
const depth = this.getDepth();
|
||||
@@ -2402,18 +2416,107 @@ module.exports = {
|
||||
RenderedInstance.toRad(this._instance.getAngle())
|
||||
);
|
||||
|
||||
let materialsDirty = false;
|
||||
let uvMappingDirty = false;
|
||||
|
||||
const shouldUseTransparentTexture =
|
||||
object.content.enableTextureTransparency;
|
||||
if (this._shouldUseTransparentTexture !== shouldUseTransparentTexture) {
|
||||
this._shouldUseTransparentTexture = shouldUseTransparentTexture;
|
||||
materialsDirty = true;
|
||||
}
|
||||
|
||||
const faceResourceNames = [
|
||||
object.content.frontFaceResourceName,
|
||||
object.content.backFaceResourceName,
|
||||
object.content.leftFaceResourceName,
|
||||
object.content.rightFaceResourceName,
|
||||
object.content.topFaceResourceName,
|
||||
object.content.bottomFaceResourceName,
|
||||
];
|
||||
if (
|
||||
this._faceResourceNames[0] !== faceResourceNames[0] ||
|
||||
this._faceResourceNames[1] !== faceResourceNames[1] ||
|
||||
this._faceResourceNames[2] !== faceResourceNames[2] ||
|
||||
this._faceResourceNames[3] !== faceResourceNames[3] ||
|
||||
this._faceResourceNames[4] !== faceResourceNames[4] ||
|
||||
this._faceResourceNames[5] !== faceResourceNames[5]
|
||||
) {
|
||||
this._faceResourceNames = faceResourceNames;
|
||||
materialsDirty = true;
|
||||
}
|
||||
|
||||
const faceVisibilities = [
|
||||
object.content.frontFaceVisible,
|
||||
object.content.backFaceVisible,
|
||||
object.content.leftFaceVisible,
|
||||
object.content.rightFaceVisible,
|
||||
object.content.topFaceVisible,
|
||||
object.content.bottomFaceVisible,
|
||||
];
|
||||
if (
|
||||
this._faceVisibilities[0] !== faceVisibilities[0] ||
|
||||
this._faceVisibilities[1] !== faceVisibilities[1] ||
|
||||
this._faceVisibilities[2] !== faceVisibilities[2] ||
|
||||
this._faceVisibilities[3] !== faceVisibilities[3] ||
|
||||
this._faceVisibilities[4] !== faceVisibilities[4] ||
|
||||
this._faceVisibilities[5] !== faceVisibilities[5]
|
||||
) {
|
||||
this._faceVisibilities = faceVisibilities;
|
||||
materialsDirty = true;
|
||||
uvMappingDirty = true;
|
||||
}
|
||||
|
||||
const shouldRepeatTextureOnFace = [
|
||||
object.content.frontFaceResourceRepeat,
|
||||
object.content.backFaceResourceRepeat,
|
||||
object.content.leftFaceResourceRepeat,
|
||||
object.content.rightFaceResourceRepeat,
|
||||
object.content.topFaceResourceRepeat,
|
||||
object.content.bottomFaceResourceRepeat,
|
||||
];
|
||||
if (
|
||||
this._shouldRepeatTextureOnFace[0] !== shouldRepeatTextureOnFace[0] ||
|
||||
this._shouldRepeatTextureOnFace[1] !== shouldRepeatTextureOnFace[1] ||
|
||||
this._shouldRepeatTextureOnFace[2] !== shouldRepeatTextureOnFace[2] ||
|
||||
this._shouldRepeatTextureOnFace[3] !== shouldRepeatTextureOnFace[3] ||
|
||||
this._shouldRepeatTextureOnFace[4] !== shouldRepeatTextureOnFace[4] ||
|
||||
this._shouldRepeatTextureOnFace[5] !== shouldRepeatTextureOnFace[5]
|
||||
) {
|
||||
this._shouldRepeatTextureOnFace = shouldRepeatTextureOnFace;
|
||||
uvMappingDirty = true;
|
||||
}
|
||||
|
||||
const backFaceUpThroughWhichAxisRotation =
|
||||
object.content.backFaceUpThroughWhichAxisRotation;
|
||||
if (
|
||||
backFaceUpThroughWhichAxisRotation !==
|
||||
this._backFaceUpThroughWhichAxisRotation
|
||||
) {
|
||||
this._backFaceUpThroughWhichAxisRotation = backFaceUpThroughWhichAxisRotation;
|
||||
uvMappingDirty = true;
|
||||
}
|
||||
|
||||
const facesOrientation = object.content.facesOrientation;
|
||||
if (facesOrientation !== this._facesOrientation) {
|
||||
this._facesOrientation = facesOrientation;
|
||||
uvMappingDirty = true;
|
||||
}
|
||||
|
||||
const scaleX = width * (this._instance.isFlippedX() ? -1 : 1);
|
||||
const scaleY = height * (this._instance.isFlippedY() ? -1 : 1);
|
||||
const scaleZ = depth * (this._instance.isFlippedZ() ? -1 : 1);
|
||||
|
||||
if (
|
||||
scaleX !== this._threeObject.scale.width ||
|
||||
scaleY !== this._threeObject.scale.height ||
|
||||
scaleZ !== this._threeObject.scale.depth
|
||||
scaleX !== this._threeObject.scale.x ||
|
||||
scaleY !== this._threeObject.scale.y ||
|
||||
scaleZ !== this._threeObject.scale.z
|
||||
) {
|
||||
this._threeObject.scale.set(scaleX, scaleY, scaleZ);
|
||||
this.updateTextureUvMapping();
|
||||
uvMappingDirty = true;
|
||||
}
|
||||
|
||||
if (materialsDirty) this._updateThreeObjectMaterials();
|
||||
if (uvMappingDirty) this._updateTextureUvMapping();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2422,7 +2525,7 @@ module.exports = {
|
||||
* The mesh must be configured with a list of materials in order
|
||||
* for the method to work.
|
||||
*/
|
||||
updateTextureUvMapping() {
|
||||
_updateTextureUvMapping() {
|
||||
// @ts-ignore - position is stored as a Float32BufferAttribute
|
||||
/** @type {THREE.BufferAttribute} */
|
||||
const pos = this._threeObject.geometry.getAttribute('position');
|
||||
@@ -2696,25 +2799,23 @@ module.exports = {
|
||||
pixiContainer,
|
||||
pixiResourcesLoader
|
||||
);
|
||||
const properties = associatedObjectConfiguration.getProperties();
|
||||
this._defaultWidth = parseFloat(properties.get('width').getValue());
|
||||
this._defaultHeight = parseFloat(properties.get('height').getValue());
|
||||
this._defaultDepth = parseFloat(properties.get('depth').getValue());
|
||||
const rotationX = parseFloat(properties.get('rotationX').getValue());
|
||||
const rotationY = parseFloat(properties.get('rotationY').getValue());
|
||||
const rotationZ = parseFloat(properties.get('rotationZ').getValue());
|
||||
const keepAspectRatio =
|
||||
properties.get('keepAspectRatio').getValue() === 'true';
|
||||
const modelResourceName = properties
|
||||
.get('modelResourceName')
|
||||
.getValue();
|
||||
this._originPoint = getPointForLocation(
|
||||
properties.get('originLocation').getValue()
|
||||
);
|
||||
this._centerPoint = getPointForLocation(
|
||||
properties.get('centerLocation').getValue()
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.Model3DObjectConfiguration
|
||||
);
|
||||
|
||||
this._defaultWidth = object.getWidth();
|
||||
this._defaultHeight = object.getHeight();
|
||||
this._defaultDepth = object.getDepth();
|
||||
const rotationX = object.getRotationX();
|
||||
const rotationY = object.getRotationY();
|
||||
const rotationZ = object.getRotationZ();
|
||||
const keepAspectRatio = object.shouldKeepAspectRatio();
|
||||
const modelResourceName = object.getModelResourceName();
|
||||
|
||||
this._originPoint = getPointForLocation(object.getOriginLocation());
|
||||
this._centerPoint = getPointForLocation(object.getCenterLocation());
|
||||
|
||||
// This renderer shows a placeholder for the object:
|
||||
this._pixiObject = new PIXI.Graphics();
|
||||
this._pixiContainer.addChild(this._pixiObject);
|
||||
@@ -2923,6 +3024,17 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
|
||||
const isSamePoint = (point1, point2) => {
|
||||
if (!point1 && !point2) return true;
|
||||
if (point1 && !point2) return false;
|
||||
if (!point1 && point2) return false;
|
||||
return (
|
||||
point1[0] === point2[0] &&
|
||||
point1[1] === point2[1] &&
|
||||
point1[2] === point2[2]
|
||||
);
|
||||
};
|
||||
|
||||
const getPointForLocation = (location) => {
|
||||
switch (location) {
|
||||
case 'ModelOrigin':
|
||||
@@ -2959,24 +3071,20 @@ module.exports = {
|
||||
threeGroup,
|
||||
pixiResourcesLoader
|
||||
);
|
||||
const properties = associatedObjectConfiguration.getProperties();
|
||||
this._defaultWidth = parseFloat(properties.get('width').getValue());
|
||||
this._defaultHeight = parseFloat(properties.get('height').getValue());
|
||||
this._defaultDepth = parseFloat(properties.get('depth').getValue());
|
||||
const rotationX = parseFloat(properties.get('rotationX').getValue());
|
||||
const rotationY = parseFloat(properties.get('rotationY').getValue());
|
||||
const rotationZ = parseFloat(properties.get('rotationZ').getValue());
|
||||
const keepAspectRatio =
|
||||
properties.get('keepAspectRatio').getValue() === 'true';
|
||||
const modelResourceName = properties
|
||||
.get('modelResourceName')
|
||||
.getValue();
|
||||
this._originPoint = getPointForLocation(
|
||||
properties.get('originLocation').getValue()
|
||||
);
|
||||
this._centerPoint = getPointForLocation(
|
||||
properties.get('centerLocation').getValue()
|
||||
);
|
||||
|
||||
this._defaultWidth = 1;
|
||||
this._defaultHeight = 1;
|
||||
this._defaultDepth = 1;
|
||||
this._originalWidth = 1;
|
||||
this._originalHeight = 1;
|
||||
this._originalDepth = 1;
|
||||
this._rotationX = 0;
|
||||
this._rotationY = 0;
|
||||
this._rotationZ = 0;
|
||||
this._keepAspectRatio = false;
|
||||
|
||||
this._originPoint = null;
|
||||
this._centerPoint = null;
|
||||
|
||||
this._pixiObject = new PIXI.Graphics();
|
||||
this._pixiContainer.addChild(this._pixiObject);
|
||||
@@ -2985,28 +3093,8 @@ module.exports = {
|
||||
this._threeObject.rotation.order = 'ZYX';
|
||||
this._threeGroup.add(this._threeObject);
|
||||
|
||||
this._pixiResourcesLoader
|
||||
.get3DModel(project, modelResourceName)
|
||||
.then((model3d) => {
|
||||
const clonedModel3D = THREE_ADDONS.SkeletonUtils.clone(
|
||||
model3d.scene
|
||||
);
|
||||
// This group hold the rotation defined by properties.
|
||||
const threeObject = new THREE.Group();
|
||||
threeObject.rotation.order = 'ZYX';
|
||||
threeObject.add(clonedModel3D);
|
||||
this._updateDefaultTransformation(
|
||||
threeObject,
|
||||
rotationX,
|
||||
rotationY,
|
||||
rotationZ,
|
||||
this._defaultWidth,
|
||||
this._defaultHeight,
|
||||
this._defaultDepth,
|
||||
keepAspectRatio
|
||||
);
|
||||
this._threeObject.add(threeObject);
|
||||
});
|
||||
this._threeModelGroup = null;
|
||||
this._clonedModel3D = null;
|
||||
}
|
||||
|
||||
getOriginX() {
|
||||
@@ -3047,23 +3135,28 @@ module.exports = {
|
||||
return this._centerPoint || this._modelOriginPoint;
|
||||
}
|
||||
|
||||
_updateDefaultTransformation(
|
||||
threeObject,
|
||||
rotationX,
|
||||
rotationY,
|
||||
rotationZ,
|
||||
originalWidth,
|
||||
originalHeight,
|
||||
originalDepth,
|
||||
keepAspectRatio
|
||||
) {
|
||||
threeObject.rotation.set(
|
||||
(rotationX * Math.PI) / 180,
|
||||
(rotationY * Math.PI) / 180,
|
||||
(rotationZ * Math.PI) / 180
|
||||
_updateDefaultTransformation() {
|
||||
if (!this._clonedModel3D) return; // Model is not ready - nothing to do.
|
||||
|
||||
if (this._threeModelGroup) {
|
||||
// Remove any previous container as we will recreate it just below
|
||||
this._threeObject.clear();
|
||||
}
|
||||
// This group hold the rotation defined by properties.
|
||||
// Always restart from a new group to avoid miscomputing bounding boxes/sizes.
|
||||
this._threeModelGroup = new THREE.Group();
|
||||
this._threeModelGroup.rotation.order = 'ZYX';
|
||||
this._threeModelGroup.add(this._clonedModel3D);
|
||||
|
||||
this._threeModelGroup.rotation.set(
|
||||
(this._rotationX * Math.PI) / 180,
|
||||
(this._rotationY * Math.PI) / 180,
|
||||
(this._rotationZ * Math.PI) / 180
|
||||
);
|
||||
this._threeModelGroup.updateMatrixWorld(true);
|
||||
const boundingBox = new THREE.Box3().setFromObject(
|
||||
this._threeModelGroup
|
||||
);
|
||||
threeObject.updateMatrixWorld(true);
|
||||
const boundingBox = new THREE.Box3().setFromObject(threeObject);
|
||||
|
||||
const shouldKeepModelOrigin = !this._originPoint;
|
||||
if (shouldKeepModelOrigin) {
|
||||
@@ -3090,7 +3183,7 @@ module.exports = {
|
||||
// Center the model.
|
||||
const centerPoint = this._centerPoint;
|
||||
if (centerPoint) {
|
||||
threeObject.position.set(
|
||||
this._threeModelGroup.position.set(
|
||||
-(boundingBox.min.x + modelWidth * centerPoint[0]),
|
||||
// The model is flipped on Y axis.
|
||||
-(boundingBox.min.y + modelHeight * (1 - centerPoint[1])),
|
||||
@@ -3099,11 +3192,11 @@ module.exports = {
|
||||
}
|
||||
|
||||
// Rotate the model.
|
||||
threeObject.scale.set(1, 1, 1);
|
||||
threeObject.rotation.set(
|
||||
(rotationX * Math.PI) / 180,
|
||||
(rotationY * Math.PI) / 180,
|
||||
(rotationZ * Math.PI) / 180
|
||||
this._threeModelGroup.scale.set(1, 1, 1);
|
||||
this._threeModelGroup.rotation.set(
|
||||
(this._rotationX * Math.PI) / 180,
|
||||
(this._rotationY * Math.PI) / 180,
|
||||
(this._rotationZ * Math.PI) / 180
|
||||
);
|
||||
|
||||
// Stretch the model in a 1x1x1 cube.
|
||||
@@ -3115,23 +3208,23 @@ module.exports = {
|
||||
// Flip on Y because the Y axis is on the opposite side of direct basis.
|
||||
// It avoids models to be like a mirror refection.
|
||||
scaleMatrix.makeScale(scaleX, -scaleY, scaleZ);
|
||||
threeObject.updateMatrix();
|
||||
threeObject.applyMatrix4(scaleMatrix);
|
||||
this._threeModelGroup.updateMatrix();
|
||||
this._threeModelGroup.applyMatrix4(scaleMatrix);
|
||||
|
||||
if (keepAspectRatio) {
|
||||
if (this._keepAspectRatio) {
|
||||
// Reduce the object dimensions to keep aspect ratio.
|
||||
const widthRatio =
|
||||
modelWidth < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalWidth / modelWidth;
|
||||
: this._originalWidth / modelWidth;
|
||||
const heightRatio =
|
||||
modelHeight < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalHeight / modelHeight;
|
||||
: this._originalHeight / modelHeight;
|
||||
const depthRatio =
|
||||
modelDepth < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalDepth / modelDepth;
|
||||
: this._originalDepth / modelDepth;
|
||||
const minScaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
|
||||
if (!Number.isFinite(minScaleRatio)) {
|
||||
this._defaultWidth = modelWidth;
|
||||
@@ -3139,48 +3232,124 @@ module.exports = {
|
||||
this._defaultDepth = modelDepth;
|
||||
} else {
|
||||
if (widthRatio === minScaleRatio) {
|
||||
this._defaultWidth = originalWidth;
|
||||
this._defaultWidth = this._originalWidth;
|
||||
this._defaultHeight = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelWidth,
|
||||
newReferenceValue: originalWidth,
|
||||
newReferenceValue: this._originalWidth,
|
||||
valueToApplyTo: modelHeight,
|
||||
});
|
||||
this._defaultDepth = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelWidth,
|
||||
newReferenceValue: originalWidth,
|
||||
newReferenceValue: this._originalWidth,
|
||||
valueToApplyTo: modelDepth,
|
||||
});
|
||||
} else if (heightRatio === minScaleRatio) {
|
||||
this._defaultWidth = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelHeight,
|
||||
newReferenceValue: originalHeight,
|
||||
newReferenceValue: this._originalHeight,
|
||||
valueToApplyTo: modelWidth,
|
||||
});
|
||||
|
||||
this._defaultHeight = originalHeight;
|
||||
this._defaultHeight = this._originalHeight;
|
||||
this._defaultDepth = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelHeight,
|
||||
newReferenceValue: originalHeight,
|
||||
newReferenceValue: this._originalHeight,
|
||||
valueToApplyTo: modelDepth,
|
||||
});
|
||||
} else {
|
||||
this._defaultWidth = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelDepth,
|
||||
newReferenceValue: originalDepth,
|
||||
newReferenceValue: this._originalDepth,
|
||||
valueToApplyTo: modelWidth,
|
||||
});
|
||||
this._defaultHeight = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelDepth,
|
||||
newReferenceValue: originalDepth,
|
||||
newReferenceValue: this._originalDepth,
|
||||
valueToApplyTo: modelHeight,
|
||||
});
|
||||
this._defaultDepth = originalDepth;
|
||||
this._defaultDepth = this._originalDepth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._threeObject.add(this._threeModelGroup);
|
||||
}
|
||||
|
||||
updateThreeObject() {
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.Model3DObjectConfiguration
|
||||
);
|
||||
|
||||
let defaultTransformationDirty = false;
|
||||
|
||||
const originalWidth = object.getWidth();
|
||||
const originalHeight = object.getHeight();
|
||||
const originalDepth = object.getDepth();
|
||||
if (
|
||||
this._originalWidth !== originalWidth ||
|
||||
this._originalHeight !== originalHeight ||
|
||||
this._originalDepth !== originalDepth
|
||||
) {
|
||||
this._originalWidth = originalWidth;
|
||||
this._originalHeight = originalHeight;
|
||||
this._originalDepth = originalDepth;
|
||||
defaultTransformationDirty = true;
|
||||
}
|
||||
|
||||
const rotationX = object.getRotationX();
|
||||
const rotationY = object.getRotationY();
|
||||
const rotationZ = object.getRotationZ();
|
||||
if (
|
||||
this._rotationX !== rotationX ||
|
||||
this._rotationY !== rotationY ||
|
||||
this._rotationZ !== rotationZ
|
||||
) {
|
||||
this._rotationX = rotationX;
|
||||
this._rotationY = rotationY;
|
||||
this._rotationZ = rotationZ;
|
||||
defaultTransformationDirty = true;
|
||||
}
|
||||
|
||||
const keepAspectRatio = object.shouldKeepAspectRatio();
|
||||
if (this._keepAspectRatio !== keepAspectRatio) {
|
||||
this._keepAspectRatio = keepAspectRatio;
|
||||
defaultTransformationDirty = true;
|
||||
}
|
||||
|
||||
const originPoint = getPointForLocation(object.getOriginLocation());
|
||||
if (!isSamePoint(originPoint, this._originPoint)) {
|
||||
this._originPoint = originPoint;
|
||||
defaultTransformationDirty = true;
|
||||
}
|
||||
|
||||
const centerPoint = getPointForLocation(object.getCenterLocation());
|
||||
if (!isSamePoint(centerPoint, this._centerPoint)) {
|
||||
this._centerPoint = centerPoint;
|
||||
defaultTransformationDirty = true;
|
||||
}
|
||||
|
||||
if (defaultTransformationDirty) this._updateDefaultTransformation();
|
||||
|
||||
const modelResourceName = object.getModelResourceName();
|
||||
if (this._modelResourceName !== modelResourceName) {
|
||||
this._modelResourceName = modelResourceName;
|
||||
|
||||
this._pixiResourcesLoader
|
||||
.get3DModel(this._project, modelResourceName)
|
||||
.then((model3d) => {
|
||||
this._clonedModel3D = THREE_ADDONS.SkeletonUtils.clone(
|
||||
model3d.scene
|
||||
);
|
||||
|
||||
this._updateDefaultTransformation();
|
||||
});
|
||||
}
|
||||
|
||||
this._updateThreeObjectPosition();
|
||||
}
|
||||
|
||||
_updateThreeObjectPosition() {
|
||||
const width = this.getWidth();
|
||||
const height = this.getHeight();
|
||||
const depth = this.getDepth();
|
||||
@@ -3204,9 +3373,9 @@ module.exports = {
|
||||
const scaleZ = depth * (this._instance.isFlippedZ() ? -1 : 1);
|
||||
|
||||
if (
|
||||
scaleX !== this._threeObject.scale.width ||
|
||||
scaleY !== this._threeObject.scale.height ||
|
||||
scaleZ !== this._threeObject.scale.depth
|
||||
scaleX !== this._threeObject.scale.x ||
|
||||
scaleY !== this._threeObject.scale.y ||
|
||||
scaleZ !== this._threeObject.scale.z
|
||||
) {
|
||||
this._threeObject.scale.set(scaleX, scaleY, scaleZ);
|
||||
}
|
||||
|
@@ -109,23 +109,26 @@ Model3DObjectConfiguration::GetProperties() const {
|
||||
objectProperties["rotationX"]
|
||||
.SetValue(gd::String::From(rotationX))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Rotation around X axis"))
|
||||
.SetLabel(_("X"))
|
||||
.SetDescription(_("Rotation around X axis"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetDegreeAngle())
|
||||
.SetGroup(_("Default orientation"));
|
||||
.SetGroup(_("Default rotation"));
|
||||
|
||||
objectProperties["rotationY"]
|
||||
.SetValue(gd::String::From(rotationY))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Rotation around Y axis"))
|
||||
.SetLabel(_("Y"))
|
||||
.SetDescription(_("Rotation around Y axis"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetDegreeAngle())
|
||||
.SetGroup(_("Default orientation"));
|
||||
.SetGroup(_("Default rotation"));
|
||||
|
||||
objectProperties["rotationZ"]
|
||||
.SetValue(gd::String::From(rotationZ))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Rotation around Z axis"))
|
||||
.SetLabel(_("Z"))
|
||||
.SetDescription(_("Rotation around Z axis"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetDegreeAngle())
|
||||
.SetGroup(_("Default orientation"));
|
||||
.SetGroup(_("Default rotation"));
|
||||
|
||||
objectProperties["modelResourceName"]
|
||||
.SetValue(modelResourceName)
|
||||
@@ -139,7 +142,7 @@ Model3DObjectConfiguration::GetProperties() const {
|
||||
.AddExtraInfo("Basic")
|
||||
.AddExtraInfo("StandardWithoutMetalness")
|
||||
.AddExtraInfo("KeepOriginal")
|
||||
.SetLabel(_("Material modifier"));
|
||||
.SetLabel(_("Material"));
|
||||
|
||||
objectProperties["originLocation"]
|
||||
.SetValue(originLocation.empty() ? "TopLeft" : originLocation)
|
||||
@@ -149,7 +152,9 @@ Model3DObjectConfiguration::GetProperties() const {
|
||||
.AddExtraInfo("ObjectCenter")
|
||||
.AddExtraInfo("BottomCenterZ")
|
||||
.AddExtraInfo("BottomCenterY")
|
||||
.SetLabel(_("Origin point"));
|
||||
.SetLabel(_("Origin point"))
|
||||
.SetGroup(_("Points"))
|
||||
.SetAdvanced(true);
|
||||
|
||||
objectProperties["centerLocation"]
|
||||
.SetValue(centerLocation.empty() ? "ObjectCenter" : centerLocation)
|
||||
@@ -158,7 +163,9 @@ Model3DObjectConfiguration::GetProperties() const {
|
||||
.AddExtraInfo("ObjectCenter")
|
||||
.AddExtraInfo("BottomCenterZ")
|
||||
.AddExtraInfo("BottomCenterY")
|
||||
.SetLabel(_("Center point"));
|
||||
.SetLabel(_("Center point"))
|
||||
.SetGroup(_("Points"))
|
||||
.SetAdvanced(true);
|
||||
|
||||
return objectProperties;
|
||||
}
|
||||
|
@@ -140,7 +140,25 @@ public:
|
||||
const std::vector<Model3DAnimation> &GetAllAnimations() const {
|
||||
return animations;
|
||||
}
|
||||
///@}
|
||||
|
||||
/** \name Getters
|
||||
* Fast access for rendering instances.
|
||||
*/
|
||||
///@{
|
||||
double GetWidth() const { return width; };
|
||||
double GetHeight() const { return height; };
|
||||
double GetDepth() const { return depth; };
|
||||
double GetRotationX() const { return rotationX; };
|
||||
double GetRotationY() const { return rotationY; };
|
||||
double GetRotationZ() const { return rotationZ; };
|
||||
|
||||
const gd::String& GetModelResourceName() const { return modelResourceName; };
|
||||
const gd::String& GetMaterialType() const { return materialType; };
|
||||
const gd::String& GetOriginLocation() const { return originLocation; };
|
||||
const gd::String& GetCenterLocation() const { return centerLocation; };
|
||||
|
||||
bool shouldKeepAspectRatio() const { return keepAspectRatio; };
|
||||
///@}
|
||||
|
||||
protected:
|
||||
|
@@ -75,7 +75,7 @@ std::map<gd::String, gd::PropertyDescriptor> AnchorBehavior::GetProperties(
|
||||
.AddExtraInfo(_("Window center"))
|
||||
.AddExtraInfo(_("Window right"))
|
||||
.AddExtraInfo(_("Proportional"))
|
||||
.SetLabel(_("Left edge anchor"))
|
||||
.SetLabel(_("Left edge"))
|
||||
.SetDescription(_("Anchor the left edge of the object on X axis."));
|
||||
|
||||
properties["rightEdgeAnchor"]
|
||||
@@ -87,7 +87,7 @@ std::map<gd::String, gd::PropertyDescriptor> AnchorBehavior::GetProperties(
|
||||
.AddExtraInfo(_("Window center"))
|
||||
.AddExtraInfo(_("Window right"))
|
||||
.AddExtraInfo(_("Proportional"))
|
||||
.SetLabel(_("Right edge anchor"))
|
||||
.SetLabel(_("Right edge"))
|
||||
.SetDescription(_("Anchor the right edge of the object on X axis."));
|
||||
|
||||
properties["topEdgeAnchor"]
|
||||
@@ -99,7 +99,7 @@ std::map<gd::String, gd::PropertyDescriptor> AnchorBehavior::GetProperties(
|
||||
.AddExtraInfo(_("Window center"))
|
||||
.AddExtraInfo(_("Window bottom"))
|
||||
.AddExtraInfo(_("Proportional"))
|
||||
.SetLabel(_("Top edge anchor"))
|
||||
.SetLabel(_("Top edge"))
|
||||
.SetDescription(_("Anchor the top edge of the object on Y axis."));
|
||||
|
||||
properties["bottomEdgeAnchor"]
|
||||
@@ -111,7 +111,7 @@ std::map<gd::String, gd::PropertyDescriptor> AnchorBehavior::GetProperties(
|
||||
.AddExtraInfo(_("Window center"))
|
||||
.AddExtraInfo(_("Window bottom"))
|
||||
.AddExtraInfo(_("Proportional"))
|
||||
.SetLabel(_("Bottom edge anchor"))
|
||||
.SetLabel(_("Bottom edge"))
|
||||
.SetDescription(_("Anchor the bottom edge of the object on Y axis."));
|
||||
|
||||
properties["useLegacyBottomAndRightAnchors"]
|
||||
@@ -119,12 +119,12 @@ std::map<gd::String, gd::PropertyDescriptor> AnchorBehavior::GetProperties(
|
||||
"Stretch object when anchoring right or bottom edge (deprecated, "
|
||||
"it's recommended to leave this unchecked and anchor both sides if "
|
||||
"you want Sprite to stretch instead.)"))
|
||||
.SetGroup(_("Deprecated options (advanced)"))
|
||||
.SetValue(behaviorContent.GetBoolAttribute(
|
||||
"useLegacyBottomAndRightAnchors", true)
|
||||
? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
.SetType("Boolean")
|
||||
.SetDeprecated(true);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
@@ -34,11 +34,8 @@ module.exports = {
|
||||
.setIcon('JsPlatform/Extensions/bbcode32.png');
|
||||
|
||||
var objectBBText = new gd.ObjectJsImplementation();
|
||||
objectBBText.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
objectBBText.updateProperty = function (propertyName, newValue) {
|
||||
const objectContent = this.content;
|
||||
if (propertyName in objectContent) {
|
||||
if (typeof objectContent[propertyName] === 'boolean')
|
||||
objectContent[propertyName] = newValue === '1';
|
||||
@@ -50,8 +47,9 @@ module.exports = {
|
||||
|
||||
return false;
|
||||
};
|
||||
objectBBText.getProperties = function (objectContent) {
|
||||
objectBBText.getProperties = function () {
|
||||
const objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
const objectContent = this.content;
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('text')
|
||||
@@ -107,29 +105,26 @@ module.exports = {
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
objectBBText.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
text:
|
||||
'[b]bold[/b] [i]italic[/i] [size=15]smaller[/size] [font=times]times[/font] font\n[spacing=12]spaced out[/spacing]\n[outline=yellow]outlined[/outline] [shadow=red]DropShadow[/shadow] ',
|
||||
opacity: 255,
|
||||
fontSize: 20,
|
||||
visible: true,
|
||||
color: '0;0;0',
|
||||
fontFamily: 'Arial',
|
||||
align: 'left',
|
||||
wordWrap: true,
|
||||
})
|
||||
);
|
||||
objectBBText.content = {
|
||||
text:
|
||||
'[b]bold[/b] [i]italic[/i] [size=15]smaller[/size] [font=times]times[/font] font\n[spacing=12]spaced out[/spacing]\n[outline=yellow]outlined[/outline] [shadow=red]DropShadow[/shadow] ',
|
||||
opacity: 255,
|
||||
fontSize: 20,
|
||||
visible: true,
|
||||
color: '0;0;0',
|
||||
fontFamily: 'Arial',
|
||||
align: 'left',
|
||||
wordWrap: true,
|
||||
};
|
||||
|
||||
objectBBText.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
return false;
|
||||
};
|
||||
objectBBText.getInitialInstanceProperties = function (content, instance) {
|
||||
objectBBText.getInitialInstanceProperties = function (instance) {
|
||||
var instanceProperties = new gd.MapStringPropertyDescriptor();
|
||||
return instanceProperties;
|
||||
};
|
||||
@@ -531,22 +526,33 @@ module.exports = {
|
||||
* This is called to update the PIXI object on the scene editor
|
||||
*/
|
||||
update() {
|
||||
const properties = this._associatedObjectConfiguration.getProperties();
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
|
||||
const rawText = properties.get('text').getValue();
|
||||
const rawText = object.content.text;
|
||||
if (rawText !== this._pixiObject.text) {
|
||||
this._pixiObject.text = rawText;
|
||||
}
|
||||
|
||||
const color = properties.get('color').getValue();
|
||||
this._pixiObject.textStyles.default.fill = objectsRenderingService.rgbOrHexToHexNumber(
|
||||
color
|
||||
);
|
||||
const color = object.content.color;
|
||||
const newColor = objectsRenderingService.rgbOrHexToHexNumber(color);
|
||||
if (newColor !== this._pixiObject.textStyles.default.fill) {
|
||||
this._pixiObject.textStyles.default.fill = newColor;
|
||||
this._pixiObject.dirty = true;
|
||||
}
|
||||
|
||||
const fontSize = properties.get('fontSize').getValue();
|
||||
this._pixiObject.textStyles.default.fontSize = `${fontSize}px`;
|
||||
const fontSize = object.content.fontSize;
|
||||
const newDefaultFontsize = `${fontSize}px`;
|
||||
if (
|
||||
newDefaultFontsize !== this._pixiObject.textStyles.default.fontSize
|
||||
) {
|
||||
this._pixiObject.textStyles.default.fontSize = `${fontSize}px`;
|
||||
this._pixiObject.dirty = true;
|
||||
}
|
||||
|
||||
const fontResourceName = properties.get('fontFamily').getValue();
|
||||
const fontResourceName = object.content.fontFamily;
|
||||
|
||||
if (this._fontResourceName !== fontResourceName) {
|
||||
this._fontResourceName = fontResourceName;
|
||||
@@ -567,13 +573,13 @@ module.exports = {
|
||||
});
|
||||
}
|
||||
|
||||
const wordWrap = properties.get('wordWrap').getValue() === 'true';
|
||||
const wordWrap = object.content.wordWrap;
|
||||
if (wordWrap !== this._pixiObject._style.wordWrap) {
|
||||
this._pixiObject._style.wordWrap = wordWrap;
|
||||
this._pixiObject.dirty = true;
|
||||
}
|
||||
|
||||
const align = properties.get('align').getValue();
|
||||
const align = object.content.align;
|
||||
if (align !== this._pixiObject._style.align) {
|
||||
this._pixiObject._style.align = align;
|
||||
this._pixiObject.dirty = true;
|
||||
|
@@ -34,11 +34,8 @@ module.exports = {
|
||||
.setIcon('JsPlatform/Extensions/bitmapfont32.png');
|
||||
|
||||
const bitmapTextObject = new gd.ObjectJsImplementation();
|
||||
bitmapTextObject.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
bitmapTextObject.updateProperty = function (propertyName, newValue) {
|
||||
const objectContent = this.content;
|
||||
if (propertyName in objectContent) {
|
||||
if (typeof objectContent[propertyName] === 'boolean')
|
||||
objectContent[propertyName] = newValue === '1';
|
||||
@@ -50,8 +47,9 @@ module.exports = {
|
||||
|
||||
return false;
|
||||
};
|
||||
bitmapTextObject.getProperties = function (objectContent) {
|
||||
bitmapTextObject.getProperties = function () {
|
||||
const objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
const objectContent = this.content;
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('text')
|
||||
@@ -66,7 +64,7 @@ module.exports = {
|
||||
.addExtraInfo('left')
|
||||
.addExtraInfo('center')
|
||||
.addExtraInfo('right')
|
||||
.setLabel(_('Alignment, when multiple lines are displayed'))
|
||||
.setLabel(_('Alignment'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
objectProperties
|
||||
@@ -82,7 +80,7 @@ module.exports = {
|
||||
.setValue(objectContent.textureAtlasResourceName)
|
||||
.setType('resource')
|
||||
.addExtraInfo('image')
|
||||
.setLabel(_('Bitmap atlas image'))
|
||||
.setLabel(_('Bitmap Atlas'))
|
||||
.setGroup(_('Font'));
|
||||
|
||||
objectProperties
|
||||
@@ -108,33 +106,27 @@ module.exports = {
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
bitmapTextObject.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
text:
|
||||
'This text use the default bitmap font.\nUse a custom Bitmap Font to create your own texts.',
|
||||
opacity: 255,
|
||||
scale: 1,
|
||||
fontSize: 20,
|
||||
tint: '255;255;255',
|
||||
bitmapFontResourceName: '',
|
||||
textureAtlasResourceName: '',
|
||||
align: 'left',
|
||||
wordWrap: true,
|
||||
})
|
||||
);
|
||||
bitmapTextObject.content = {
|
||||
text:
|
||||
'This text use the default bitmap font.\nUse a custom Bitmap Font to create your own texts.',
|
||||
opacity: 255,
|
||||
scale: 1,
|
||||
fontSize: 20,
|
||||
tint: '255;255;255',
|
||||
bitmapFontResourceName: '',
|
||||
textureAtlasResourceName: '',
|
||||
align: 'left',
|
||||
wordWrap: true,
|
||||
};
|
||||
|
||||
bitmapTextObject.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
return false;
|
||||
};
|
||||
bitmapTextObject.getInitialInstanceProperties = function (
|
||||
content,
|
||||
instance
|
||||
) {
|
||||
bitmapTextObject.getInitialInstanceProperties = function (instance) {
|
||||
var instanceProperties = new gd.MapStringPropertyDescriptor();
|
||||
return instanceProperties;
|
||||
};
|
||||
@@ -659,31 +651,31 @@ module.exports = {
|
||||
}
|
||||
|
||||
update() {
|
||||
const properties = this._associatedObjectConfiguration.getProperties();
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
|
||||
// Update the rendered text properties (note: Pixi is only
|
||||
// applying changes if there were changed).
|
||||
const rawText = properties.get('text').getValue();
|
||||
const rawText = object.content.text;
|
||||
this._pixiObject.text = rawText;
|
||||
|
||||
const align = properties.get('align').getValue();
|
||||
const align = object.content.align;
|
||||
this._pixiObject.align = align;
|
||||
|
||||
const color = properties.get('tint').getValue();
|
||||
const color = object.content.tint;
|
||||
this._pixiObject.tint = objectsRenderingService.rgbOrHexToHexNumber(
|
||||
color
|
||||
);
|
||||
|
||||
const scale = +(properties.get('scale').getValue() || 1);
|
||||
const scale = object.content.scale;
|
||||
this._pixiObject.scale.set(scale);
|
||||
|
||||
// Track the changes in font to load the new requested font.
|
||||
const bitmapFontResourceName = properties
|
||||
.get('bitmapFontResourceName')
|
||||
.getValue();
|
||||
const textureAtlasResourceName = properties
|
||||
.get('textureAtlasResourceName')
|
||||
.getValue();
|
||||
const bitmapFontResourceName = object.content.bitmapFontResourceName;
|
||||
const textureAtlasResourceName =
|
||||
object.content.textureAtlasResourceName;
|
||||
|
||||
if (
|
||||
this._currentBitmapFontResourceName !== bitmapFontResourceName ||
|
||||
@@ -712,7 +704,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
// Set up the wrapping width if enabled.
|
||||
const wordWrap = properties.get('wordWrap').getValue() === 'true';
|
||||
const wordWrap = object.content.wordWrap;
|
||||
if (wordWrap && this._instance.hasCustomSize()) {
|
||||
this._pixiObject.maxWidth =
|
||||
this.getCustomWidth() / this._pixiObject.scale.x;
|
||||
|
@@ -26,7 +26,8 @@ DestroyOutsideBehavior::GetProperties(
|
||||
behaviorContent.GetDoubleAttribute("extraBorder", 0)))
|
||||
.SetType("Number")
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetLabel(_("Margin before deleting the object, in pixels"));
|
||||
.SetLabel(_("Deletion margin"))
|
||||
.SetDescription(_("Margin before deleting the object, in pixels"));
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ std::map<gd::String, gd::PropertyDescriptor> DraggableBehavior::GetProperties(
|
||||
? "true"
|
||||
: "false")
|
||||
.SetType("Boolean")
|
||||
.SetLabel(_("Do a precision check against the object's collision mask"))
|
||||
.SetLabel(_("Precise check"))
|
||||
.SetDescription(
|
||||
_("Use the object (custom) collision mask instead of the bounding "
|
||||
"box, making the behavior more precise at the cost of "
|
||||
|
@@ -287,11 +287,9 @@ module.exports = {
|
||||
// Everything that is stored inside the object is in "content" and is automatically
|
||||
// saved/loaded to JSON.
|
||||
var dummyObject = new gd.ObjectJsImplementation();
|
||||
dummyObject.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
dummyObject.updateProperty = function (propertyName, newValue) {
|
||||
const objectContent = this.content;
|
||||
|
||||
if (propertyName === 'My first property') {
|
||||
objectContent.property1 = newValue;
|
||||
return true;
|
||||
@@ -311,8 +309,9 @@ module.exports = {
|
||||
|
||||
return false;
|
||||
};
|
||||
dummyObject.getProperties = function (objectContent) {
|
||||
dummyObject.getProperties = function () {
|
||||
var objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
const objectContent = this.content;
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('My first property')
|
||||
@@ -336,17 +335,14 @@ module.exports = {
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
dummyObject.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
property1: 'Hello world',
|
||||
property2: true,
|
||||
property3: 123,
|
||||
myImage: '',
|
||||
})
|
||||
);
|
||||
dummyObject.content = {
|
||||
property1: 'Hello world',
|
||||
property2: true,
|
||||
property3: 123,
|
||||
myImage: '',
|
||||
};
|
||||
|
||||
dummyObject.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
propertyName,
|
||||
newValue
|
||||
@@ -362,7 +358,7 @@ module.exports = {
|
||||
|
||||
return false;
|
||||
};
|
||||
dummyObject.getInitialInstanceProperties = function (content, instance) {
|
||||
dummyObject.getInitialInstanceProperties = function (instance) {
|
||||
var instanceProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
instanceProperties
|
||||
@@ -507,12 +503,13 @@ module.exports = {
|
||||
* This is called to update the PIXI object on the scene editor
|
||||
*/
|
||||
update() {
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
|
||||
// Read a property from the object
|
||||
const property1Value = this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('My first property')
|
||||
.getValue();
|
||||
this._pixiObject.text = property1Value;
|
||||
this._pixiObject.text = object.content.property1;
|
||||
|
||||
// Read position and angle from the instance
|
||||
this._pixiObject.position.x =
|
||||
|
@@ -68,11 +68,8 @@ module.exports = {
|
||||
|
||||
const lightObject = new gd.ObjectJsImplementation();
|
||||
|
||||
lightObject.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
lightObject.updateProperty = function (propertyName, newValue) {
|
||||
const objectContent = this.content;
|
||||
if (propertyName === 'radius') {
|
||||
objectContent.radius = parseFloat(newValue);
|
||||
return true;
|
||||
@@ -96,8 +93,9 @@ module.exports = {
|
||||
return false;
|
||||
};
|
||||
|
||||
lightObject.getProperties = function (objectContent) {
|
||||
lightObject.getProperties = function () {
|
||||
const objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
const objectContent = this.content;
|
||||
|
||||
objectProperties.set(
|
||||
'radius',
|
||||
@@ -140,17 +138,14 @@ module.exports = {
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
lightObject.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
radius: 50,
|
||||
color: '255;255;255',
|
||||
debugMode: false,
|
||||
texture: '',
|
||||
})
|
||||
);
|
||||
lightObject.content = {
|
||||
radius: 50,
|
||||
color: '255;255;255',
|
||||
debugMode: false,
|
||||
texture: '',
|
||||
};
|
||||
|
||||
lightObject.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
propertyName,
|
||||
newValue
|
||||
@@ -158,7 +153,7 @@ module.exports = {
|
||||
return false;
|
||||
};
|
||||
|
||||
lightObject.getInitialInstanceProperties = function (content, instance) {
|
||||
lightObject.getInitialInstanceProperties = function (instance) {
|
||||
const instanceProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
return instanceProperties;
|
||||
@@ -238,6 +233,10 @@ module.exports = {
|
||||
* Renderer for instances of LightObject inside the IDE.
|
||||
*/
|
||||
class RenderedLightObjectInstance extends RenderedInstance {
|
||||
_radius = 0;
|
||||
_color = 0;
|
||||
_radiusGraphics = null;
|
||||
|
||||
constructor(
|
||||
project,
|
||||
instance,
|
||||
@@ -252,19 +251,6 @@ module.exports = {
|
||||
pixiContainer,
|
||||
pixiResourcesLoader
|
||||
);
|
||||
this._radius = parseFloat(
|
||||
this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('radius')
|
||||
.getValue()
|
||||
);
|
||||
if (this._radius <= 0) this._radius = 1;
|
||||
const color = objectsRenderingService.rgbOrHexToHexNumber(
|
||||
this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('color')
|
||||
.getValue()
|
||||
);
|
||||
|
||||
// The icon in the middle.
|
||||
const lightIconSprite = new PIXI.Sprite(
|
||||
@@ -274,18 +260,11 @@ module.exports = {
|
||||
lightIconSprite.anchor.y = 0.5;
|
||||
|
||||
// The circle to show the radius of the light.
|
||||
const radiusBorderWidth = 2;
|
||||
const radiusGraphics = new PIXI.Graphics();
|
||||
radiusGraphics.lineStyle(radiusBorderWidth, color, 0.8);
|
||||
radiusGraphics.drawCircle(
|
||||
0,
|
||||
0,
|
||||
Math.max(1, this._radius - radiusBorderWidth)
|
||||
);
|
||||
this._radiusGraphics = new PIXI.Graphics();
|
||||
|
||||
this._pixiObject = new PIXI.Container();
|
||||
this._pixiObject.addChild(lightIconSprite);
|
||||
this._pixiObject.addChild(radiusGraphics);
|
||||
this._pixiObject.addChild(this._radiusGraphics);
|
||||
this._pixiContainer.addChild(this._pixiObject);
|
||||
this.update();
|
||||
}
|
||||
@@ -307,8 +286,41 @@ module.exports = {
|
||||
* This is called to update the PIXI object on the scene editor
|
||||
*/
|
||||
update() {
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
|
||||
this._pixiObject.position.x = this._instance.getX();
|
||||
this._pixiObject.position.y = this._instance.getY();
|
||||
|
||||
let radiusGraphicsDirty = false;
|
||||
|
||||
let radius = object.content.radius;
|
||||
if (radius <= 0) radius = 1;
|
||||
if (radius !== this._radius) {
|
||||
this._radius = radius;
|
||||
radiusGraphicsDirty = true;
|
||||
}
|
||||
|
||||
const color = objectsRenderingService.rgbOrHexToHexNumber(
|
||||
object.content.color
|
||||
);
|
||||
if (color !== this._color) {
|
||||
this._color = color;
|
||||
radiusGraphicsDirty = true;
|
||||
}
|
||||
|
||||
if (radiusGraphicsDirty) {
|
||||
const radiusBorderWidth = 2;
|
||||
this._radiusGraphics.clear();
|
||||
this._radiusGraphics.lineStyle(radiusBorderWidth, color, 0.8);
|
||||
this._radiusGraphics.drawCircle(
|
||||
0,
|
||||
0,
|
||||
Math.max(1, this._radius - radiusBorderWidth)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -33,12 +33,8 @@ ParticleEmitterBase::ParticleEmitterBase()
|
||||
particleGravityY(0.0f),
|
||||
particleLifeTimeMin(0.5f),
|
||||
particleLifeTimeMax(2.5f),
|
||||
particleRed1(255.0f),
|
||||
particleRed2(255.0f),
|
||||
particleGreen1(51),
|
||||
particleGreen2(255),
|
||||
particleBlue1(51),
|
||||
particleBlue2(0.0f),
|
||||
particleColor1("255;51;51"),
|
||||
particleColor2("255;255;0"),
|
||||
particleAlpha1(204),
|
||||
particleAlpha2(0.0f),
|
||||
particleSize1(100.0f),
|
||||
@@ -57,6 +53,341 @@ ParticleEmitterBase::ParticleEmitterBase()
|
||||
|
||||
ParticleEmitterObject::ParticleEmitterObject() {}
|
||||
|
||||
bool ParticleEmitterObject::UpdateProperty(const gd::String& propertyName,
|
||||
const gd::String& newValue) {
|
||||
if (propertyName == "textureParticleName") {
|
||||
SetParticleTexture(newValue);
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "rendererType") {
|
||||
auto newRendererType = newValue == "Circle" ? Point
|
||||
: newValue == "Line" ? Line
|
||||
: Quad;
|
||||
SetRendererType(newRendererType);
|
||||
if (newRendererType != Quad) {
|
||||
SetParticleTexture("");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "particlesWidth") {
|
||||
SetRendererParam1(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "particlesHeight") {
|
||||
SetRendererParam2(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "lineLength") {
|
||||
SetRendererParam1(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "lineThickness") {
|
||||
SetRendererParam2(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "particlesSize") {
|
||||
SetRendererParam1(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "particlesStartSize") {
|
||||
SetParticleSize1(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "particlesEndSize") {
|
||||
SetParticleSize2(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "particlesStartColor") {
|
||||
SetParticleColor1(newValue);
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "particlesEndColor") {
|
||||
SetParticleColor2(newValue);
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "particlesStartOpacity") {
|
||||
SetParticleAlpha1(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "particlesEndOpacity") {
|
||||
SetParticleAlpha2(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "additiveRendering") {
|
||||
if (newValue == "1")
|
||||
SetRenderingAdditive();
|
||||
else
|
||||
SetRenderingAlpha();
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "deleteWhenOutOfParticles") {
|
||||
SetDestroyWhenNoParticles(newValue == "1");
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "maxParticlesCount") {
|
||||
SetMaxParticleNb(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "tank") {
|
||||
SetTank(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "flow") {
|
||||
SetFlow(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "emitterForceMin") {
|
||||
SetEmitterForceMin(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "emitterForceMax") {
|
||||
SetEmitterForceMax(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "particleRotationSpeedMin") {
|
||||
SetParticleAngle1(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "particleRotationSpeedMax") {
|
||||
SetParticleAngle2(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "coneSprayAngle") {
|
||||
SetConeSprayAngle(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "zoneRadius") {
|
||||
SetZoneRadius(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "particleGravityX") {
|
||||
SetParticleGravityX(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "particleGravityY") {
|
||||
SetParticleGravityY(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "particleLifeTimeMin") {
|
||||
SetParticleLifeTimeMin(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "particleLifeTimeMax") {
|
||||
SetParticleLifeTimeMax(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "jumpForwardInTimeOnCreation") {
|
||||
SetJumpForwardInTimeOnCreation(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor>
|
||||
ParticleEmitterObject::GetProperties() const {
|
||||
std::map<gd::String, gd::PropertyDescriptor> objectProperties;
|
||||
|
||||
objectProperties["rendererType"]
|
||||
.SetValue(GetRendererType() == Point ? "Circle"
|
||||
: GetRendererType() == Line ? "Line"
|
||||
: "Image")
|
||||
.SetType("choice")
|
||||
.AddExtraInfo("Circle")
|
||||
.AddExtraInfo("Line")
|
||||
.AddExtraInfo("Image")
|
||||
.SetLabel(_("Particle type"))
|
||||
.SetHasImpactOnOtherProperties(true);
|
||||
|
||||
if (GetRendererType() == Quad) {
|
||||
objectProperties["textureParticleName"]
|
||||
.SetValue(GetParticleTexture())
|
||||
.SetType("resource")
|
||||
.AddExtraInfo("image")
|
||||
.SetLabel(_("Texture"));
|
||||
|
||||
objectProperties["particlesWidth"]
|
||||
.SetValue(gd::String::From(GetRendererParam1()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Width"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetGroup(_("Particles size"));
|
||||
|
||||
objectProperties["particlesHeight"]
|
||||
.SetValue(gd::String::From(GetRendererParam2()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Height"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetGroup(_("Particles size"));
|
||||
} else if (GetRendererType() == Line) {
|
||||
objectProperties["lineLength"]
|
||||
.SetValue(gd::String::From(GetRendererParam1()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Lines length"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetGroup(_("Particles size"));
|
||||
|
||||
objectProperties["lineThickness"]
|
||||
.SetValue(gd::String::From(GetRendererParam2()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Lines thickness"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetGroup(_("Particles size"));
|
||||
} else {
|
||||
objectProperties["particlesSize"]
|
||||
.SetValue(gd::String::From(GetRendererParam1()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Size"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetGroup(_("Particles size"));
|
||||
}
|
||||
|
||||
objectProperties["particlesStartSize"]
|
||||
.SetValue(gd::String::From(GetParticleSize1()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Start size (in percents)"))
|
||||
.SetGroup(_("Particles size"));
|
||||
|
||||
objectProperties["particlesEndSize"]
|
||||
.SetValue(gd::String::From(GetParticleSize2()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("End size (in percents)"))
|
||||
.SetGroup(_("Particles size"));
|
||||
|
||||
objectProperties["particlesStartColor"]
|
||||
.SetValue(GetParticleColor1())
|
||||
.SetType("color")
|
||||
.SetLabel(_("Start color"))
|
||||
.SetGroup(_("Particles color"));
|
||||
|
||||
objectProperties["particlesEndColor"]
|
||||
.SetValue(GetParticleColor2())
|
||||
.SetType("color")
|
||||
.SetLabel(_("End color"))
|
||||
.SetGroup(_("Particles color"));
|
||||
|
||||
objectProperties["particlesStartOpacity"]
|
||||
.SetValue(gd::String::From(GetParticleAlpha1()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Start opacity (0-255)"))
|
||||
.SetGroup(_("Particles color"));
|
||||
|
||||
objectProperties["particlesEndOpacity"]
|
||||
.SetValue(gd::String::From(GetParticleAlpha2()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("End opacity (0-255)"))
|
||||
.SetGroup(_("Particles color"));
|
||||
|
||||
objectProperties["additiveRendering"]
|
||||
.SetValue(IsRenderingAdditive() ? "true" : "false")
|
||||
.SetType("boolean")
|
||||
.SetLabel(_("Additive rendering"))
|
||||
.SetGroup(_("Particles color"));
|
||||
|
||||
objectProperties["deleteWhenOutOfParticles"]
|
||||
.SetValue(GetDestroyWhenNoParticles() ? "true" : "false")
|
||||
.SetType("boolean")
|
||||
.SetLabel(_("Delete when out of particles"))
|
||||
.SetGroup(_("Particles flow"));
|
||||
|
||||
objectProperties["maxParticlesCount"]
|
||||
.SetValue(gd::String::From(GetMaxParticleNb()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Max particles count"))
|
||||
.SetGroup(_("Particles flow"));
|
||||
|
||||
objectProperties["tank"]
|
||||
.SetValue(gd::String::From(GetTank()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Tank"))
|
||||
.SetGroup(_("Particles flow"))
|
||||
.AddExtraInfo("canBeUnlimitedUsingMinus1");
|
||||
|
||||
objectProperties["flow"]
|
||||
.SetValue(gd::String::From(GetFlow()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Flow"))
|
||||
.SetGroup(_("Particles flow (particles/seconds)"));
|
||||
|
||||
objectProperties["emitterForceMin"]
|
||||
.SetValue(gd::String::From(GetEmitterForceMin()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Emitter force min"))
|
||||
.SetGroup(_("Particles movement"));
|
||||
|
||||
objectProperties["emitterForceMax"]
|
||||
.SetValue(gd::String::From(GetEmitterForceMax()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Emitter force max"))
|
||||
.SetGroup(_("Particles movement"));
|
||||
|
||||
objectProperties["particleRotationSpeedMin"]
|
||||
.SetValue(gd::String::From(GetParticleAngle1()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Minimum rotation speed"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetDegreeAngle())
|
||||
.SetGroup(_("Particles movement"));
|
||||
|
||||
objectProperties["particleRotationSpeedMax"]
|
||||
.SetValue(gd::String::From(GetParticleAngle2()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Maximum rotation speed"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetDegreeAngle())
|
||||
.SetGroup(_("Particles movement"));
|
||||
|
||||
objectProperties["coneSprayAngle"]
|
||||
.SetValue(gd::String::From(GetConeSprayAngle()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Cone spray angle"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetDegreeAngle())
|
||||
.SetGroup(_("Particles movement"));
|
||||
|
||||
objectProperties["zoneRadius"]
|
||||
.SetValue(gd::String::From(GetZoneRadius()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Emitter radius"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetGroup(_("Particles movement"));
|
||||
|
||||
objectProperties["particleGravityX"]
|
||||
.SetValue(gd::String::From(GetParticleGravityX()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Gravity X"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetGroup(_("Particles gravity"));
|
||||
|
||||
objectProperties["particleGravityY"]
|
||||
.SetValue(gd::String::From(GetParticleGravityY()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Gravity Y"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetGroup(_("Particles gravity"));
|
||||
|
||||
objectProperties["particleLifeTimeMin"]
|
||||
.SetValue(gd::String::From(GetParticleLifeTimeMin()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Minimum lifetime"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetSecond())
|
||||
.SetGroup(_("Particles life time"));
|
||||
|
||||
objectProperties["particleLifeTimeMax"]
|
||||
.SetValue(gd::String::From(GetParticleLifeTimeMax()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Maximum lifetime"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetSecond())
|
||||
.SetGroup(_("Particles life time"));
|
||||
|
||||
objectProperties["jumpForwardInTimeOnCreation"]
|
||||
.SetValue(gd::String::From(GetJumpForwardInTimeOnCreation()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Jump forward in time on creation"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetSecond())
|
||||
.SetGroup(_("Particles life time"));
|
||||
|
||||
return objectProperties;
|
||||
}
|
||||
|
||||
void ParticleEmitterObject::DoUnserializeFrom(
|
||||
gd::Project& project, const gd::SerializerElement& element) {
|
||||
ParticleEmitterBase::UnserializeParticleEmitterBaseFrom(element);
|
||||
@@ -75,12 +406,27 @@ void ParticleEmitterBase::UnserializeParticleEmitterBaseFrom(
|
||||
particleGravityY = element.GetDoubleAttribute("particleGravityY");
|
||||
particleLifeTimeMin = element.GetDoubleAttribute("particleLifeTimeMin");
|
||||
particleLifeTimeMax = element.GetDoubleAttribute("particleLifeTimeMax");
|
||||
particleRed1 = element.GetDoubleAttribute("particleRed1");
|
||||
particleRed2 = element.GetDoubleAttribute("particleRed2");
|
||||
particleGreen1 = element.GetDoubleAttribute("particleGreen1");
|
||||
particleGreen2 = element.GetDoubleAttribute("particleGreen2");
|
||||
particleBlue1 = element.GetDoubleAttribute("particleBlue1");
|
||||
particleBlue2 = element.GetDoubleAttribute("particleBlue2");
|
||||
|
||||
particleColor1 = element.GetStringAttribute("particleColor1");
|
||||
// Compatibility with GD <= 5.4.210
|
||||
if (element.HasChild("particleRed1") && !element.HasChild("particleColor1")) {
|
||||
particleColor1 =
|
||||
element.GetChild("particleRed1").GetValue().GetString() + ";" +
|
||||
element.GetChild("particleGreen1").GetValue().GetString() + ";" +
|
||||
element.GetChild("particleBlue1").GetValue().GetString();
|
||||
}
|
||||
// end of compatibility code
|
||||
|
||||
particleColor2 = element.GetStringAttribute("particleColor2");
|
||||
// Compatibility with GD <= 5.4.210
|
||||
if (element.HasChild("particleRed2") && !element.HasChild("particleColor2")) {
|
||||
particleColor2 =
|
||||
element.GetChild("particleRed2").GetValue().GetString() + ";" +
|
||||
element.GetChild("particleGreen2").GetValue().GetString() + ";" +
|
||||
element.GetChild("particleBlue2").GetValue().GetString();
|
||||
}
|
||||
// end of compatibility code
|
||||
|
||||
particleAlpha1 = element.GetDoubleAttribute("particleAlpha1");
|
||||
particleAlpha2 = element.GetDoubleAttribute("particleAlpha2");
|
||||
rendererParam1 = element.GetDoubleAttribute("rendererParam1");
|
||||
@@ -106,7 +452,8 @@ void ParticleEmitterBase::UnserializeParticleEmitterBaseFrom(
|
||||
element.GetBoolAttribute("destroyWhenNoParticles", false);
|
||||
textureParticleName = element.GetStringAttribute("textureParticleName");
|
||||
maxParticleNb = element.GetIntAttribute("maxParticleNb", 5000);
|
||||
jumpForwardInTimeOnCreation = element.GetDoubleAttribute("jumpForwardInTimeOnCreation");
|
||||
jumpForwardInTimeOnCreation =
|
||||
element.GetDoubleAttribute("jumpForwardInTimeOnCreation");
|
||||
|
||||
{
|
||||
gd::String result = element.GetStringAttribute("rendererType");
|
||||
@@ -137,12 +484,8 @@ void ParticleEmitterBase::SerializeParticleEmitterBaseTo(
|
||||
element.SetAttribute("particleGravityY", particleGravityY);
|
||||
element.SetAttribute("particleLifeTimeMin", particleLifeTimeMin);
|
||||
element.SetAttribute("particleLifeTimeMax", particleLifeTimeMax);
|
||||
element.SetAttribute("particleRed1", particleRed1);
|
||||
element.SetAttribute("particleRed2", particleRed2);
|
||||
element.SetAttribute("particleGreen1", particleGreen1);
|
||||
element.SetAttribute("particleGreen2", particleGreen2);
|
||||
element.SetAttribute("particleBlue1", particleBlue1);
|
||||
element.SetAttribute("particleBlue2", particleBlue2);
|
||||
element.SetAttribute("particleColor1", particleColor1);
|
||||
element.SetAttribute("particleColor2", particleColor2);
|
||||
element.SetAttribute("particleAlpha1", particleAlpha1);
|
||||
element.SetAttribute("particleAlpha2", particleAlpha2);
|
||||
element.SetAttribute("particleSize1", particleSize1);
|
||||
@@ -161,7 +504,8 @@ void ParticleEmitterBase::SerializeParticleEmitterBaseTo(
|
||||
element.SetAttribute("destroyWhenNoParticles", destroyWhenNoParticles);
|
||||
element.SetAttribute("textureParticleName", textureParticleName);
|
||||
element.SetAttribute("maxParticleNb", (int)maxParticleNb);
|
||||
element.SetAttribute("jumpForwardInTimeOnCreation", jumpForwardInTimeOnCreation);
|
||||
element.SetAttribute("jumpForwardInTimeOnCreation",
|
||||
jumpForwardInTimeOnCreation);
|
||||
|
||||
gd::String rendererTypeStr = "Point";
|
||||
if (rendererType == Line)
|
||||
@@ -169,6 +513,26 @@ void ParticleEmitterBase::SerializeParticleEmitterBaseTo(
|
||||
else if (rendererType == Quad)
|
||||
rendererTypeStr = "Quad";
|
||||
element.SetAttribute("rendererType", rendererTypeStr);
|
||||
|
||||
// Still serialize the old particle color components for compatibility with GDevelop <= 5.4.210.
|
||||
// Remove this in a few releases (or when hex strings are accepted for the color).
|
||||
{
|
||||
auto rgb = particleColor1.Split(';');
|
||||
if (rgb.size() == 3) {
|
||||
element.SetAttribute("particleRed1", rgb[0].To<double>());
|
||||
element.SetAttribute("particleGreen1", rgb[1].To<double>());
|
||||
element.SetAttribute("particleBlue1", rgb[2].To<double>());
|
||||
}
|
||||
}
|
||||
{
|
||||
auto rgb = particleColor2.Split(';');
|
||||
if (rgb.size() == 3) {
|
||||
element.SetAttribute("particleRed2", rgb[0].To<double>());
|
||||
element.SetAttribute("particleGreen2", rgb[1].To<double>());
|
||||
element.SetAttribute("particleBlue2", rgb[2].To<double>());
|
||||
}
|
||||
}
|
||||
// end of compatibility code
|
||||
}
|
||||
|
||||
ParticleEmitterBase::~ParticleEmitterBase() {}
|
||||
@@ -227,26 +591,6 @@ double ParticleEmitterBase::GetParticleGravityLength() const {
|
||||
GetParticleGravityX() * GetParticleGravityX());
|
||||
}
|
||||
|
||||
void ParticleEmitterBase::SetParticleColor1(const gd::String& color) {
|
||||
std::vector<gd::String> colors = color.Split(U';');
|
||||
|
||||
if (colors.size() < 3) return; // Color is incorrect
|
||||
|
||||
SetParticleRed1(colors[0].To<int>());
|
||||
SetParticleGreen1(colors[1].To<int>());
|
||||
SetParticleBlue1(colors[2].To<int>());
|
||||
}
|
||||
|
||||
void ParticleEmitterBase::SetParticleColor2(const gd::String& color) {
|
||||
std::vector<gd::String> colors = color.Split(U';');
|
||||
|
||||
if (colors.size() < 3) return; // Color is incorrect
|
||||
|
||||
SetParticleRed2(colors[0].To<int>());
|
||||
SetParticleGreen2(colors[1].To<int>());
|
||||
SetParticleBlue2(colors[2].To<int>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by copy constructor and assignment operator.
|
||||
* \warning Do not forget to update me if members were changed!
|
||||
@@ -269,12 +613,8 @@ void ParticleEmitterBase::Init(const ParticleEmitterBase& other) {
|
||||
particleGravityY = other.particleGravityY;
|
||||
particleLifeTimeMin = other.particleLifeTimeMin;
|
||||
particleLifeTimeMax = other.particleLifeTimeMax;
|
||||
particleRed1 = other.particleRed1;
|
||||
particleRed2 = other.particleRed2;
|
||||
particleGreen1 = other.particleGreen1;
|
||||
particleGreen2 = other.particleGreen2;
|
||||
particleBlue1 = other.particleBlue1;
|
||||
particleBlue2 = other.particleBlue2;
|
||||
particleColor1 = other.particleColor1;
|
||||
particleColor2 = other.particleColor2;
|
||||
particleAlpha1 = other.particleAlpha1;
|
||||
particleAlpha2 = other.particleAlpha2;
|
||||
particleSize1 = other.particleSize1;
|
||||
|
@@ -48,15 +48,9 @@ class GD_EXTENSION_API ParticleEmitterBase {
|
||||
void SetParticleGravityAngle(double newAngleInDegree);
|
||||
void SetParticleGravityLength(double newLength);
|
||||
|
||||
void SetParticleColor1(const gd::String& color);
|
||||
void SetParticleColor2(const gd::String& color);
|
||||
void SetParticleColor1(const gd::String& color) { particleColor1 = color; };
|
||||
void SetParticleColor2(const gd::String& color) { particleColor2 = color; };
|
||||
|
||||
void SetParticleRed1(double newValue) { particleRed1 = newValue; };
|
||||
void SetParticleRed2(double newValue) { particleRed2 = newValue; };
|
||||
void SetParticleGreen1(double newValue) { particleGreen1 = newValue; };
|
||||
void SetParticleGreen2(double newValue) { particleGreen2 = newValue; };
|
||||
void SetParticleBlue1(double newValue) { particleBlue1 = newValue; };
|
||||
void SetParticleBlue2(double newValue) { particleBlue2 = newValue; };
|
||||
void SetParticleAlpha1(double newValue) { particleAlpha1 = newValue; };
|
||||
void SetParticleAlpha2(double newValue) { particleAlpha2 = newValue; };
|
||||
void SetParticleSize1(double newValue) { particleSize1 = newValue; };
|
||||
@@ -91,7 +85,7 @@ class GD_EXTENSION_API ParticleEmitterBase {
|
||||
void SetDestroyWhenNoParticles(bool enable = true) {
|
||||
destroyWhenNoParticles = enable;
|
||||
};
|
||||
void SetJumpForwardInTimeOnCreation(double newValue) {
|
||||
void SetJumpForwardInTimeOnCreation(double newValue) {
|
||||
jumpForwardInTimeOnCreation = newValue;
|
||||
};
|
||||
|
||||
@@ -114,12 +108,8 @@ class GD_EXTENSION_API ParticleEmitterBase {
|
||||
std::size_t GetMaxParticleNb() const { return maxParticleNb; };
|
||||
bool GetDestroyWhenNoParticles() const { return destroyWhenNoParticles; };
|
||||
|
||||
double GetParticleRed1() const { return particleRed1; };
|
||||
double GetParticleRed2() const { return particleRed2; };
|
||||
double GetParticleGreen1() const { return particleGreen1; };
|
||||
double GetParticleGreen2() const { return particleGreen2; };
|
||||
double GetParticleBlue1() const { return particleBlue1; };
|
||||
double GetParticleBlue2() const { return particleBlue2; };
|
||||
const gd::String& GetParticleColor1() const { return particleColor1; };
|
||||
const gd::String& GetParticleColor2() const { return particleColor2; };
|
||||
double GetParticleAlpha1() const { return particleAlpha1; };
|
||||
double GetParticleAlpha2() const { return particleAlpha2; };
|
||||
double GetParticleSize1() const { return particleSize1; };
|
||||
@@ -146,7 +136,7 @@ class GD_EXTENSION_API ParticleEmitterBase {
|
||||
void SetRendererType(RendererType type) { rendererType = type; };
|
||||
RendererType GetRendererType() const { return rendererType; };
|
||||
|
||||
bool IsRenderingAdditive() { return additive; };
|
||||
bool IsRenderingAdditive() const { return additive; };
|
||||
void SetRenderingAdditive() { additive = true; };
|
||||
void SetRenderingAlpha() { additive = false; };
|
||||
|
||||
@@ -173,8 +163,9 @@ class GD_EXTENSION_API ParticleEmitterBase {
|
||||
double zoneRadius;
|
||||
double particleGravityX, particleGravityY;
|
||||
double particleLifeTimeMin, particleLifeTimeMax;
|
||||
double particleRed1, particleRed2, particleGreen1, particleGreen2,
|
||||
particleBlue1, particleBlue2, particleAlpha1, particleAlpha2;
|
||||
gd::String particleColor1;
|
||||
gd::String particleColor2;
|
||||
double particleAlpha1, particleAlpha2;
|
||||
double particleSize1, particleSize2, particleAngle1, particleAngle2;
|
||||
double particleAlphaRandomness1, particleAlphaRandomness2;
|
||||
double particleSizeRandomness1, particleSizeRandomness2,
|
||||
@@ -194,16 +185,22 @@ class GD_EXTENSION_API ParticleEmitterObject : public gd::ObjectConfiguration,
|
||||
public:
|
||||
ParticleEmitterObject();
|
||||
virtual ~ParticleEmitterObject(){};
|
||||
virtual std::unique_ptr<gd::ObjectConfiguration> Clone() const {
|
||||
virtual std::unique_ptr<gd::ObjectConfiguration> Clone() const override {
|
||||
return gd::make_unique<ParticleEmitterObject>(*this);
|
||||
}
|
||||
|
||||
virtual void ExposeResources(gd::ArbitraryResourceWorker& worker);
|
||||
virtual void ExposeResources(gd::ArbitraryResourceWorker& worker) override;
|
||||
|
||||
virtual std::map<gd::String, gd::PropertyDescriptor>
|
||||
GetProperties() const override;
|
||||
|
||||
virtual bool UpdateProperty(const gd::String &name,
|
||||
const gd::String &value) override;
|
||||
|
||||
private:
|
||||
virtual void DoUnserializeFrom(gd::Project& project,
|
||||
const gd::SerializerElement& element);
|
||||
virtual void DoSerializeTo(gd::SerializerElement& element) const;
|
||||
const gd::SerializerElement& element) override;
|
||||
virtual void DoSerializeTo(gd::SerializerElement& element) const override;
|
||||
};
|
||||
|
||||
#endif // PARTICLEEMITTEROBJECT_H
|
||||
|
@@ -146,17 +146,13 @@ namespace gdjs {
|
||||
{
|
||||
time: 0,
|
||||
value: gdjs.rgbToHex(
|
||||
objectData.particleRed1,
|
||||
objectData.particleGreen1,
|
||||
objectData.particleBlue1
|
||||
...gdjs.rgbOrHexToRGBColor(objectData.particleColor1)
|
||||
),
|
||||
},
|
||||
{
|
||||
time: 1,
|
||||
value: gdjs.rgbToHex(
|
||||
objectData.particleRed2,
|
||||
objectData.particleGreen2,
|
||||
objectData.particleBlue2
|
||||
...gdjs.rgbOrHexToRGBColor(objectData.particleColor2)
|
||||
),
|
||||
},
|
||||
],
|
||||
@@ -292,31 +288,30 @@ namespace gdjs {
|
||||
moveAcceleration.maxStart < 0);
|
||||
}
|
||||
|
||||
setColor(
|
||||
r1: number,
|
||||
g1: number,
|
||||
b1: number,
|
||||
r2: number,
|
||||
g2: number,
|
||||
b2: number
|
||||
): void {
|
||||
setColor(color1: number, color2: number): void {
|
||||
// console.log({color1,color2})
|
||||
// debugger;
|
||||
// Access private members of the behavior to apply changes right away.
|
||||
const behavior: any = this.emitter.getBehavior('color');
|
||||
const first = behavior.list.first;
|
||||
|
||||
const startColor = first.value;
|
||||
startColor.r = r1;
|
||||
startColor.g = g1;
|
||||
startColor.b = b1;
|
||||
{
|
||||
const [r, g, b] = gdjs.hexNumberToRGBArray(color1);
|
||||
first.value.r = r;
|
||||
first.value.g = g;
|
||||
first.value.b = b;
|
||||
}
|
||||
|
||||
first.next = first.next || {
|
||||
time: 1,
|
||||
value: {},
|
||||
};
|
||||
const endColor = first.next.value;
|
||||
endColor.r = r2;
|
||||
endColor.g = g2;
|
||||
endColor.b = b2;
|
||||
{
|
||||
const [r, g, b] = gdjs.hexNumberToRGBArray(color2);
|
||||
first.next.value.r = r;
|
||||
first.next.value.g = g;
|
||||
first.next.value.b = b;
|
||||
}
|
||||
}
|
||||
|
||||
setSize(size1: float, size2: float): void {
|
||||
|
@@ -21,12 +21,8 @@ namespace gdjs {
|
||||
particleLifeTimeMin: number;
|
||||
particleGravityY: number;
|
||||
particleGravityX: number;
|
||||
particleRed2: number;
|
||||
particleRed1: number;
|
||||
particleGreen2: number;
|
||||
particleGreen1: number;
|
||||
particleBlue2: number;
|
||||
particleBlue1: number;
|
||||
particleColor2: string;
|
||||
particleColor1: string;
|
||||
particleSize2: number;
|
||||
particleSize1: number;
|
||||
/**
|
||||
@@ -83,12 +79,8 @@ namespace gdjs {
|
||||
gravx: number;
|
||||
gravy: number;
|
||||
// Color
|
||||
colr1: number;
|
||||
colr2: number;
|
||||
colg1: number;
|
||||
colg2: number;
|
||||
colb1: number;
|
||||
colb2: number;
|
||||
color1: number;
|
||||
color2: number;
|
||||
// Size
|
||||
size1: number;
|
||||
size2: number;
|
||||
@@ -124,12 +116,8 @@ namespace gdjs {
|
||||
lifeTimeMax: float;
|
||||
gravityX: number;
|
||||
gravityY: number;
|
||||
colorR1: number;
|
||||
colorR2: number;
|
||||
colorG1: number;
|
||||
colorG2: number;
|
||||
colorB1: number;
|
||||
colorB2: number;
|
||||
color1: number;
|
||||
color2: number;
|
||||
size1: number;
|
||||
size2: number;
|
||||
alpha1: number;
|
||||
@@ -195,12 +183,12 @@ namespace gdjs {
|
||||
this.lifeTimeMax = particleObjectData.particleLifeTimeMax;
|
||||
this.gravityX = particleObjectData.particleGravityX;
|
||||
this.gravityY = particleObjectData.particleGravityY;
|
||||
this.colorR1 = particleObjectData.particleRed1;
|
||||
this.colorR2 = particleObjectData.particleRed2;
|
||||
this.colorG1 = particleObjectData.particleGreen1;
|
||||
this.colorG2 = particleObjectData.particleGreen2;
|
||||
this.colorB1 = particleObjectData.particleBlue1;
|
||||
this.colorB2 = particleObjectData.particleBlue2;
|
||||
this.color1 = gdjs.rgbOrHexStringToNumber(
|
||||
particleObjectData.particleColor1
|
||||
);
|
||||
this.color2 = gdjs.rgbOrHexStringToNumber(
|
||||
particleObjectData.particleColor2
|
||||
);
|
||||
this.size1 = particleObjectData.particleSize1;
|
||||
this.size2 = particleObjectData.particleSize2;
|
||||
this.alpha1 = particleObjectData.particleAlpha1;
|
||||
@@ -296,23 +284,11 @@ namespace gdjs {
|
||||
if (oldObjectData.particleGravityY !== newObjectData.particleGravityY) {
|
||||
this.setParticleGravityY(newObjectData.particleGravityY);
|
||||
}
|
||||
if (oldObjectData.particleRed1 !== newObjectData.particleRed1) {
|
||||
this.setParticleRed1(newObjectData.particleRed1);
|
||||
if (oldObjectData.particleColor1 !== newObjectData.particleColor1) {
|
||||
this.setParticleColor1(newObjectData.particleColor1);
|
||||
}
|
||||
if (oldObjectData.particleRed2 !== newObjectData.particleRed2) {
|
||||
this.setParticleRed2(newObjectData.particleRed2);
|
||||
}
|
||||
if (oldObjectData.particleGreen1 !== newObjectData.particleGreen1) {
|
||||
this.setParticleGreen1(newObjectData.particleGreen1);
|
||||
}
|
||||
if (oldObjectData.particleGreen2 !== newObjectData.particleGreen2) {
|
||||
this.setParticleGreen2(newObjectData.particleGreen2);
|
||||
}
|
||||
if (oldObjectData.particleBlue1 !== newObjectData.particleBlue1) {
|
||||
this.setParticleBlue1(newObjectData.particleBlue1);
|
||||
}
|
||||
if (oldObjectData.particleBlue2 !== newObjectData.particleBlue2) {
|
||||
this.setParticleBlue2(newObjectData.particleBlue2);
|
||||
if (oldObjectData.particleColor2 !== newObjectData.particleColor2) {
|
||||
this.setParticleColor2(newObjectData.particleColor2);
|
||||
}
|
||||
if (oldObjectData.particleSize1 !== newObjectData.particleSize1) {
|
||||
this.setParticleSize1(newObjectData.particleSize1);
|
||||
@@ -397,12 +373,8 @@ namespace gdjs {
|
||||
ltmax: this.lifeTimeMax,
|
||||
gravx: this.gravityX,
|
||||
gravy: this.gravityY,
|
||||
colr1: this.colorR1,
|
||||
colr2: this.colorR2,
|
||||
colg1: this.colorG1,
|
||||
colg2: this.colorG2,
|
||||
colb1: this.colorB1,
|
||||
colb2: this.colorB2,
|
||||
color1: this.color1,
|
||||
color2: this.color2,
|
||||
size1: this.size1,
|
||||
size2: this.size2,
|
||||
alp1: this.alpha1,
|
||||
@@ -463,23 +435,11 @@ namespace gdjs {
|
||||
if (syncData.gravy !== undefined) {
|
||||
this.setParticleGravityY(syncData.gravy);
|
||||
}
|
||||
if (syncData.colr1 !== undefined) {
|
||||
this.setParticleRed1(syncData.colr1);
|
||||
if (syncData.color1 !== undefined) {
|
||||
this.setParticleColor1AsNumber(syncData.color1);
|
||||
}
|
||||
if (syncData.colr2 !== undefined) {
|
||||
this.setParticleRed2(syncData.colr2);
|
||||
}
|
||||
if (syncData.colg1 !== undefined) {
|
||||
this.setParticleGreen1(syncData.colg1);
|
||||
}
|
||||
if (syncData.colg2 !== undefined) {
|
||||
this.setParticleGreen2(syncData.colg2);
|
||||
}
|
||||
if (syncData.colb1 !== undefined) {
|
||||
this.setParticleBlue1(syncData.colb1);
|
||||
}
|
||||
if (syncData.colb2 !== undefined) {
|
||||
this.setParticleBlue2(syncData.colb2);
|
||||
if (syncData.color2 !== undefined) {
|
||||
this.setParticleColor2AsNumber(syncData.color2);
|
||||
}
|
||||
if (syncData.size1 !== undefined) {
|
||||
this.setParticleSize1(syncData.size1);
|
||||
@@ -547,14 +507,7 @@ namespace gdjs {
|
||||
this._renderer.setGravity(this.gravityX, this.gravityY);
|
||||
}
|
||||
if (this._colorDirty) {
|
||||
this._renderer.setColor(
|
||||
this.colorR1,
|
||||
this.colorG1,
|
||||
this.colorB1,
|
||||
this.colorR2,
|
||||
this.colorG2,
|
||||
this.colorB2
|
||||
);
|
||||
this._renderer.setColor(this.color1, this.color2);
|
||||
}
|
||||
if (this._sizeDirty) {
|
||||
this._renderer.setSize(this.size1, this.size2);
|
||||
@@ -820,7 +773,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
getParticleRed1(): number {
|
||||
return this.colorR1;
|
||||
return gdjs.hexNumberToRGBArray(this.color1)[0];
|
||||
}
|
||||
|
||||
setParticleRed1(red: number): void {
|
||||
@@ -830,14 +783,14 @@ namespace gdjs {
|
||||
if (red > 255) {
|
||||
red = 255;
|
||||
}
|
||||
if (this.colorR1 !== red) {
|
||||
this._colorDirty = true;
|
||||
this.colorR1 = red;
|
||||
}
|
||||
const existingColor = gdjs.hexNumberToRGBArray(this.color1);
|
||||
this.setParticleColor1AsNumber(
|
||||
gdjs.rgbToHexNumber(red, existingColor[1], existingColor[2])
|
||||
);
|
||||
}
|
||||
|
||||
getParticleRed2(): number {
|
||||
return this.colorR2;
|
||||
return gdjs.hexNumberToRGBArray(this.color2)[0];
|
||||
}
|
||||
|
||||
setParticleRed2(red: number): void {
|
||||
@@ -847,14 +800,14 @@ namespace gdjs {
|
||||
if (red > 255) {
|
||||
red = 255;
|
||||
}
|
||||
if (this.colorR2 !== red) {
|
||||
this._colorDirty = true;
|
||||
this.colorR2 = red;
|
||||
}
|
||||
const existingColor = gdjs.hexNumberToRGBArray(this.color2);
|
||||
this.setParticleColor2AsNumber(
|
||||
gdjs.rgbToHexNumber(red, existingColor[1], existingColor[2])
|
||||
);
|
||||
}
|
||||
|
||||
getParticleGreen1(): number {
|
||||
return this.colorG1;
|
||||
return gdjs.hexNumberToRGBArray(this.color1)[1];
|
||||
}
|
||||
|
||||
setParticleGreen1(green: number): void {
|
||||
@@ -864,14 +817,14 @@ namespace gdjs {
|
||||
if (green > 255) {
|
||||
green = 255;
|
||||
}
|
||||
if (this.colorG1 !== green) {
|
||||
this._colorDirty = true;
|
||||
this.colorG1 = green;
|
||||
}
|
||||
const existingColor = gdjs.hexNumberToRGBArray(this.color1);
|
||||
this.setParticleColor1AsNumber(
|
||||
gdjs.rgbToHexNumber(existingColor[0], green, existingColor[2])
|
||||
);
|
||||
}
|
||||
|
||||
getParticleGreen2(): number {
|
||||
return this.colorG2;
|
||||
return gdjs.hexNumberToRGBArray(this.color2)[1];
|
||||
}
|
||||
|
||||
setParticleGreen2(green: number): void {
|
||||
@@ -881,14 +834,14 @@ namespace gdjs {
|
||||
if (green > 255) {
|
||||
green = 255;
|
||||
}
|
||||
if (this.colorG2 !== green) {
|
||||
this._colorDirty = true;
|
||||
this.colorG2 = green;
|
||||
}
|
||||
const existingColor = gdjs.hexNumberToRGBArray(this.color2);
|
||||
this.setParticleColor2AsNumber(
|
||||
gdjs.rgbToHexNumber(existingColor[0], green, existingColor[2])
|
||||
);
|
||||
}
|
||||
|
||||
getParticleBlue1(): number {
|
||||
return this.colorB1;
|
||||
return gdjs.hexNumberToRGBArray(this.color1)[2];
|
||||
}
|
||||
|
||||
setParticleBlue1(blue: number): void {
|
||||
@@ -898,14 +851,14 @@ namespace gdjs {
|
||||
if (blue > 255) {
|
||||
blue = 255;
|
||||
}
|
||||
if (this.colorB1 !== blue) {
|
||||
this._colorDirty = true;
|
||||
this.colorB1 = blue;
|
||||
}
|
||||
const existingColor = gdjs.hexNumberToRGBArray(this.color1);
|
||||
this.setParticleColor1AsNumber(
|
||||
gdjs.rgbToHexNumber(existingColor[0], existingColor[1], blue)
|
||||
);
|
||||
}
|
||||
|
||||
getParticleBlue2(): number {
|
||||
return this.colorB2;
|
||||
return gdjs.hexNumberToRGBArray(this.color2)[2];
|
||||
}
|
||||
|
||||
setParticleBlue2(blue: number): void {
|
||||
@@ -915,24 +868,32 @@ namespace gdjs {
|
||||
if (blue > 255) {
|
||||
blue = 255;
|
||||
}
|
||||
if (this.colorB2 !== blue) {
|
||||
this._colorDirty = true;
|
||||
this.colorB2 = blue;
|
||||
}
|
||||
const existingColor = gdjs.hexNumberToRGBArray(this.color2);
|
||||
this.setParticleColor2AsNumber(
|
||||
gdjs.rgbToHexNumber(existingColor[0], existingColor[1], blue)
|
||||
);
|
||||
}
|
||||
|
||||
setParticleColor1(rgbColor: string): void {
|
||||
const colors = gdjs.rgbOrHexToRGBColor(rgbColor);
|
||||
this.setParticleRed1(colors[0]);
|
||||
this.setParticleGreen1(colors[1]);
|
||||
this.setParticleBlue1(colors[2]);
|
||||
setParticleColor1AsNumber(color: number): void {
|
||||
this.color1 = color;
|
||||
this._colorDirty = true;
|
||||
}
|
||||
|
||||
setParticleColor2(rgbColor: string): void {
|
||||
const colors = gdjs.rgbOrHexToRGBColor(rgbColor);
|
||||
this.setParticleRed2(colors[0]);
|
||||
this.setParticleGreen2(colors[1]);
|
||||
this.setParticleBlue2(colors[2]);
|
||||
setParticleColor1(rgbOrHexColor: string): void {
|
||||
this.setParticleColor1AsNumber(
|
||||
gdjs.rgbOrHexStringToNumber(rgbOrHexColor)
|
||||
);
|
||||
}
|
||||
|
||||
setParticleColor2AsNumber(color: number): void {
|
||||
this.color2 = color;
|
||||
this._colorDirty = true;
|
||||
}
|
||||
|
||||
setParticleColor2(rgbOrHexColor: string): void {
|
||||
this.setParticleColor2AsNumber(
|
||||
gdjs.rgbOrHexStringToNumber(rgbOrHexColor)
|
||||
);
|
||||
}
|
||||
|
||||
getParticleSize1(): float {
|
||||
|
@@ -70,28 +70,28 @@ std::map<gd::String, gd::PropertyDescriptor> PathfindingBehavior::GetProperties(
|
||||
.SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("angleOffset")));
|
||||
properties["CellWidth"]
|
||||
.SetLabel(_("Virtual cell width"))
|
||||
.SetLabel(_("Cell width"))
|
||||
.SetGroup(_("Virtual Grid"))
|
||||
.SetType("Number")
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("cellWidth", 0)));
|
||||
properties["CellHeight"]
|
||||
.SetLabel(_("Virtual cell height"))
|
||||
.SetLabel(_("Cell height"))
|
||||
.SetGroup(_("Virtual Grid"))
|
||||
.SetType("Number")
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetValue(gd::String::From(
|
||||
behaviorContent.GetDoubleAttribute("cellHeight", 0)));
|
||||
properties["GridOffsetX"]
|
||||
.SetLabel(_("Virtual grid X offset"))
|
||||
.SetLabel(_("X offset"))
|
||||
.SetGroup(_("Virtual Grid"))
|
||||
.SetType("Number")
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetValue(gd::String::From(
|
||||
behaviorContent.GetDoubleAttribute("gridOffsetX", 0)));
|
||||
properties["GridOffsetY"]
|
||||
.SetLabel(_("Virtual grid Y offset"))
|
||||
.SetLabel(_("Y offset"))
|
||||
.SetGroup(_("Virtual Grid"))
|
||||
.SetType("Number")
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
|
@@ -185,7 +185,12 @@ module.exports = {
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.addExtraInfo('Static')
|
||||
.addExtraInfo('Dynamic')
|
||||
.addExtraInfo('Kinematic');
|
||||
.addExtraInfo('Kinematic')
|
||||
.setDescription(
|
||||
_(
|
||||
"A static object won't move (perfect for obstacles). Dynamic objects can move. Kinematic will move according to forces applied to it only (useful for characters or specific mechanisms)."
|
||||
)
|
||||
);
|
||||
behaviorProperties
|
||||
.getOrCreate('bullet')
|
||||
.setValue(
|
||||
@@ -193,7 +198,14 @@ module.exports = {
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setType('Boolean')
|
||||
.setLabel('Bullet');
|
||||
.setLabel(_('Considered as a bullet'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Useful for fast moving objects which requires a more accurate collision detection.'
|
||||
)
|
||||
)
|
||||
.setGroup(_('Physics body advanced settings'))
|
||||
.setAdvanced(true);
|
||||
behaviorProperties
|
||||
.getOrCreate('fixedRotation')
|
||||
.setValue(
|
||||
@@ -203,7 +215,13 @@ module.exports = {
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setType('Boolean')
|
||||
.setLabel('Fixed Rotation');
|
||||
.setLabel('Fixed Rotation')
|
||||
.setDescription(
|
||||
_(
|
||||
"If enabled, the object won't rotate and will stay at the same angle. Useful for characters for example."
|
||||
)
|
||||
)
|
||||
.setGroup(_('Movement'));
|
||||
behaviorProperties
|
||||
.getOrCreate('canSleep')
|
||||
.setValue(
|
||||
@@ -211,7 +229,14 @@ module.exports = {
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setType('Boolean')
|
||||
.setLabel('Can Sleep');
|
||||
.setLabel(_('Can be put to sleep by the engine'))
|
||||
.setDescription(
|
||||
_(
|
||||
"Allows the physics engine to stop computing interaction with the object when it's not touched. It's recommended to keep this on."
|
||||
)
|
||||
)
|
||||
.setGroup(_('Physics body advanced settings'))
|
||||
.setAdvanced(true);
|
||||
behaviorProperties
|
||||
.getOrCreate('shape')
|
||||
.setValue(behaviorContent.getChild('shape').getStringValue())
|
||||
@@ -233,7 +258,8 @@ module.exports = {
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setLabel('Shape Dimension A')
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
behaviorProperties
|
||||
.getOrCreate('shapeDimensionB')
|
||||
.setValue(
|
||||
@@ -245,7 +271,8 @@ module.exports = {
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setLabel('Shape Dimension B')
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
behaviorProperties
|
||||
.getOrCreate('shapeOffsetX')
|
||||
.setValue(
|
||||
@@ -254,7 +281,8 @@ module.exports = {
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setLabel('Shape Offset X')
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
behaviorProperties
|
||||
.getOrCreate('shapeOffsetY')
|
||||
.setValue(
|
||||
@@ -263,7 +291,8 @@ module.exports = {
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setLabel('Shape Offset Y')
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
behaviorProperties
|
||||
.getOrCreate('polygonOrigin')
|
||||
.setValue(
|
||||
@@ -276,7 +305,8 @@ module.exports = {
|
||||
.addExtraInfo('Center')
|
||||
.addExtraInfo('Origin')
|
||||
.addExtraInfo('TopLeft')
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
behaviorProperties
|
||||
.getOrCreate('vertices')
|
||||
.setValue(
|
||||
@@ -285,28 +315,44 @@ module.exports = {
|
||||
: '[]'
|
||||
)
|
||||
.setLabel('Vertices')
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
behaviorProperties
|
||||
.getOrCreate('density')
|
||||
.setValue(
|
||||
behaviorContent.getChild('density').getDoubleValue().toString(10)
|
||||
)
|
||||
.setType('Number')
|
||||
.setLabel('Density');
|
||||
.setLabel(_('Density'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Define the weight of the object, according to its size. The biggeer the density, the heavier the object.'
|
||||
)
|
||||
);
|
||||
behaviorProperties
|
||||
.getOrCreate('friction')
|
||||
.setValue(
|
||||
behaviorContent.getChild('friction').getDoubleValue().toString(10)
|
||||
)
|
||||
.setType('Number')
|
||||
.setLabel('Friction');
|
||||
.setLabel(_('Friction'))
|
||||
.setDescription(
|
||||
_(
|
||||
'The friction applied when touching other objects. The higher the value, the more friction.'
|
||||
)
|
||||
);
|
||||
behaviorProperties
|
||||
.getOrCreate('restitution')
|
||||
.setValue(
|
||||
behaviorContent.getChild('restitution').getDoubleValue().toString(10)
|
||||
)
|
||||
.setType('Number')
|
||||
.setLabel('Restitution');
|
||||
.setLabel(_('Restitution'))
|
||||
.setDescription(
|
||||
_(
|
||||
'The "bounciness" of the object. The higher the value, the more other objects will bounce against it.'
|
||||
)
|
||||
);
|
||||
behaviorProperties
|
||||
.getOrCreate('linearDamping')
|
||||
.setValue(
|
||||
@@ -316,7 +362,9 @@ module.exports = {
|
||||
.toString(10)
|
||||
)
|
||||
.setType('Number')
|
||||
.setLabel('Linear Damping');
|
||||
.setLabel(_('Linear Damping'))
|
||||
.setGroup(_('Movement'));
|
||||
|
||||
behaviorProperties
|
||||
.getOrCreate('angularDamping')
|
||||
.setValue(
|
||||
@@ -326,8 +374,9 @@ module.exports = {
|
||||
.toString(10)
|
||||
)
|
||||
.setType('Number')
|
||||
.setLabel('Angular Damping')
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
|
||||
.setLabel(_('Angular Damping'))
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setGroup(_('Movement'));
|
||||
behaviorProperties
|
||||
.getOrCreate('gravityScale')
|
||||
.setValue(
|
||||
@@ -335,19 +384,23 @@ module.exports = {
|
||||
)
|
||||
.setType('Number')
|
||||
.setLabel('Gravity Scale')
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setGroup(_('Gravity'))
|
||||
.setAdvanced(true);
|
||||
behaviorProperties
|
||||
.getOrCreate('layers')
|
||||
.setValue(behaviorContent.getChild('layers').getIntValue().toString(10))
|
||||
.setType('Number')
|
||||
.setLabel('Layers')
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
behaviorProperties
|
||||
.getOrCreate('masks')
|
||||
.setValue(behaviorContent.getChild('masks').getIntValue().toString(10))
|
||||
.setType('Number')
|
||||
.setLabel('Masks')
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
|
||||
return behaviorProperties;
|
||||
};
|
||||
@@ -474,9 +527,8 @@ module.exports = {
|
||||
)
|
||||
.setIncludeFile('Extensions/Physics2Behavior/physics2runtimebehavior.js')
|
||||
.addIncludeFile('Extensions/Physics2Behavior/Box2D_v2.3.1_min.wasm.js')
|
||||
.addRequiredFile(
|
||||
'Extensions/Physics2Behavior/Box2D_v2.3.1_min.wasm.wasm'
|
||||
);
|
||||
.addRequiredFile('Extensions/Physics2Behavior/Box2D_v2.3.1_min.wasm.wasm')
|
||||
.setOpenFullEditorLabel(_('Edit shape and avanced settings'));
|
||||
|
||||
// Global
|
||||
aut
|
||||
|
@@ -13,18 +13,13 @@ This project is released under the MIT License.
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
#include "GDCore/Project/PropertyDescriptor.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
ShapePainterObjectBase::ShapePainterObjectBase()
|
||||
: fillColorR(255),
|
||||
fillColorG(255),
|
||||
fillColorB(255),
|
||||
fillOpacity(255),
|
||||
: fillOpacity(255),
|
||||
outlineSize(1),
|
||||
outlineColorR(0),
|
||||
outlineColorG(0),
|
||||
outlineColorB(0),
|
||||
outlineOpacity(255),
|
||||
clearBetweenFrames(true),
|
||||
absoluteCoordinates(false),
|
||||
@@ -42,19 +37,30 @@ void ShapePainterObjectBase::DoUnserializeFrom(
|
||||
.GetValue()
|
||||
.GetInt();
|
||||
|
||||
fillColorR =
|
||||
element.GetChild("fillColor", 0, "FillColor").GetIntAttribute("r");
|
||||
fillColorG =
|
||||
element.GetChild("fillColor", 0, "FillColor").GetIntAttribute("g");
|
||||
fillColorB =
|
||||
element.GetChild("fillColor", 0, "FillColor").GetIntAttribute("b");
|
||||
|
||||
outlineColorR =
|
||||
element.GetChild("outlineColor", 0, "OutlineColor").GetIntAttribute("r");
|
||||
outlineColorG =
|
||||
element.GetChild("outlineColor", 0, "OutlineColor").GetIntAttribute("g");
|
||||
outlineColorB =
|
||||
element.GetChild("outlineColor", 0, "OutlineColor").GetIntAttribute("b");
|
||||
const auto& fillColorElement = element.GetChild("fillColor", 0, "FillColor");
|
||||
if (fillColorElement.GetValue().IsString()) {
|
||||
fillColor = fillColorElement.GetStringValue();
|
||||
} else {
|
||||
// Compatibility with GD <= 5.4.212
|
||||
int fillColorR = fillColorElement.GetIntAttribute("r");
|
||||
int fillColorG = fillColorElement.GetIntAttribute("g");
|
||||
int fillColorB = fillColorElement.GetIntAttribute("b");
|
||||
fillColor = gd::String::From(fillColorR) + ";" + gd::String::From(fillColorG) + ";" + gd::String::From(fillColorB);
|
||||
// end of compatibility code
|
||||
}
|
||||
|
||||
const auto& outlineColorElement = element.GetChild("outlineColor", 0, "OutlineColor");
|
||||
if (outlineColorElement.GetValue().IsString()) {
|
||||
outlineColor = outlineColorElement.GetStringValue();
|
||||
} else {
|
||||
// Compatibility with GD <= 5.4.212
|
||||
int outlineColorR = outlineColorElement.GetIntAttribute("r");
|
||||
int outlineColorG = outlineColorElement.GetIntAttribute("g");
|
||||
int outlineColorB = outlineColorElement.GetIntAttribute("b");
|
||||
outlineColor = gd::String::From(outlineColorR) + ";" + gd::String::From(outlineColorG) + ";" + gd::String::From(outlineColorB);
|
||||
// end of compatibility code
|
||||
}
|
||||
|
||||
absoluteCoordinates =
|
||||
element.GetChild("absoluteCoordinates", 0, "AbsoluteCoordinates")
|
||||
@@ -80,34 +86,44 @@ void ShapePainterObjectBase::DoSerializeTo(
|
||||
element.AddChild("fillOpacity").SetValue(fillOpacity);
|
||||
element.AddChild("outlineSize").SetValue(outlineSize);
|
||||
element.AddChild("outlineOpacity").SetValue(outlineOpacity);
|
||||
element.AddChild("fillColor")
|
||||
.SetAttribute("r", (int)fillColorR)
|
||||
.SetAttribute("g", (int)fillColorG)
|
||||
.SetAttribute("b", (int)fillColorB);
|
||||
element.AddChild("outlineColor")
|
||||
.SetAttribute("r", (int)outlineColorR)
|
||||
.SetAttribute("g", (int)outlineColorG)
|
||||
.SetAttribute("b", (int)outlineColorB);
|
||||
element.AddChild("absoluteCoordinates").SetValue(absoluteCoordinates);
|
||||
element.AddChild("clearBetweenFrames").SetValue(clearBetweenFrames);
|
||||
element.AddChild("antialiasing").SetValue(antialiasing);
|
||||
|
||||
{
|
||||
auto rgb = fillColor.Split(';');
|
||||
auto& fillColorElement = element.AddChild("fillColor");
|
||||
if (rgb.size() == 3) {
|
||||
// Still serialize the old particle color components for compatibility with GDevelop <= 5.4.212.
|
||||
// Remove this in a few releases (or when hex strings are accepted for the color).
|
||||
fillColorElement.AddChild("r").SetValue(rgb[0].To<double>());
|
||||
fillColorElement.AddChild("g").SetValue(rgb[1].To<double>());
|
||||
fillColorElement.AddChild("b").SetValue(rgb[2].To<double>());
|
||||
// end of compatibility code
|
||||
} else {
|
||||
fillColorElement.SetValue(fillColor);
|
||||
}
|
||||
}
|
||||
{
|
||||
auto rgb = outlineColor.Split(';');
|
||||
auto& outlineColorElement = element.AddChild("outlineColor");
|
||||
if (rgb.size() == 3) {
|
||||
// Still serialize the old particle color components for compatibility with GDevelop <= 5.4.212.
|
||||
// Remove this in a few releases (or when hex strings are accepted for the color).
|
||||
outlineColorElement.AddChild("r").SetValue(rgb[0].To<double>());
|
||||
outlineColorElement.AddChild("g").SetValue(rgb[1].To<double>());
|
||||
outlineColorElement.AddChild("b").SetValue(rgb[2].To<double>());
|
||||
// end of compatibility code
|
||||
} else {
|
||||
outlineColorElement.SetValue(outlineColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShapePainterObject::DoSerializeTo(gd::SerializerElement& element) const {
|
||||
ShapePainterObjectBase::DoSerializeTo(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the color filter of the sprite object
|
||||
*/
|
||||
void ShapePainterObjectBase::SetFillColor(unsigned int r,
|
||||
unsigned int g,
|
||||
unsigned int b) {
|
||||
fillColorR = r;
|
||||
fillColorG = g;
|
||||
fillColorB = b;
|
||||
}
|
||||
|
||||
void ShapePainterObjectBase::SetFillOpacity(double val) {
|
||||
if (val > 255)
|
||||
val = 255;
|
||||
@@ -117,17 +133,6 @@ void ShapePainterObjectBase::SetFillOpacity(double val) {
|
||||
fillOpacity = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the color filter of the sprite object
|
||||
*/
|
||||
void ShapePainterObjectBase::SetOutlineColor(unsigned int r,
|
||||
unsigned int g,
|
||||
unsigned int b) {
|
||||
outlineColorR = r;
|
||||
outlineColorG = g;
|
||||
outlineColorB = b;
|
||||
}
|
||||
|
||||
void ShapePainterObjectBase::SetOutlineOpacity(double val) {
|
||||
if (val > 255)
|
||||
val = 255;
|
||||
@@ -137,26 +142,107 @@ void ShapePainterObjectBase::SetOutlineOpacity(double val) {
|
||||
outlineOpacity = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the fill color
|
||||
*/
|
||||
void ShapePainterObjectBase::SetFillColor(const gd::String& color) {
|
||||
std::vector<gd::String> colors = color.Split(U';');
|
||||
if (colors.size() < 3) return;
|
||||
bool ShapePainterObject::UpdateProperty(const gd::String& propertyName,
|
||||
const gd::String& newValue) {
|
||||
if (propertyName == "fillOpacity") {
|
||||
SetFillOpacity(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "fillColor") {
|
||||
SetFillColor(newValue);
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "outlineColor") {
|
||||
SetOutlineColor(newValue);
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "outlineOpacity") {
|
||||
SetOutlineOpacity(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "outlineSize") {
|
||||
SetOutlineSize(newValue.To<double>());
|
||||
return true;
|
||||
}
|
||||
|
||||
fillColorR = colors[0].To<int>();
|
||||
fillColorG = colors[1].To<int>();
|
||||
fillColorB = colors[2].To<int>();
|
||||
if (propertyName == "absoluteCoordinates") {
|
||||
if (newValue == "1")
|
||||
SetCoordinatesAbsolute();
|
||||
else
|
||||
SetCoordinatesRelative();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName == "clearBetweenFrames") {
|
||||
SetClearBetweenFrames(newValue == "1");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName == "antialiasing") {
|
||||
SetAntialiasing(newValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the color of the outline
|
||||
*/
|
||||
void ShapePainterObjectBase::SetOutlineColor(const gd::String& color) {
|
||||
std::vector<gd::String> colors = color.Split(U';');
|
||||
if (colors.size() < 3) return;
|
||||
std::map<gd::String, gd::PropertyDescriptor>
|
||||
ShapePainterObject::GetProperties() const {
|
||||
std::map<gd::String, gd::PropertyDescriptor> objectProperties;
|
||||
|
||||
outlineColorR = colors[0].To<int>();
|
||||
outlineColorG = colors[1].To<int>();
|
||||
outlineColorB = colors[2].To<int>();
|
||||
objectProperties["fillColor"]
|
||||
.SetValue(GetFillColor())
|
||||
.SetType("color")
|
||||
.SetLabel(_("Fill color"))
|
||||
.SetGroup(_("Fill"));
|
||||
|
||||
objectProperties["fillOpacity"]
|
||||
.SetValue(gd::String::From(GetFillOpacity()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Fill opacity"))
|
||||
.SetGroup(_("Fill"));
|
||||
|
||||
objectProperties["outlineColor"]
|
||||
.SetValue(GetOutlineColor())
|
||||
.SetType("color")
|
||||
.SetLabel(_("Outline color"))
|
||||
.SetGroup(_("Outline"));
|
||||
|
||||
objectProperties["outlineOpacity"]
|
||||
.SetValue(gd::String::From(GetOutlineOpacity()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Outline opacity"))
|
||||
.SetGroup(_("Outline"));
|
||||
|
||||
objectProperties["outlineSize"]
|
||||
.SetValue(gd::String::From(GetOutlineSize()))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Outline size"))
|
||||
.SetGroup(_("Outline"));
|
||||
|
||||
objectProperties["absoluteCoordinates"]
|
||||
.SetValue(AreCoordinatesAbsolute() ? "true" : "false")
|
||||
.SetType("boolean")
|
||||
.SetLabel(_("Use absolute coordinates"))
|
||||
.SetGroup(_("Drawing"));
|
||||
|
||||
objectProperties["clearBetweenFrames"]
|
||||
.SetValue(IsClearedBetweenFrames() ? "true" : "false")
|
||||
.SetType("boolean")
|
||||
.SetLabel(_("Clear drawing at each frame"))
|
||||
.SetGroup(_("Drawing"))
|
||||
.SetDescription(_("When activated, clear the previous render at each frame. Otherwise, shapes are staying on the screen until you clear manually the object in events."));
|
||||
|
||||
objectProperties["antialiasing"]
|
||||
.SetValue(GetAntialiasing())
|
||||
.SetType("choice")
|
||||
.AddExtraInfo("none")
|
||||
.AddExtraInfo("low")
|
||||
.AddExtraInfo("medium")
|
||||
.AddExtraInfo("high")
|
||||
.SetGroup(_("Drawing"))
|
||||
.SetLabel(_("Antialiasing"))
|
||||
.SetDescription(_("Antialiasing mode"));
|
||||
|
||||
return objectProperties;
|
||||
}
|
||||
|
@@ -32,35 +32,23 @@ class GD_EXTENSION_API ShapePainterObjectBase {
|
||||
void SetOutlineOpacity(double val);
|
||||
inline double GetOutlineOpacity() const { return outlineOpacity; };
|
||||
|
||||
void SetOutlineColor(unsigned int r, unsigned int v, unsigned int b);
|
||||
inline unsigned int GetOutlineColorR() const { return outlineColorR; };
|
||||
inline unsigned int GetOutlineColorG() const { return outlineColorG; };
|
||||
inline unsigned int GetOutlineColorB() const { return outlineColorB; };
|
||||
|
||||
/** Used by GD events generated code : Prefer using original SetOutlineColor
|
||||
*/
|
||||
void SetOutlineColor(const gd::String& color);
|
||||
const gd::String& GetOutlineColor() const { return outlineColor; };
|
||||
|
||||
void SetFillOpacity(double val);
|
||||
inline double GetFillOpacity() const { return fillOpacity; };
|
||||
|
||||
void SetFillColor(unsigned int r, unsigned int v, unsigned int b);
|
||||
inline unsigned int GetFillColorR() const { return fillColorR; };
|
||||
inline unsigned int GetFillColorG() const { return fillColorG; };
|
||||
inline unsigned int GetFillColorB() const { return fillColorB; };
|
||||
|
||||
/** Used by GD events generated code : Prefer using original SetFillColor
|
||||
*/
|
||||
void SetFillColor(const gd::String& color);
|
||||
const gd::String& GetFillColor() const { return fillColor; };
|
||||
|
||||
inline void SetCoordinatesAbsolute() { absoluteCoordinates = true; }
|
||||
inline void SetCoordinatesRelative() { absoluteCoordinates = false; }
|
||||
inline bool AreCoordinatesAbsolute() { return absoluteCoordinates; }
|
||||
inline bool AreCoordinatesAbsolute() const { return absoluteCoordinates; }
|
||||
|
||||
inline void SetClearBetweenFrames(bool value) { clearBetweenFrames = value; }
|
||||
inline bool IsClearedBetweenFrames() { return clearBetweenFrames; }
|
||||
inline bool IsClearedBetweenFrames() const { return clearBetweenFrames; }
|
||||
|
||||
inline gd::String GetAntialiasing() { return antialiasing; }
|
||||
inline const gd::String& GetAntialiasing() const { return antialiasing; }
|
||||
inline void SetAntialiasing(const gd::String& value) { antialiasing = value; }
|
||||
|
||||
protected:
|
||||
@@ -70,16 +58,12 @@ class GD_EXTENSION_API ShapePainterObjectBase {
|
||||
|
||||
private:
|
||||
// Fill color
|
||||
unsigned int fillColorR;
|
||||
unsigned int fillColorG;
|
||||
unsigned int fillColorB;
|
||||
gd::String fillColor;
|
||||
float fillOpacity;
|
||||
|
||||
// Outline
|
||||
int outlineSize;
|
||||
unsigned int outlineColorR;
|
||||
unsigned int outlineColorG;
|
||||
unsigned int outlineColorB;
|
||||
gd::String outlineColor;
|
||||
float outlineOpacity;
|
||||
|
||||
bool absoluteCoordinates;
|
||||
@@ -97,14 +81,20 @@ class GD_EXTENSION_API ShapePainterObject : public gd::ObjectConfiguration,
|
||||
public:
|
||||
ShapePainterObject();
|
||||
virtual ~ShapePainterObject(){};
|
||||
virtual std::unique_ptr<gd::ObjectConfiguration> Clone() const {
|
||||
virtual std::unique_ptr<gd::ObjectConfiguration> Clone() const override {
|
||||
return gd::make_unique<ShapePainterObject>(*this);
|
||||
}
|
||||
|
||||
virtual std::map<gd::String, gd::PropertyDescriptor>
|
||||
GetProperties() const override;
|
||||
|
||||
virtual bool UpdateProperty(const gd::String &name,
|
||||
const gd::String &value) override;
|
||||
|
||||
private:
|
||||
virtual void DoUnserializeFrom(gd::Project& project,
|
||||
const gd::SerializerElement& element);
|
||||
virtual void DoSerializeTo(gd::SerializerElement& element) const;
|
||||
const gd::SerializerElement& element) override;
|
||||
virtual void DoSerializeTo(gd::SerializerElement& element) const override;
|
||||
};
|
||||
|
||||
#endif // SHAPEPAINTEROBJECT_H
|
||||
|
@@ -17,10 +17,10 @@ namespace gdjs {
|
||||
|
||||
/** Initial properties for a for {@link gdjs.ShapePainterRuntimeObject}. */
|
||||
export type ShapePainterObjectDataType = {
|
||||
/** The color (in RGB format) of the inner part of the painted shape */
|
||||
fillColor: RGBColor;
|
||||
/** The color (in RGB format) of the outline of the painted shape */
|
||||
outlineColor: RGBColor;
|
||||
/** The color of the inner part of the painted shape */
|
||||
fillColor: RGBColor | string;
|
||||
/** The color of the outline of the painted shape */
|
||||
outlineColor: RGBColor | string;
|
||||
/** The opacity of the inner part of the painted shape, from 0 to 255 */
|
||||
fillOpacity: float;
|
||||
/** The opacity of the outline of the painted shape, from 0 to 255 */
|
||||
@@ -72,22 +72,28 @@ namespace gdjs {
|
||||
shapePainterObjectData: ShapePainterObjectData
|
||||
) {
|
||||
super(instanceContainer, shapePainterObjectData);
|
||||
this._fillColor = parseInt(
|
||||
gdjs.rgbToHex(
|
||||
shapePainterObjectData.fillColor.r,
|
||||
shapePainterObjectData.fillColor.g,
|
||||
shapePainterObjectData.fillColor.b
|
||||
),
|
||||
16
|
||||
);
|
||||
this._outlineColor = parseInt(
|
||||
gdjs.rgbToHex(
|
||||
shapePainterObjectData.outlineColor.r,
|
||||
shapePainterObjectData.outlineColor.g,
|
||||
shapePainterObjectData.outlineColor.b
|
||||
),
|
||||
16
|
||||
);
|
||||
this._fillColor =
|
||||
typeof shapePainterObjectData.fillColor === 'string'
|
||||
? gdjs.rgbOrHexStringToNumber(shapePainterObjectData.fillColor)
|
||||
: parseInt(
|
||||
gdjs.rgbToHex(
|
||||
shapePainterObjectData.fillColor.r,
|
||||
shapePainterObjectData.fillColor.g,
|
||||
shapePainterObjectData.fillColor.b
|
||||
),
|
||||
16
|
||||
);
|
||||
this._outlineColor =
|
||||
typeof shapePainterObjectData.outlineColor === 'string'
|
||||
? gdjs.rgbOrHexStringToNumber(shapePainterObjectData.outlineColor)
|
||||
: parseInt(
|
||||
gdjs.rgbToHex(
|
||||
shapePainterObjectData.outlineColor.r,
|
||||
shapePainterObjectData.outlineColor.g,
|
||||
shapePainterObjectData.outlineColor.b
|
||||
),
|
||||
16
|
||||
);
|
||||
this._fillOpacity = shapePainterObjectData.fillOpacity;
|
||||
this._outlineOpacity = shapePainterObjectData.outlineOpacity;
|
||||
this._outlineSize = shapePainterObjectData.outlineSize;
|
||||
@@ -111,10 +117,17 @@ namespace gdjs {
|
||||
oldObjectData: ShapePainterObjectData,
|
||||
newObjectData: ShapePainterObjectData
|
||||
): boolean {
|
||||
if (typeof newObjectData.fillColor === 'string') {
|
||||
if (oldObjectData.fillColor !== newObjectData.fillColor) {
|
||||
this.setFillColor(newObjectData.fillColor);
|
||||
}
|
||||
}
|
||||
if (
|
||||
oldObjectData.fillColor.r !== newObjectData.fillColor.r ||
|
||||
oldObjectData.fillColor.g !== newObjectData.fillColor.g ||
|
||||
oldObjectData.fillColor.b !== newObjectData.fillColor.b
|
||||
typeof oldObjectData.fillColor !== 'string' &&
|
||||
typeof newObjectData.fillColor !== 'string' &&
|
||||
(oldObjectData.fillColor.r !== newObjectData.fillColor.r ||
|
||||
oldObjectData.fillColor.g !== newObjectData.fillColor.g ||
|
||||
oldObjectData.fillColor.b !== newObjectData.fillColor.b)
|
||||
) {
|
||||
this.setFillColor(
|
||||
'' +
|
||||
@@ -125,10 +138,17 @@ namespace gdjs {
|
||||
newObjectData.fillColor.b
|
||||
);
|
||||
}
|
||||
if (typeof newObjectData.outlineColor === 'string') {
|
||||
if (oldObjectData.outlineColor !== newObjectData.outlineColor) {
|
||||
this.setOutlineColor(newObjectData.outlineColor);
|
||||
}
|
||||
}
|
||||
if (
|
||||
oldObjectData.outlineColor.r !== newObjectData.outlineColor.r ||
|
||||
oldObjectData.outlineColor.g !== newObjectData.outlineColor.g ||
|
||||
oldObjectData.outlineColor.b !== newObjectData.outlineColor.b
|
||||
typeof oldObjectData.outlineColor !== 'string' &&
|
||||
typeof newObjectData.outlineColor !== 'string' &&
|
||||
(oldObjectData.outlineColor.r !== newObjectData.outlineColor.r ||
|
||||
oldObjectData.outlineColor.g !== newObjectData.outlineColor.g ||
|
||||
oldObjectData.outlineColor.b !== newObjectData.outlineColor.b)
|
||||
) {
|
||||
this.setOutlineColor(
|
||||
'' +
|
||||
@@ -163,6 +183,10 @@ namespace gdjs {
|
||||
) {
|
||||
this._clearBetweenFrames = newObjectData.clearBetweenFrames;
|
||||
}
|
||||
if (oldObjectData.antialiasing !== newObjectData.antialiasing) {
|
||||
this.setAntialiasing(newObjectData.antialiasing);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -450,23 +474,8 @@ namespace gdjs {
|
||||
return !this._useAbsoluteCoordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param rgbColor semicolon separated decimal values
|
||||
*/
|
||||
setFillColor(rgbColor: string): void {
|
||||
const colors = rgbColor.split(';');
|
||||
if (colors.length < 3) {
|
||||
return;
|
||||
}
|
||||
this._fillColor = parseInt(
|
||||
gdjs.rgbToHex(
|
||||
parseInt(colors[0], 10),
|
||||
parseInt(colors[1], 10),
|
||||
parseInt(colors[2], 10)
|
||||
),
|
||||
16
|
||||
);
|
||||
setFillColor(color: string): void {
|
||||
this._fillColor = gdjs.rgbOrHexStringToNumber(color);
|
||||
}
|
||||
|
||||
getFillColorR(): integer {
|
||||
@@ -479,23 +488,8 @@ namespace gdjs {
|
||||
return gdjs.hexNumberToRGB(this._fillColor).b;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param rgbColor semicolon separated decimal values
|
||||
*/
|
||||
setOutlineColor(rgbColor: string): void {
|
||||
const colors = rgbColor.split(';');
|
||||
if (colors.length < 3) {
|
||||
return;
|
||||
}
|
||||
this._outlineColor = parseInt(
|
||||
gdjs.rgbToHex(
|
||||
parseInt(colors[0], 10),
|
||||
parseInt(colors[1], 10),
|
||||
parseInt(colors[2], 10)
|
||||
),
|
||||
16
|
||||
);
|
||||
this._outlineColor = gdjs.rgbOrHexStringToNumber(rgbColor);
|
||||
this._renderer.updateOutline();
|
||||
}
|
||||
|
||||
|
@@ -54,7 +54,8 @@ module.exports = {
|
||||
.addIncludeFile('Extensions/Spine/pixi-spine/pixi-spine.js')
|
||||
.addIncludeFile('Extensions/Spine/managers/pixi-spine-atlas-manager.js')
|
||||
.addIncludeFile('Extensions/Spine/managers/pixi-spine-manager.js')
|
||||
.setCategoryFullName(_('Advanced'));
|
||||
.setCategoryFullName(_('Advanced'))
|
||||
.setOpenFullEditorLabel(_('Edit animations'));
|
||||
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
@@ -166,6 +167,7 @@ module.exports = {
|
||||
this._pixiObject.addChild(this._rect);
|
||||
this._pixiContainer.addChild(this._pixiObject);
|
||||
|
||||
this._spineResourceName = '';
|
||||
this._loadSpine();
|
||||
}
|
||||
|
||||
@@ -174,6 +176,17 @@ module.exports = {
|
||||
}
|
||||
|
||||
update() {
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.SpineObjectConfiguration
|
||||
);
|
||||
|
||||
const spineResourceName = object.getSpineResourceName();
|
||||
if (this._spineResourceName !== spineResourceName) {
|
||||
this._spineResourceName = spineResourceName;
|
||||
this._loadSpine();
|
||||
}
|
||||
|
||||
this._pixiObject.position.set(
|
||||
this._instance.getX(),
|
||||
this._instance.getY()
|
||||
@@ -198,10 +211,17 @@ module.exports = {
|
||||
|
||||
this.setAnimation(this._instance.getRawDoubleProperty('animation'));
|
||||
|
||||
const scale = object.getScale() || 1;
|
||||
|
||||
const spine = this._spine;
|
||||
if (spine) {
|
||||
const localBounds = spine.getLocalBounds(undefined, true);
|
||||
this._initialWidth = localBounds.width * scale;
|
||||
this._initialHeight = localBounds.height * scale;
|
||||
}
|
||||
|
||||
const width = this.getWidth();
|
||||
const height = this.getHeight();
|
||||
const { _spine: spine } = this;
|
||||
|
||||
if (spine) {
|
||||
spine.width = width;
|
||||
spine.height = height;
|
||||
@@ -248,7 +268,9 @@ module.exports = {
|
||||
*/
|
||||
setAnimation(index) {
|
||||
const { _spine: spine } = this;
|
||||
const configuration = this._getConfiguration();
|
||||
const configuration = gd.asSpineConfiguration(
|
||||
this._associatedObjectConfiguration
|
||||
);
|
||||
|
||||
if (
|
||||
!spine ||
|
||||
@@ -276,8 +298,6 @@ module.exports = {
|
||||
spine.state.tracks[0].trackTime = 0;
|
||||
spine.update(0);
|
||||
spine.autoUpdate = false;
|
||||
this._initialWidth = spine.width * this.getScale();
|
||||
this._initialHeight = spine.height * this.getScale();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,41 +314,17 @@ module.exports = {
|
||||
return this._initialHeight !== null ? this._initialHeight : 256;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number} defined scale
|
||||
*/
|
||||
getScale() {
|
||||
return Number(this._getProperties().get('scale').getValue()) || 1;
|
||||
}
|
||||
|
||||
onRemovedFromScene() {
|
||||
super.onRemovedFromScene();
|
||||
this._pixiObject.destroy({ children: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns this spine object configuration
|
||||
*/
|
||||
_getConfiguration() {
|
||||
return gd.asSpineConfiguration(this._associatedObjectConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns this object properties container
|
||||
*/
|
||||
_getProperties() {
|
||||
return this._associatedObjectConfiguration.getProperties();
|
||||
}
|
||||
|
||||
_loadSpine() {
|
||||
const properties = this._getProperties();
|
||||
const spineResourceName = properties
|
||||
.get('spineResourceName')
|
||||
.getValue();
|
||||
|
||||
this._pixiResourcesLoader
|
||||
.getSpineData(this._project, spineResourceName)
|
||||
.getSpineData(this._project, this._spineResourceName)
|
||||
.then((spineDataOrLoadingError) => {
|
||||
if (this._spine) this._pixiObject.removeChild(this._spine);
|
||||
|
||||
if (!spineDataOrLoadingError.skeleton) {
|
||||
console.error(
|
||||
'Unable to load Spine (' +
|
||||
@@ -343,13 +339,11 @@ module.exports = {
|
||||
|
||||
try {
|
||||
this._spine = new PIXI.Spine(spineDataOrLoadingError.skeleton);
|
||||
this._pixiObject.addChild(this._spine);
|
||||
} catch (error) {
|
||||
console.error('Exception while loading Spine.', error);
|
||||
this._spine = null;
|
||||
return;
|
||||
}
|
||||
this._pixiObject.addChild(this._spine);
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -65,9 +65,9 @@ public:
|
||||
}
|
||||
|
||||
virtual void ExposeResources(gd::ArbitraryResourceWorker &worker) override;
|
||||
|
||||
|
||||
virtual std::map<gd::String, gd::PropertyDescriptor>GetProperties() const override;
|
||||
|
||||
|
||||
virtual bool UpdateProperty(const gd::String &name, const gd::String &value) override;
|
||||
|
||||
virtual std::map<gd::String, gd::PropertyDescriptor>
|
||||
@@ -139,6 +139,15 @@ public:
|
||||
|
||||
///@}
|
||||
|
||||
/** \name Getters
|
||||
* Fast access for rendering instances.
|
||||
*/
|
||||
///@{
|
||||
double GetScale() const { return scale; };
|
||||
|
||||
const gd::String& GetSpineResourceName() const { return spineResourceName; };
|
||||
///@}
|
||||
|
||||
protected:
|
||||
virtual void DoUnserializeFrom(gd::Project &project, const gd::SerializerElement &element) override;
|
||||
virtual void DoSerializeTo(gd::SerializerElement &element) const override;
|
||||
|
@@ -31,11 +31,8 @@ module.exports = {
|
||||
.setIcon('JsPlatform/Extensions/text_input.svg');
|
||||
|
||||
const textInputObject = new gd.ObjectJsImplementation();
|
||||
textInputObject.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
textInputObject.updateProperty = function (propertyName, newValue) {
|
||||
const objectContent = this.content;
|
||||
if (propertyName === 'initialValue') {
|
||||
objectContent.initialValue = newValue;
|
||||
return true;
|
||||
@@ -85,8 +82,9 @@ module.exports = {
|
||||
|
||||
return false;
|
||||
};
|
||||
textInputObject.getProperties = function (objectContent) {
|
||||
textInputObject.getProperties = function () {
|
||||
const objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
const objectContent = this.content;
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('initialValue')
|
||||
@@ -141,21 +139,21 @@ module.exports = {
|
||||
.setValue(objectContent.readOnly ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Read only'))
|
||||
.setGroup(_('Field appearance'));
|
||||
.setGroup(_('Field'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('disabled')
|
||||
.setValue(objectContent.disabled ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Disabled'))
|
||||
.setGroup(_('Field appearance'));
|
||||
.setGroup(_('Field'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('textColor')
|
||||
.setValue(objectContent.textColor || '0;0;0')
|
||||
.setType('color')
|
||||
.setLabel(_('Text color'))
|
||||
.setGroup(_('Field appearance'));
|
||||
.setGroup(_('Font'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('fillColor')
|
||||
@@ -180,8 +178,8 @@ module.exports = {
|
||||
.getOrCreate('borderColor')
|
||||
.setValue(objectContent.borderColor || '0;0;0')
|
||||
.setType('color')
|
||||
.setLabel(_('Border color'))
|
||||
.setGroup(_('Field appearance'));
|
||||
.setLabel(_('Color'))
|
||||
.setGroup(_('Border appearance'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('borderOpacity')
|
||||
@@ -192,38 +190,35 @@ module.exports = {
|
||||
).toString()
|
||||
)
|
||||
.setType('number')
|
||||
.setLabel(_('Border opacity'))
|
||||
.setGroup(_('Field appearance'));
|
||||
.setLabel(_('Opacity'))
|
||||
.setGroup(_('Border appearance'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('borderWidth')
|
||||
.setValue((objectContent.borderWidth || 0).toString())
|
||||
.setType('number')
|
||||
.setLabel(_('Border width'))
|
||||
.setGroup(_('Field appearance'));
|
||||
.setLabel(_('Width'))
|
||||
.setGroup(_('Border appearance'));
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
textInputObject.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
initialValue: '',
|
||||
placeholder: 'Touch to start typing',
|
||||
fontResourceName: '',
|
||||
fontSize: 20,
|
||||
inputType: 'text',
|
||||
textColor: '0;0;0',
|
||||
fillColor: '255;255;255',
|
||||
fillOpacity: 255,
|
||||
borderColor: '0;0;0',
|
||||
borderOpacity: 255,
|
||||
borderWidth: 1,
|
||||
readOnly: false,
|
||||
disabled: false,
|
||||
})
|
||||
);
|
||||
textInputObject.content = {
|
||||
initialValue: '',
|
||||
placeholder: 'Touch to start typing',
|
||||
fontResourceName: '',
|
||||
fontSize: 20,
|
||||
inputType: 'text',
|
||||
textColor: '0;0;0',
|
||||
fillColor: '255;255;255',
|
||||
fillOpacity: 255,
|
||||
borderColor: '0;0;0',
|
||||
borderOpacity: 255,
|
||||
borderWidth: 1,
|
||||
readOnly: false,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
textInputObject.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
propertyName,
|
||||
newValue
|
||||
@@ -238,10 +233,7 @@ module.exports = {
|
||||
|
||||
return false;
|
||||
};
|
||||
textInputObject.getInitialInstanceProperties = function (
|
||||
content,
|
||||
instance
|
||||
) {
|
||||
textInputObject.getInitialInstanceProperties = function (instance) {
|
||||
const instanceProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
instanceProperties
|
||||
@@ -682,18 +674,21 @@ module.exports = {
|
||||
|
||||
update() {
|
||||
const instance = this._instance;
|
||||
const properties = this._associatedObjectConfiguration.getProperties();
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
|
||||
const placeholder =
|
||||
instance.getRawStringProperty('placeholder') ||
|
||||
properties.get('placeholder').getValue();
|
||||
object.content.placeholder;
|
||||
const initialValue =
|
||||
instance.getRawStringProperty('initialValue') ||
|
||||
properties.get('initialValue').getValue();
|
||||
object.content.initialValue;
|
||||
const hasInitialValue = initialValue !== '';
|
||||
this._pixiText.text = hasInitialValue ? initialValue : placeholder;
|
||||
|
||||
const textColor = properties.get('textColor').getValue();
|
||||
const textColor = object.content.textColor;
|
||||
const finalTextColor = hasInitialValue
|
||||
? objectsRenderingService.rgbOrHexToHexNumber(textColor)
|
||||
: 0x888888;
|
||||
@@ -703,13 +698,13 @@ module.exports = {
|
||||
this._pixiText.dirty = true;
|
||||
}
|
||||
|
||||
const fontSize = parseFloat(properties.get('fontSize').getValue());
|
||||
const fontSize = object.content.fontSize;
|
||||
if (this._pixiText.style.fontSize !== fontSize) {
|
||||
this._pixiText.style.fontSize = fontSize;
|
||||
this._pixiText.dirty = true;
|
||||
}
|
||||
|
||||
const fontResourceName = properties.get('fontResourceName').getValue();
|
||||
const fontResourceName = object.content.fontResourceName;
|
||||
if (this._fontResourceName !== fontResourceName) {
|
||||
this._fontResourceName = fontResourceName;
|
||||
|
||||
@@ -744,8 +739,7 @@ module.exports = {
|
||||
this._instance.getAngle()
|
||||
);
|
||||
|
||||
const borderWidth =
|
||||
parseFloat(properties.get('borderWidth').getValue()) || 0;
|
||||
const borderWidth = object.content.borderWidth || 0;
|
||||
|
||||
// Draw the mask for the text.
|
||||
const textOffset = borderWidth + TEXT_MASK_PADDING;
|
||||
@@ -759,8 +753,7 @@ module.exports = {
|
||||
);
|
||||
this._pixiTextMask.endFill();
|
||||
|
||||
const isTextArea =
|
||||
properties.get('inputType').getValue() === 'text area';
|
||||
const isTextArea = object.content.inputType === 'text area';
|
||||
|
||||
this._pixiText.position.x = textOffset;
|
||||
this._pixiText.position.y = isTextArea
|
||||
@@ -768,14 +761,10 @@ module.exports = {
|
||||
: height / 2 - this._pixiText.height / 2;
|
||||
|
||||
// Draw the background and border.
|
||||
const fillColor = properties.get('fillColor').getValue();
|
||||
const fillOpacity = parseFloat(
|
||||
properties.get('fillOpacity').getValue()
|
||||
);
|
||||
const borderColor = properties.get('borderColor').getValue();
|
||||
const borderOpacity = parseFloat(
|
||||
properties.get('borderOpacity').getValue()
|
||||
);
|
||||
const fillColor = object.content.fillColor;
|
||||
const fillOpacity = object.content.fillOpacity;
|
||||
const borderColor = object.content.borderColor;
|
||||
const borderOpacity = object.content.borderOpacity;
|
||||
|
||||
this._pixiGraphics.clear();
|
||||
this._pixiGraphics.lineStyle(
|
||||
|
@@ -158,76 +158,86 @@ std::map<gd::String, gd::PropertyDescriptor> TextObject::GetProperties() const {
|
||||
.AddExtraInfo("left")
|
||||
.AddExtraInfo("center")
|
||||
.AddExtraInfo("right")
|
||||
.SetLabel(_("Alignment, when multiple lines are displayed"))
|
||||
.SetLabel(_("Alignment"))
|
||||
.SetDescription(_("Alignment of the text when multiple lines are displayed"))
|
||||
.SetGroup(_("Font"))
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
|
||||
objectProperties["isOutlineEnabled"]
|
||||
.SetValue(isOutlineEnabled ? "true" : "false")
|
||||
.SetType("boolean")
|
||||
.SetLabel(_("Outline"))
|
||||
.SetLabel(_("Show outline"))
|
||||
.SetGroup(_("Outline"))
|
||||
.SetAdvanced()
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
|
||||
objectProperties["outlineColor"]
|
||||
.SetValue(outlineColor)
|
||||
.SetType("color")
|
||||
.SetLabel(_("Outline color"))
|
||||
.SetLabel(_("Color"))
|
||||
.SetGroup(_("Outline"))
|
||||
.SetAdvanced()
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
|
||||
objectProperties["outlineThickness"]
|
||||
.SetValue(gd::String::From(outlineThickness))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Outline thickness"))
|
||||
.SetLabel(_("Thickness"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetGroup(_("Outline"))
|
||||
.SetAdvanced()
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
|
||||
objectProperties["isShadowEnabled"]
|
||||
.SetValue(isShadowEnabled ? "true" : "false")
|
||||
.SetType("boolean")
|
||||
.SetLabel(_("Shadow"))
|
||||
.SetLabel(_("Show shadow"))
|
||||
.SetGroup(_("Shadow"))
|
||||
.SetAdvanced()
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
|
||||
objectProperties["shadowColor"]
|
||||
.SetValue(shadowColor)
|
||||
.SetType("color")
|
||||
.SetLabel(_("Shadow color"))
|
||||
.SetLabel(_("Color"))
|
||||
.SetGroup(_("Shadow"))
|
||||
.SetAdvanced()
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
|
||||
objectProperties["shadowOpacity"]
|
||||
.SetValue(gd::String::From(shadowOpacity))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Shadow opacity"))
|
||||
.SetLabel(_("Opacity"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetGroup(_("Shadow"))
|
||||
.SetAdvanced()
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
|
||||
objectProperties["shadowAngle"]
|
||||
.SetValue(gd::String::From(shadowAngle))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Shadow angle"))
|
||||
.SetLabel(_("Angle"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetDegreeAngle())
|
||||
.SetGroup(_("Shadow"))
|
||||
.SetAdvanced()
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
|
||||
objectProperties["shadowDistance"]
|
||||
.SetValue(gd::String::From(shadowDistance))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Shadow distance"))
|
||||
.SetLabel(_("Distance"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetGroup(_("Shadow"))
|
||||
.SetAdvanced()
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
|
||||
objectProperties["shadowBlurRadius"]
|
||||
.SetValue(gd::String::From(shadowBlurRadius))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Shadow blur radius"))
|
||||
.SetLabel(_("Blur radius"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetGroup(_("Shadow"))
|
||||
.SetAdvanced()
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
|
||||
return objectProperties;
|
||||
|
@@ -22,11 +22,8 @@
|
||||
*/
|
||||
const defineTileMap = function (extension, _, gd) {
|
||||
var objectTileMap = new gd.ObjectJsImplementation();
|
||||
objectTileMap.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
objectTileMap.updateProperty = function (propertyName, newValue) {
|
||||
const objectContent = this.content;
|
||||
if (propertyName === 'tilemapJsonFile') {
|
||||
objectContent.tilemapJsonFile = newValue;
|
||||
return true;
|
||||
@@ -62,8 +59,9 @@ const defineTileMap = function (extension, _, gd) {
|
||||
|
||||
return false;
|
||||
};
|
||||
objectTileMap.getProperties = function (objectContent) {
|
||||
objectTileMap.getProperties = function () {
|
||||
var objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
const objectContent = this.content;
|
||||
|
||||
objectProperties.set(
|
||||
'tilemapJsonFile',
|
||||
@@ -147,29 +145,26 @@ const defineTileMap = function (extension, _, gd) {
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
objectTileMap.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
tilemapJsonFile: '',
|
||||
tilesetJsonFile: '',
|
||||
tilemapAtlasImage: '',
|
||||
displayMode: 'visible',
|
||||
layerIndex: 0,
|
||||
levelIndex: 0,
|
||||
animationSpeedScale: 1,
|
||||
animationFps: 4,
|
||||
})
|
||||
);
|
||||
objectTileMap.content = {
|
||||
tilemapJsonFile: '',
|
||||
tilesetJsonFile: '',
|
||||
tilemapAtlasImage: '',
|
||||
displayMode: 'visible',
|
||||
layerIndex: 0,
|
||||
levelIndex: 0,
|
||||
animationSpeedScale: 1,
|
||||
animationFps: 4,
|
||||
};
|
||||
|
||||
objectTileMap.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
return false;
|
||||
};
|
||||
objectTileMap.getInitialInstanceProperties = function (content, instance) {
|
||||
var instanceProperties = new gd.MapStringPropertyDescriptor();
|
||||
objectTileMap.getInitialInstanceProperties = function (instance) {
|
||||
const instanceProperties = new gd.MapStringPropertyDescriptor();
|
||||
return instanceProperties;
|
||||
};
|
||||
|
||||
@@ -597,11 +592,8 @@ const defineTileMap = function (extension, _, gd) {
|
||||
*/
|
||||
const defineSimpleTileMap = function (extension, _, gd) {
|
||||
var objectSimpleTileMap = new gd.ObjectJsImplementation();
|
||||
objectSimpleTileMap.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
objectSimpleTileMap.updateProperty = function (propertyName, newValue) {
|
||||
const objectContent = this.content;
|
||||
if (propertyName === 'atlasImage') {
|
||||
objectContent.atlasImage = newValue;
|
||||
return true;
|
||||
@@ -625,8 +617,9 @@ const defineSimpleTileMap = function (extension, _, gd) {
|
||||
|
||||
return false;
|
||||
};
|
||||
objectSimpleTileMap.getProperties = function (objectContent) {
|
||||
objectSimpleTileMap.getProperties = function () {
|
||||
var objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
const objectContent = this.content;
|
||||
|
||||
objectProperties.set(
|
||||
'columnCount',
|
||||
@@ -665,6 +658,7 @@ const defineSimpleTileMap = function (extension, _, gd) {
|
||||
.setType('number')
|
||||
.setLabel(_('Tile size'))
|
||||
.setDescription(_('Tile size in pixels.'))
|
||||
.setHidden(true) // Hidden because a full editor is needed to recompute column/row counts
|
||||
);
|
||||
objectProperties.set(
|
||||
'tilesWithHitBox',
|
||||
@@ -684,19 +678,18 @@ const defineSimpleTileMap = function (extension, _, gd) {
|
||||
.addExtraInfo('image')
|
||||
.setLabel(_('Atlas image'))
|
||||
.setDescription(_('The Atlas image containing the tileset.'))
|
||||
.setHidden(true) // Hidden because a full editor is needed to recompute column/row counts
|
||||
);
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
objectSimpleTileMap.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
atlasImage: '',
|
||||
rowCount: 1,
|
||||
columnCount: 1,
|
||||
tileSize: 8,
|
||||
tilesWithHitBox: '',
|
||||
})
|
||||
);
|
||||
objectSimpleTileMap.content = {
|
||||
atlasImage: '',
|
||||
rowCount: 1,
|
||||
columnCount: 1,
|
||||
tileSize: 8,
|
||||
tilesWithHitBox: '',
|
||||
};
|
||||
|
||||
objectSimpleTileMap.updateInitialInstanceProperty = function (
|
||||
instance,
|
||||
@@ -710,10 +703,7 @@ const defineSimpleTileMap = function (extension, _, gd) {
|
||||
return false;
|
||||
};
|
||||
|
||||
objectSimpleTileMap.getInitialInstanceProperties = function (
|
||||
objectContent,
|
||||
instance
|
||||
) {
|
||||
objectSimpleTileMap.getInitialInstanceProperties = function (instance) {
|
||||
var instanceProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
instanceProperties
|
||||
@@ -735,6 +725,7 @@ const defineSimpleTileMap = function (extension, _, gd) {
|
||||
objectSimpleTileMap
|
||||
)
|
||||
.setCategoryFullName(_('General'))
|
||||
.setOpenFullEditorLabel(_('Edit tileset and collisions'))
|
||||
.addDefaultBehavior('ResizableCapability::ResizableBehavior')
|
||||
.addDefaultBehavior('ScalableCapability::ScalableBehavior')
|
||||
.addDefaultBehavior('OpacityCapability::OpacityBehavior')
|
||||
@@ -1076,11 +1067,8 @@ const defineSimpleTileMap = function (extension, _, gd) {
|
||||
*/
|
||||
const defineCollisionMask = function (extension, _, gd) {
|
||||
var collisionMaskObject = new gd.ObjectJsImplementation();
|
||||
collisionMaskObject.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
collisionMaskObject.updateProperty = function (propertyName, newValue) {
|
||||
const objectContent = this.content;
|
||||
if (propertyName === 'tilemapJsonFile') {
|
||||
objectContent.tilemapJsonFile = newValue;
|
||||
return true;
|
||||
@@ -1120,8 +1108,9 @@ const defineCollisionMask = function (extension, _, gd) {
|
||||
|
||||
return false;
|
||||
};
|
||||
collisionMaskObject.getProperties = function (objectContent) {
|
||||
var objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
collisionMaskObject.getProperties = function () {
|
||||
const objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
const objectContent = this.content;
|
||||
|
||||
objectProperties.set(
|
||||
'tilemapJsonFile',
|
||||
@@ -1214,32 +1203,26 @@ const defineCollisionMask = function (extension, _, gd) {
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
collisionMaskObject.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
tilemapJsonFile: '',
|
||||
tilesetJsonFile: '',
|
||||
collisionMaskTag: '',
|
||||
debugMode: false,
|
||||
fillColor: '255;255;255',
|
||||
outlineColor: '255;255;255',
|
||||
fillOpacity: 64,
|
||||
outlineOpacity: 128,
|
||||
outlineSize: 1,
|
||||
})
|
||||
);
|
||||
collisionMaskObject.content = {
|
||||
tilemapJsonFile: '',
|
||||
tilesetJsonFile: '',
|
||||
collisionMaskTag: '',
|
||||
debugMode: false,
|
||||
fillColor: '255;255;255',
|
||||
outlineColor: '255;255;255',
|
||||
fillOpacity: 64,
|
||||
outlineOpacity: 128,
|
||||
outlineSize: 1,
|
||||
};
|
||||
|
||||
collisionMaskObject.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
return false;
|
||||
};
|
||||
collisionMaskObject.getInitialInstanceProperties = function (
|
||||
content,
|
||||
instance
|
||||
) {
|
||||
collisionMaskObject.getInitialInstanceProperties = function (instance) {
|
||||
var instanceProperties = new gd.MapStringPropertyDescriptor();
|
||||
return instanceProperties;
|
||||
};
|
||||
@@ -1702,29 +1685,18 @@ module.exports = {
|
||||
* This is used to reload the Tilemap
|
||||
*/
|
||||
updateTileMap() {
|
||||
const tilemapObjectProperties = this._associatedObjectConfiguration.getProperties();
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
|
||||
// Get the tileset resource to use
|
||||
const tilemapAtlasImage = tilemapObjectProperties
|
||||
.get('tilemapAtlasImage')
|
||||
.getValue();
|
||||
const tilemapJsonFile = tilemapObjectProperties
|
||||
.get('tilemapJsonFile')
|
||||
.getValue();
|
||||
const tilesetJsonFile = tilemapObjectProperties
|
||||
.get('tilesetJsonFile')
|
||||
.getValue();
|
||||
const layerIndex = parseInt(
|
||||
tilemapObjectProperties.get('layerIndex').getValue(),
|
||||
10
|
||||
);
|
||||
const levelIndex = parseInt(
|
||||
tilemapObjectProperties.get('levelIndex').getValue(),
|
||||
10
|
||||
);
|
||||
const displayMode = tilemapObjectProperties
|
||||
.get('displayMode')
|
||||
.getValue();
|
||||
const tilemapAtlasImage = object.content.tilemapAtlasImage;
|
||||
const tilemapJsonFile = object.content.tilemapJsonFile;
|
||||
const tilesetJsonFile = object.content.tilesetJsonFile;
|
||||
const layerIndex = object.content.layerIndex;
|
||||
const levelIndex = object.content.levelIndex;
|
||||
const displayMode = object.content.displayMode;
|
||||
|
||||
const tilemapResource = this._project
|
||||
.getResourcesManager()
|
||||
@@ -1815,29 +1787,18 @@ module.exports = {
|
||||
* This is called to update the PIXI object on the scene editor, without reloading the tilemap.
|
||||
*/
|
||||
updatePixiTileMap() {
|
||||
const tilemapObjectProperties = this._associatedObjectConfiguration.getProperties();
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
|
||||
// Get the tileset resource to use
|
||||
const tilemapAtlasImage = tilemapObjectProperties
|
||||
.get('tilemapAtlasImage')
|
||||
.getValue();
|
||||
const tilemapJsonFile = tilemapObjectProperties
|
||||
.get('tilemapJsonFile')
|
||||
.getValue();
|
||||
const tilesetJsonFile = tilemapObjectProperties
|
||||
.get('tilesetJsonFile')
|
||||
.getValue();
|
||||
const layerIndex = parseInt(
|
||||
tilemapObjectProperties.get('layerIndex').getValue(),
|
||||
10
|
||||
);
|
||||
const levelIndex = parseInt(
|
||||
tilemapObjectProperties.get('levelIndex').getValue(),
|
||||
10
|
||||
);
|
||||
const displayMode = tilemapObjectProperties
|
||||
.get('displayMode')
|
||||
.getValue();
|
||||
const tilemapAtlasImage = object.content.tilemapAtlasImage;
|
||||
const tilemapJsonFile = object.content.tilemapJsonFile;
|
||||
const tilesetJsonFile = object.content.tilesetJsonFile;
|
||||
const layerIndex = object.content.layerIndex;
|
||||
const levelIndex = object.content.levelIndex;
|
||||
const displayMode = object.content.displayMode;
|
||||
|
||||
const tilemapResource = this._project
|
||||
.getResourcesManager()
|
||||
@@ -2139,10 +2100,12 @@ module.exports = {
|
||||
* Return the path to the thumbnail of the specified object.
|
||||
*/
|
||||
static getThumbnail(project, resourcesLoader, objectConfiguration) {
|
||||
const atlasImageResourceName = objectConfiguration
|
||||
.getProperties()
|
||||
.get('atlasImage')
|
||||
.getValue();
|
||||
const object = gd.castObject(
|
||||
objectConfiguration,
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
|
||||
const atlasImageResourceName = object.content.atlasImage || '';
|
||||
return resourcesLoader.getResourceFullUrl(
|
||||
project,
|
||||
atlasImageResourceName,
|
||||
@@ -2158,37 +2121,20 @@ module.exports = {
|
||||
* This is used to reload the Tilemap
|
||||
*/
|
||||
updateTileMap() {
|
||||
const atlasImageResourceName = this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('atlasImage')
|
||||
.getValue();
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
const atlasImageResourceName = object.content.atlasImage;
|
||||
if (!atlasImageResourceName) return;
|
||||
|
||||
const tilemapAsJSObject = JSON.parse(
|
||||
this._instance.getRawStringProperty('tilemap') || '{}'
|
||||
);
|
||||
|
||||
const tileSize = parseInt(
|
||||
this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('tileSize')
|
||||
.getValue(),
|
||||
10
|
||||
);
|
||||
const columnCount = parseInt(
|
||||
this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('columnCount')
|
||||
.getValue(),
|
||||
10
|
||||
);
|
||||
const rowCount = parseInt(
|
||||
this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('rowCount')
|
||||
.getValue(),
|
||||
10
|
||||
);
|
||||
const tileSize = object.content.tileSize;
|
||||
const columnCount = object.content.columnCount;
|
||||
const rowCount = object.content.rowCount;
|
||||
|
||||
const atlasTexture = this._pixiResourcesLoader.getPIXITexture(
|
||||
this._project,
|
||||
@@ -2266,32 +2212,16 @@ module.exports = {
|
||||
* This is called to update the PIXI object on the scene editor, without reloading the tilemap.
|
||||
*/
|
||||
updatePixiTileMap() {
|
||||
const atlasImageResourceName = this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('atlasImage')
|
||||
.getValue();
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
|
||||
const tileSize = parseInt(
|
||||
this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('tileSize')
|
||||
.getValue(),
|
||||
10
|
||||
);
|
||||
const columnCount = parseInt(
|
||||
this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('columnCount')
|
||||
.getValue(),
|
||||
10
|
||||
);
|
||||
const rowCount = parseInt(
|
||||
this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('rowCount')
|
||||
.getValue(),
|
||||
10
|
||||
);
|
||||
const atlasImageResourceName = object.content.atlasImage;
|
||||
|
||||
const tileSize = object.content.tileSize;
|
||||
const columnCount = object.content.columnCount;
|
||||
const rowCount = object.content.rowCount;
|
||||
/** @type {TileMapHelper.TileMapManager} */
|
||||
const manager = TilemapHelper.TileMapManager.getManager(this._project);
|
||||
|
||||
@@ -2329,10 +2259,11 @@ module.exports = {
|
||||
* This is called to update the PIXI object on the scene editor
|
||||
*/
|
||||
update() {
|
||||
const atlasImageResourceName = this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('atlasImage')
|
||||
.getValue();
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
const atlasImageResourceName = object.content.atlasImage;
|
||||
|
||||
const isTileMapEmpty = this._editableTileMap
|
||||
? this._editableTileMap.isEmpty()
|
||||
@@ -2524,47 +2455,22 @@ module.exports = {
|
||||
* This is used to reload the Tilemap
|
||||
*/
|
||||
updateTileMap() {
|
||||
// This might become useful in the future
|
||||
/*
|
||||
const tilemapAtlasImage = this._associatedObjectConfiguration
|
||||
.getProperties(this.project)
|
||||
.get('tilemapAtlasImage')
|
||||
.getValue();
|
||||
*/
|
||||
const tilemapJsonFile = this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('tilemapJsonFile')
|
||||
.getValue();
|
||||
const tilesetJsonFile = this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('tilesetJsonFile')
|
||||
.getValue();
|
||||
const collisionMaskTag = this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('collisionMaskTag')
|
||||
.getValue();
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
|
||||
const tilemapJsonFile = object.content.tilemapJsonFile;
|
||||
const tilesetJsonFile = object.content.tilesetJsonFile;
|
||||
const collisionMaskTag = object.content.collisionMaskTag;
|
||||
const outlineColor = objectsRenderingService.rgbOrHexToHexNumber(
|
||||
this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('outlineColor')
|
||||
.getValue()
|
||||
object.content.outlineColor
|
||||
);
|
||||
const fillColor = objectsRenderingService.rgbOrHexToHexNumber(
|
||||
this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('fillColor')
|
||||
.getValue()
|
||||
object.content.fillColor
|
||||
);
|
||||
const outlineOpacity =
|
||||
+this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('outlineOpacity')
|
||||
.getValue() / 255;
|
||||
const fillOpacity =
|
||||
+this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('fillOpacity')
|
||||
.getValue() / 255;
|
||||
const outlineOpacity = object.content.outlineOpacity / 255;
|
||||
const fillOpacity = object.content.fillOpacity / 255;
|
||||
const outlineSize = 1;
|
||||
|
||||
/** @type {TileMapHelper.TileMapManager} */
|
||||
|
@@ -35,72 +35,67 @@ module.exports = {
|
||||
.setIcon('JsPlatform/Extensions/videoicon16.png');
|
||||
|
||||
var videoObject = new gd.ObjectJsImplementation();
|
||||
videoObject.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
videoObject.updateProperty = function (propertyName, newValue) {
|
||||
console.log('update', this.content);
|
||||
if (propertyName === 'Opacity') {
|
||||
objectContent.opacity = parseFloat(newValue);
|
||||
this.content.opacity = parseFloat(newValue);
|
||||
return true;
|
||||
}
|
||||
if (propertyName === 'Looped') {
|
||||
objectContent.loop = newValue === '1';
|
||||
this.content.loop = newValue === '1';
|
||||
return true;
|
||||
}
|
||||
if (propertyName === 'Volume') {
|
||||
objectContent.volume = parseFloat(newValue);
|
||||
this.content.volume = parseFloat(newValue);
|
||||
return true;
|
||||
}
|
||||
if (propertyName === 'videoResource') {
|
||||
objectContent.videoResource = newValue;
|
||||
this.content.videoResource = newValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
videoObject.getProperties = function (objectContent) {
|
||||
videoObject.getProperties = function () {
|
||||
var objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
console.log('getProperties', this.content);
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('Looped')
|
||||
.setValue(objectContent.loop ? 'true' : 'false')
|
||||
.setValue(this.content.loop ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Loop the video'))
|
||||
.setGroup(_('Playback settings'));
|
||||
objectProperties
|
||||
.getOrCreate('Volume')
|
||||
.setValue(objectContent.volume.toString())
|
||||
.setValue(this.content.volume.toString())
|
||||
.setType('number')
|
||||
.setLabel(_('Video volume (0-100)'))
|
||||
.setGroup(_('Playback settings'));
|
||||
objectProperties
|
||||
.getOrCreate('videoResource')
|
||||
.setValue(objectContent.videoResource)
|
||||
.setValue(this.content.videoResource)
|
||||
.setType('resource')
|
||||
.addExtraInfo('video')
|
||||
.setLabel(_('Video resource'));
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
videoObject.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
opacity: 255,
|
||||
loop: false,
|
||||
volume: 100,
|
||||
videoResource: '',
|
||||
})
|
||||
);
|
||||
videoObject.content = {
|
||||
opacity: 255,
|
||||
loop: false,
|
||||
volume: 100,
|
||||
videoResource: '',
|
||||
};
|
||||
|
||||
videoObject.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
return false;
|
||||
};
|
||||
videoObject.getInitialInstanceProperties = function (content, instance) {
|
||||
videoObject.getInitialInstanceProperties = function (instance) {
|
||||
var instanceProperties = new gd.MapStringPropertyDescriptor();
|
||||
return instanceProperties;
|
||||
};
|
||||
@@ -583,10 +578,11 @@ module.exports = {
|
||||
|
||||
_getVideoTexture() {
|
||||
// Get the video resource to use
|
||||
const videoResource = this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('videoResource')
|
||||
.getValue();
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
const videoResource = object.content.videoResource;
|
||||
|
||||
// This returns a VideoTexture with autoPlay set to false
|
||||
return this._pixiResourcesLoader.getPIXIVideoTexture(
|
||||
@@ -600,10 +596,12 @@ module.exports = {
|
||||
*/
|
||||
update() {
|
||||
// Check if the video resource has changed
|
||||
const videoResource = this._associatedObjectConfiguration
|
||||
.getProperties()
|
||||
.get('videoResource')
|
||||
.getValue();
|
||||
const object = gd.castObject(
|
||||
this._associatedObjectConfiguration,
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
const videoResource = object.content.videoResource;
|
||||
|
||||
if (videoResource !== this._videoResource) {
|
||||
this._videoResource = videoResource;
|
||||
this._pixiObject.texture = this._getVideoTexture();
|
||||
|
@@ -867,9 +867,6 @@ interface ObjectJsImplementation {
|
||||
[Const] DOMString name,
|
||||
[Const] DOMString value);
|
||||
|
||||
[Const, Ref] DOMString GetRawJSONContent();
|
||||
[Ref] ObjectJsImplementation SetRawJSONContent([Const] DOMString newContent);
|
||||
|
||||
void SerializeTo([Ref] SerializerElement element);
|
||||
void UnserializeFrom([Ref] Project project, [Const, Ref] SerializerElement element);
|
||||
|
||||
@@ -909,6 +906,9 @@ interface CustomObjectConfiguration {
|
||||
|
||||
[Ref] SpriteAnimationList GetAnimations();
|
||||
|
||||
boolean IsChildObjectFolded([Const] DOMString childName);
|
||||
void SetChildObjectFolded([Const] DOMString childName, boolean folded);
|
||||
|
||||
CustomObjectConfiguration_EdgeAnchor STATIC_GetEdgeAnchorFromString([Const] DOMString value);
|
||||
};
|
||||
CustomObjectConfiguration implements ObjectConfiguration;
|
||||
@@ -1907,6 +1907,9 @@ interface ObjectMetadata {
|
||||
|
||||
[Ref] ObjectMetadata MarkAsRenderedIn3D();
|
||||
boolean IsRenderedIn3D();
|
||||
|
||||
[Ref] ObjectMetadata SetOpenFullEditorLabel([Const] DOMString label);
|
||||
[Const, Ref] DOMString GetOpenFullEditorLabel();
|
||||
};
|
||||
|
||||
enum QuickCustomization_Visibility {
|
||||
@@ -2032,6 +2035,9 @@ interface BehaviorMetadata {
|
||||
QuickCustomization_Visibility GetQuickCustomizationVisibility();
|
||||
[Ref] BehaviorMetadata SetQuickCustomizationVisibility(QuickCustomization_Visibility visibility);
|
||||
|
||||
[Ref] BehaviorMetadata SetOpenFullEditorLabel([Const] DOMString label);
|
||||
[Const, Ref] DOMString GetOpenFullEditorLabel();
|
||||
|
||||
[Ref] Behavior Get();
|
||||
BehaviorsSharedData GetSharedDataInstance();
|
||||
|
||||
@@ -3429,6 +3435,20 @@ interface Model3DObjectConfiguration {
|
||||
boolean HasNoAnimations();
|
||||
void SwapAnimations(unsigned long first, unsigned long second);
|
||||
void MoveAnimation(unsigned long oldIndex, unsigned long newIndex);
|
||||
|
||||
double GetWidth();
|
||||
double GetHeight();
|
||||
double GetDepth();
|
||||
double GetRotationX();
|
||||
double GetRotationY();
|
||||
double GetRotationZ();
|
||||
|
||||
[Const, Ref] DOMString GetModelResourceName();
|
||||
[Const, Ref] DOMString GetMaterialType();
|
||||
[Const, Ref] DOMString GetOriginLocation();
|
||||
[Const, Ref] DOMString GetCenterLocation();
|
||||
|
||||
boolean shouldKeepAspectRatio();
|
||||
};
|
||||
Model3DObjectConfiguration implements ObjectConfiguration;
|
||||
|
||||
@@ -3458,6 +3478,9 @@ interface SpineObjectConfiguration {
|
||||
boolean HasNoAnimations();
|
||||
void SwapAnimations(unsigned long first, unsigned long second);
|
||||
void MoveAnimation(unsigned long oldIndex, unsigned long newIndex);
|
||||
|
||||
double GetScale();
|
||||
[Const, Ref] DOMString GetSpineResourceName();
|
||||
};
|
||||
SpineObjectConfiguration implements ObjectConfiguration;
|
||||
|
||||
@@ -3577,21 +3600,18 @@ interface ShapePainterObject {
|
||||
void SetOutlineSize(double size);
|
||||
double GetOutlineSize();
|
||||
|
||||
void SetOutlineColor([Const] DOMString color);
|
||||
[Const, Ref] DOMString GetOutlineColor();
|
||||
|
||||
void SetOutlineOpacity(double val);
|
||||
double GetOutlineOpacity();
|
||||
|
||||
void SetOutlineColor(unsigned long r, unsigned long g, unsigned long b);
|
||||
unsigned long GetOutlineColorR();
|
||||
unsigned long GetOutlineColorG();
|
||||
unsigned long GetOutlineColorB();
|
||||
void SetFillColor([Const] DOMString color);
|
||||
[Const, Ref] DOMString GetFillColor();
|
||||
|
||||
void SetFillOpacity(double val);
|
||||
double GetFillOpacity();
|
||||
|
||||
void SetFillColor(unsigned long r,unsigned long g, unsigned long b);
|
||||
unsigned long GetFillColorR();
|
||||
unsigned long GetFillColorG();
|
||||
unsigned long GetFillColorB();
|
||||
[Const, Value] DOMString GetAntialiasing();
|
||||
void SetAntialiasing([Const] DOMString value);
|
||||
|
||||
@@ -3660,18 +3680,10 @@ interface ParticleEmitterObject {
|
||||
void SetParticleLifeTimeMax(double newValue);
|
||||
double GetParticleLifeTimeMax();
|
||||
|
||||
void SetParticleRed1(double newValue);
|
||||
double GetParticleRed1();
|
||||
void SetParticleRed2(double newValue);
|
||||
double GetParticleRed2();
|
||||
void SetParticleGreen1(double newValue);
|
||||
double GetParticleGreen1();
|
||||
void SetParticleGreen2(double newValue);
|
||||
double GetParticleGreen2();
|
||||
void SetParticleBlue1(double newValue);
|
||||
double GetParticleBlue1();
|
||||
void SetParticleBlue2(double newValue);
|
||||
double GetParticleBlue2();
|
||||
void SetParticleColor1([Const] DOMString newValue);
|
||||
[Const, Ref] DOMString GetParticleColor1();
|
||||
void SetParticleColor2([Const] DOMString newValue);
|
||||
[Const, Ref] DOMString GetParticleColor2();
|
||||
void SetParticleAlpha1(double newValue);
|
||||
double GetParticleAlpha1();
|
||||
void SetParticleAlpha2(double newValue);
|
||||
|
@@ -27,6 +27,10 @@ std::unique_ptr<gd::ObjectConfiguration> ObjectJsImplementation::Clone() const {
|
||||
self['getInitialInstanceProperties'];
|
||||
clone['updateInitialInstanceProperty'] =
|
||||
self['updateInitialInstanceProperty'];
|
||||
|
||||
// Make a clone of the JavaScript object containing the data. If we don't do that, the
|
||||
// content of the object would be shared between the original and the clone.
|
||||
clone['content'] = Module['_deepCloneForObjectJsImplementationContent'](self['content']);
|
||||
},
|
||||
(int)clone,
|
||||
(int)this);
|
||||
@@ -45,15 +49,13 @@ ObjectJsImplementation::GetProperties() const {
|
||||
if (!self.hasOwnProperty('getProperties'))
|
||||
throw 'getProperties is not defined on a ObjectJsImplementation.';
|
||||
|
||||
var objectContent = JSON.parse(UTF8ToString($1));
|
||||
var newProperties = self['getProperties'](objectContent);
|
||||
var newProperties = self['getProperties']();
|
||||
if (!newProperties)
|
||||
throw 'getProperties returned nothing in a gd::ObjectJsImplementation.';
|
||||
|
||||
return getPointer(newProperties);
|
||||
},
|
||||
(int)this,
|
||||
jsonContent.c_str());
|
||||
(int)this);
|
||||
|
||||
copiedProperties = *jsCreatedProperties;
|
||||
delete jsCreatedProperties;
|
||||
@@ -61,18 +63,15 @@ ObjectJsImplementation::GetProperties() const {
|
||||
}
|
||||
bool ObjectJsImplementation::UpdateProperty(const gd::String& arg0,
|
||||
const gd::String& arg1) {
|
||||
jsonContent = (const char*)EM_ASM_INT(
|
||||
EM_ASM_INT(
|
||||
{
|
||||
var self = Module['getCache'](Module['ObjectJsImplementation'])[$0];
|
||||
if (!self.hasOwnProperty('updateProperty'))
|
||||
throw 'updateProperty is not defined on a ObjectJsImplementation.';
|
||||
var objectContent = JSON.parse(UTF8ToString($1));
|
||||
self['updateProperty'](
|
||||
objectContent, UTF8ToString($2), UTF8ToString($3));
|
||||
return ensureString(JSON.stringify(objectContent));
|
||||
|
||||
self['updateProperty'](UTF8ToString($1), UTF8ToString($2));
|
||||
},
|
||||
(int)this,
|
||||
jsonContent.c_str(),
|
||||
arg0.c_str(),
|
||||
arg1.c_str());
|
||||
|
||||
@@ -91,17 +90,14 @@ ObjectJsImplementation::GetInitialInstanceProperties(
|
||||
if (!self.hasOwnProperty('getInitialInstanceProperties'))
|
||||
throw 'getInitialInstanceProperties is not defined on a ObjectJsImplementation.';
|
||||
|
||||
var objectContent = JSON.parse(UTF8ToString($1));
|
||||
var newProperties = self['getInitialInstanceProperties'](
|
||||
objectContent,
|
||||
wrapPointer($2, Module['InitialInstance']));
|
||||
wrapPointer($1, Module['InitialInstance']));
|
||||
if (!newProperties)
|
||||
throw 'getInitialInstanceProperties returned nothing in a gd::ObjectJsImplementation.';
|
||||
|
||||
return getPointer(newProperties);
|
||||
},
|
||||
(int)this,
|
||||
jsonContent.c_str(),
|
||||
(int)&instance);
|
||||
|
||||
copiedProperties = *jsCreatedProperties;
|
||||
@@ -118,26 +114,53 @@ bool ObjectJsImplementation::UpdateInitialInstanceProperty(
|
||||
var self = Module['getCache'](Module['ObjectJsImplementation'])[$0];
|
||||
if (!self.hasOwnProperty('updateInitialInstanceProperty'))
|
||||
throw 'updateInitialInstanceProperty is not defined on a ObjectJsImplementation.';
|
||||
var objectContent = JSON.parse(UTF8ToString($1));
|
||||
|
||||
return self['updateInitialInstanceProperty'](
|
||||
objectContent,
|
||||
wrapPointer($2, Module['InitialInstance']),
|
||||
UTF8ToString($3),
|
||||
UTF8ToString($4));
|
||||
wrapPointer($1, Module['InitialInstance']),
|
||||
UTF8ToString($2),
|
||||
UTF8ToString($3));
|
||||
},
|
||||
(int)this,
|
||||
jsonContent.c_str(),
|
||||
(int)&instance,
|
||||
name.c_str(),
|
||||
value.c_str());
|
||||
}
|
||||
|
||||
void ObjectJsImplementation::DoSerializeTo(SerializerElement& element) const {
|
||||
element.AddChild("content") = gd::Serializer::FromJSON(jsonContent);
|
||||
SerializerElement* jsCreatedElement = (SerializerElement*)EM_ASM_INT(
|
||||
{
|
||||
var self = Module['getCache'](Module['ObjectJsImplementation'])[$0];
|
||||
if (!self.content)
|
||||
throw '`content` is not defined on a ObjectJsImplementation.';
|
||||
|
||||
var serializerElement = gd.Serializer.fromJSObject(self.content);
|
||||
return getPointer(serializerElement);
|
||||
},
|
||||
(int)this);
|
||||
|
||||
// We could avoid a copy by using making a function on gd.Serializer that manipulates
|
||||
// directly the SerializerElement passed to it.
|
||||
element.AddChild("content") = *jsCreatedElement;
|
||||
delete jsCreatedElement;
|
||||
}
|
||||
void ObjectJsImplementation::DoUnserializeFrom(Project& project,
|
||||
const SerializerElement& element) {
|
||||
jsonContent = gd::Serializer::ToJSON(element.GetChild("content"));
|
||||
EM_ASM_INT(
|
||||
{
|
||||
var self = Module['getCache'](Module['ObjectJsImplementation'])[$0];
|
||||
if (!self.content)
|
||||
throw '`content` is not defined on a ObjectJsImplementation.';
|
||||
|
||||
var serializerElement = wrapPointer($1, Module['SerializerElement']);
|
||||
if (!serializerElement.isValueUndefined() || serializerElement.consideredAsArray()) {
|
||||
throw new Error('The element passed to ObjectJsImplementation::DoUnserializeFrom is not an object.');
|
||||
}
|
||||
|
||||
// JSON.parse + toJSON is 30% faster than gd.Serializer.toJSObject.
|
||||
self.content = JSON.parse(gd.Serializer.toJSON(serializerElement));
|
||||
},
|
||||
(int)this,
|
||||
(int)&element.GetChild("content"));
|
||||
}
|
||||
|
||||
void ObjectJsImplementation::__destroy__() { // Useless?
|
||||
|
@@ -9,15 +9,17 @@
|
||||
using namespace gd;
|
||||
|
||||
/**
|
||||
* \brief A gd::Object that stores its content in JSON and forward the
|
||||
* properties related functions to Javascript with Emscripten.
|
||||
* \brief A gd::ObjectConfiguration that wraps a Javascript object:
|
||||
* the content of the object is stored in JavaScript in a "content" property,
|
||||
* allowing both fast access in JavaScript and still ability to access properties
|
||||
* via the usual methods.
|
||||
*
|
||||
* It also implements "ExposeResources" to expose the properties of type
|
||||
* "resource".
|
||||
*/
|
||||
class ObjectJsImplementation : public gd::ObjectConfiguration {
|
||||
public:
|
||||
ObjectJsImplementation() : jsonContent("{}") {}
|
||||
ObjectJsImplementation() {}
|
||||
std::unique_ptr<gd::ObjectConfiguration> Clone() const override;
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor> GetProperties() const override;
|
||||
@@ -31,16 +33,9 @@ class ObjectJsImplementation : public gd::ObjectConfiguration {
|
||||
|
||||
void __destroy__();
|
||||
|
||||
const gd::String& GetRawJSONContent() const { return jsonContent; };
|
||||
ObjectJsImplementation& SetRawJSONContent(const gd::String& newContent) {
|
||||
jsonContent = newContent;
|
||||
return *this;
|
||||
};
|
||||
|
||||
void ExposeResources(gd::ArbitraryResourceWorker& worker) override;
|
||||
|
||||
protected:
|
||||
void DoSerializeTo(SerializerElement& arg0) const override;
|
||||
void DoUnserializeFrom(Project& arg0, const SerializerElement& arg1) override;
|
||||
gd::String jsonContent;
|
||||
};
|
||||
|
@@ -277,6 +277,42 @@ var adaptNamingConventions = function (gd) {
|
||||
this.insert(e, this.size() - 1);
|
||||
};
|
||||
|
||||
// A deep clone function for the `content` of `ObjectJsImplementation`.
|
||||
function deepClone(obj) {
|
||||
// Handle null, undefined, and non-object values
|
||||
if (obj === null || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Handle Date
|
||||
if (obj instanceof Date) {
|
||||
return new Date(obj.getTime());
|
||||
}
|
||||
|
||||
// Handle Array
|
||||
if (Array.isArray(obj)) {
|
||||
const clonedArr = [];
|
||||
for (let i = 0; i < obj.length; i++) {
|
||||
clonedArr.push(deepClone(obj[i]));
|
||||
}
|
||||
return clonedArr;
|
||||
}
|
||||
|
||||
// Handle Objects
|
||||
const clonedObj = {};
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) { // Ensure key is directly on obj
|
||||
clonedObj[key] = deepClone(obj[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return clonedObj;
|
||||
}
|
||||
|
||||
gd._deepCloneForObjectJsImplementationContent = function (obj) {
|
||||
return deepClone(obj);
|
||||
}
|
||||
|
||||
return gd;
|
||||
};
|
||||
|
||||
|
@@ -84,15 +84,15 @@ module.exports = {
|
||||
const fakeObject = new gd.ObjectJsImplementation();
|
||||
|
||||
fakeObject.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
return false;
|
||||
};
|
||||
|
||||
fakeObject.getProperties = function (objectContent) {
|
||||
fakeObject.getProperties = function () {
|
||||
const objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
const objectContent = this.content;
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('text')
|
||||
@@ -102,14 +102,11 @@ module.exports = {
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
fakeObject.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
text: 'Some text.',
|
||||
})
|
||||
);
|
||||
fakeObject.content = {
|
||||
text: 'Some text.',
|
||||
};
|
||||
|
||||
fakeObject.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
propertyName,
|
||||
newValue
|
||||
@@ -118,7 +115,6 @@ module.exports = {
|
||||
};
|
||||
|
||||
fakeObject.getInitialInstanceProperties = function (
|
||||
content,
|
||||
instance
|
||||
) {
|
||||
var instanceProperties = new gd.MapStringPropertyDescriptor();
|
||||
@@ -142,21 +138,19 @@ module.exports = {
|
||||
const fakeObject = new gd.ObjectJsImplementation();
|
||||
|
||||
fakeObject.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
return false;
|
||||
};
|
||||
|
||||
fakeObject.getProperties = function (objectContent) {
|
||||
fakeObject.getProperties = function () {
|
||||
const objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
return objectProperties;
|
||||
};
|
||||
fakeObject.setRawJSONContent(JSON.stringify({}));
|
||||
fakeObject.content = {};
|
||||
|
||||
fakeObject.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
propertyName,
|
||||
newValue
|
||||
@@ -165,7 +159,6 @@ module.exports = {
|
||||
};
|
||||
|
||||
fakeObject.getInitialInstanceProperties = function (
|
||||
content,
|
||||
instance
|
||||
) {
|
||||
var instanceProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
@@ -2105,38 +2105,35 @@ describe('libGD.js', function () {
|
||||
describe('gd.ObjectJsImplementation', function () {
|
||||
const createSampleObjectJsImplementation = () => {
|
||||
let myObject = new gd.ObjectJsImplementation();
|
||||
myObject.updateProperty = function (content, propertyName, newValue) {
|
||||
myObject.updateProperty = function (propertyName, newValue) {
|
||||
if (propertyName === 'My first property') {
|
||||
content.property1 = newValue;
|
||||
this.content.property1 = newValue;
|
||||
return true;
|
||||
}
|
||||
if (propertyName === 'My other property') {
|
||||
content.property2 = newValue === '1';
|
||||
this.content.property2 = newValue === '1';
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
myObject.getProperties = function (content) {
|
||||
myObject.getProperties = function () {
|
||||
let properties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
properties.getOrCreate('My first property').setValue(content.property1);
|
||||
properties.getOrCreate('My first property').setValue(this.content.property1);
|
||||
properties
|
||||
.getOrCreate('My other property')
|
||||
.setValue(content.property2 ? '1' : '0')
|
||||
.setValue(this.content.property2 ? '1' : '0')
|
||||
.setType('Boolean');
|
||||
|
||||
return properties;
|
||||
};
|
||||
myObject.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
property1: 'Initial value 1',
|
||||
property2: true,
|
||||
})
|
||||
);
|
||||
myObject.content = {
|
||||
property1: 'Initial value 1',
|
||||
property2: true,
|
||||
};
|
||||
|
||||
myObject.updateInitialInstanceProperty = function (
|
||||
content,
|
||||
instance,
|
||||
propertyName,
|
||||
newValue
|
||||
@@ -2153,7 +2150,6 @@ describe('libGD.js', function () {
|
||||
return false;
|
||||
};
|
||||
myObject.getInitialInstanceProperties = function (
|
||||
content,
|
||||
instance
|
||||
) {
|
||||
let properties = new gd.MapStringPropertyDescriptor();
|
||||
@@ -2169,11 +2165,8 @@ describe('libGD.js', function () {
|
||||
return properties;
|
||||
};
|
||||
|
||||
// TODO: Workaround a bad design of ObjectJsImplementation. When getProperties
|
||||
// and associated methods are redefined in JS, they have different arguments (
|
||||
// see ObjectJsImplementation C++ implementation). If called directly here from JS,
|
||||
// the arguments will be mismatched. To workaround this, always case the object to
|
||||
// a base gdObject to ensure C++ methods are called.
|
||||
// Cast the object to a base gdObjectConfiguration to ensure C++ methods are called,
|
||||
// and verify they properly call their JS equivalents.
|
||||
return gd.castObject(myObject, gd.ObjectConfiguration);
|
||||
};
|
||||
|
||||
@@ -2229,7 +2222,12 @@ describe('libGD.js', function () {
|
||||
const object2 = object1.clone().release();
|
||||
const object3 = object1.clone().release();
|
||||
|
||||
const object1jsImplementation = gd.castObject(object1, gd.ObjectJsImplementation);
|
||||
const object2jsImplementation = gd.castObject(object2, gd.ObjectJsImplementation);
|
||||
const object3jsImplementation = gd.castObject(object3, gd.ObjectJsImplementation);
|
||||
|
||||
{
|
||||
// Check properties can be accessed.
|
||||
const propertiesObject1 = object1.getProperties();
|
||||
expect(propertiesObject1.has('My first property'));
|
||||
expect(
|
||||
@@ -2240,9 +2238,24 @@ describe('libGD.js', function () {
|
||||
expect(
|
||||
propertiesObject2.get('My first property').getValue() == 'test1'
|
||||
);
|
||||
|
||||
// Check the JavaScript objects are unchanged for now.
|
||||
expect(object1jsImplementation.content).toEqual({
|
||||
property1: 'test1',
|
||||
property2: true,
|
||||
});
|
||||
expect(object2jsImplementation.content).toEqual({
|
||||
property1: 'test1',
|
||||
property2: true,
|
||||
});
|
||||
expect(object3jsImplementation.content).toEqual({
|
||||
property1: 'test1',
|
||||
property2: true,
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
// Check a property can be updated.
|
||||
object1.updateProperty('My first property', 'updated value');
|
||||
const propertiesObject1 = object1.getProperties();
|
||||
expect(propertiesObject1.has('My first property'));
|
||||
@@ -2255,9 +2268,24 @@ describe('libGD.js', function () {
|
||||
expect(
|
||||
propertiesObject2.get('My first property').getValue() == 'test1'
|
||||
);
|
||||
|
||||
// Check the JavaScript objects are updated.
|
||||
expect(object1jsImplementation.content).toEqual({
|
||||
property1: 'updated value',
|
||||
property2: true,
|
||||
});
|
||||
expect(object2jsImplementation.content).toEqual({
|
||||
property1: 'test1',
|
||||
property2: true,
|
||||
});
|
||||
expect(object3jsImplementation.content).toEqual({
|
||||
property1: 'test1',
|
||||
property2: true,
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
// Check a property from another object can be updated.
|
||||
object2.updateProperty('My first property', 'updated value object 2');
|
||||
const propertiesObject1 = object1.getProperties();
|
||||
expect(propertiesObject1.has('My first property'));
|
||||
@@ -2276,6 +2304,20 @@ describe('libGD.js', function () {
|
||||
expect(
|
||||
propertiesObject3.get('My first property').getValue() == 'test1'
|
||||
);
|
||||
|
||||
// Check the JavaScript objects are updated.
|
||||
expect(object1jsImplementation.content).toEqual({
|
||||
property1: 'updated value',
|
||||
property2: true,
|
||||
});
|
||||
expect(object2jsImplementation.content).toEqual({
|
||||
property1: 'updated value object 2',
|
||||
property2: true,
|
||||
});
|
||||
expect(object3jsImplementation.content).toEqual({
|
||||
property1: 'test1',
|
||||
property2: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@@ -77,8 +77,9 @@ const extraClassAttributes = {
|
||||
'static Default = 0;',
|
||||
'static Visible = 1;',
|
||||
'static Hidden = 2;',
|
||||
]
|
||||
}
|
||||
],
|
||||
ObjectJsImplementation: ['content: Record<string, any>;'],
|
||||
};
|
||||
|
||||
const PrimitiveTypes = new Map([
|
||||
['DOMString', 'string'],
|
||||
@@ -340,7 +341,9 @@ for (const [
|
||||
} {${methods.length ? '\n ' + methods.join('\n ') : ''}${
|
||||
attributes.length ? '\n ' + attributes.join('\n ') : ''
|
||||
}${
|
||||
(extraClassAttributes[interfaceName] || []).join('\n ')
|
||||
extraClassAttributes[interfaceName]
|
||||
? '\n ' + extraClassAttributes[interfaceName].join('\n ')
|
||||
: ''
|
||||
}
|
||||
}`
|
||||
);
|
||||
|
53
GDevelop.js/types.d.ts
vendored
53
GDevelop.js/types.d.ts
vendored
@@ -733,10 +733,9 @@ export class ObjectJsImplementation extends ObjectConfiguration {
|
||||
updateProperty(name: string, value: string): boolean;
|
||||
getInitialInstanceProperties(instance: InitialInstance): MapStringPropertyDescriptor;
|
||||
updateInitialInstanceProperty(instance: InitialInstance, name: string, value: string): boolean;
|
||||
getRawJSONContent(): string;
|
||||
setRawJSONContent(newContent: string): ObjectJsImplementation;
|
||||
serializeTo(element: SerializerElement): void;
|
||||
unserializeFrom(project: Project, element: SerializerElement): void;
|
||||
content: Record<string, any>;
|
||||
}
|
||||
|
||||
export class CustomObjectConfiguration extends ObjectConfiguration {
|
||||
@@ -751,6 +750,8 @@ export class CustomObjectConfiguration extends ObjectConfiguration {
|
||||
getInitialInstanceProperties(instance: InitialInstance): MapStringPropertyDescriptor;
|
||||
updateInitialInstanceProperty(instance: InitialInstance, name: string, value: string): boolean;
|
||||
getAnimations(): SpriteAnimationList;
|
||||
isChildObjectFolded(childName: string): boolean;
|
||||
setChildObjectFolded(childName: string, folded: boolean): void;
|
||||
static getEdgeAnchorFromString(value: string): CustomObjectConfiguration_EdgeAnchor;
|
||||
}
|
||||
|
||||
@@ -1525,9 +1526,12 @@ export class ObjectMetadata extends EmscriptenObject {
|
||||
isHidden(): boolean;
|
||||
markAsRenderedIn3D(): ObjectMetadata;
|
||||
isRenderedIn3D(): boolean;
|
||||
setOpenFullEditorLabel(label: string): ObjectMetadata;
|
||||
getOpenFullEditorLabel(): string;
|
||||
}
|
||||
|
||||
export class QuickCustomization extends EmscriptenObject {static Default = 0;
|
||||
export class QuickCustomization extends EmscriptenObject {
|
||||
static Default = 0;
|
||||
static Visible = 1;
|
||||
static Hidden = 2;
|
||||
}
|
||||
@@ -1573,6 +1577,8 @@ export class BehaviorMetadata extends EmscriptenObject {
|
||||
setHidden(): BehaviorMetadata;
|
||||
getQuickCustomizationVisibility(): QuickCustomization_Visibility;
|
||||
setQuickCustomizationVisibility(visibility: QuickCustomization_Visibility): BehaviorMetadata;
|
||||
setOpenFullEditorLabel(label: string): BehaviorMetadata;
|
||||
getOpenFullEditorLabel(): string;
|
||||
get(): Behavior;
|
||||
getSharedDataInstance(): BehaviorsSharedData;
|
||||
getProperties(): MapStringPropertyDescriptor;
|
||||
@@ -2544,6 +2550,17 @@ export class Model3DObjectConfiguration extends ObjectConfiguration {
|
||||
hasNoAnimations(): boolean;
|
||||
swapAnimations(first: number, second: number): void;
|
||||
moveAnimation(oldIndex: number, newIndex: number): void;
|
||||
getWidth(): number;
|
||||
getHeight(): number;
|
||||
getDepth(): number;
|
||||
getRotationX(): number;
|
||||
getRotationY(): number;
|
||||
getRotationZ(): number;
|
||||
getModelResourceName(): string;
|
||||
getMaterialType(): string;
|
||||
getOriginLocation(): string;
|
||||
getCenterLocation(): string;
|
||||
shouldKeepAspectRatio(): boolean;
|
||||
}
|
||||
|
||||
export class SpineAnimation extends EmscriptenObject {
|
||||
@@ -2567,6 +2584,8 @@ export class SpineObjectConfiguration extends ObjectConfiguration {
|
||||
hasNoAnimations(): boolean;
|
||||
swapAnimations(first: number, second: number): void;
|
||||
moveAnimation(oldIndex: number, newIndex: number): void;
|
||||
getScale(): number;
|
||||
getSpineResourceName(): string;
|
||||
}
|
||||
|
||||
export class Vector2f extends EmscriptenObject {
|
||||
@@ -2663,18 +2682,14 @@ export class ShapePainterObject extends ObjectConfiguration {
|
||||
isClearedBetweenFrames(): boolean;
|
||||
setOutlineSize(size: number): void;
|
||||
getOutlineSize(): number;
|
||||
setOutlineColor(color: string): void;
|
||||
getOutlineColor(): string;
|
||||
setOutlineOpacity(val: number): void;
|
||||
getOutlineOpacity(): number;
|
||||
setOutlineColor(r: number, g: number, b: number): void;
|
||||
getOutlineColorR(): number;
|
||||
getOutlineColorG(): number;
|
||||
getOutlineColorB(): number;
|
||||
setFillColor(color: string): void;
|
||||
getFillColor(): string;
|
||||
setFillOpacity(val: number): void;
|
||||
getFillOpacity(): number;
|
||||
setFillColor(r: number, g: number, b: number): void;
|
||||
getFillColorR(): number;
|
||||
getFillColorG(): number;
|
||||
getFillColorB(): number;
|
||||
getAntialiasing(): string;
|
||||
setAntialiasing(value: string): void;
|
||||
}
|
||||
@@ -2724,18 +2739,10 @@ export class ParticleEmitterObject extends ObjectConfiguration {
|
||||
getParticleLifeTimeMin(): number;
|
||||
setParticleLifeTimeMax(newValue: number): void;
|
||||
getParticleLifeTimeMax(): number;
|
||||
setParticleRed1(newValue: number): void;
|
||||
getParticleRed1(): number;
|
||||
setParticleRed2(newValue: number): void;
|
||||
getParticleRed2(): number;
|
||||
setParticleGreen1(newValue: number): void;
|
||||
getParticleGreen1(): number;
|
||||
setParticleGreen2(newValue: number): void;
|
||||
getParticleGreen2(): number;
|
||||
setParticleBlue1(newValue: number): void;
|
||||
getParticleBlue1(): number;
|
||||
setParticleBlue2(newValue: number): void;
|
||||
getParticleBlue2(): number;
|
||||
setParticleColor1(newValue: string): void;
|
||||
getParticleColor1(): string;
|
||||
setParticleColor2(newValue: string): void;
|
||||
getParticleColor2(): string;
|
||||
setParticleAlpha1(newValue: number): void;
|
||||
getParticleAlpha1(): number;
|
||||
setParticleAlpha2(newValue: number): void;
|
||||
|
@@ -35,6 +35,8 @@ declare class gdBehaviorMetadata {
|
||||
setHidden(): gdBehaviorMetadata;
|
||||
getQuickCustomizationVisibility(): QuickCustomization_Visibility;
|
||||
setQuickCustomizationVisibility(visibility: QuickCustomization_Visibility): gdBehaviorMetadata;
|
||||
setOpenFullEditorLabel(label: string): gdBehaviorMetadata;
|
||||
getOpenFullEditorLabel(): string;
|
||||
get(): gdBehavior;
|
||||
getSharedDataInstance(): gdBehaviorsSharedData;
|
||||
getProperties(): gdMapStringPropertyDescriptor;
|
||||
|
@@ -16,6 +16,8 @@ declare class gdCustomObjectConfiguration extends gdObjectConfiguration {
|
||||
getInitialInstanceProperties(instance: gdInitialInstance): gdMapStringPropertyDescriptor;
|
||||
updateInitialInstanceProperty(instance: gdInitialInstance, name: string, value: string): boolean;
|
||||
getAnimations(): gdSpriteAnimationList;
|
||||
isChildObjectFolded(childName: string): boolean;
|
||||
setChildObjectFolded(childName: string, folded: boolean): void;
|
||||
static getEdgeAnchorFromString(value: string): CustomObjectConfiguration_EdgeAnchor;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
|
@@ -10,6 +10,17 @@ declare class gdModel3DObjectConfiguration extends gdObjectConfiguration {
|
||||
hasNoAnimations(): boolean;
|
||||
swapAnimations(first: number, second: number): void;
|
||||
moveAnimation(oldIndex: number, newIndex: number): void;
|
||||
getWidth(): number;
|
||||
getHeight(): number;
|
||||
getDepth(): number;
|
||||
getRotationX(): number;
|
||||
getRotationY(): number;
|
||||
getRotationZ(): number;
|
||||
getModelResourceName(): string;
|
||||
getMaterialType(): string;
|
||||
getOriginLocation(): string;
|
||||
getCenterLocation(): string;
|
||||
shouldKeepAspectRatio(): boolean;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
};
|
@@ -6,8 +6,6 @@ declare class gdObjectJsImplementation extends gdObjectConfiguration {
|
||||
updateProperty(name: string, value: string): boolean;
|
||||
getInitialInstanceProperties(instance: gdInitialInstance): gdMapStringPropertyDescriptor;
|
||||
updateInitialInstanceProperty(instance: gdInitialInstance, name: string, value: string): boolean;
|
||||
getRawJSONContent(): string;
|
||||
setRawJSONContent(newContent: string): gdObjectJsImplementation;
|
||||
serializeTo(element: gdSerializerElement): void;
|
||||
unserializeFrom(project: gdProject, element: gdSerializerElement): void;
|
||||
delete(): void;
|
||||
|
@@ -28,6 +28,8 @@ declare class gdObjectMetadata {
|
||||
isHidden(): boolean;
|
||||
markAsRenderedIn3D(): gdObjectMetadata;
|
||||
isRenderedIn3D(): boolean;
|
||||
setOpenFullEditorLabel(label: string): gdObjectMetadata;
|
||||
getOpenFullEditorLabel(): string;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
};
|
@@ -40,18 +40,10 @@ declare class gdParticleEmitterObject extends gdObjectConfiguration {
|
||||
getParticleLifeTimeMin(): number;
|
||||
setParticleLifeTimeMax(newValue: number): void;
|
||||
getParticleLifeTimeMax(): number;
|
||||
setParticleRed1(newValue: number): void;
|
||||
getParticleRed1(): number;
|
||||
setParticleRed2(newValue: number): void;
|
||||
getParticleRed2(): number;
|
||||
setParticleGreen1(newValue: number): void;
|
||||
getParticleGreen1(): number;
|
||||
setParticleGreen2(newValue: number): void;
|
||||
getParticleGreen2(): number;
|
||||
setParticleBlue1(newValue: number): void;
|
||||
getParticleBlue1(): number;
|
||||
setParticleBlue2(newValue: number): void;
|
||||
getParticleBlue2(): number;
|
||||
setParticleColor1(newValue: string): void;
|
||||
getParticleColor1(): string;
|
||||
setParticleColor2(newValue: string): void;
|
||||
getParticleColor2(): string;
|
||||
setParticleAlpha1(newValue: number): void;
|
||||
getParticleAlpha1(): number;
|
||||
setParticleAlpha2(newValue: number): void;
|
||||
|
@@ -8,18 +8,14 @@ declare class gdShapePainterObject extends gdObjectConfiguration {
|
||||
isClearedBetweenFrames(): boolean;
|
||||
setOutlineSize(size: number): void;
|
||||
getOutlineSize(): number;
|
||||
setOutlineColor(color: string): void;
|
||||
getOutlineColor(): string;
|
||||
setOutlineOpacity(val: number): void;
|
||||
getOutlineOpacity(): number;
|
||||
setOutlineColor(r: number, g: number, b: number): void;
|
||||
getOutlineColorR(): number;
|
||||
getOutlineColorG(): number;
|
||||
getOutlineColorB(): number;
|
||||
setFillColor(color: string): void;
|
||||
getFillColor(): string;
|
||||
setFillOpacity(val: number): void;
|
||||
getFillOpacity(): number;
|
||||
setFillColor(r: number, g: number, b: number): void;
|
||||
getFillColorR(): number;
|
||||
getFillColorG(): number;
|
||||
getFillColorB(): number;
|
||||
getAntialiasing(): string;
|
||||
setAntialiasing(value: string): void;
|
||||
delete(): void;
|
||||
|
@@ -10,6 +10,8 @@ declare class gdSpineObjectConfiguration extends gdObjectConfiguration {
|
||||
hasNoAnimations(): boolean;
|
||||
swapAnimations(first: number, second: number): void;
|
||||
moveAnimation(oldIndex: number, newIndex: number): void;
|
||||
getScale(): number;
|
||||
getSpineResourceName(): string;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
};
|
@@ -284,74 +284,55 @@ const BehaviorConfigurationEditor = React.forwardRef<
|
||||
}
|
||||
);
|
||||
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
eventsFunctionsExtension: gdEventsFunctionsExtension | null,
|
||||
object: gdObject,
|
||||
onUpdateBehaviorsSharedData: () => void,
|
||||
onSizeUpdated?: ?() => void,
|
||||
resourceManagementProps: ResourceManagementProps,
|
||||
onBehaviorsUpdated: () => void,
|
||||
openBehaviorEvents: (
|
||||
extensionName: string,
|
||||
behaviorName: string
|
||||
) => Promise<void>,
|
||||
type UseManageBehaviorsState = {|
|
||||
// Operations:
|
||||
changeBehaviorName: (behavior: gdBehavior, newName: string) => void,
|
||||
removeBehavior: (behaviorName: string) => void,
|
||||
copyBehavior: (behaviorName: string) => void,
|
||||
copyAllBehaviors: () => void,
|
||||
pasteBehaviors: () => Promise<void>,
|
||||
openNewBehaviorDialog: () => void,
|
||||
resetJustAddedBehaviorName: () => void,
|
||||
|
||||
// Visual state:
|
||||
newBehaviorDialog: React.Node,
|
||||
justAddedBehaviorName: ?string,
|
||||
|};
|
||||
|
||||
const BehaviorsEditor = (props: Props) => {
|
||||
const { isMobile } = useResponsiveWindowSize();
|
||||
const scrollView = React.useRef<?ScrollViewInterface>(null);
|
||||
/**
|
||||
* A hook allowing to add/remove/modify behaviors of an object.
|
||||
*/
|
||||
export const useManageObjectBehaviors = ({
|
||||
project,
|
||||
object,
|
||||
eventsFunctionsExtension,
|
||||
onUpdate,
|
||||
onSizeUpdated,
|
||||
onBehaviorsUpdated,
|
||||
onUpdateBehaviorsSharedData,
|
||||
}: {
|
||||
project: gdProject,
|
||||
object: gdObject,
|
||||
eventsFunctionsExtension: gdEventsFunctionsExtension | null,
|
||||
onUpdate: () => void,
|
||||
onSizeUpdated?: ?() => void,
|
||||
onBehaviorsUpdated?: ?() => void,
|
||||
onUpdateBehaviorsSharedData: () => void,
|
||||
}): UseManageBehaviorsState => {
|
||||
const [
|
||||
justAddedBehaviorName,
|
||||
setJustAddedBehaviorName,
|
||||
] = React.useState<?string>(null);
|
||||
const justAddedBehaviorAccordionElement = React.useRef<?BehaviorConfigurationEditorInterface>(
|
||||
null
|
||||
);
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
if (
|
||||
scrollView.current &&
|
||||
justAddedBehaviorAccordionElement.current &&
|
||||
justAddedBehaviorName
|
||||
) {
|
||||
scrollView.current.scrollTo(justAddedBehaviorAccordionElement.current);
|
||||
setJustAddedBehaviorName(null);
|
||||
justAddedBehaviorAccordionElement.current = null;
|
||||
}
|
||||
},
|
||||
[justAddedBehaviorName]
|
||||
);
|
||||
|
||||
const [newBehaviorDialogOpen, setNewBehaviorDialogOpen] = React.useState(
|
||||
false
|
||||
);
|
||||
const [
|
||||
selectedQuickCustomizationPropertiesBehavior,
|
||||
setSelectedQuickCustomizationPropertiesBehavior,
|
||||
] = React.useState<?gdBehavior>(null);
|
||||
|
||||
const openNewBehaviorDialog = React.useCallback(() => {
|
||||
setNewBehaviorDialogOpen(true);
|
||||
}, []);
|
||||
|
||||
const showBehaviorOverridingConfirmation = useBehaviorOverridingAlertDialog();
|
||||
|
||||
const {
|
||||
object,
|
||||
project,
|
||||
eventsFunctionsExtension,
|
||||
onSizeUpdated,
|
||||
onBehaviorsUpdated,
|
||||
onUpdateBehaviorsSharedData,
|
||||
openBehaviorEvents,
|
||||
} = props;
|
||||
// As for now, any default behavior is hidden,
|
||||
// it avoids to get behavior metadata to check the "hidden" flag.
|
||||
const allVisibleBehaviors = object
|
||||
.getAllBehaviorNames()
|
||||
.toJSArray()
|
||||
.map(behaviorName => object.getBehavior(behaviorName))
|
||||
.filter(behavior => !behavior.isDefaultBehavior());
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const addBehavior = React.useCallback(
|
||||
(type: string, defaultName: string) => {
|
||||
const wasBehaviorAdded = addBehaviorToObject(
|
||||
@@ -370,13 +351,13 @@ const BehaviorsEditor = (props: Props) => {
|
||||
setJustAddedBehaviorName(defaultName);
|
||||
}
|
||||
|
||||
forceUpdate();
|
||||
onUpdate();
|
||||
if (onSizeUpdated) onSizeUpdated();
|
||||
onUpdateBehaviorsSharedData();
|
||||
if (onBehaviorsUpdated) onBehaviorsUpdated();
|
||||
},
|
||||
[
|
||||
forceUpdate,
|
||||
onUpdate,
|
||||
object,
|
||||
onBehaviorsUpdated,
|
||||
onSizeUpdated,
|
||||
@@ -385,7 +366,7 @@ const BehaviorsEditor = (props: Props) => {
|
||||
]
|
||||
);
|
||||
|
||||
const onChangeBehaviorName = React.useCallback(
|
||||
const changeBehaviorName = React.useCallback(
|
||||
(behavior: gdBehavior, newName: string) => {
|
||||
// TODO: This is disabled for now as there is no proper refactoring
|
||||
// of events after a behavior renaming. Once refactoring is available,
|
||||
@@ -395,13 +376,13 @@ const BehaviorsEditor = (props: Props) => {
|
||||
|
||||
if (object.hasBehaviorNamed(newName)) return;
|
||||
object.renameBehavior(behavior.getName(), newName);
|
||||
forceUpdate();
|
||||
onUpdate();
|
||||
if (onBehaviorsUpdated) onBehaviorsUpdated();
|
||||
},
|
||||
[forceUpdate, object, onBehaviorsUpdated]
|
||||
[onUpdate, object, onBehaviorsUpdated]
|
||||
);
|
||||
|
||||
const onRemoveBehavior = React.useCallback(
|
||||
const removeBehavior = React.useCallback(
|
||||
(behaviorName: string) => {
|
||||
let message =
|
||||
"Are you sure you want to remove this behavior? This can't be undone.";
|
||||
@@ -437,9 +418,9 @@ const BehaviorsEditor = (props: Props) => {
|
||||
serializedBehavior: serializeToJSObject(behavior),
|
||||
},
|
||||
]);
|
||||
forceUpdate();
|
||||
onUpdate();
|
||||
},
|
||||
[forceUpdate, object]
|
||||
[onUpdate, object]
|
||||
);
|
||||
|
||||
const copyAllBehaviors = React.useCallback(
|
||||
@@ -458,9 +439,9 @@ const BehaviorsEditor = (props: Props) => {
|
||||
};
|
||||
}).filter(Boolean)
|
||||
);
|
||||
forceUpdate();
|
||||
onUpdate();
|
||||
},
|
||||
[forceUpdate, object]
|
||||
[onUpdate, object]
|
||||
);
|
||||
|
||||
const pasteBehaviors = React.useCallback(
|
||||
@@ -560,7 +541,7 @@ const BehaviorsEditor = (props: Props) => {
|
||||
}
|
||||
}
|
||||
|
||||
forceUpdate();
|
||||
onUpdate();
|
||||
if (firstAddedBehaviorName) {
|
||||
setJustAddedBehaviorName(firstAddedBehaviorName);
|
||||
if (onSizeUpdated) onSizeUpdated();
|
||||
@@ -573,7 +554,7 @@ const BehaviorsEditor = (props: Props) => {
|
||||
}
|
||||
},
|
||||
[
|
||||
forceUpdate,
|
||||
onUpdate,
|
||||
object,
|
||||
onBehaviorsUpdated,
|
||||
onSizeUpdated,
|
||||
@@ -583,6 +564,115 @@ const BehaviorsEditor = (props: Props) => {
|
||||
]
|
||||
);
|
||||
|
||||
const newBehaviorDialog = newBehaviorDialogOpen && (
|
||||
<NewBehaviorDialog
|
||||
open
|
||||
objectType={object.getType()}
|
||||
objectBehaviorsTypes={listObjectBehaviorsTypes(object)}
|
||||
onClose={() => setNewBehaviorDialogOpen(false)}
|
||||
onChoose={addBehavior}
|
||||
project={project}
|
||||
eventsFunctionsExtension={eventsFunctionsExtension}
|
||||
/>
|
||||
);
|
||||
|
||||
const resetJustAddedBehaviorName = React.useCallback(() => {
|
||||
setJustAddedBehaviorName(null);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
changeBehaviorName,
|
||||
removeBehavior,
|
||||
copyBehavior,
|
||||
copyAllBehaviors,
|
||||
pasteBehaviors,
|
||||
newBehaviorDialog,
|
||||
openNewBehaviorDialog,
|
||||
justAddedBehaviorName,
|
||||
resetJustAddedBehaviorName,
|
||||
};
|
||||
};
|
||||
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
eventsFunctionsExtension: gdEventsFunctionsExtension | null,
|
||||
object: gdObject,
|
||||
onUpdateBehaviorsSharedData: () => void,
|
||||
onSizeUpdated?: ?() => void,
|
||||
resourceManagementProps: ResourceManagementProps,
|
||||
onBehaviorsUpdated: () => void,
|
||||
openBehaviorEvents: (
|
||||
extensionName: string,
|
||||
behaviorName: string
|
||||
) => Promise<void>,
|
||||
|};
|
||||
|
||||
const BehaviorsEditor = (props: Props) => {
|
||||
const { isMobile } = useResponsiveWindowSize();
|
||||
const scrollView = React.useRef<?ScrollViewInterface>(null);
|
||||
const justAddedBehaviorAccordionElement = React.useRef<?BehaviorConfigurationEditorInterface>(
|
||||
null
|
||||
);
|
||||
|
||||
const {
|
||||
object,
|
||||
project,
|
||||
eventsFunctionsExtension,
|
||||
onSizeUpdated,
|
||||
onBehaviorsUpdated,
|
||||
onUpdateBehaviorsSharedData,
|
||||
openBehaviorEvents,
|
||||
} = props;
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const [
|
||||
selectedQuickCustomizationPropertiesBehavior,
|
||||
setSelectedQuickCustomizationPropertiesBehavior,
|
||||
] = React.useState<?gdBehavior>(null);
|
||||
|
||||
const {
|
||||
changeBehaviorName,
|
||||
removeBehavior,
|
||||
copyBehavior,
|
||||
copyAllBehaviors,
|
||||
pasteBehaviors,
|
||||
newBehaviorDialog,
|
||||
openNewBehaviorDialog,
|
||||
justAddedBehaviorName,
|
||||
resetJustAddedBehaviorName,
|
||||
} = useManageObjectBehaviors({
|
||||
project,
|
||||
object,
|
||||
eventsFunctionsExtension,
|
||||
onUpdate: forceUpdate,
|
||||
onSizeUpdated,
|
||||
onBehaviorsUpdated,
|
||||
onUpdateBehaviorsSharedData,
|
||||
});
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
if (
|
||||
scrollView.current &&
|
||||
justAddedBehaviorAccordionElement.current &&
|
||||
justAddedBehaviorName
|
||||
) {
|
||||
scrollView.current.scrollTo(justAddedBehaviorAccordionElement.current);
|
||||
resetJustAddedBehaviorName();
|
||||
justAddedBehaviorAccordionElement.current = null;
|
||||
}
|
||||
},
|
||||
[justAddedBehaviorName, resetJustAddedBehaviorName]
|
||||
);
|
||||
|
||||
// As for now, any default behavior is hidden,
|
||||
// it avoids to get behavior metadata to check the "hidden" flag.
|
||||
const allVisibleBehaviors = object
|
||||
.getAllBehaviorNames()
|
||||
.toJSArray()
|
||||
.map(behaviorName => object.getBehavior(behaviorName))
|
||||
.filter(behavior => !behavior.isDefaultBehavior());
|
||||
|
||||
const openExtension = React.useCallback(
|
||||
(behaviorType: string) => {
|
||||
const elements = behaviorType.split('::');
|
||||
@@ -634,7 +724,7 @@ const BehaviorsEditor = (props: Props) => {
|
||||
actionLabel={
|
||||
isMobile ? <Trans>Add</Trans> : <Trans>Add a behavior</Trans>
|
||||
}
|
||||
onAction={() => setNewBehaviorDialogOpen(true)}
|
||||
onAction={openNewBehaviorDialog}
|
||||
secondaryActionIcon={<PasteIcon />}
|
||||
secondaryActionLabel={
|
||||
isClipboardContainingBehaviors ? <Trans>Paste</Trans> : null
|
||||
@@ -663,9 +753,9 @@ const BehaviorsEditor = (props: Props) => {
|
||||
object={object}
|
||||
behavior={behavior}
|
||||
copyBehavior={copyBehavior}
|
||||
onRemoveBehavior={onRemoveBehavior}
|
||||
onRemoveBehavior={removeBehavior}
|
||||
onBehaviorsUpdated={onBehaviorsUpdated}
|
||||
onChangeBehaviorName={onChangeBehaviorName}
|
||||
onChangeBehaviorName={changeBehaviorName}
|
||||
openExtension={openExtension}
|
||||
openBehaviorPropertiesQuickCustomizationDialog={
|
||||
openBehaviorPropertiesQuickCustomizationDialog
|
||||
@@ -715,7 +805,7 @@ const BehaviorsEditor = (props: Props) => {
|
||||
)
|
||||
}
|
||||
primary
|
||||
onClick={() => setNewBehaviorDialogOpen(true)}
|
||||
onClick={openNewBehaviorDialog}
|
||||
icon={<Add />}
|
||||
id="add-behavior-button"
|
||||
/>
|
||||
@@ -724,19 +814,7 @@ const BehaviorsEditor = (props: Props) => {
|
||||
</Column>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
{newBehaviorDialogOpen && (
|
||||
<NewBehaviorDialog
|
||||
open={newBehaviorDialogOpen}
|
||||
objectType={object.getType()}
|
||||
objectBehaviorsTypes={listObjectBehaviorsTypes(object)}
|
||||
onClose={() => setNewBehaviorDialogOpen(false)}
|
||||
onChoose={addBehavior}
|
||||
project={project}
|
||||
eventsFunctionsExtension={eventsFunctionsExtension}
|
||||
/>
|
||||
)}
|
||||
|
||||
{newBehaviorDialog}
|
||||
{!!selectedQuickCustomizationPropertiesBehavior && (
|
||||
<QuickCustomizationPropertiesVisibilityDialog
|
||||
open={!!selectedQuickCustomizationPropertiesBehavior}
|
||||
|
@@ -9,7 +9,7 @@ import { tooltipEnterDelay } from '../UI/Tooltip';
|
||||
|
||||
const styles = {
|
||||
leftColumn: { flex: 2, minWidth: 0, maxWidth: 150 },
|
||||
rightColumn: { flex: 3, minWidth: 75 },
|
||||
rightColumn: { flex: 3, minWidth: 25 },
|
||||
label: {
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
|
@@ -69,6 +69,10 @@ const createField = (
|
||||
getLabel,
|
||||
getDescription,
|
||||
hasImpactOnAllOtherFields: property.hasImpactOnOtherProperties(),
|
||||
canBeUnlimitedUsingMinus1: property
|
||||
.getExtraInfo()
|
||||
.toJSArray()
|
||||
.includes('canBeUnlimitedUsingMinus1'),
|
||||
getEndAdornment,
|
||||
};
|
||||
} else if (valueType === 'string' || valueType === '') {
|
||||
@@ -132,6 +136,7 @@ const createField = (
|
||||
property.getExtraInfo().size() > 0 ? property.getExtraInfo().at(0) : '';
|
||||
return {
|
||||
name,
|
||||
isHiddenWhenOnlyOneChoice: true,
|
||||
valueType: 'string',
|
||||
getChoices: () => {
|
||||
return !object || behaviorType === ''
|
||||
@@ -331,7 +336,7 @@ const propertiesMapToSchema = ({
|
||||
object,
|
||||
visibility = 'All',
|
||||
quickCustomizationVisibilities,
|
||||
}: {
|
||||
}: {|
|
||||
properties: gdMapStringPropertyDescriptor,
|
||||
getProperties: (instance: Instance) => any,
|
||||
onUpdateProperty: (
|
||||
@@ -342,7 +347,7 @@ const propertiesMapToSchema = ({
|
||||
object?: gdObject,
|
||||
visibility?: 'All' | 'Basic' | 'Advanced' | 'Deprecated' | 'Basic-Quick',
|
||||
quickCustomizationVisibilities?: gdQuickCustomizationVisibilitiesContainer,
|
||||
}): Schema => {
|
||||
|}): Schema => {
|
||||
const propertyNames = properties.keys();
|
||||
// Aggregate field by groups to be able to build field groups with a title.
|
||||
const fieldsByGroups = new Map<string, Array<Field>>();
|
||||
|
@@ -45,6 +45,7 @@ export type ValueFieldCommonProperties = {|
|
||||
hideLabel?: boolean,
|
||||
getExtraDescription?: Instance => string,
|
||||
hasImpactOnAllOtherFields?: boolean,
|
||||
canBeUnlimitedUsingMinus1?: boolean,
|
||||
disabled?: (instances: Array<gdInitialInstance>) => boolean,
|
||||
onEditButtonBuildMenuTemplate?: (i18n: I18nType) => Array<MenuItemTemplate>,
|
||||
onEditButtonClick?: () => void,
|
||||
@@ -150,6 +151,7 @@ export type ActionButton = {|
|
||||
getValue: Instance => string,
|
||||
nonFieldType: 'button',
|
||||
getIcon?: ({| fontSize: string |}) => React.Node,
|
||||
showRightIcon?: boolean,
|
||||
onClick: (instance: Instance) => void,
|
||||
|};
|
||||
|
||||
@@ -231,6 +233,10 @@ const styles = {
|
||||
marginTop: marginsSize,
|
||||
borderTop: '1px solid black', // Border color is changed in the component.
|
||||
},
|
||||
level2Separator: {
|
||||
flex: 1,
|
||||
borderTop: '1px solid black', // Border color is changed in the component.
|
||||
},
|
||||
};
|
||||
|
||||
export const Separator = () => {
|
||||
@@ -245,6 +251,18 @@ export const Separator = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const Level2Separator = () => {
|
||||
const gdevelopTheme = React.useContext(GDevelopThemeContext);
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...styles.level2Separator,
|
||||
borderColor: gdevelopTheme.listItem.separatorColor,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getDisabled = ({
|
||||
instances,
|
||||
field,
|
||||
@@ -398,26 +416,21 @@ const CompactPropertiesEditor = ({
|
||||
const { setValue } = field;
|
||||
|
||||
return (
|
||||
<CompactPropertiesEditorRowField
|
||||
<CompactToggleField
|
||||
key={field.name}
|
||||
label={getFieldLabel({ instances, field })}
|
||||
markdownDescription={getFieldDescription(field)}
|
||||
field={
|
||||
<CompactToggleField
|
||||
key={field.name}
|
||||
id={field.name}
|
||||
checked={getFieldValue({ instances, field })}
|
||||
onCheck={newValue => {
|
||||
instances.forEach(i => setValue(i, newValue));
|
||||
onFieldChanged({
|
||||
instances,
|
||||
hasImpactOnAllOtherFields: field.hasImpactOnAllOtherFields,
|
||||
});
|
||||
}}
|
||||
disabled={getDisabled({ instances, field })}
|
||||
fullWidth
|
||||
/>
|
||||
}
|
||||
id={field.name}
|
||||
checked={getFieldValue({ instances, field })}
|
||||
onCheck={newValue => {
|
||||
instances.forEach(i => setValue(i, newValue));
|
||||
onFieldChanged({
|
||||
instances,
|
||||
hasImpactOnAllOtherFields: field.hasImpactOnAllOtherFields,
|
||||
});
|
||||
}}
|
||||
disabled={getDisabled({ instances, field })}
|
||||
fullWidth
|
||||
/>
|
||||
);
|
||||
} else if (field.valueType === 'number') {
|
||||
@@ -458,6 +471,7 @@ const CompactPropertiesEditor = ({
|
||||
return (
|
||||
<CompactSemiControlledNumberField
|
||||
{...commonProps}
|
||||
canBeUnlimitedUsingMinus1={field.canBeUnlimitedUsingMinus1}
|
||||
useLeftIconAsNumberControl
|
||||
renderLeftIcon={field.renderLeftIcon}
|
||||
leftIconTooltip={getFieldLabel({ instances, field })}
|
||||
@@ -470,7 +484,12 @@ const CompactPropertiesEditor = ({
|
||||
key={key}
|
||||
label={getFieldLabel({ instances, field })}
|
||||
markdownDescription={getFieldDescription(field)}
|
||||
field={<CompactSemiControlledNumberField {...otherCommonProps} />}
|
||||
field={
|
||||
<CompactSemiControlledNumberField
|
||||
canBeUnlimitedUsingMinus1={field.canBeUnlimitedUsingMinus1}
|
||||
{...otherCommonProps}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -603,24 +622,27 @@ const CompactPropertiesEditor = ({
|
||||
(field: ValueField) => {
|
||||
if (!field.getChoices || !field.getValue) return;
|
||||
|
||||
const children = field
|
||||
.getChoices()
|
||||
.map(({ value, label, labelIsUserDefined }) => (
|
||||
<SelectOption
|
||||
key={value}
|
||||
value={value}
|
||||
label={label}
|
||||
shouldNotTranslate={labelIsUserDefined}
|
||||
/>
|
||||
));
|
||||
const choices = field.getChoices();
|
||||
if (choices.length < 2 && field.isHiddenWhenOnlyOneChoice) {
|
||||
return;
|
||||
}
|
||||
|
||||
const children = choices.map(({ value, label, labelIsUserDefined }) => (
|
||||
<SelectOption
|
||||
key={value}
|
||||
value={value}
|
||||
label={label}
|
||||
shouldNotTranslate={labelIsUserDefined}
|
||||
/>
|
||||
));
|
||||
|
||||
let compactSelectField;
|
||||
if (field.valueType === 'number') {
|
||||
const { setValue } = field;
|
||||
compactSelectField = (
|
||||
<CompactSelectField
|
||||
value={getFieldValue({ instances, field })}
|
||||
key={field.name}
|
||||
value={getFieldValue({ instances, field })}
|
||||
id={field.name}
|
||||
onChange={(newValue: string) => {
|
||||
instances.forEach(i => setValue(i, parseFloat(newValue) || 0));
|
||||
@@ -638,12 +660,12 @@ const CompactPropertiesEditor = ({
|
||||
const { setValue } = field;
|
||||
compactSelectField = (
|
||||
<CompactSelectField
|
||||
key={field.name}
|
||||
value={getFieldValue({
|
||||
instances,
|
||||
field,
|
||||
defaultValue: '(Multiple values)',
|
||||
})}
|
||||
key={field.name}
|
||||
id={field.name}
|
||||
onChange={(newValue: string) => {
|
||||
instances.forEach(i => setValue(i, newValue || ''));
|
||||
@@ -689,26 +711,31 @@ const CompactPropertiesEditor = ({
|
||||
}) === DIFFERENT_VALUES;
|
||||
}
|
||||
return (
|
||||
<React.Fragment key={`button-${field.label}`}>
|
||||
<FlatButton
|
||||
fullWidth
|
||||
primary
|
||||
leftIcon={
|
||||
field.getIcon ? (
|
||||
field.getIcon({ fontSize: 'small' })
|
||||
) : (
|
||||
<Edit fontSize="small" />
|
||||
)
|
||||
}
|
||||
disabled={disabled}
|
||||
label={field.label}
|
||||
onClick={() => {
|
||||
if (!instances[0]) return;
|
||||
field.onClick(instances[0]);
|
||||
}}
|
||||
/>
|
||||
<Spacer />
|
||||
</React.Fragment>
|
||||
<FlatButton
|
||||
key={`button-${field.label}`}
|
||||
fullWidth
|
||||
primary
|
||||
leftIcon={
|
||||
field.showRightIcon ? null : field.getIcon ? (
|
||||
field.getIcon({ fontSize: 'small' })
|
||||
) : (
|
||||
<Edit fontSize="small" />
|
||||
)
|
||||
}
|
||||
rightIcon={
|
||||
!field.showRightIcon ? null : field.getIcon ? (
|
||||
field.getIcon({ fontSize: 'small' })
|
||||
) : (
|
||||
<Edit fontSize="small" />
|
||||
)
|
||||
}
|
||||
disabled={disabled}
|
||||
label={field.label}
|
||||
onClick={() => {
|
||||
if (!instances[0]) return;
|
||||
field.onClick(instances[0]);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[instances]
|
||||
@@ -880,16 +907,36 @@ const CompactPropertiesEditor = ({
|
||||
},
|
||||
[instances]
|
||||
);
|
||||
const renderSectionTitle = React.useCallback((field: SectionTitle) => {
|
||||
return [
|
||||
<Separator key={field.name + '-separator'} />,
|
||||
<Line key={`section-title-${field.name}`} noMargin>
|
||||
<Text displayInlineAsSpan size="sub-title" noMargin>
|
||||
{field.title}
|
||||
</Text>
|
||||
</Line>,
|
||||
];
|
||||
}, []);
|
||||
const renderSectionTitle = React.useCallback(
|
||||
(field: { name: string, title: string }) => {
|
||||
return [
|
||||
<Separator key={field.name + '-separator'} />,
|
||||
<Line key={`section-title-${field.name}`} noMargin>
|
||||
<Text displayInlineAsSpan size="sub-title" noMargin>
|
||||
{field.title}
|
||||
</Text>
|
||||
</Line>,
|
||||
];
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const renderSectionLevel2Title = React.useCallback(
|
||||
(field: { name: string, title: string }) => {
|
||||
return [
|
||||
<Column expand noMargin key={field.name + '-title'}>
|
||||
<Spacer />
|
||||
<LineStackLayout expand noMargin alignItems="center">
|
||||
<Text size="sub-title" noMargin>
|
||||
{field.title}
|
||||
</Text>
|
||||
<Level2Separator key={field.name + '-separator'} />
|
||||
</LineStackLayout>
|
||||
</Column>,
|
||||
];
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return renderContainer(
|
||||
schema.map(field => {
|
||||
@@ -941,10 +988,10 @@ const CompactPropertiesEditor = ({
|
||||
|
||||
if (field.title) {
|
||||
return [
|
||||
<Separator key={field.name + '-separator'} />,
|
||||
<Text key={field.name + '-title'} size="sub-title" noMargin>
|
||||
{field.title}
|
||||
</Text>,
|
||||
...renderSectionLevel2Title({
|
||||
title: field.title,
|
||||
name: field.name,
|
||||
}),
|
||||
contentView,
|
||||
];
|
||||
}
|
||||
|
36
newIDE/app/src/EffectsList/CompactEffectPropertiesEditor.js
Normal file
36
newIDE/app/src/EffectsList/CompactEffectPropertiesEditor.js
Normal file
@@ -0,0 +1,36 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { type EnumeratedEffectMetadata } from './EnumerateEffects';
|
||||
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
|
||||
|
||||
import CompactPropertiesEditor from '../CompactPropertiesEditor';
|
||||
|
||||
const noRefreshOfAllFields = () => {
|
||||
console.warn(
|
||||
"An effect tried to refresh all fields, but the editor doesn't support it."
|
||||
);
|
||||
};
|
||||
|
||||
export const CompactEffectPropertiesEditor = ({
|
||||
project,
|
||||
effect,
|
||||
effectMetadata,
|
||||
resourceManagementProps,
|
||||
}: {|
|
||||
project: gdProject,
|
||||
effect: gdEffect,
|
||||
effectMetadata: ?EnumeratedEffectMetadata,
|
||||
resourceManagementProps: ResourceManagementProps,
|
||||
|}) => {
|
||||
if (!effectMetadata) return null;
|
||||
|
||||
return (
|
||||
<CompactPropertiesEditor
|
||||
project={project}
|
||||
schema={effectMetadata.parametersSchema}
|
||||
instances={[effect]}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
onRefreshAllFields={noRefreshOfAllFields}
|
||||
/>
|
||||
);
|
||||
};
|
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
import { mapFor } from '../Utils/MapFor';
|
||||
import { type Schema } from '../PropertiesEditor';
|
||||
import { type Schema } from '../CompactPropertiesEditor';
|
||||
import { type ResourceKind } from '../ResourcesList/ResourceSource';
|
||||
import flatten from 'lodash/flatten';
|
||||
|
||||
|
@@ -333,7 +333,7 @@ type Props = {|
|
||||
layerRenderingType: string,
|
||||
|};
|
||||
|
||||
const getEnumeratedEffectMetadata = (
|
||||
export const getEnumeratedEffectMetadata = (
|
||||
allEffectDescriptions: Array<EnumeratedEffectMetadata>,
|
||||
effectType: string
|
||||
): ?EnumeratedEffectMetadata => {
|
||||
@@ -380,50 +380,47 @@ export const getEffects3DCount = (
|
||||
return effect3DCount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Display a list of effects and allow to add/remove/edit them.
|
||||
*
|
||||
* All available effects are fetched from the project's platform.
|
||||
*/
|
||||
export default function EffectsList(props: Props) {
|
||||
const {
|
||||
effectsContainer,
|
||||
onEffectsUpdated,
|
||||
onEffectsRenamed,
|
||||
project,
|
||||
target,
|
||||
} = props;
|
||||
const scrollView = React.useRef<?ScrollViewInterface>(null);
|
||||
type UseManageEffectsState = {|
|
||||
allEffectMetadata: Array<EnumeratedEffectMetadata>,
|
||||
all2DEffectMetadata: Array<EnumeratedEffectMetadata>,
|
||||
all3DEffectMetadata: Array<EnumeratedEffectMetadata>,
|
||||
draggedEffect: {| current: ?gdEffect |},
|
||||
addEffect: boolean => void,
|
||||
chooseEffectType: (effect: gdEffect, newEffectType: string) => void,
|
||||
copyAllEffects: () => void,
|
||||
copyEffect: (effect: gdEffect) => void,
|
||||
duplicatedUniqueEffectMetadata: ?EnumeratedEffectMetadata,
|
||||
isClipboardContainingEffects: boolean,
|
||||
moveEffect: (targetEffect: gdEffect) => void,
|
||||
pasteEffectsAtTheEnd: () => Promise<void>,
|
||||
pasteEffectsBefore: (effect: gdEffect) => Promise<void>,
|
||||
removeEffect: (effect: gdEffect) => void,
|
||||
resetJustAddedEffectName: () => void,
|
||||
justAddedEffectName: ?string,
|
||||
|};
|
||||
|
||||
export const useManageEffects = ({
|
||||
effectsContainer,
|
||||
project,
|
||||
onEffectsUpdated,
|
||||
onUpdate,
|
||||
target,
|
||||
}: {|
|
||||
effectsContainer: gdEffectsContainer,
|
||||
project: gdProject,
|
||||
onEffectsUpdated: () => void,
|
||||
onUpdate: () => void,
|
||||
target: 'object' | 'layer',
|
||||
|}): UseManageEffectsState => {
|
||||
const authenticatedUser = React.useContext(AuthenticatedUserContext);
|
||||
const [justAddedEffectName, setJustAddedEffectName] = React.useState<?string>(
|
||||
null
|
||||
);
|
||||
const justAddedEffectElement = React.useRef<?any>(null);
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
if (
|
||||
scrollView.current &&
|
||||
justAddedEffectElement.current &&
|
||||
justAddedEffectName
|
||||
) {
|
||||
scrollView.current.scrollTo(justAddedEffectElement.current);
|
||||
setJustAddedEffectName(null);
|
||||
justAddedEffectElement.current = null;
|
||||
}
|
||||
},
|
||||
[justAddedEffectName]
|
||||
);
|
||||
|
||||
const draggedEffect = React.useRef<?gdEffect>(null);
|
||||
|
||||
const authenticatedUser = React.useContext(AuthenticatedUserContext);
|
||||
const [nameErrors, setNameErrors] = React.useState<{ [number]: React.Node }>(
|
||||
{}
|
||||
);
|
||||
|
||||
const allEffectMetadata = React.useMemo(
|
||||
() => enumerateEffectsMetadata(props.project),
|
||||
[props.project]
|
||||
() => enumerateEffectsMetadata(project),
|
||||
[project]
|
||||
);
|
||||
|
||||
const all3DEffectMetadata = React.useMemo(
|
||||
@@ -452,15 +449,13 @@ export default function EffectsList(props: Props) {
|
||||
[allEffectMetadata]
|
||||
);
|
||||
|
||||
const all2DEffectMetadata = React.useMemo(
|
||||
const all2DEffectMetadata: Array<EnumeratedEffectMetadata> = React.useMemo(
|
||||
() => allEffectMetadata.filter(effect => effect.isMarkedAsOnlyWorkingFor2D),
|
||||
[allEffectMetadata]
|
||||
);
|
||||
|
||||
const showEffectOverridingConfirmation = useEffectOverridingAlertDialog();
|
||||
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const chooseEffectType = React.useCallback(
|
||||
(effect: gdEffect, newEffectType: string) => {
|
||||
effect.setEffectType(newEffectType);
|
||||
@@ -473,10 +468,10 @@ export default function EffectsList(props: Props) {
|
||||
setEffectDefaultParameters(effect, effectMetadata.effectMetadata);
|
||||
}
|
||||
|
||||
forceUpdate();
|
||||
onUpdate();
|
||||
onEffectsUpdated();
|
||||
},
|
||||
[allEffectMetadata, forceUpdate, onEffectsUpdated]
|
||||
[allEffectMetadata, onUpdate, onEffectsUpdated]
|
||||
);
|
||||
|
||||
const _addEffect = React.useCallback(
|
||||
@@ -495,11 +490,11 @@ export default function EffectsList(props: Props) {
|
||||
chooseEffectType(effect, 'Outline');
|
||||
}
|
||||
|
||||
forceUpdate();
|
||||
onUpdate();
|
||||
onEffectsUpdated();
|
||||
setJustAddedEffectName(newName);
|
||||
},
|
||||
[chooseEffectType, effectsContainer, forceUpdate, onEffectsUpdated]
|
||||
[chooseEffectType, effectsContainer, onUpdate, onEffectsUpdated]
|
||||
);
|
||||
|
||||
const addEffect = addCreateBadgePreHookIfNotClaimed(
|
||||
@@ -511,10 +506,10 @@ export default function EffectsList(props: Props) {
|
||||
const removeEffect = React.useCallback(
|
||||
(effect: gdEffect) => {
|
||||
effectsContainer.removeEffect(effect.getName());
|
||||
forceUpdate();
|
||||
onUpdate();
|
||||
onEffectsUpdated();
|
||||
},
|
||||
[effectsContainer, forceUpdate, onEffectsUpdated]
|
||||
[effectsContainer, onUpdate, onEffectsUpdated]
|
||||
);
|
||||
|
||||
const copyEffect = React.useCallback(
|
||||
@@ -526,9 +521,9 @@ export default function EffectsList(props: Props) {
|
||||
serializedEffect: serializeToJSObject(effect),
|
||||
},
|
||||
]);
|
||||
forceUpdate();
|
||||
onUpdate();
|
||||
},
|
||||
[forceUpdate]
|
||||
[onUpdate]
|
||||
);
|
||||
|
||||
const copyAllEffects = React.useCallback(
|
||||
@@ -544,9 +539,9 @@ export default function EffectsList(props: Props) {
|
||||
};
|
||||
})
|
||||
);
|
||||
forceUpdate();
|
||||
onUpdate();
|
||||
},
|
||||
[forceUpdate, effectsContainer]
|
||||
[onUpdate, effectsContainer]
|
||||
);
|
||||
|
||||
const pasteEffects = React.useCallback(
|
||||
@@ -624,7 +619,7 @@ export default function EffectsList(props: Props) {
|
||||
}
|
||||
}
|
||||
|
||||
forceUpdate();
|
||||
onUpdate();
|
||||
if (firstAddedEffectName) {
|
||||
setJustAddedEffectName(firstAddedEffectName);
|
||||
} else if (existingNamedEffects.length === 1) {
|
||||
@@ -635,7 +630,7 @@ export default function EffectsList(props: Props) {
|
||||
}
|
||||
},
|
||||
[
|
||||
forceUpdate,
|
||||
onUpdate,
|
||||
project,
|
||||
target,
|
||||
effectsContainer,
|
||||
@@ -674,10 +669,10 @@ export default function EffectsList(props: Props) {
|
||||
draggedIndex,
|
||||
targetIndex > draggedIndex ? targetIndex - 1 : targetIndex
|
||||
);
|
||||
forceUpdate();
|
||||
onUpdate();
|
||||
onEffectsUpdated();
|
||||
},
|
||||
[effectsContainer, forceUpdate, onEffectsUpdated]
|
||||
[effectsContainer, onUpdate, onEffectsUpdated]
|
||||
);
|
||||
|
||||
const isClipboardContainingEffects = Clipboard.has(EFFECTS_CLIPBOARD_KIND);
|
||||
@@ -716,6 +711,91 @@ export default function EffectsList(props: Props) {
|
||||
|
||||
const duplicatedUniqueEffectMetadata = getDuplicatedUniqueEffectMetadata();
|
||||
|
||||
const resetJustAddedEffectName = React.useCallback(() => {
|
||||
setJustAddedEffectName(null);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
allEffectMetadata,
|
||||
all2DEffectMetadata,
|
||||
all3DEffectMetadata,
|
||||
draggedEffect,
|
||||
addEffect,
|
||||
chooseEffectType,
|
||||
copyAllEffects,
|
||||
copyEffect,
|
||||
duplicatedUniqueEffectMetadata,
|
||||
isClipboardContainingEffects,
|
||||
moveEffect,
|
||||
pasteEffectsAtTheEnd,
|
||||
pasteEffectsBefore,
|
||||
removeEffect,
|
||||
resetJustAddedEffectName,
|
||||
justAddedEffectName,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Display a list of effects and allow to add/remove/edit them.
|
||||
*
|
||||
* All available effects are fetched from the project's platform.
|
||||
*/
|
||||
export default function EffectsList(props: Props) {
|
||||
const {
|
||||
effectsContainer,
|
||||
onEffectsUpdated,
|
||||
onEffectsRenamed,
|
||||
project,
|
||||
target,
|
||||
} = props;
|
||||
const scrollView = React.useRef<?ScrollViewInterface>(null);
|
||||
const justAddedEffectElement = React.useRef<?any>(null);
|
||||
|
||||
const forceUpdate = useForceUpdate();
|
||||
const {
|
||||
allEffectMetadata,
|
||||
all2DEffectMetadata,
|
||||
all3DEffectMetadata,
|
||||
addEffect,
|
||||
chooseEffectType,
|
||||
copyAllEffects,
|
||||
copyEffect,
|
||||
duplicatedUniqueEffectMetadata,
|
||||
isClipboardContainingEffects,
|
||||
moveEffect,
|
||||
pasteEffectsAtTheEnd,
|
||||
pasteEffectsBefore,
|
||||
removeEffect,
|
||||
resetJustAddedEffectName,
|
||||
justAddedEffectName,
|
||||
draggedEffect,
|
||||
} = useManageEffects({
|
||||
effectsContainer,
|
||||
project,
|
||||
onEffectsUpdated,
|
||||
onUpdate: forceUpdate,
|
||||
target,
|
||||
});
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
if (
|
||||
scrollView.current &&
|
||||
justAddedEffectElement.current &&
|
||||
justAddedEffectName
|
||||
) {
|
||||
scrollView.current.scrollTo(justAddedEffectElement.current);
|
||||
resetJustAddedEffectName();
|
||||
justAddedEffectElement.current = null;
|
||||
}
|
||||
},
|
||||
[justAddedEffectName, resetJustAddedEffectName]
|
||||
);
|
||||
|
||||
const [nameErrors, setNameErrors] = React.useState<{ [number]: React.Node }>(
|
||||
{}
|
||||
);
|
||||
|
||||
// Count the number of effects to hide titles of empty sections.
|
||||
const platform = project.getCurrentPlatform();
|
||||
const effects2DCount = getEffects2DCount(platform, effectsContainer);
|
||||
|
@@ -49,11 +49,11 @@ const applyRatio = ({
|
||||
|
||||
const getEditObjectButton = ({
|
||||
i18n,
|
||||
onEditObjectByName,
|
||||
onEditObject,
|
||||
is3DInstance,
|
||||
}: {|
|
||||
i18n: I18nType,
|
||||
onEditObjectByName: (name: string) => void,
|
||||
onEditObject: (name: string) => void,
|
||||
is3DInstance: boolean,
|
||||
|}) => ({
|
||||
label: i18n._(t`Edit object`),
|
||||
@@ -64,7 +64,7 @@ const getEditObjectButton = ({
|
||||
: props => <Object2d {...props} />,
|
||||
getValue: (instance: gdInitialInstance) => instance.getObjectName(),
|
||||
onClick: (instance: gdInitialInstance) =>
|
||||
onEditObjectByName(instance.getObjectName()),
|
||||
onEditObject(instance.getObjectName()),
|
||||
});
|
||||
|
||||
const getRotationXAndRotationYFields = ({ i18n }: {| i18n: I18nType |}) => [
|
||||
@@ -487,7 +487,7 @@ export const makeSchema = ({
|
||||
canBeFlippedZ,
|
||||
i18n,
|
||||
forceUpdate,
|
||||
onEditObjectByName,
|
||||
onEditObject,
|
||||
onGetInstanceSize,
|
||||
layersContainer,
|
||||
}: {|
|
||||
@@ -497,7 +497,7 @@ export const makeSchema = ({
|
||||
canBeFlippedZ: boolean,
|
||||
i18n: I18nType,
|
||||
forceUpdate: () => void,
|
||||
onEditObjectByName: (name: string) => void,
|
||||
onEditObject: (name: string) => void,
|
||||
onGetInstanceSize: gdInitialInstance => [number, number, number],
|
||||
layersContainer: gdLayersContainer,
|
||||
|}): Schema => {
|
||||
@@ -519,7 +519,7 @@ export const makeSchema = ({
|
||||
if (is3DInstance) {
|
||||
return [
|
||||
getTitleRow({ i18n }),
|
||||
getEditObjectButton({ i18n, onEditObjectByName, is3DInstance }),
|
||||
getEditObjectButton({ i18n, onEditObject, is3DInstance }),
|
||||
{
|
||||
name: 'Position',
|
||||
type: 'row',
|
||||
@@ -607,7 +607,7 @@ export const makeSchema = ({
|
||||
|
||||
return [
|
||||
getTitleRow({ i18n }),
|
||||
getEditObjectButton({ i18n, onEditObjectByName, is3DInstance }),
|
||||
getEditObjectButton({ i18n, onEditObject, is3DInstance }),
|
||||
{
|
||||
name: 'Position',
|
||||
type: 'row',
|
@@ -3,8 +3,6 @@ import * as React from 'react';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
|
||||
import Paper from '../../UI/Paper';
|
||||
import EmptyMessage from '../../UI/EmptyMessage';
|
||||
import CompactPropertiesEditor, {
|
||||
Separator,
|
||||
} from '../../CompactPropertiesEditor';
|
||||
@@ -19,6 +17,7 @@ import ScrollView, { type ScrollViewInterface } from '../../UI/ScrollView';
|
||||
import EventsRootVariablesFinder from '../../Utils/EventsRootVariablesFinder';
|
||||
import VariablesList, {
|
||||
type HistoryHandler,
|
||||
type VariablesListInterface,
|
||||
} from '../../VariablesList/VariablesList';
|
||||
import ShareExternal from '../../UI/CustomSvgIcons/ShareExternal';
|
||||
import useForceUpdate from '../../Utils/UseForceUpdate';
|
||||
@@ -26,19 +25,16 @@ import ErrorBoundary from '../../UI/ErrorBoundary';
|
||||
import {
|
||||
makeSchema,
|
||||
reorderInstanceSchemaForCustomProperties,
|
||||
} from './CompactPropertiesSchema';
|
||||
} from './CompactInstancePropertiesSchema';
|
||||
import { ProjectScopedContainersAccessor } from '../../InstructionOrExpression/EventsScope';
|
||||
import TileSetVisualizer, {
|
||||
type TileMapTileSelection,
|
||||
} from '../TileSetVisualizer';
|
||||
import Add from '../../UI/CustomSvgIcons/Add';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
export const styles = {
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
flexDirection: 'column',
|
||||
},
|
||||
icon: {
|
||||
fontSize: 18,
|
||||
},
|
||||
@@ -51,8 +47,6 @@ const noRefreshOfAllFields = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
layout?: ?gdLayout,
|
||||
@@ -61,7 +55,7 @@ type Props = {|
|
||||
layersContainer: gdLayersContainer,
|
||||
projectScopedContainersAccessor: ProjectScopedContainersAccessor,
|
||||
instances: Array<gdInitialInstance>,
|
||||
onEditObjectByName: string => void,
|
||||
editObjectInPropertiesPanel: string => void,
|
||||
onInstancesModified?: (Array<gdInitialInstance>) => void,
|
||||
onGetInstanceSize: gdInitialInstance => [number, number, number],
|
||||
editInstanceVariables: gdInitialInstance => void,
|
||||
@@ -72,11 +66,7 @@ type Props = {|
|
||||
onSelectTileMapTile: (?TileMapTileSelection) => void,
|
||||
|};
|
||||
|
||||
export type CompactInstancePropertiesEditorInterface = {|
|
||||
forceUpdate: () => void,
|
||||
|};
|
||||
|
||||
const CompactInstancePropertiesEditor = ({
|
||||
export const CompactInstancePropertiesEditor = ({
|
||||
instances,
|
||||
i18n,
|
||||
project,
|
||||
@@ -86,7 +76,7 @@ const CompactInstancePropertiesEditor = ({
|
||||
layersContainer,
|
||||
unsavedChanges,
|
||||
historyHandler,
|
||||
onEditObjectByName,
|
||||
editObjectInPropertiesPanel,
|
||||
onGetInstanceSize,
|
||||
editInstanceVariables,
|
||||
onInstancesModified,
|
||||
@@ -95,6 +85,8 @@ const CompactInstancePropertiesEditor = ({
|
||||
onSelectTileMapTile,
|
||||
}: Props) => {
|
||||
const forceUpdate = useForceUpdate();
|
||||
const variablesListRef = React.useRef<?VariablesListInterface>(null);
|
||||
|
||||
const scrollViewRef = React.useRef<?ScrollViewInterface>(null);
|
||||
const instance = instances[0];
|
||||
/**
|
||||
@@ -171,7 +163,7 @@ const CompactInstancePropertiesEditor = ({
|
||||
canBeFlippedXY,
|
||||
canBeFlippedZ,
|
||||
onGetInstanceSize,
|
||||
onEditObjectByName,
|
||||
onEditObject: editObjectInPropertiesPanel,
|
||||
layersContainer,
|
||||
forceUpdate,
|
||||
}).concat(reorderedInstanceSchemaForCustomProperties);
|
||||
@@ -189,7 +181,7 @@ const CompactInstancePropertiesEditor = ({
|
||||
forceUpdate,
|
||||
layersContainer,
|
||||
onGetInstanceSize,
|
||||
onEditObjectByName,
|
||||
editObjectInPropertiesPanel,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -246,8 +238,8 @@ const CompactInstancePropertiesEditor = ({
|
||||
</Column>
|
||||
{shouldDisplayTileSetVisualizer && (
|
||||
<>
|
||||
<Separator />
|
||||
<Column>
|
||||
<Separator />
|
||||
<Line alignItems="center" justifyContent="space-between">
|
||||
<Text size="sub-title" noMargin>
|
||||
<Trans>Tilemap painter</Trans>
|
||||
@@ -269,23 +261,36 @@ const CompactInstancePropertiesEditor = ({
|
||||
)}
|
||||
{object && shouldDisplayVariablesList ? (
|
||||
<>
|
||||
<Separator />
|
||||
<Column>
|
||||
<Separator />
|
||||
<Line alignItems="center" justifyContent="space-between">
|
||||
<Text size="sub-title" noMargin>
|
||||
<Trans>Instance Variables</Trans>
|
||||
</Text>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
editInstanceVariables(instance);
|
||||
}}
|
||||
>
|
||||
<ShareExternal style={styles.icon} />
|
||||
</IconButton>
|
||||
<Line alignItems="center" noMargin>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
editInstanceVariables(instance);
|
||||
}}
|
||||
>
|
||||
<ShareExternal style={styles.icon} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={
|
||||
variablesListRef.current
|
||||
? variablesListRef.current.addVariable
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Add style={styles.icon} />
|
||||
</IconButton>
|
||||
</Line>
|
||||
</Line>
|
||||
</Column>
|
||||
<VariablesList
|
||||
ref={variablesListRef}
|
||||
projectScopedContainersAccessor={
|
||||
projectScopedContainersAccessor
|
||||
}
|
||||
@@ -293,7 +298,7 @@ const CompactInstancePropertiesEditor = ({
|
||||
inheritedVariablesContainer={object.getVariables()}
|
||||
variablesContainer={instance.getVariables()}
|
||||
areObjectVariables
|
||||
size="small"
|
||||
size="compact"
|
||||
onComputeAllVariableNames={() =>
|
||||
object && layout
|
||||
? EventsRootVariablesFinder.findAllObjectVariables(
|
||||
@@ -306,6 +311,9 @@ const CompactInstancePropertiesEditor = ({
|
||||
}
|
||||
historyHandler={historyHandler}
|
||||
toolbarIconStyle={styles.icon}
|
||||
compactEmptyPlaceholderText={
|
||||
<Trans>There are no variables on this instance.</Trans>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
@@ -314,29 +322,3 @@ const CompactInstancePropertiesEditor = ({
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
const CompactInstancePropertiesEditorContainer = React.forwardRef<
|
||||
Props,
|
||||
CompactInstancePropertiesEditorInterface
|
||||
>((props, ref) => {
|
||||
const forceUpdate = useForceUpdate();
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
forceUpdate,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Paper background="dark" square style={styles.paper}>
|
||||
{!props.instances || !props.instances.length ? (
|
||||
<EmptyMessage>
|
||||
<Trans>
|
||||
Click on an instance in the scene to display its properties
|
||||
</Trans>
|
||||
</EmptyMessage>
|
||||
) : (
|
||||
<CompactInstancePropertiesEditor {...props} />
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
});
|
||||
|
||||
export default CompactInstancePropertiesEditorContainer;
|
||||
|
@@ -433,6 +433,7 @@ const FullSizeInstancesEditorWithScrollbars = (props: Props) => {
|
||||
}
|
||||
}}
|
||||
showObjectInstancesIn3D={values.use3DEditor}
|
||||
showBasicProfilingCounters={values.showBasicProfilingCounters}
|
||||
{...otherProps}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
|
@@ -1,456 +0,0 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import * as React from 'react';
|
||||
import Background from '../../UI/Background';
|
||||
import enumerateLayers from '../../LayersList/EnumerateLayers';
|
||||
import EmptyMessage from '../../UI/EmptyMessage';
|
||||
import PropertiesEditor from '../../PropertiesEditor';
|
||||
import propertiesMapToSchema from '../../PropertiesEditor/PropertiesMapToSchema';
|
||||
import { type Schema } from '../../PropertiesEditor';
|
||||
import getObjectByName from '../../Utils/GetObjectByName';
|
||||
import IconButton from '../../UI/IconButton';
|
||||
import { Line, Column } from '../../UI/Grid';
|
||||
import Text from '../../UI/Text';
|
||||
import { type UnsavedChanges } from '../../MainFrame/UnsavedChangesContext';
|
||||
import ScrollView from '../../UI/ScrollView';
|
||||
import EventsRootVariablesFinder from '../../Utils/EventsRootVariablesFinder';
|
||||
import VariablesList, {
|
||||
type HistoryHandler,
|
||||
} from '../../VariablesList/VariablesList';
|
||||
import ShareExternal from '../../UI/CustomSvgIcons/ShareExternal';
|
||||
import useForceUpdate from '../../Utils/UseForceUpdate';
|
||||
import ErrorBoundary from '../../UI/ErrorBoundary';
|
||||
import { ProjectScopedContainersAccessor } from '../../InstructionOrExpression/EventsScope';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
layout: gdLayout,
|
||||
projectScopedContainersAccessor: ProjectScopedContainersAccessor,
|
||||
instances: Array<gdInitialInstance>,
|
||||
onEditObjectByName: string => void,
|
||||
onInstancesModified?: (Array<gdInitialInstance>) => void,
|
||||
onGetInstanceSize: gdInitialInstance => [number, number, number],
|
||||
editInstanceVariables: gdInitialInstance => void,
|
||||
unsavedChanges?: ?UnsavedChanges,
|
||||
i18n: I18nType,
|
||||
historyHandler?: HistoryHandler,
|
||||
|};
|
||||
|
||||
export type InstancePropertiesEditorInterface = {| forceUpdate: () => void |};
|
||||
|
||||
const makeSchema = ({
|
||||
is3DInstance,
|
||||
i18n,
|
||||
forceUpdate,
|
||||
onEditObjectByName,
|
||||
onGetInstanceSize,
|
||||
layout,
|
||||
}) => {
|
||||
const getInstanceWidth = (instance: gdInitialInstance) =>
|
||||
instance.hasCustomSize()
|
||||
? instance.getCustomWidth()
|
||||
: onGetInstanceSize(instance)[0];
|
||||
|
||||
const getInstanceHeight = (instance: gdInitialInstance) =>
|
||||
instance.hasCustomSize()
|
||||
? instance.getCustomHeight()
|
||||
: onGetInstanceSize(instance)[1];
|
||||
|
||||
const getInstanceDepth = (instance: gdInitialInstance) =>
|
||||
instance.hasCustomDepth()
|
||||
? instance.getCustomDepth()
|
||||
: onGetInstanceSize(instance)[2];
|
||||
|
||||
return [
|
||||
{
|
||||
name: i18n._(t`Object`),
|
||||
getValue: (instance: gdInitialInstance) => instance.getObjectName(),
|
||||
nonFieldType: 'sectionTitle',
|
||||
defaultValue: i18n._(t`Different objects`),
|
||||
},
|
||||
{
|
||||
label: i18n._(t`Edit object`),
|
||||
disabled: 'onValuesDifferent',
|
||||
nonFieldType: 'button',
|
||||
getValue: (instance: gdInitialInstance) => instance.getObjectName(),
|
||||
onClick: (instance: gdInitialInstance) =>
|
||||
onEditObjectByName(instance.getObjectName()),
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Instance`),
|
||||
nonFieldType: 'sectionTitle',
|
||||
},
|
||||
{
|
||||
name: 'Position',
|
||||
type: 'row',
|
||||
children: [
|
||||
{
|
||||
name: 'X',
|
||||
getLabel: () => i18n._(t`X`),
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getX(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setX(newValue),
|
||||
},
|
||||
{
|
||||
name: 'Y',
|
||||
getLabel: () => i18n._(t`Y`),
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getY(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setY(newValue),
|
||||
},
|
||||
is3DInstance
|
||||
? {
|
||||
name: 'Z',
|
||||
getLabel: () => i18n._(t`Z`),
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getZ(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setZ(newValue),
|
||||
}
|
||||
: null,
|
||||
].filter(Boolean),
|
||||
},
|
||||
{
|
||||
name: 'Angles',
|
||||
type: 'row',
|
||||
children: [
|
||||
is3DInstance
|
||||
? {
|
||||
name: 'RotationX',
|
||||
getLabel: () => i18n._(t`Rotation (X)`),
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) =>
|
||||
instance.getRotationX(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setRotationX(newValue),
|
||||
}
|
||||
: null,
|
||||
is3DInstance
|
||||
? {
|
||||
name: 'RotationY',
|
||||
getLabel: () => i18n._(t`Rotation (Y)`),
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) =>
|
||||
instance.getRotationY(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setRotationY(newValue),
|
||||
}
|
||||
: null,
|
||||
{
|
||||
name: 'Angle',
|
||||
getLabel: () =>
|
||||
is3DInstance ? i18n._(t`Rotation (Z)`) : i18n._(t`Angle`),
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getAngle(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setAngle(newValue),
|
||||
},
|
||||
].filter(Boolean),
|
||||
},
|
||||
{
|
||||
name: 'Lock instance position angle',
|
||||
getLabel: () => i18n._(t`Lock position/angle in the editor`),
|
||||
valueType: 'boolean',
|
||||
getValue: (instance: gdInitialInstance) => instance.isLocked(),
|
||||
setValue: (instance: gdInitialInstance, newValue: boolean) => {
|
||||
instance.setLocked(newValue);
|
||||
if (!newValue) {
|
||||
instance.setSealed(newValue);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Prevent instance selection',
|
||||
getLabel: () => i18n._(t`Prevent selection in the editor`),
|
||||
valueType: 'boolean',
|
||||
disabled: (instances: gdInitialInstance[]) => {
|
||||
return instances.some(instance => !instance.isLocked());
|
||||
},
|
||||
getValue: (instance: gdInitialInstance) => instance.isSealed(),
|
||||
setValue: (instance: gdInitialInstance, newValue: boolean) =>
|
||||
instance.setSealed(newValue),
|
||||
},
|
||||
!is3DInstance
|
||||
? {
|
||||
name: 'Z Order',
|
||||
getLabel: () => i18n._(t`Z Order`),
|
||||
valueType: 'number',
|
||||
getValue: (instance: gdInitialInstance) => instance.getZOrder(),
|
||||
setValue: (instance: gdInitialInstance, newValue: number) =>
|
||||
instance.setZOrder(newValue),
|
||||
}
|
||||
: null,
|
||||
{
|
||||
name: 'Layer',
|
||||
getLabel: () => i18n._(t`Layer`),
|
||||
valueType: 'string',
|
||||
getChoices: () => enumerateLayers(layout),
|
||||
getValue: (instance: gdInitialInstance) => instance.getLayer(),
|
||||
setValue: (instance: gdInitialInstance, newValue: string) =>
|
||||
instance.setLayer(newValue),
|
||||
},
|
||||
{
|
||||
name: 'Custom size',
|
||||
getLabel: () => i18n._(t`Custom size`),
|
||||
valueType: 'boolean',
|
||||
getValue: (instance: gdInitialInstance) => instance.hasCustomSize(),
|
||||
setValue: (instance: gdInitialInstance, newValue: boolean) => {
|
||||
if (
|
||||
instance.getCustomHeight() === 0 &&
|
||||
instance.getCustomWidth() === 0 &&
|
||||
instance.getCustomDepth() === 0
|
||||
) {
|
||||
// The instance custom dimensions have never been set before.
|
||||
// To avoid setting setting all the dimensions to 0 when enabling
|
||||
// the instance custom size flag, the current instance dimensions are used.
|
||||
instance.setCustomWidth(getInstanceWidth(instance));
|
||||
instance.setCustomHeight(getInstanceHeight(instance));
|
||||
instance.setCustomDepth(getInstanceDepth(instance));
|
||||
}
|
||||
instance.setHasCustomSize(newValue);
|
||||
instance.setHasCustomDepth(newValue);
|
||||
forceUpdate();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'custom-size-row',
|
||||
type: 'row',
|
||||
children: [
|
||||
{
|
||||
name: 'Width',
|
||||
getLabel: () => i18n._(t`Width`),
|
||||
valueType: 'number',
|
||||
getValue: getInstanceWidth,
|
||||
setValue: (instance: gdInitialInstance, newValue: number) => {
|
||||
instance.setCustomWidth(Math.max(newValue, 0));
|
||||
instance.setCustomHeight(getInstanceHeight(instance));
|
||||
instance.setCustomDepth(getInstanceDepth(instance));
|
||||
|
||||
// This must be done after reading the size.
|
||||
instance.setHasCustomSize(true);
|
||||
instance.setHasCustomDepth(true);
|
||||
forceUpdate();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Height',
|
||||
getLabel: () => i18n._(t`Height`),
|
||||
valueType: 'number',
|
||||
getValue: getInstanceHeight,
|
||||
setValue: (instance: gdInitialInstance, newValue: number) => {
|
||||
instance.setCustomWidth(getInstanceWidth(instance));
|
||||
instance.setCustomHeight(Math.max(newValue, 0));
|
||||
instance.setCustomDepth(getInstanceDepth(instance));
|
||||
|
||||
// This must be done after reading the size.
|
||||
instance.setHasCustomSize(true);
|
||||
instance.setHasCustomDepth(true);
|
||||
forceUpdate();
|
||||
},
|
||||
},
|
||||
is3DInstance
|
||||
? {
|
||||
name: 'Depth',
|
||||
getLabel: () => i18n._(t`Depth`),
|
||||
valueType: 'number',
|
||||
getValue: getInstanceDepth,
|
||||
setValue: (instance: gdInitialInstance, newValue: number) => {
|
||||
instance.setCustomWidth(getInstanceWidth(instance));
|
||||
instance.setCustomHeight(getInstanceHeight(instance));
|
||||
instance.setCustomDepth(Math.max(newValue, 0));
|
||||
|
||||
// This must be done after reading the size.
|
||||
instance.setHasCustomSize(true);
|
||||
instance.setHasCustomDepth(true);
|
||||
forceUpdate();
|
||||
},
|
||||
}
|
||||
: null,
|
||||
].filter(Boolean),
|
||||
},
|
||||
].filter(Boolean);
|
||||
};
|
||||
|
||||
const InstancePropertiesEditor = ({
|
||||
instances,
|
||||
i18n,
|
||||
project,
|
||||
layout,
|
||||
projectScopedContainersAccessor,
|
||||
unsavedChanges,
|
||||
historyHandler,
|
||||
onEditObjectByName,
|
||||
onGetInstanceSize,
|
||||
editInstanceVariables,
|
||||
onInstancesModified,
|
||||
}: Props) => {
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const schemaFor2D: Schema = React.useMemo(
|
||||
() =>
|
||||
makeSchema({
|
||||
i18n,
|
||||
is3DInstance: false,
|
||||
onGetInstanceSize,
|
||||
onEditObjectByName,
|
||||
layout,
|
||||
forceUpdate,
|
||||
}),
|
||||
[i18n, onGetInstanceSize, onEditObjectByName, layout, forceUpdate]
|
||||
);
|
||||
|
||||
const schemaFor3D: Schema = React.useMemo(
|
||||
() =>
|
||||
makeSchema({
|
||||
i18n,
|
||||
is3DInstance: true,
|
||||
onGetInstanceSize,
|
||||
onEditObjectByName,
|
||||
layout,
|
||||
forceUpdate,
|
||||
}),
|
||||
[i18n, onGetInstanceSize, onEditObjectByName, layout, forceUpdate]
|
||||
);
|
||||
|
||||
// TODO: multiple instances support.
|
||||
const instance = instances[0];
|
||||
|
||||
const { object, instanceSchema } = React.useMemo(
|
||||
() => {
|
||||
if (!instance) return {};
|
||||
|
||||
const associatedObjectName = instance.getObjectName();
|
||||
const object = getObjectByName(
|
||||
project.getObjects(),
|
||||
layout.getObjects(),
|
||||
associatedObjectName
|
||||
);
|
||||
const properties = instance.getCustomProperties(
|
||||
project.getObjects(),
|
||||
layout.getObjects()
|
||||
);
|
||||
if (!object) return {};
|
||||
|
||||
const is3DInstance = gd.MetadataProvider.getObjectMetadata(
|
||||
project.getCurrentPlatform(),
|
||||
object.getType()
|
||||
).isRenderedIn3D();
|
||||
const instanceSchemaForCustomProperties = propertiesMapToSchema(
|
||||
properties,
|
||||
(instance: gdInitialInstance) =>
|
||||
instance.getCustomProperties(
|
||||
project.getObjects(),
|
||||
layout.getObjects()
|
||||
),
|
||||
(instance: gdInitialInstance, name, value) =>
|
||||
instance.updateCustomProperty(
|
||||
name,
|
||||
value,
|
||||
project.getObjects(),
|
||||
layout.getObjects()
|
||||
)
|
||||
);
|
||||
return {
|
||||
object,
|
||||
instanceSchema: is3DInstance
|
||||
? schemaFor3D.concat(instanceSchemaForCustomProperties)
|
||||
: schemaFor2D.concat(instanceSchemaForCustomProperties),
|
||||
};
|
||||
},
|
||||
[project, layout, instance, schemaFor2D, schemaFor3D]
|
||||
);
|
||||
|
||||
if (!object || !instance || !instanceSchema) return null;
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
componentTitle={<Trans>Instance properties</Trans>}
|
||||
scope="scene-editor-instance-properties"
|
||||
>
|
||||
<ScrollView
|
||||
autoHideScrollbar
|
||||
key={instances
|
||||
.map((instance: gdInitialInstance) => '' + instance.ptr)
|
||||
.join(';')}
|
||||
>
|
||||
<Column expand noMargin id="instance-properties-editor">
|
||||
<Column>
|
||||
<PropertiesEditor
|
||||
unsavedChanges={unsavedChanges}
|
||||
schema={instanceSchema}
|
||||
instances={instances}
|
||||
onInstancesModified={onInstancesModified}
|
||||
/>
|
||||
<Line alignItems="center" justifyContent="space-between">
|
||||
<Text>
|
||||
<Trans>Instance Variables</Trans>
|
||||
</Text>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
editInstanceVariables(instance);
|
||||
}}
|
||||
>
|
||||
<ShareExternal />
|
||||
</IconButton>
|
||||
</Line>
|
||||
</Column>
|
||||
{object ? (
|
||||
<VariablesList
|
||||
projectScopedContainersAccessor={projectScopedContainersAccessor}
|
||||
directlyStoreValueChangesWhileEditing
|
||||
inheritedVariablesContainer={object.getVariables()}
|
||||
variablesContainer={instance.getVariables()}
|
||||
areObjectVariables
|
||||
size="small"
|
||||
onComputeAllVariableNames={() =>
|
||||
object
|
||||
? EventsRootVariablesFinder.findAllObjectVariables(
|
||||
project.getCurrentPlatform(),
|
||||
project,
|
||||
layout,
|
||||
object.getName()
|
||||
)
|
||||
: []
|
||||
}
|
||||
historyHandler={historyHandler}
|
||||
/>
|
||||
) : null}
|
||||
</Column>
|
||||
</ScrollView>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
const InstancePropertiesEditorContainer = React.forwardRef<
|
||||
Props,
|
||||
InstancePropertiesEditorInterface
|
||||
>((props, ref) => {
|
||||
const forceUpdate = useForceUpdate();
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
forceUpdate,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Background>
|
||||
{!props.instances || !props.instances.length ? (
|
||||
<EmptyMessage>
|
||||
<Trans>
|
||||
Click on an instance in the scene to display its properties
|
||||
</Trans>
|
||||
</EmptyMessage>
|
||||
) : (
|
||||
<InstancePropertiesEditor {...props} />
|
||||
)}
|
||||
</Background>
|
||||
);
|
||||
});
|
||||
|
||||
export default InstancePropertiesEditorContainer;
|
@@ -0,0 +1,150 @@
|
||||
// @flow
|
||||
|
||||
type InstanceCounter = {
|
||||
updateCount: number,
|
||||
totalUpdateTime: number,
|
||||
};
|
||||
|
||||
export type BasicProfilingCounters = {
|
||||
instanceCounters: { [string]: InstanceCounter },
|
||||
totalInstancesUpdateCount: number,
|
||||
totalInstancesUpdateTime: number,
|
||||
totalPixiRenderingTime: number,
|
||||
totalPixiUiRenderingTime: number,
|
||||
totalThreeRenderingTime: number,
|
||||
};
|
||||
|
||||
export const makeBasicProfilingCounters = (): BasicProfilingCounters => {
|
||||
return {
|
||||
instanceCounters: {},
|
||||
totalInstancesUpdateCount: 0,
|
||||
totalInstancesUpdateTime: 0,
|
||||
totalPixiRenderingTime: 0,
|
||||
totalPixiUiRenderingTime: 0,
|
||||
totalThreeRenderingTime: 0,
|
||||
};
|
||||
};
|
||||
|
||||
export const resetBasicProfilingCounters = (
|
||||
basicProfilingCounters: BasicProfilingCounters
|
||||
): BasicProfilingCounters => {
|
||||
basicProfilingCounters.instanceCounters = {};
|
||||
basicProfilingCounters.totalInstancesUpdateCount = 0;
|
||||
basicProfilingCounters.totalInstancesUpdateTime = 0;
|
||||
basicProfilingCounters.totalPixiRenderingTime = 0;
|
||||
basicProfilingCounters.totalPixiUiRenderingTime = 0;
|
||||
basicProfilingCounters.totalThreeRenderingTime = 0;
|
||||
return basicProfilingCounters;
|
||||
};
|
||||
|
||||
export const increaseInstanceUpdate = (
|
||||
basicProfilingCounters: BasicProfilingCounters,
|
||||
objectName: string,
|
||||
updateDuration: number
|
||||
) => {
|
||||
let instanceCounter = basicProfilingCounters.instanceCounters[objectName];
|
||||
if (!instanceCounter) {
|
||||
basicProfilingCounters.instanceCounters[objectName] = {
|
||||
updateCount: 1,
|
||||
totalUpdateTime: updateDuration,
|
||||
};
|
||||
} else {
|
||||
instanceCounter.updateCount++;
|
||||
instanceCounter.totalUpdateTime += updateDuration;
|
||||
}
|
||||
basicProfilingCounters.totalInstancesUpdateCount++;
|
||||
basicProfilingCounters.totalInstancesUpdateTime += updateDuration;
|
||||
};
|
||||
|
||||
export const increasePixiRenderingTime = (
|
||||
basicProfilingCounters: BasicProfilingCounters,
|
||||
pixiRenderingTime: number
|
||||
) => {
|
||||
basicProfilingCounters.totalPixiRenderingTime += pixiRenderingTime;
|
||||
};
|
||||
|
||||
export const increasePixiUiRenderingTime = (
|
||||
basicProfilingCounters: BasicProfilingCounters,
|
||||
pixiUiRenderingTime: number
|
||||
) => {
|
||||
basicProfilingCounters.totalPixiUiRenderingTime += pixiUiRenderingTime;
|
||||
};
|
||||
|
||||
export const increaseThreeRenderingTime = (
|
||||
basicProfilingCounters: BasicProfilingCounters,
|
||||
threeRenderingTime: number
|
||||
) => {
|
||||
basicProfilingCounters.totalThreeRenderingTime += threeRenderingTime;
|
||||
};
|
||||
|
||||
export const mergeBasicProfilingCounters = (
|
||||
destination: BasicProfilingCounters,
|
||||
source: BasicProfilingCounters
|
||||
): BasicProfilingCounters => {
|
||||
for (const objectName in source.instanceCounters) {
|
||||
if (source.instanceCounters.hasOwnProperty(objectName)) {
|
||||
const instanceCounter = source.instanceCounters[objectName];
|
||||
let destinationInstanceCounter = destination.instanceCounters[objectName];
|
||||
if (!destinationInstanceCounter) {
|
||||
destinationInstanceCounter = destination.instanceCounters[
|
||||
objectName
|
||||
] = {
|
||||
updateCount: 0,
|
||||
totalUpdateTime: 0,
|
||||
};
|
||||
}
|
||||
destinationInstanceCounter.updateCount += instanceCounter.updateCount;
|
||||
destinationInstanceCounter.totalUpdateTime +=
|
||||
instanceCounter.totalUpdateTime;
|
||||
}
|
||||
}
|
||||
destination.totalInstancesUpdateCount += source.totalInstancesUpdateCount;
|
||||
destination.totalInstancesUpdateTime += source.totalInstancesUpdateTime;
|
||||
destination.totalPixiRenderingTime += source.totalPixiRenderingTime;
|
||||
destination.totalPixiUiRenderingTime += source.totalPixiUiRenderingTime;
|
||||
destination.totalThreeRenderingTime += source.totalThreeRenderingTime;
|
||||
return destination;
|
||||
};
|
||||
|
||||
export const getBasicProfilingCountersText = (
|
||||
basicProfilingCounters: BasicProfilingCounters
|
||||
): string => {
|
||||
const texts = [];
|
||||
texts.push(
|
||||
`Instances update count: ${
|
||||
basicProfilingCounters.totalInstancesUpdateCount
|
||||
}`
|
||||
);
|
||||
texts.push(
|
||||
`Instances update time: ${basicProfilingCounters.totalInstancesUpdateTime.toFixed(
|
||||
2
|
||||
)}ms`
|
||||
);
|
||||
texts.push(
|
||||
`Pixi rendering time: ${basicProfilingCounters.totalPixiRenderingTime.toFixed(
|
||||
2
|
||||
)}ms`
|
||||
);
|
||||
texts.push(
|
||||
`Three rendering time: ${basicProfilingCounters.totalThreeRenderingTime.toFixed(
|
||||
2
|
||||
)}ms`
|
||||
);
|
||||
texts.push(
|
||||
`Pixi UI rendering time: ${basicProfilingCounters.totalPixiUiRenderingTime.toFixed(
|
||||
2
|
||||
)}ms`
|
||||
);
|
||||
texts.push(' ');
|
||||
for (const objectName in basicProfilingCounters.instanceCounters) {
|
||||
const instanceCounters =
|
||||
basicProfilingCounters.instanceCounters[objectName];
|
||||
texts.push(
|
||||
`${objectName}: ${
|
||||
instanceCounters.updateCount
|
||||
} updates, ${instanceCounters.totalUpdateTime.toFixed(2)}ms`
|
||||
);
|
||||
}
|
||||
|
||||
return texts.join('\n');
|
||||
};
|
@@ -16,6 +16,12 @@ import {
|
||||
type Polygon,
|
||||
} from '../../Utils/PolygonHelper';
|
||||
import Rendered3DInstance from '../../ObjectsRendering/Renderers/Rendered3DInstance';
|
||||
import {
|
||||
type BasicProfilingCounters,
|
||||
increaseInstanceUpdate,
|
||||
makeBasicProfilingCounters,
|
||||
resetBasicProfilingCounters,
|
||||
} from './BasicProfilingCounters';
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
export default class LayerRenderer {
|
||||
@@ -83,6 +89,8 @@ export default class LayerRenderer {
|
||||
|
||||
_showObjectInstancesIn3D: boolean;
|
||||
|
||||
_basicProfilingCounters = makeBasicProfilingCounters();
|
||||
|
||||
constructor({
|
||||
project,
|
||||
globalObjectsContainer,
|
||||
@@ -186,7 +194,18 @@ export default class LayerRenderer {
|
||||
? 'auto'
|
||||
: 'static';
|
||||
}
|
||||
if (isVisible) renderedInstance.update();
|
||||
if (isVisible) {
|
||||
const objectName = instance.getObjectName();
|
||||
const time = performance.now();
|
||||
renderedInstance.update();
|
||||
const duration = performance.now() - time;
|
||||
|
||||
increaseInstanceUpdate(
|
||||
this._basicProfilingCounters,
|
||||
objectName,
|
||||
duration
|
||||
);
|
||||
}
|
||||
|
||||
if (renderedInstance instanceof Rendered3DInstance) {
|
||||
const threeObject = renderedInstance.getThreeObject();
|
||||
@@ -555,6 +574,8 @@ export default class LayerRenderer {
|
||||
}
|
||||
|
||||
render() {
|
||||
resetBasicProfilingCounters(this._basicProfilingCounters);
|
||||
|
||||
this._computeViewBounds();
|
||||
this.instances.iterateOverInstancesWithZOrdering(
|
||||
// $FlowFixMe - gd.castObject is not supporting typings.
|
||||
@@ -566,6 +587,10 @@ export default class LayerRenderer {
|
||||
this._destroyUnusedInstanceRenderers();
|
||||
}
|
||||
|
||||
getBasicProfilingCounters(): BasicProfilingCounters {
|
||||
return this._basicProfilingCounters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Three.js objects for 3D rendering of this layer.
|
||||
*/
|
||||
|
@@ -5,6 +5,15 @@ import * as PIXI from 'pixi.js-legacy';
|
||||
import * as THREE from 'three';
|
||||
import { rgbToHexNumber } from '../../Utils/ColorTransformer';
|
||||
import Rectangle from '../../Utils/Rectangle';
|
||||
import {
|
||||
type BasicProfilingCounters,
|
||||
makeBasicProfilingCounters,
|
||||
mergeBasicProfilingCounters,
|
||||
resetBasicProfilingCounters,
|
||||
increasePixiRenderingTime,
|
||||
increaseThreeRenderingTime,
|
||||
increasePixiUiRenderingTime,
|
||||
} from './BasicProfilingCounters';
|
||||
|
||||
export type InstanceMeasurer = {|
|
||||
getInstanceAABB: (gdInitialInstance, Rectangle) => Rectangle,
|
||||
@@ -49,6 +58,8 @@ export default class InstancesRenderer {
|
||||
temporaryRectangle: Rectangle;
|
||||
instanceMeasurer: InstanceMeasurer;
|
||||
|
||||
_basicProfilingCounters = makeBasicProfilingCounters();
|
||||
|
||||
constructor({
|
||||
project,
|
||||
layersContainer,
|
||||
@@ -176,6 +187,10 @@ export default class InstancesRenderer {
|
||||
return this.instanceMeasurer;
|
||||
}
|
||||
|
||||
getBasicProfilingCounters(): BasicProfilingCounters {
|
||||
return this._basicProfilingCounters;
|
||||
}
|
||||
|
||||
render(
|
||||
pixiRenderer: PIXI.Renderer,
|
||||
threeRenderer: THREE.WebGLRenderer | null,
|
||||
@@ -183,6 +198,8 @@ export default class InstancesRenderer {
|
||||
uiPixiContainer: PIXI.Container,
|
||||
backgroundPixiContainer: PIXI.Container
|
||||
) {
|
||||
resetBasicProfilingCounters(this._basicProfilingCounters);
|
||||
|
||||
// Even if no rendering at all has been made already, setting up the Three.js/PixiJS renderers
|
||||
// might have changed some WebGL states already. Reset the state for the very first frame.
|
||||
// And, out of caution, keep doing it for every frame.
|
||||
@@ -244,6 +261,11 @@ export default class InstancesRenderer {
|
||||
layerRenderer.wasUsed = true;
|
||||
layerRenderer.getPixiContainer().zOrder = i;
|
||||
layerRenderer.render();
|
||||
mergeBasicProfilingCounters(
|
||||
this._basicProfilingCounters,
|
||||
layerRenderer.getBasicProfilingCounters()
|
||||
);
|
||||
|
||||
const layerContainer = layerRenderer.getPixiContainer();
|
||||
viewPosition.applyTransformationToPixi(layerContainer);
|
||||
|
||||
@@ -256,7 +278,12 @@ export default class InstancesRenderer {
|
||||
|
||||
if (!threeRenderer) {
|
||||
// Render a layer with 2D rendering (PixiJS) only.
|
||||
const time = performance.now();
|
||||
pixiRenderer.render(layerContainer, { clear: false });
|
||||
increasePixiRenderingTime(
|
||||
this._basicProfilingCounters,
|
||||
performance.now() - time
|
||||
);
|
||||
} else {
|
||||
// Render a layer with 3D rendering, and possibly some 2D rendering too.
|
||||
const threeScene = layerRenderer.getThreeScene();
|
||||
@@ -272,12 +299,17 @@ export default class InstancesRenderer {
|
||||
// Do the rendering of the PixiJS objects of the layer on the render texture.
|
||||
// Then, update the texture of the plane showing the PixiJS rendering,
|
||||
// so that the 2D rendering made by PixiJS can be shown in the 3D world.
|
||||
const pixiStartTime = performance.now();
|
||||
layerRenderer.renderOnPixiRenderTexture(pixiRenderer);
|
||||
layerRenderer.updateThreePlaneTextureFromPixiRenderTexture(
|
||||
// The renderers are needed to find the internal WebGL texture.
|
||||
threeRenderer,
|
||||
pixiRenderer
|
||||
);
|
||||
increasePixiRenderingTime(
|
||||
this._basicProfilingCounters,
|
||||
performance.now() - pixiStartTime
|
||||
);
|
||||
|
||||
// It's important to reset the internal WebGL state of PixiJS, then Three.js
|
||||
// to ensure the 3D rendering is made properly by Three.js
|
||||
@@ -287,7 +319,13 @@ export default class InstancesRenderer {
|
||||
// Clear the depth as each layer is independent and display on top of the previous one,
|
||||
// even 3D objects.
|
||||
threeRenderer.clearDepth();
|
||||
|
||||
const threeStartTime = performance.now();
|
||||
threeRenderer.render(threeScene, threeCamera);
|
||||
increaseThreeRenderingTime(
|
||||
this._basicProfilingCounters,
|
||||
performance.now() - threeStartTime
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -300,7 +338,12 @@ export default class InstancesRenderer {
|
||||
pixiRenderer.reset();
|
||||
}
|
||||
|
||||
const time = performance.now();
|
||||
pixiRenderer.render(uiPixiContainer);
|
||||
increasePixiUiRenderingTime(
|
||||
this._basicProfilingCounters,
|
||||
performance.now() - time
|
||||
);
|
||||
|
||||
if (threeRenderer) {
|
||||
// It's important to reset the internal WebGL state of PixiJS, then Three.js
|
||||
|
72
newIDE/app/src/InstancesEditor/ProfilerBar.js
Normal file
72
newIDE/app/src/InstancesEditor/ProfilerBar.js
Normal file
@@ -0,0 +1,72 @@
|
||||
// @flow
|
||||
import * as PIXI from 'pixi.js-legacy';
|
||||
import {
|
||||
getBasicProfilingCountersText,
|
||||
type BasicProfilingCounters,
|
||||
} from './InstancesRenderer/BasicProfilingCounters';
|
||||
|
||||
export default class ProfilerBar {
|
||||
_profilerBarContainer: PIXI.Container;
|
||||
_profilerBarBackground: PIXI.Graphics;
|
||||
_profilerBarText: PIXI.Text;
|
||||
|
||||
constructor() {
|
||||
this._profilerBarContainer = new PIXI.Container();
|
||||
this._profilerBarContainer.alpha = 0.8;
|
||||
this._profilerBarContainer.hitArea = new PIXI.Rectangle(0, 0, 0, 0);
|
||||
this._profilerBarBackground = new PIXI.Graphics();
|
||||
this._profilerBarText = new PIXI.Text('', {
|
||||
fontSize: 12,
|
||||
fill: 0xffffff,
|
||||
align: 'left',
|
||||
});
|
||||
this._profilerBarContainer.addChild(this._profilerBarBackground);
|
||||
this._profilerBarContainer.addChild(this._profilerBarText);
|
||||
}
|
||||
|
||||
getPixiObject(): PIXI.Container {
|
||||
return this._profilerBarContainer;
|
||||
}
|
||||
|
||||
render({
|
||||
basicProfilingCounters,
|
||||
display,
|
||||
}: {|
|
||||
basicProfilingCounters: BasicProfilingCounters,
|
||||
display: boolean,
|
||||
|}) {
|
||||
if (!display) {
|
||||
this._profilerBarContainer.visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._profilerBarContainer.visible = true;
|
||||
const textPadding = 5;
|
||||
const profilerBarPadding = 15;
|
||||
const borderRadius = 6;
|
||||
const textXPosition = profilerBarPadding + textPadding;
|
||||
const textYPosition = profilerBarPadding + textPadding;
|
||||
|
||||
this._profilerBarText.text = getBasicProfilingCountersText(
|
||||
basicProfilingCounters
|
||||
);
|
||||
this._profilerBarText.position.x = textXPosition;
|
||||
this._profilerBarText.position.y = textYPosition;
|
||||
|
||||
const profilerBarXPosition = profilerBarPadding;
|
||||
const profilerBarYPosition = profilerBarPadding;
|
||||
const profilerBarWidth = this._profilerBarText.width + textPadding * 2;
|
||||
const profilerBarHeight = this._profilerBarText.height + textPadding * 2;
|
||||
|
||||
this._profilerBarBackground.clear();
|
||||
this._profilerBarBackground.beginFill(0x000000, 0.8);
|
||||
this._profilerBarBackground.drawRoundedRect(
|
||||
profilerBarXPosition,
|
||||
profilerBarYPosition,
|
||||
profilerBarWidth,
|
||||
profilerBarHeight,
|
||||
borderRadius
|
||||
);
|
||||
this._profilerBarBackground.endFill();
|
||||
}
|
||||
}
|
@@ -23,6 +23,7 @@ import * as THREE from 'three';
|
||||
import FpsLimiter from './FpsLimiter';
|
||||
import { startPIXITicker, stopPIXITicker } from '../Utils/PIXITicker';
|
||||
import StatusBar from './StatusBar';
|
||||
import ProfilerBar from './ProfilerBar';
|
||||
import CanvasCursor from './CanvasCursor';
|
||||
import InstancesAdder from './InstancesAdder';
|
||||
import { makeDropTarget } from '../UI/DragAndDrop/DropTarget';
|
||||
@@ -128,6 +129,7 @@ type Props = {|
|
||||
onMouseLeave?: MouseEvent => void,
|
||||
screenType: ScreenType,
|
||||
showObjectInstancesIn3D: boolean,
|
||||
showBasicProfilingCounters: boolean,
|
||||
|};
|
||||
|
||||
type State = {|
|
||||
@@ -161,6 +163,7 @@ export default class InstancesEditor extends Component<Props, State> {
|
||||
windowBorder: WindowBorder;
|
||||
windowMask: WindowMask;
|
||||
statusBar: StatusBar;
|
||||
profilerBar: ProfilerBar;
|
||||
uiPixiContainer: PIXI.Container;
|
||||
backgroundPixiContainer: PIXI.Container;
|
||||
backgroundArea: PIXI.Container;
|
||||
@@ -480,6 +483,9 @@ export default class InstancesEditor extends Component<Props, State> {
|
||||
if (this.background) {
|
||||
this.backgroundPixiContainer.removeChild(this.background.getPixiObject());
|
||||
}
|
||||
if (this.profilerBar) {
|
||||
this.uiPixiContainer.removeChild(this.profilerBar.getPixiObject());
|
||||
}
|
||||
|
||||
this.instancesRenderer = new InstancesRenderer({
|
||||
project: props.project,
|
||||
@@ -570,6 +576,7 @@ export default class InstancesEditor extends Component<Props, State> {
|
||||
height: this.props.height,
|
||||
getLastCursorSceneCoordinates: this.getLastCursorSceneCoordinates,
|
||||
});
|
||||
this.profilerBar = new ProfilerBar();
|
||||
|
||||
this.uiPixiContainer.addChild(this.selectionRectangle.getPixiObject());
|
||||
this.uiPixiContainer.addChild(this.instancesRenderer.getPixiContainer());
|
||||
@@ -578,6 +585,7 @@ export default class InstancesEditor extends Component<Props, State> {
|
||||
this.uiPixiContainer.addChild(this.selectedInstances.getPixiContainer());
|
||||
this.uiPixiContainer.addChild(this.highlightedInstance.getPixiObject());
|
||||
this.uiPixiContainer.addChild(this.statusBar.getPixiObject());
|
||||
this.uiPixiContainer.addChild(this.profilerBar.getPixiObject());
|
||||
this.uiPixiContainer.addChild(this.tileMapPaintingPreview.getPixiObject());
|
||||
this.uiPixiContainer.addChild(this.clickInterceptor.getPixiObject());
|
||||
|
||||
@@ -1576,6 +1584,10 @@ export default class InstancesEditor extends Component<Props, State> {
|
||||
this.windowBorder.render();
|
||||
this.windowMask.render();
|
||||
this.statusBar.render();
|
||||
this.profilerBar.render({
|
||||
basicProfilingCounters: this.instancesRenderer.getBasicProfilingCounters(),
|
||||
display: this.props.showBasicProfilingCounters,
|
||||
});
|
||||
this.background.render();
|
||||
|
||||
this.instancesRenderer.render(
|
||||
|
@@ -216,6 +216,7 @@ export type PreferencesValues = {|
|
||||
showDeprecatedInstructionWarning: boolean,
|
||||
openDiagnosticReportAutomatically: boolean,
|
||||
use3DEditor: boolean,
|
||||
showBasicProfilingCounters: boolean,
|
||||
inAppTutorialsProgress: InAppTutorialProgressDatabase,
|
||||
newProjectsDefaultFolder: string,
|
||||
newProjectsDefaultStorageProviderName: string,
|
||||
@@ -299,6 +300,7 @@ export type Preferences = {|
|
||||
getShowDeprecatedInstructionWarning: () => boolean,
|
||||
setUse3DEditor: (enabled: boolean) => void,
|
||||
getUse3DEditor: () => boolean,
|
||||
setShowBasicProfilingCounters: (enabled: boolean) => void,
|
||||
setNewProjectsDefaultStorageProviderName: (name: string) => void,
|
||||
saveTutorialProgress: ({|
|
||||
tutorialId: string,
|
||||
@@ -369,6 +371,7 @@ export const initialPreferences = {
|
||||
openDiagnosticReportAutomatically: true,
|
||||
showDeprecatedInstructionWarning: false,
|
||||
use3DEditor: isWebGLSupported(),
|
||||
showBasicProfilingCounters: false,
|
||||
inAppTutorialsProgress: {},
|
||||
newProjectsDefaultFolder: app ? findDefaultFolder(app) : '',
|
||||
newProjectsDefaultStorageProviderName: 'Cloud',
|
||||
@@ -436,6 +439,7 @@ export const initialPreferences = {
|
||||
getShowDeprecatedInstructionWarning: () => false,
|
||||
setUse3DEditor: (enabled: boolean) => {},
|
||||
getUse3DEditor: () => false,
|
||||
setShowBasicProfilingCounters: (enabled: boolean) => {},
|
||||
saveTutorialProgress: () => {},
|
||||
getTutorialProgress: () => {},
|
||||
setNewProjectsDefaultFolder: () => {},
|
||||
|
@@ -75,6 +75,7 @@ const PreferencesDialog = ({
|
||||
setOpenDiagnosticReportAutomatically,
|
||||
setShowDeprecatedInstructionWarning,
|
||||
setUse3DEditor,
|
||||
setShowBasicProfilingCounters,
|
||||
setNewProjectsDefaultFolder,
|
||||
setUseShortcutToClosePreviewWindow,
|
||||
setWatchProjectFolderFilesForLocalProjects,
|
||||
@@ -448,6 +449,12 @@ const PreferencesDialog = ({
|
||||
<Trans>Show a warning on deprecated actions and conditions</Trans>
|
||||
}
|
||||
/>
|
||||
<Toggle
|
||||
onToggle={(e, check) => setShowBasicProfilingCounters(check)}
|
||||
toggled={values.showBasicProfilingCounters}
|
||||
labelPosition="right"
|
||||
label={<Trans>Display profiling information in scene editor</Trans>}
|
||||
/>
|
||||
<Toggle
|
||||
onToggle={(e, check) => setUse3DEditor(check)}
|
||||
toggled={values.use3DEditor}
|
||||
|
@@ -169,6 +169,9 @@ export default class PreferencesProvider extends React.Component<Props, State> {
|
||||
),
|
||||
setUse3DEditor: this._setUse3DEditor.bind(this),
|
||||
getUse3DEditor: this._getUse3DEditor.bind(this),
|
||||
setShowBasicProfilingCounters: this._setShowBasicProfilingCounters.bind(
|
||||
this
|
||||
),
|
||||
saveTutorialProgress: this._saveTutorialProgress.bind(this),
|
||||
getTutorialProgress: this._getTutorialProgress.bind(this),
|
||||
setNewProjectsDefaultFolder: this._setNewProjectsDefaultFolder.bind(this),
|
||||
@@ -511,6 +514,18 @@ export default class PreferencesProvider extends React.Component<Props, State> {
|
||||
return this.state.values.use3DEditor;
|
||||
}
|
||||
|
||||
_setShowBasicProfilingCounters(showBasicProfilingCounters: boolean) {
|
||||
this.setState(
|
||||
state => ({
|
||||
values: {
|
||||
...state.values,
|
||||
showBasicProfilingCounters,
|
||||
},
|
||||
}),
|
||||
() => this._persistValuesToLocalStorage(this.state)
|
||||
);
|
||||
}
|
||||
|
||||
_checkUpdates(forceDownload?: boolean) {
|
||||
// Checking for updates is only done on Electron.
|
||||
// Note: This could be abstracted away later if other updates mechanisms
|
||||
|
@@ -1114,6 +1114,7 @@ const MainFrame = (props: Props) => {
|
||||
if (error.name === 'CloudProjectReadingError') {
|
||||
setCloudProjectFileMetadataToRecover(fileMetadata);
|
||||
} else {
|
||||
console.error('Failed to open the project:', error);
|
||||
const errorMessage = getOpenErrorMessage
|
||||
? getOpenErrorMessage(error)
|
||||
: t`Ensure that you are connected to internet and that the URL used is correct, then try again.`;
|
||||
|
@@ -0,0 +1,153 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import CompactPropertiesEditor from '../../CompactPropertiesEditor';
|
||||
import propertiesMapToSchema from '../../CompactPropertiesEditor/PropertiesMapToCompactSchema';
|
||||
import { type ResourceManagementProps } from '../../ResourcesList/ResourceSource';
|
||||
import { type UnsavedChanges } from '../../MainFrame/UnsavedChangesContext';
|
||||
import { ColumnStackLayout } from '../../UI/Layout';
|
||||
import ChevronArrowRight from '../../UI/CustomSvgIcons/ChevronArrowRight';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import FlatButton from '../../UI/FlatButton';
|
||||
import ChevronArrowTop from '../../UI/CustomSvgIcons/ChevronArrowTop';
|
||||
import Text from '../../UI/Text';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
onRefreshAllFields: () => void,
|
||||
resourceManagementProps: ResourceManagementProps,
|
||||
unsavedChanges?: ?UnsavedChanges,
|
||||
eventsBasedObject: gdEventsBasedObject,
|
||||
customObjectConfiguration: gdCustomObjectConfiguration,
|
||||
childObject: gdObject,
|
||||
|};
|
||||
|
||||
export const ChildObjectPropertiesEditor = ({
|
||||
project,
|
||||
onRefreshAllFields,
|
||||
resourceManagementProps,
|
||||
unsavedChanges,
|
||||
eventsBasedObject,
|
||||
customObjectConfiguration,
|
||||
childObject,
|
||||
}: Props) => {
|
||||
const [
|
||||
showObjectAdvancedOptions,
|
||||
setShowObjectAdvancedOptions,
|
||||
] = React.useState(false);
|
||||
const childObjectConfiguration = customObjectConfiguration.getChildObjectConfiguration(
|
||||
childObject.getName()
|
||||
);
|
||||
|
||||
const childObjectConfigurationAsGd = gd.castObject(
|
||||
childObjectConfiguration,
|
||||
gd.ObjectConfiguration
|
||||
);
|
||||
|
||||
// Properties:
|
||||
const objectBasicPropertiesSchema = React.useMemo(
|
||||
() => {
|
||||
const properties = childObjectConfigurationAsGd.getProperties();
|
||||
const schema = propertiesMapToSchema({
|
||||
properties,
|
||||
getProperties: ({ object, objectConfiguration }) =>
|
||||
objectConfiguration.getProperties(),
|
||||
onUpdateProperty: ({ object, objectConfiguration }, name, value) =>
|
||||
objectConfiguration.updateProperty(name, value),
|
||||
visibility: 'Basic',
|
||||
});
|
||||
|
||||
return schema;
|
||||
},
|
||||
[childObjectConfigurationAsGd]
|
||||
);
|
||||
|
||||
const objectAdvancedPropertiesSchema = React.useMemo(
|
||||
() => {
|
||||
const properties = childObjectConfigurationAsGd.getProperties();
|
||||
const schema = propertiesMapToSchema({
|
||||
properties,
|
||||
getProperties: ({ object, objectConfiguration }) =>
|
||||
objectConfiguration.getProperties(),
|
||||
onUpdateProperty: ({ object, objectConfiguration }, name, value) =>
|
||||
objectConfiguration.updateProperty(name, value),
|
||||
visibility: 'Advanced',
|
||||
});
|
||||
|
||||
return schema;
|
||||
},
|
||||
[childObjectConfigurationAsGd]
|
||||
);
|
||||
const hasObjectAdvancedProperties = objectAdvancedPropertiesSchema.length > 0;
|
||||
const hasSomeObjectProperties =
|
||||
objectBasicPropertiesSchema.length > 0 || hasObjectAdvancedProperties;
|
||||
|
||||
return (
|
||||
<ColumnStackLayout noMargin noOverflowParent>
|
||||
{!hasSomeObjectProperties && (
|
||||
<Text size="body2" align="center" color="secondary">
|
||||
<Trans>This object has no properties.</Trans>
|
||||
</Text>
|
||||
)}
|
||||
{hasSomeObjectProperties && (
|
||||
<CompactPropertiesEditor
|
||||
project={project}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
unsavedChanges={unsavedChanges}
|
||||
schema={objectBasicPropertiesSchema}
|
||||
instances={[
|
||||
{
|
||||
object: childObject,
|
||||
objectConfiguration: childObjectConfigurationAsGd,
|
||||
},
|
||||
]}
|
||||
onInstancesModified={() => {
|
||||
// TODO: undo/redo?
|
||||
}}
|
||||
onRefreshAllFields={onRefreshAllFields}
|
||||
/>
|
||||
)}
|
||||
{!showObjectAdvancedOptions && hasObjectAdvancedProperties && (
|
||||
<FlatButton
|
||||
fullWidth
|
||||
primary
|
||||
leftIcon={<ChevronArrowRight />}
|
||||
label={<Trans>Show more</Trans>}
|
||||
onClick={() => {
|
||||
setShowObjectAdvancedOptions(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showObjectAdvancedOptions && hasObjectAdvancedProperties && (
|
||||
<CompactPropertiesEditor
|
||||
project={project}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
unsavedChanges={unsavedChanges}
|
||||
schema={objectAdvancedPropertiesSchema}
|
||||
instances={[
|
||||
{
|
||||
object: childObject,
|
||||
objectConfiguration: childObjectConfigurationAsGd,
|
||||
},
|
||||
]}
|
||||
onInstancesModified={() => {
|
||||
// TODO: undo/redo?
|
||||
}}
|
||||
onRefreshAllFields={onRefreshAllFields}
|
||||
/>
|
||||
)}
|
||||
{showObjectAdvancedOptions && hasObjectAdvancedProperties && (
|
||||
<FlatButton
|
||||
fullWidth
|
||||
primary
|
||||
leftIcon={<ChevronArrowTop />}
|
||||
label={<Trans>Show less</Trans>}
|
||||
onClick={() => {
|
||||
setShowObjectAdvancedOptions(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ColumnStackLayout>
|
||||
);
|
||||
};
|
@@ -0,0 +1,182 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { type ResourceManagementProps } from '../../ResourcesList/ResourceSource';
|
||||
import propertiesMapToSchema from '../../CompactPropertiesEditor/PropertiesMapToCompactSchema';
|
||||
import CompactPropertiesEditor from '../../CompactPropertiesEditor';
|
||||
import FlatButton from '../../UI/FlatButton';
|
||||
import { ColumnStackLayout } from '../../UI/Layout';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import ChevronArrowTop from '../../UI/CustomSvgIcons/ChevronArrowTop';
|
||||
import ChevronArrowRight from '../../UI/CustomSvgIcons/ChevronArrowRight';
|
||||
import Text from '../../UI/Text';
|
||||
import { Line } from '../../UI/Grid';
|
||||
import { useForceRecompute } from '../../Utils/UseForceUpdate';
|
||||
import { type Schema, type ActionButton } from '../../CompactPropertiesEditor';
|
||||
import ShareExternal from '../../UI/CustomSvgIcons/ShareExternal';
|
||||
|
||||
export const getSchemaWithOpenFullEditorButton = ({
|
||||
schema,
|
||||
fullEditorLabel,
|
||||
behavior,
|
||||
onOpenFullEditor,
|
||||
}: {|
|
||||
schema: Schema,
|
||||
fullEditorLabel: ?string,
|
||||
behavior: gdBehavior,
|
||||
onOpenFullEditor: () => void,
|
||||
|}): Schema => {
|
||||
if (!fullEditorLabel) return schema;
|
||||
|
||||
const actionButton: ActionButton = {
|
||||
label: fullEditorLabel,
|
||||
disabled: 'onValuesDifferent',
|
||||
nonFieldType: 'button',
|
||||
showRightIcon: true,
|
||||
getIcon: style => <ShareExternal style={style} />,
|
||||
getValue: behavior => behavior.getName(),
|
||||
onClick: behavior => onOpenFullEditor(),
|
||||
};
|
||||
|
||||
let added = false;
|
||||
schema.forEach(field => {
|
||||
if (field.children && field.name === '') {
|
||||
field.children.push(actionButton);
|
||||
added = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!added) schema.push(actionButton);
|
||||
|
||||
return schema;
|
||||
};
|
||||
|
||||
export const CompactBehaviorPropertiesEditor = ({
|
||||
project,
|
||||
behaviorMetadata,
|
||||
behavior,
|
||||
object,
|
||||
onOpenFullEditor,
|
||||
onBehaviorUpdated,
|
||||
resourceManagementProps,
|
||||
}: {|
|
||||
project: gdProject,
|
||||
behaviorMetadata: gdBehaviorMetadata,
|
||||
behavior: gdBehavior,
|
||||
object: gdObject,
|
||||
onOpenFullEditor: () => void,
|
||||
onBehaviorUpdated: () => void,
|
||||
resourceManagementProps: ResourceManagementProps,
|
||||
|}) => {
|
||||
const [showAdvancedOptions, setShowAdvancedOptions] = React.useState(false);
|
||||
const [schemaRecomputeTrigger, forceRecomputeSchema] = useForceRecompute();
|
||||
|
||||
const fullEditorLabel = behaviorMetadata.getOpenFullEditorLabel();
|
||||
|
||||
const basicPropertiesSchema = React.useMemo(
|
||||
() => {
|
||||
if (schemaRecomputeTrigger) {
|
||||
// schemaRecomputeTrigger allows to invalidate the schema when required.
|
||||
}
|
||||
|
||||
const schema = propertiesMapToSchema({
|
||||
properties: behavior.getProperties(),
|
||||
getProperties: behavior => behavior.getProperties(),
|
||||
onUpdateProperty: (behavior, name, value) => {
|
||||
behavior.updateProperty(name, value);
|
||||
},
|
||||
object,
|
||||
visibility: 'Basic',
|
||||
});
|
||||
|
||||
return getSchemaWithOpenFullEditorButton({
|
||||
schema,
|
||||
fullEditorLabel,
|
||||
behavior,
|
||||
onOpenFullEditor,
|
||||
});
|
||||
},
|
||||
[
|
||||
behavior,
|
||||
object,
|
||||
schemaRecomputeTrigger,
|
||||
fullEditorLabel,
|
||||
onOpenFullEditor,
|
||||
]
|
||||
);
|
||||
|
||||
const advancedPropertiesSchema = React.useMemo(
|
||||
() => {
|
||||
if (schemaRecomputeTrigger) {
|
||||
// schemaRecomputeTrigger allows to invalidate the schema when required.
|
||||
}
|
||||
|
||||
return propertiesMapToSchema({
|
||||
properties: behavior.getProperties(),
|
||||
getProperties: behavior => behavior.getProperties(),
|
||||
onUpdateProperty: (behavior, name, value) => {
|
||||
behavior.updateProperty(name, value);
|
||||
},
|
||||
object,
|
||||
visibility: 'Advanced',
|
||||
});
|
||||
},
|
||||
[behavior, object, schemaRecomputeTrigger]
|
||||
);
|
||||
const hasAdvancedProperties = advancedPropertiesSchema.length > 0;
|
||||
const hasSomeProperties =
|
||||
basicPropertiesSchema.length > 0 || hasAdvancedProperties;
|
||||
|
||||
return (
|
||||
<ColumnStackLayout expand noMargin noOverflowParent>
|
||||
{!hasSomeProperties && (
|
||||
<Line justifyContent="center" expand>
|
||||
<Text size="body2" color="secondary" align="center" noMargin>
|
||||
<Trans>Nothing to configure for this behavior.</Trans>
|
||||
</Text>
|
||||
</Line>
|
||||
)}
|
||||
{hasSomeProperties && (
|
||||
<CompactPropertiesEditor
|
||||
project={project}
|
||||
schema={basicPropertiesSchema}
|
||||
instances={[behavior]}
|
||||
onInstancesModified={onBehaviorUpdated}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
onRefreshAllFields={forceRecomputeSchema}
|
||||
/>
|
||||
)}
|
||||
{!showAdvancedOptions && hasAdvancedProperties && (
|
||||
<FlatButton
|
||||
fullWidth
|
||||
primary
|
||||
leftIcon={<ChevronArrowRight />}
|
||||
label={<Trans>Show more</Trans>}
|
||||
onClick={() => {
|
||||
setShowAdvancedOptions(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showAdvancedOptions && hasAdvancedProperties && (
|
||||
<CompactPropertiesEditor
|
||||
project={project}
|
||||
schema={advancedPropertiesSchema}
|
||||
instances={[behavior]}
|
||||
onInstancesModified={onBehaviorUpdated}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
onRefreshAllFields={forceRecomputeSchema}
|
||||
/>
|
||||
)}
|
||||
{showAdvancedOptions && hasAdvancedProperties && (
|
||||
<FlatButton
|
||||
fullWidth
|
||||
primary
|
||||
leftIcon={<ChevronArrowTop />}
|
||||
label={<Trans>Show less</Trans>}
|
||||
onClick={() => {
|
||||
setShowAdvancedOptions(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ColumnStackLayout>
|
||||
);
|
||||
};
|
@@ -0,0 +1,40 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { type Schema, type ActionButton } from '../../CompactPropertiesEditor';
|
||||
import ShareExternal from '../../UI/CustomSvgIcons/ShareExternal';
|
||||
|
||||
export const getSchemaWithOpenFullEditorButton = ({
|
||||
schema,
|
||||
fullEditorLabel,
|
||||
object,
|
||||
onEditObject,
|
||||
}: {|
|
||||
schema: Schema,
|
||||
fullEditorLabel: ?string,
|
||||
object: gdObject,
|
||||
onEditObject: (object: gdObject) => void,
|
||||
|}): Schema => {
|
||||
if (!fullEditorLabel) return schema;
|
||||
|
||||
const actionButton: ActionButton = {
|
||||
label: fullEditorLabel,
|
||||
disabled: 'onValuesDifferent',
|
||||
nonFieldType: 'button',
|
||||
showRightIcon: true,
|
||||
getIcon: style => <ShareExternal style={style} />,
|
||||
getValue: ({ object }) => object.getName(),
|
||||
onClick: ({ object }) => onEditObject(object),
|
||||
};
|
||||
|
||||
let added = false;
|
||||
schema.forEach(field => {
|
||||
if (field.children && field.name === '') {
|
||||
field.children.push(actionButton);
|
||||
added = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!added) schema.push(actionButton);
|
||||
|
||||
return schema;
|
||||
};
|
@@ -0,0 +1,703 @@
|
||||
// @flow
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
import * as React from 'react';
|
||||
import { type UnsavedChanges } from '../../MainFrame/UnsavedChangesContext';
|
||||
import VariablesList, {
|
||||
type HistoryHandler,
|
||||
type VariablesListInterface,
|
||||
} from '../../VariablesList/VariablesList';
|
||||
import { type ProjectScopedContainersAccessor } from '../../InstructionOrExpression/EventsScope';
|
||||
import ErrorBoundary from '../../UI/ErrorBoundary';
|
||||
import ScrollView from '../../UI/ScrollView';
|
||||
import { Column, Line, Spacer, marginsSize } from '../../UI/Grid';
|
||||
import CompactPropertiesEditor, {
|
||||
Separator,
|
||||
} from '../../CompactPropertiesEditor';
|
||||
import Text from '../../UI/Text';
|
||||
import { Trans, t } from '@lingui/macro';
|
||||
import IconButton from '../../UI/IconButton';
|
||||
import ShareExternal from '../../UI/CustomSvgIcons/ShareExternal';
|
||||
import EventsRootVariablesFinder from '../../Utils/EventsRootVariablesFinder';
|
||||
import propertiesMapToSchema from '../../CompactPropertiesEditor/PropertiesMapToCompactSchema';
|
||||
import { type ObjectEditorTab } from '../../ObjectEditor/ObjectEditorDialog';
|
||||
import { CompactBehaviorPropertiesEditor } from './CompactBehaviorPropertiesEditor';
|
||||
import { type ResourceManagementProps } from '../../ResourcesList/ResourceSource';
|
||||
import Paper from '../../UI/Paper';
|
||||
import { ColumnStackLayout, LineStackLayout } from '../../UI/Layout';
|
||||
import { IconContainer } from '../../UI/IconContainer';
|
||||
import Remove from '../../UI/CustomSvgIcons/Remove';
|
||||
import useForceUpdate, { useForceRecompute } from '../../Utils/UseForceUpdate';
|
||||
import ChevronArrowRight from '../../UI/CustomSvgIcons/ChevronArrowRight';
|
||||
import ChevronArrowBottom from '../../UI/CustomSvgIcons/ChevronArrowBottom';
|
||||
import Add from '../../UI/CustomSvgIcons/Add';
|
||||
import { useManageObjectBehaviors } from '../../BehaviorsEditor';
|
||||
import Object3d from '../../UI/CustomSvgIcons/Object3d';
|
||||
import Object2d from '../../UI/CustomSvgIcons/Object2d';
|
||||
import { CompactEffectPropertiesEditor } from '../../EffectsList/CompactEffectPropertiesEditor';
|
||||
import { mapFor } from '../../Utils/MapFor';
|
||||
import {
|
||||
getEnumeratedEffectMetadata,
|
||||
useManageEffects,
|
||||
} from '../../EffectsList';
|
||||
import CompactSelectField from '../../UI/CompactSelectField';
|
||||
import SelectOption from '../../UI/SelectOption';
|
||||
import { ChildObjectPropertiesEditor } from './ChildObjectPropertiesEditor';
|
||||
import { getSchemaWithOpenFullEditorButton } from './CompactObjectPropertiesSchema';
|
||||
import FlatButton from '../../UI/FlatButton';
|
||||
import ChevronArrowTop from '../../UI/CustomSvgIcons/ChevronArrowTop';
|
||||
import Help from '../../UI/CustomSvgIcons/Help';
|
||||
import { getHelpLink } from '../../Utils/HelpLink';
|
||||
import Window from '../../Utils/Window';
|
||||
import CompactTextField from '../../UI/CompactTextField';
|
||||
import SquaredDoubleChevronArrowDown from '../../UI/CustomSvgIcons/SquaredDoubleChevronArrowDown';
|
||||
import SquaredDoubleChevronArrowUp from '../../UI/CustomSvgIcons/SquaredDoubleChevronArrowUp';
|
||||
import { textEllipsisStyle } from '../../UI/TextEllipsis';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
export const styles = {
|
||||
icon: {
|
||||
fontSize: 18,
|
||||
},
|
||||
scrollView: {
|
||||
paddingTop: marginsSize,
|
||||
// In theory, should not be needed (the children should be responsible for not
|
||||
// overflowing the parent). In practice, even when no horizontal scroll is shown
|
||||
// on Chrome, it might happen on Safari. Prevent any scroll to be 100% sure no
|
||||
// scrollbar will be shown.
|
||||
overflowX: 'hidden',
|
||||
},
|
||||
};
|
||||
|
||||
const CollapsibleSubPanel = ({
|
||||
renderContent,
|
||||
isFolded,
|
||||
toggleFolded,
|
||||
title,
|
||||
titleIcon,
|
||||
onRemove,
|
||||
}: {|
|
||||
renderContent: () => React.Node,
|
||||
isFolded: boolean,
|
||||
toggleFolded: () => void,
|
||||
titleIcon?: ?React.Node,
|
||||
title: string,
|
||||
onRemove?: () => void,
|
||||
|}) => (
|
||||
<Paper background="medium">
|
||||
<Line expand>
|
||||
<ColumnStackLayout expand noOverflowParent>
|
||||
<LineStackLayout noMargin justifyContent="space-between">
|
||||
<Line noMargin alignItems="center">
|
||||
<IconButton onClick={toggleFolded} size="small">
|
||||
{isFolded ? (
|
||||
<ChevronArrowRight style={styles.icon} />
|
||||
) : (
|
||||
<ChevronArrowBottom style={styles.icon} />
|
||||
)}
|
||||
</IconButton>
|
||||
|
||||
{titleIcon}
|
||||
{titleIcon && <Spacer />}
|
||||
<Text noMargin size="body" style={textEllipsisStyle}>
|
||||
{title}
|
||||
</Text>
|
||||
</Line>
|
||||
|
||||
{onRemove ? (
|
||||
<IconButton
|
||||
tooltip={t`Remove behavior`}
|
||||
onClick={onRemove}
|
||||
size="small"
|
||||
>
|
||||
<Remove style={styles.icon} />
|
||||
</IconButton>
|
||||
) : null}
|
||||
</LineStackLayout>
|
||||
{isFolded ? null : renderContent()}
|
||||
</ColumnStackLayout>
|
||||
</Line>
|
||||
</Paper>
|
||||
);
|
||||
|
||||
const TopLevelCollapsibleSection = ({
|
||||
title,
|
||||
isFolded,
|
||||
toggleFolded,
|
||||
renderContent,
|
||||
renderContentAsHiddenWhenFolded,
|
||||
noContentMargin,
|
||||
onOpenFullEditor,
|
||||
onAdd,
|
||||
}: {|
|
||||
title: React.Node,
|
||||
isFolded: boolean,
|
||||
toggleFolded: () => void,
|
||||
renderContent: () => React.Node,
|
||||
renderContentAsHiddenWhenFolded?: boolean,
|
||||
noContentMargin?: boolean,
|
||||
onOpenFullEditor: () => void,
|
||||
onAdd?: () => void,
|
||||
|}) => (
|
||||
<>
|
||||
<Separator />
|
||||
<Column noOverflowParent>
|
||||
<LineStackLayout alignItems="center" justifyContent="space-between">
|
||||
<LineStackLayout noMargin alignItems="center">
|
||||
<IconButton size="small" onClick={toggleFolded}>
|
||||
{isFolded ? (
|
||||
<SquaredDoubleChevronArrowUp style={styles.icon} />
|
||||
) : (
|
||||
<SquaredDoubleChevronArrowDown style={styles.icon} />
|
||||
)}
|
||||
</IconButton>
|
||||
<Text size="sub-title" noMargin style={textEllipsisStyle}>
|
||||
{title}
|
||||
</Text>
|
||||
</LineStackLayout>
|
||||
<Line alignItems="center" noMargin>
|
||||
<IconButton size="small" onClick={onOpenFullEditor}>
|
||||
<ShareExternal style={styles.icon} />
|
||||
</IconButton>
|
||||
{onAdd && (
|
||||
<IconButton size="small" onClick={onAdd}>
|
||||
<Add style={styles.icon} />
|
||||
</IconButton>
|
||||
)}
|
||||
</Line>
|
||||
</LineStackLayout>
|
||||
</Column>
|
||||
<Column noMargin={noContentMargin}>
|
||||
{isFolded ? (
|
||||
renderContentAsHiddenWhenFolded ? (
|
||||
<div style={{ display: 'none' }}>{renderContent()}</div>
|
||||
) : null
|
||||
) : (
|
||||
renderContent()
|
||||
)}
|
||||
</Column>
|
||||
</>
|
||||
);
|
||||
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
resourceManagementProps: ResourceManagementProps,
|
||||
layout?: ?gdLayout,
|
||||
eventsFunctionsExtension: gdEventsFunctionsExtension | null,
|
||||
onUpdateBehaviorsSharedData: () => void,
|
||||
objectsContainer: gdObjectsContainer,
|
||||
globalObjectsContainer: gdObjectsContainer | null,
|
||||
layersContainer: gdLayersContainer,
|
||||
projectScopedContainersAccessor: ProjectScopedContainersAccessor,
|
||||
unsavedChanges?: ?UnsavedChanges,
|
||||
i18n: I18nType,
|
||||
historyHandler?: HistoryHandler,
|
||||
|
||||
objects: Array<gdObject>,
|
||||
onEditObject: (object: gdObject, initialTab: ?ObjectEditorTab) => void,
|
||||
|};
|
||||
|
||||
export const CompactObjectPropertiesEditor = ({
|
||||
project,
|
||||
resourceManagementProps,
|
||||
layout,
|
||||
eventsFunctionsExtension,
|
||||
onUpdateBehaviorsSharedData,
|
||||
objectsContainer,
|
||||
globalObjectsContainer,
|
||||
layersContainer,
|
||||
projectScopedContainersAccessor,
|
||||
unsavedChanges,
|
||||
i18n,
|
||||
historyHandler,
|
||||
objects,
|
||||
onEditObject,
|
||||
}: Props) => {
|
||||
const forceUpdate = useForceUpdate();
|
||||
const [
|
||||
showObjectAdvancedOptions,
|
||||
setShowObjectAdvancedOptions,
|
||||
] = React.useState(false);
|
||||
const [isPropertiesFolded, setIsPropertiesFolded] = React.useState(false);
|
||||
const [isBehaviorsFolded, setIsBehaviorsFolded] = React.useState(false);
|
||||
const [isVariablesFolded, setIsVariablesFolded] = React.useState(false);
|
||||
const [isEffectsFolded, setIsEffectsFolded] = React.useState(false);
|
||||
const [schemaRecomputeTrigger, forceRecomputeSchema] = useForceRecompute();
|
||||
const variablesListRef = React.useRef<?VariablesListInterface>(null);
|
||||
const object = objects[0];
|
||||
const objectConfiguration = object.getConfiguration();
|
||||
|
||||
// Don't use a memo for this because metadata from custom objects are built
|
||||
// from event-based object when extensions are refreshed after an extension
|
||||
// installation.
|
||||
const objectMetadata = gd.MetadataProvider.getObjectMetadata(
|
||||
project.getCurrentPlatform(),
|
||||
object.getType()
|
||||
);
|
||||
const is3DObject = !!objectMetadata && objectMetadata.isRenderedIn3D();
|
||||
const fullEditorLabel = objectMetadata
|
||||
? objectMetadata.getOpenFullEditorLabel()
|
||||
: null;
|
||||
|
||||
// TODO: Workaround a bad design of ObjectJsImplementation. When getProperties
|
||||
// and associated methods are redefined in JS, they have different arguments (
|
||||
// see ObjectJsImplementation C++ implementation). If called directly here from JS,
|
||||
// the arguments will be mismatched. To workaround this, always cast the object to
|
||||
// a base gdObject to ensure C++ methods are called.
|
||||
const objectConfigurationAsGd = gd.castObject(
|
||||
objectConfiguration,
|
||||
gd.ObjectConfiguration
|
||||
);
|
||||
|
||||
// Properties:
|
||||
const objectBasicPropertiesSchema = React.useMemo(
|
||||
() => {
|
||||
if (schemaRecomputeTrigger) {
|
||||
// schemaRecomputeTrigger allows to invalidate the schema when required.
|
||||
}
|
||||
|
||||
const properties = objectConfigurationAsGd.getProperties();
|
||||
const objectBasicPropertiesSchema = propertiesMapToSchema({
|
||||
properties,
|
||||
getProperties: ({ object, objectConfiguration }) =>
|
||||
objectConfiguration.getProperties(),
|
||||
onUpdateProperty: ({ object, objectConfiguration }, name, value) =>
|
||||
objectConfiguration.updateProperty(name, value),
|
||||
visibility: 'Basic',
|
||||
});
|
||||
|
||||
return getSchemaWithOpenFullEditorButton({
|
||||
schema: objectBasicPropertiesSchema,
|
||||
fullEditorLabel,
|
||||
object,
|
||||
onEditObject,
|
||||
});
|
||||
},
|
||||
[
|
||||
objectConfigurationAsGd,
|
||||
schemaRecomputeTrigger,
|
||||
fullEditorLabel,
|
||||
object,
|
||||
onEditObject,
|
||||
]
|
||||
);
|
||||
const objectAdvancedPropertiesSchema = React.useMemo(
|
||||
() => {
|
||||
if (schemaRecomputeTrigger) {
|
||||
// schemaRecomputeTrigger allows to invalidate the schema when required.
|
||||
}
|
||||
|
||||
const properties = objectConfigurationAsGd.getProperties();
|
||||
return propertiesMapToSchema({
|
||||
properties,
|
||||
getProperties: ({ object, objectConfiguration }) =>
|
||||
objectConfiguration.getProperties(),
|
||||
onUpdateProperty: ({ object, objectConfiguration }, name, value) =>
|
||||
objectConfiguration.updateProperty(name, value),
|
||||
visibility: 'Advanced',
|
||||
});
|
||||
},
|
||||
[objectConfigurationAsGd, schemaRecomputeTrigger]
|
||||
);
|
||||
const hasObjectAdvancedProperties = objectAdvancedPropertiesSchema.length > 0;
|
||||
const hasSomeObjectProperties =
|
||||
objectBasicPropertiesSchema.length > 0 || hasObjectAdvancedProperties;
|
||||
|
||||
// Behaviors:
|
||||
const {
|
||||
openNewBehaviorDialog,
|
||||
newBehaviorDialog,
|
||||
removeBehavior,
|
||||
} = useManageObjectBehaviors({
|
||||
project,
|
||||
object,
|
||||
eventsFunctionsExtension,
|
||||
onUpdate: forceUpdate,
|
||||
onBehaviorsUpdated: forceUpdate,
|
||||
onUpdateBehaviorsSharedData,
|
||||
});
|
||||
|
||||
const allVisibleBehaviors = object
|
||||
.getAllBehaviorNames()
|
||||
.toJSArray()
|
||||
.map(behaviorName => object.getBehavior(behaviorName))
|
||||
.filter(behavior => !behavior.isDefaultBehavior());
|
||||
|
||||
// Effects:
|
||||
const effectsContainer = object.getEffects();
|
||||
const {
|
||||
allEffectMetadata,
|
||||
all2DEffectMetadata,
|
||||
addEffect,
|
||||
removeEffect,
|
||||
chooseEffectType,
|
||||
} = useManageEffects({
|
||||
effectsContainer,
|
||||
project,
|
||||
onEffectsUpdated: forceUpdate,
|
||||
onUpdate: forceUpdate,
|
||||
target: 'object',
|
||||
});
|
||||
|
||||
// Events based object children:
|
||||
const eventsBasedObject = project.hasEventsBasedObject(
|
||||
objectConfiguration.getType()
|
||||
)
|
||||
? project.getEventsBasedObject(objectConfiguration.getType())
|
||||
: null;
|
||||
const customObjectConfiguration = eventsBasedObject
|
||||
? gd.asCustomObjectConfiguration(objectConfiguration)
|
||||
: null;
|
||||
|
||||
const shouldDisplayEventsBasedObjectChildren =
|
||||
customObjectConfiguration &&
|
||||
(customObjectConfiguration.isForcedToOverrideEventsBasedObjectChildrenConfiguration() ||
|
||||
customObjectConfiguration.isMarkedAsOverridingEventsBasedObjectChildrenConfiguration());
|
||||
|
||||
const helpLink = getHelpLink(objectMetadata.getHelpPath());
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
componentTitle={<Trans>Object properties</Trans>}
|
||||
scope="scene-editor-object-properties"
|
||||
>
|
||||
<ScrollView
|
||||
autoHideScrollbar
|
||||
style={styles.scrollView}
|
||||
key={objects.map((instance: gdObject) => '' + instance.ptr).join(';')}
|
||||
>
|
||||
<Column expand noMargin id="object-properties-editor" noOverflowParent>
|
||||
<ColumnStackLayout expand noOverflowParent>
|
||||
<LineStackLayout
|
||||
noMargin
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<LineStackLayout noMargin alignItems="center">
|
||||
{is3DObject ? (
|
||||
<Object3d style={styles.icon} />
|
||||
) : (
|
||||
<Object2d style={styles.icon} />
|
||||
)}
|
||||
<Text size="body" noMargin>
|
||||
<Trans>{objectMetadata.getFullName()}</Trans>
|
||||
</Text>
|
||||
{helpLink && (
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
Window.openExternalURL(helpLink);
|
||||
}}
|
||||
>
|
||||
<Help style={styles.icon} />
|
||||
</IconButton>
|
||||
)}
|
||||
</LineStackLayout>
|
||||
</LineStackLayout>
|
||||
<CompactTextField
|
||||
value={object.getName()}
|
||||
onChange={() => {}}
|
||||
disabled
|
||||
/>
|
||||
</ColumnStackLayout>
|
||||
<TopLevelCollapsibleSection
|
||||
title={<Trans>Properties</Trans>}
|
||||
isFolded={isPropertiesFolded}
|
||||
toggleFolded={() => setIsPropertiesFolded(!isPropertiesFolded)}
|
||||
onOpenFullEditor={() => onEditObject(object, 'properties')}
|
||||
renderContent={() => (
|
||||
<ColumnStackLayout noMargin noOverflowParent>
|
||||
{!hasSomeObjectProperties && (
|
||||
<Text size="body2" align="center" color="secondary">
|
||||
<Trans>This object has no properties.</Trans>
|
||||
</Text>
|
||||
)}
|
||||
{hasSomeObjectProperties && (
|
||||
<CompactPropertiesEditor
|
||||
project={project}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
unsavedChanges={unsavedChanges}
|
||||
schema={objectBasicPropertiesSchema}
|
||||
instances={[
|
||||
{ object, objectConfiguration: objectConfigurationAsGd },
|
||||
]}
|
||||
onInstancesModified={() => {
|
||||
// TODO: undo/redo?
|
||||
}}
|
||||
onRefreshAllFields={forceRecomputeSchema}
|
||||
/>
|
||||
)}
|
||||
{!showObjectAdvancedOptions && hasObjectAdvancedProperties && (
|
||||
<FlatButton
|
||||
fullWidth
|
||||
primary
|
||||
leftIcon={<ChevronArrowRight />}
|
||||
label={<Trans>Show more</Trans>}
|
||||
onClick={() => {
|
||||
setShowObjectAdvancedOptions(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showObjectAdvancedOptions && hasObjectAdvancedProperties && (
|
||||
<CompactPropertiesEditor
|
||||
project={project}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
unsavedChanges={unsavedChanges}
|
||||
schema={objectAdvancedPropertiesSchema}
|
||||
instances={[
|
||||
{ object, objectConfiguration: objectConfigurationAsGd },
|
||||
]}
|
||||
onInstancesModified={() => {
|
||||
// TODO: undo/redo?
|
||||
}}
|
||||
onRefreshAllFields={forceRecomputeSchema}
|
||||
/>
|
||||
)}
|
||||
{showObjectAdvancedOptions && hasObjectAdvancedProperties && (
|
||||
<FlatButton
|
||||
fullWidth
|
||||
primary
|
||||
leftIcon={<ChevronArrowTop />}
|
||||
label={<Trans>Show less</Trans>}
|
||||
onClick={() => {
|
||||
setShowObjectAdvancedOptions(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{eventsBasedObject &&
|
||||
customObjectConfiguration &&
|
||||
shouldDisplayEventsBasedObjectChildren &&
|
||||
mapFor(
|
||||
0,
|
||||
eventsBasedObject.getObjects().getObjectsCount(),
|
||||
i => {
|
||||
const childObject = eventsBasedObject
|
||||
.getObjects()
|
||||
.getObjectAt(i);
|
||||
const childObjectName = childObject.getName();
|
||||
const isFolded = customObjectConfiguration.isChildObjectFolded(
|
||||
childObjectName
|
||||
);
|
||||
return (
|
||||
<CollapsibleSubPanel
|
||||
key={i}
|
||||
renderContent={() => (
|
||||
<ChildObjectPropertiesEditor
|
||||
key={i}
|
||||
project={project}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
unsavedChanges={unsavedChanges}
|
||||
eventsBasedObject={eventsBasedObject}
|
||||
customObjectConfiguration={
|
||||
customObjectConfiguration
|
||||
}
|
||||
childObject={childObject}
|
||||
onRefreshAllFields={forceRecomputeSchema}
|
||||
/>
|
||||
)}
|
||||
isFolded={isFolded}
|
||||
toggleFolded={() => {
|
||||
customObjectConfiguration.setChildObjectFolded(
|
||||
childObjectName,
|
||||
!isFolded
|
||||
);
|
||||
forceUpdate();
|
||||
}}
|
||||
title={childObjectName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</ColumnStackLayout>
|
||||
)}
|
||||
/>
|
||||
<TopLevelCollapsibleSection
|
||||
title={<Trans>Behaviors</Trans>}
|
||||
isFolded={isBehaviorsFolded}
|
||||
toggleFolded={() => setIsBehaviorsFolded(!isBehaviorsFolded)}
|
||||
onOpenFullEditor={() => onEditObject(object, 'behaviors')}
|
||||
onAdd={openNewBehaviorDialog}
|
||||
renderContent={() => (
|
||||
<ColumnStackLayout noMargin>
|
||||
{!allVisibleBehaviors.length && (
|
||||
<Text size="body2" align="center" color="secondary">
|
||||
<Trans>There are no behaviors on this object.</Trans>
|
||||
</Text>
|
||||
)}
|
||||
{allVisibleBehaviors.map(behavior => {
|
||||
const behaviorTypeName = behavior.getTypeName();
|
||||
const behaviorMetadata = gd.MetadataProvider.getBehaviorMetadata(
|
||||
gd.JsPlatform.get(),
|
||||
behaviorTypeName
|
||||
);
|
||||
|
||||
const iconUrl = behaviorMetadata.getIconFilename();
|
||||
|
||||
return (
|
||||
<CollapsibleSubPanel
|
||||
key={behavior.ptr}
|
||||
renderContent={() => (
|
||||
<CompactBehaviorPropertiesEditor
|
||||
project={project}
|
||||
behaviorMetadata={behaviorMetadata}
|
||||
behavior={behavior}
|
||||
object={object}
|
||||
onBehaviorUpdated={() => {}}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
onOpenFullEditor={() =>
|
||||
onEditObject(object, 'behaviors')
|
||||
}
|
||||
/>
|
||||
)}
|
||||
isFolded={behavior.isFolded()}
|
||||
toggleFolded={() => {
|
||||
behavior.setFolded(!behavior.isFolded());
|
||||
forceUpdate();
|
||||
}}
|
||||
titleIcon={
|
||||
iconUrl ? (
|
||||
<IconContainer
|
||||
src={iconUrl}
|
||||
alt={behaviorMetadata.getFullName()}
|
||||
size={16}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
title={behavior.getName()}
|
||||
onRemove={() => {
|
||||
removeBehavior(behavior.getName());
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ColumnStackLayout>
|
||||
)}
|
||||
/>
|
||||
<TopLevelCollapsibleSection
|
||||
title={<Trans>Object Variables</Trans>}
|
||||
isFolded={isVariablesFolded}
|
||||
toggleFolded={() => setIsVariablesFolded(!isVariablesFolded)}
|
||||
onOpenFullEditor={() => onEditObject(object, 'variables')}
|
||||
onAdd={() => {
|
||||
if (variablesListRef.current) {
|
||||
variablesListRef.current.addVariable();
|
||||
}
|
||||
setIsVariablesFolded(false);
|
||||
}}
|
||||
renderContentAsHiddenWhenFolded={
|
||||
true /* Allows to keep a ref to the variables list for add button to work. */
|
||||
}
|
||||
noContentMargin
|
||||
renderContent={() => (
|
||||
<VariablesList
|
||||
ref={variablesListRef}
|
||||
projectScopedContainersAccessor={
|
||||
projectScopedContainersAccessor
|
||||
}
|
||||
directlyStoreValueChangesWhileEditing
|
||||
variablesContainer={object.getVariables()}
|
||||
areObjectVariables
|
||||
size="compact"
|
||||
onComputeAllVariableNames={() =>
|
||||
object && layout
|
||||
? EventsRootVariablesFinder.findAllObjectVariables(
|
||||
project.getCurrentPlatform(),
|
||||
project,
|
||||
layout,
|
||||
object.getName()
|
||||
)
|
||||
: []
|
||||
}
|
||||
historyHandler={historyHandler}
|
||||
toolbarIconStyle={styles.icon}
|
||||
compactEmptyPlaceholderText={
|
||||
<Trans>There are no variables on this object.</Trans>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{objectMetadata &&
|
||||
objectMetadata.hasDefaultBehavior(
|
||||
'EffectCapability::EffectBehavior'
|
||||
) && (
|
||||
<TopLevelCollapsibleSection
|
||||
title={<Trans>Effects</Trans>}
|
||||
isFolded={isEffectsFolded}
|
||||
toggleFolded={() => setIsEffectsFolded(!isEffectsFolded)}
|
||||
onOpenFullEditor={() => onEditObject(object, 'effects')}
|
||||
onAdd={() => addEffect(false)}
|
||||
renderContent={() => (
|
||||
<ColumnStackLayout>
|
||||
{effectsContainer.getEffectsCount() === 0 && (
|
||||
<Text size="body2" align="center" color="secondary">
|
||||
<Trans>There are no effects on this object.</Trans>
|
||||
</Text>
|
||||
)}
|
||||
{mapFor(
|
||||
0,
|
||||
effectsContainer.getEffectsCount(),
|
||||
(index: number) => {
|
||||
const effect: gdEffect = effectsContainer.getEffectAt(
|
||||
index
|
||||
);
|
||||
const effectType = effect.getEffectType();
|
||||
const effectMetadata = getEnumeratedEffectMetadata(
|
||||
allEffectMetadata,
|
||||
effectType
|
||||
);
|
||||
|
||||
return (
|
||||
<CollapsibleSubPanel
|
||||
key={effect.ptr}
|
||||
renderContent={() => (
|
||||
<ColumnStackLayout expand noOverflowParent>
|
||||
<CompactSelectField
|
||||
value={effectType}
|
||||
onChange={type =>
|
||||
chooseEffectType(effect, type)
|
||||
}
|
||||
>
|
||||
{all2DEffectMetadata.map(effectMetadata => (
|
||||
<SelectOption
|
||||
key={effectMetadata.type}
|
||||
value={effectMetadata.type}
|
||||
label={effectMetadata.fullName}
|
||||
disabled={
|
||||
effectMetadata.isMarkedAsNotWorkingForObjects
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</CompactSelectField>
|
||||
<CompactEffectPropertiesEditor
|
||||
project={project}
|
||||
effect={effect}
|
||||
effectMetadata={effectMetadata}
|
||||
resourceManagementProps={
|
||||
resourceManagementProps
|
||||
}
|
||||
/>
|
||||
</ColumnStackLayout>
|
||||
)}
|
||||
isFolded={effect.isFolded()}
|
||||
toggleFolded={() => {
|
||||
effect.setFolded(!effect.isFolded());
|
||||
forceUpdate();
|
||||
}}
|
||||
title={effect.getName()}
|
||||
onRemove={() => {
|
||||
removeEffect(effect);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</ColumnStackLayout>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</Column>
|
||||
</ScrollView>
|
||||
{newBehaviorDialog}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
@@ -164,18 +164,13 @@ export default class ParticleEmitterEditor extends React.Component<
|
||||
floatingLabelText={<Trans>Particles start color</Trans>}
|
||||
disableAlpha
|
||||
fullWidth
|
||||
color={rgbColorToRGBString({
|
||||
r: particleEmitterConfiguration.getParticleRed1(),
|
||||
g: particleEmitterConfiguration.getParticleGreen1(),
|
||||
b: particleEmitterConfiguration.getParticleBlue1(),
|
||||
})}
|
||||
color={particleEmitterConfiguration.getParticleColor1()}
|
||||
onChange={color => {
|
||||
const rgbColor = rgbStringAndAlphaToRGBColor(color);
|
||||
if (rgbColor) {
|
||||
particleEmitterConfiguration.setParticleRed1(rgbColor.r);
|
||||
particleEmitterConfiguration.setParticleGreen1(rgbColor.g);
|
||||
particleEmitterConfiguration.setParticleBlue1(rgbColor.b);
|
||||
|
||||
particleEmitterConfiguration.setParticleColor1(
|
||||
rgbColorToRGBString(rgbColor)
|
||||
);
|
||||
this.forceUpdate();
|
||||
}
|
||||
}}
|
||||
@@ -199,18 +194,13 @@ export default class ParticleEmitterEditor extends React.Component<
|
||||
floatingLabelText={<Trans>Particles end color</Trans>}
|
||||
disableAlpha
|
||||
fullWidth
|
||||
color={rgbColorToRGBString({
|
||||
r: particleEmitterConfiguration.getParticleRed2(),
|
||||
g: particleEmitterConfiguration.getParticleGreen2(),
|
||||
b: particleEmitterConfiguration.getParticleBlue2(),
|
||||
})}
|
||||
color={particleEmitterConfiguration.getParticleColor2()}
|
||||
onChange={color => {
|
||||
const rgbColor = rgbStringAndAlphaToRGBColor(color);
|
||||
if (rgbColor) {
|
||||
particleEmitterConfiguration.setParticleRed2(rgbColor.r);
|
||||
particleEmitterConfiguration.setParticleGreen2(rgbColor.g);
|
||||
particleEmitterConfiguration.setParticleBlue2(rgbColor.b);
|
||||
|
||||
particleEmitterConfiguration.setParticleColor2(
|
||||
rgbColorToRGBString(rgbColor)
|
||||
);
|
||||
this.forceUpdate();
|
||||
}
|
||||
}}
|
||||
|
@@ -54,18 +54,12 @@ export default class PanelSpriteEditor extends React.Component<
|
||||
floatingLabelText={<Trans>Outline color</Trans>}
|
||||
disableAlpha
|
||||
fullWidth
|
||||
color={rgbColorToRGBString({
|
||||
r: shapePainterConfiguration.getOutlineColorR(),
|
||||
g: shapePainterConfiguration.getOutlineColorG(),
|
||||
b: shapePainterConfiguration.getOutlineColorB(),
|
||||
})}
|
||||
color={shapePainterConfiguration.getOutlineColor()}
|
||||
onChange={color => {
|
||||
const rgbColor = rgbStringAndAlphaToRGBColor(color);
|
||||
if (rgbColor) {
|
||||
shapePainterConfiguration.setOutlineColor(
|
||||
rgbColor.r,
|
||||
rgbColor.g,
|
||||
rgbColor.b
|
||||
rgbColorToRGBString(rgbColor)
|
||||
);
|
||||
|
||||
this.forceUpdate();
|
||||
@@ -104,18 +98,12 @@ export default class PanelSpriteEditor extends React.Component<
|
||||
floatingLabelText={<Trans>Fill color</Trans>}
|
||||
disableAlpha
|
||||
fullWidth
|
||||
color={rgbColorToRGBString({
|
||||
r: shapePainterConfiguration.getFillColorR(),
|
||||
g: shapePainterConfiguration.getFillColorG(),
|
||||
b: shapePainterConfiguration.getFillColorB(),
|
||||
})}
|
||||
color={shapePainterConfiguration.getFillColor()}
|
||||
onChange={color => {
|
||||
const rgbColor = rgbStringAndAlphaToRGBColor(color);
|
||||
if (rgbColor) {
|
||||
shapePainterConfiguration.setFillColor(
|
||||
rgbColor.r,
|
||||
rgbColor.g,
|
||||
rgbColor.b
|
||||
rgbColorToRGBString(rgbColor)
|
||||
);
|
||||
|
||||
this.forceUpdate();
|
||||
|
@@ -95,7 +95,6 @@ const SpineEditor = ({
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const spineConfiguration = gd.asSpineConfiguration(objectConfiguration);
|
||||
const properties = objectConfiguration.getProperties();
|
||||
|
||||
const [nameErrors, setNameErrors] = React.useState<{ [number]: React.Node }>(
|
||||
{}
|
||||
@@ -110,7 +109,7 @@ const SpineEditor = ({
|
||||
const [sourceSelectOptions, setSourceSelectOptions] = React.useState<
|
||||
Array<Object>
|
||||
>([]);
|
||||
const spineResourceName = properties.get('spineResourceName').getValue();
|
||||
const spineResourceName = spineConfiguration.getSpineResourceName();
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
|
@@ -47,8 +47,8 @@ const invalidTexture = PIXI.Texture.from('res/error48.png');
|
||||
const loadingTexture = PIXI.Texture.from(
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAAA1BMVEXX19f5cgrAAAAAAXRSTlMz/za5cAAAAApJREFUCNdjQAMAABAAAbSqgB8AAAAASUVORK5CYII='
|
||||
);
|
||||
let loadedThreeTextures = {};
|
||||
let loadedThreeMaterials = {};
|
||||
let loadedOrLoadingThreeTextures: ResourcePromise<THREE.Texture> = {};
|
||||
let loadedOrLoadingThreeMaterials: ResourcePromise<THREE.Material> = {};
|
||||
let loadedOrLoading3DModelPromises: ResourcePromise<THREE.THREE_ADDONS.GLTF> = {};
|
||||
let spineAtlasPromises: ResourcePromise<SpineTextureAtlasOrLoadingError> = {};
|
||||
let spineDataPromises: ResourcePromise<SpineDataOrLoadingError> = {};
|
||||
@@ -244,8 +244,8 @@ export default class PixiResourcesLoader {
|
||||
loadedBitmapFonts = {};
|
||||
loadedFontFamilies = {};
|
||||
loadedTextures = {};
|
||||
loadedThreeTextures = {};
|
||||
loadedThreeMaterials = {};
|
||||
loadedOrLoadingThreeTextures = {};
|
||||
loadedOrLoadingThreeMaterials = {};
|
||||
loadedOrLoading3DModelPromises = {};
|
||||
spineAtlasPromises = {};
|
||||
spineDataPromises = {};
|
||||
@@ -296,9 +296,10 @@ export default class PixiResourcesLoader {
|
||||
if (loadedBitmapFonts[resourceName]) {
|
||||
delete loadedBitmapFonts[resourceName];
|
||||
}
|
||||
if (loadedThreeTextures[resourceName]) {
|
||||
loadedThreeTextures[resourceName].dispose();
|
||||
delete loadedThreeTextures[resourceName];
|
||||
if (loadedOrLoadingThreeTextures[resourceName]) {
|
||||
const threeTexture = await loadedOrLoadingThreeTextures[resourceName];
|
||||
threeTexture.dispose();
|
||||
delete loadedOrLoadingThreeTextures[resourceName];
|
||||
}
|
||||
if (spineAtlasPromises[resourceName]) {
|
||||
await PIXI.Assets.unload(resourceName).catch(async () => {
|
||||
@@ -328,14 +329,17 @@ export default class PixiResourcesLoader {
|
||||
PIXI.Assets.resolver.prefer();
|
||||
}
|
||||
|
||||
const matchingMaterials = Object.keys(loadedThreeMaterials).filter(key =>
|
||||
key.startsWith(resourceName)
|
||||
);
|
||||
if (matchingMaterials.length > 0) {
|
||||
matchingMaterials.forEach(key => {
|
||||
loadedThreeMaterials[key].dispose();
|
||||
delete loadedThreeMaterials[key];
|
||||
});
|
||||
const matchingMaterialCacheKeys = Object.keys(
|
||||
loadedOrLoadingThreeMaterials
|
||||
).filter(key => key.startsWith(resourceName));
|
||||
if (matchingMaterialCacheKeys.length > 0) {
|
||||
await Promise.all(
|
||||
matchingMaterialCacheKeys.map(async key => {
|
||||
const material = await loadedOrLoadingThreeMaterials[key];
|
||||
material.dispose();
|
||||
delete loadedOrLoadingThreeMaterials[key];
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
@@ -503,12 +507,12 @@ export default class PixiResourcesLoader {
|
||||
* @param resourceName The name of the resource
|
||||
* @returns The requested texture, or a placeholder if not found.
|
||||
*/
|
||||
static getThreeTexture(
|
||||
static async getThreeTexture(
|
||||
project: gdProject,
|
||||
resourceName: string
|
||||
): THREE.Texture {
|
||||
const loadedThreeTexture = loadedThreeTextures[resourceName];
|
||||
if (loadedThreeTexture) return loadedThreeTexture;
|
||||
): Promise<THREE.Texture> {
|
||||
const loadedOrLoadingPromise = loadedOrLoadingThreeTextures[resourceName];
|
||||
if (loadedOrLoadingPromise) return loadedOrLoadingPromise;
|
||||
|
||||
// Texture is not loaded, load it now from the PixiJS texture.
|
||||
// TODO (3D) - optimization: don't load the PixiJS Texture if not used by PixiJS.
|
||||
@@ -518,6 +522,15 @@ export default class PixiResourcesLoader {
|
||||
resourceName
|
||||
);
|
||||
|
||||
if (!pixiTexture.baseTexture.valid) {
|
||||
// Post pone texture update if texture is not loaded.
|
||||
return new Promise(resolve => {
|
||||
pixiTexture.once('update', () =>
|
||||
resolve(this.getThreeTexture(project, resourceName))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// @ts-ignore - source does exist on resource.
|
||||
const image = pixiTexture.baseTexture.resource.source;
|
||||
if (!(image instanceof HTMLImageElement)) {
|
||||
@@ -537,9 +550,9 @@ export default class PixiResourcesLoader {
|
||||
const resource = project.getResourcesManager().getResource(resourceName);
|
||||
applyThreeTextureSettings(resource, threeTexture);
|
||||
|
||||
loadedThreeTextures[resourceName] = threeTexture;
|
||||
|
||||
return threeTexture;
|
||||
return (loadedOrLoadingThreeTextures[resourceName] = Promise.resolve(
|
||||
threeTexture
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -549,23 +562,31 @@ export default class PixiResourcesLoader {
|
||||
* @param options Set if the material should be transparent or not.
|
||||
* @returns The requested material.
|
||||
*/
|
||||
static getThreeMaterial(
|
||||
static async getThreeMaterial(
|
||||
project: gdProject,
|
||||
resourceName: string,
|
||||
{ useTransparentTexture }: {| useTransparentTexture: boolean |}
|
||||
) {
|
||||
{
|
||||
useTransparentTexture,
|
||||
}: {|
|
||||
useTransparentTexture: boolean,
|
||||
|}
|
||||
): Promise<THREE.Material> {
|
||||
const cacheKey = `${resourceName}|transparent:${useTransparentTexture.toString()}`;
|
||||
const loadedThreeMaterial = loadedThreeMaterials[cacheKey];
|
||||
if (loadedThreeMaterial) return loadedThreeMaterial;
|
||||
const loadedOrLoadingPromise = loadedOrLoadingThreeMaterials[cacheKey];
|
||||
if (loadedOrLoadingPromise) return loadedOrLoadingPromise;
|
||||
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: this.getThreeTexture(project, resourceName),
|
||||
side: useTransparentTexture ? THREE.DoubleSide : THREE.FrontSide,
|
||||
transparent: useTransparentTexture,
|
||||
});
|
||||
return (loadedOrLoadingThreeMaterials[cacheKey] = this.getThreeTexture(
|
||||
project,
|
||||
resourceName
|
||||
).then(texture => {
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: texture,
|
||||
side: useTransparentTexture ? THREE.DoubleSide : THREE.FrontSide,
|
||||
transparent: useTransparentTexture,
|
||||
});
|
||||
|
||||
loadedThreeMaterials[cacheKey] = material;
|
||||
return material;
|
||||
return material;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -11,16 +11,21 @@ type StretchedSprite = PIXI.Sprite | PIXI.TilingSprite;
|
||||
* Renderer for gd.PanelSpriteObject
|
||||
*
|
||||
* Heavily inspired from the GDJS PIXI renderer for PanelSprite objects.
|
||||
* TODO: Find a way to factor GDJS objects and IDE instances renderers.
|
||||
*/
|
||||
export default class RenderedPanelSpriteInstance extends RenderedInstance {
|
||||
_centerSprite: StretchedSprite;
|
||||
_borderSprites: StretchedSprite[];
|
||||
|
||||
// Cache of the values of the properties of the object, to detect
|
||||
// changes
|
||||
_textureName: string;
|
||||
_width: number;
|
||||
_height: number;
|
||||
_tiled: boolean;
|
||||
_wasRendered: boolean;
|
||||
_leftMargin: number;
|
||||
_topMargin: number;
|
||||
_rightMargin: number;
|
||||
_bottomMargin: number;
|
||||
|
||||
constructor(
|
||||
project: gdProject,
|
||||
@@ -41,25 +46,31 @@ export default class RenderedPanelSpriteInstance extends RenderedInstance {
|
||||
}
|
||||
|
||||
update() {
|
||||
//TODO
|
||||
// if (this._pixiObject.visible && this._wasRendered) {
|
||||
// this._pixiObject.cacheAsBitmap = true;
|
||||
// }
|
||||
// this._wasRendered = true;
|
||||
|
||||
const panelSprite = gd.asPanelSpriteConfiguration(
|
||||
this._associatedObjectConfiguration
|
||||
);
|
||||
|
||||
// Change in tiling needs PIXI objects to be recreated.
|
||||
if (panelSprite.isTiled() !== this._tiled) {
|
||||
this.makeObjectsAndUpdateTextures();
|
||||
}
|
||||
if (panelSprite.getTexture() !== this._textureName) {
|
||||
|
||||
// Change in texture or margins needs textures to be recreated.
|
||||
if (
|
||||
panelSprite.getTexture() !== this._textureName ||
|
||||
panelSprite.getLeftMargin() !== this._leftMargin ||
|
||||
panelSprite.getTopMargin() !== this._topMargin ||
|
||||
panelSprite.getRightMargin() !== this._rightMargin ||
|
||||
panelSprite.getBottomMargin() !== this._bottomMargin
|
||||
) {
|
||||
this.updateTextures();
|
||||
}
|
||||
|
||||
// Change in position/angle is always applied.
|
||||
this.updateAngle();
|
||||
this.updatePosition();
|
||||
|
||||
// Handle change in size.
|
||||
const oldWidth = this._width;
|
||||
const oldHeight = this._height;
|
||||
if (this._instance.hasCustomSize()) {
|
||||
@@ -232,7 +243,6 @@ export default class RenderedPanelSpriteInstance extends RenderedInstance {
|
||||
0
|
||||
);
|
||||
|
||||
this._wasRendered = true;
|
||||
this._pixiObject.cacheAsBitmap = false;
|
||||
}
|
||||
|
||||
@@ -240,12 +250,19 @@ export default class RenderedPanelSpriteInstance extends RenderedInstance {
|
||||
const panelSprite = gd.asPanelSpriteConfiguration(
|
||||
this._associatedObjectConfiguration
|
||||
);
|
||||
|
||||
// Store the values used for rendering, to detect changes
|
||||
// that would need later to update the texture again.
|
||||
this._textureName = panelSprite.getTexture();
|
||||
this._leftMargin = panelSprite.getLeftMargin();
|
||||
this._topMargin = panelSprite.getTopMargin();
|
||||
this._rightMargin = panelSprite.getRightMargin();
|
||||
this._bottomMargin = panelSprite.getBottomMargin();
|
||||
|
||||
const texture = PixiResourcesLoader.getPIXITexture(
|
||||
this._project,
|
||||
this._textureName
|
||||
);
|
||||
|
||||
if (!texture.baseTexture.valid) {
|
||||
// Post pone texture update if texture is not loaded.
|
||||
texture.once('update', () => this.updateTextures());
|
||||
|
@@ -3,7 +3,7 @@ import RenderedInstance from './RenderedInstance';
|
||||
import PixiResourcesLoader from '../../ObjectsRendering/PixiResourcesLoader';
|
||||
import ResourcesLoader from '../../ResourcesLoader';
|
||||
import * as PIXI from 'pixi.js-legacy';
|
||||
import { rgbToHexNumber } from '../../Utils/ColorTransformer';
|
||||
import { rgbOrHexToHexNumber } from '../../Utils/ColorTransformer';
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
/**
|
||||
@@ -70,11 +70,7 @@ export default class RenderedParticleEmitterInstance extends RenderedInstance {
|
||||
this._pixiObject.beginFill(0, 0);
|
||||
this._pixiObject.lineStyle(
|
||||
3,
|
||||
rgbToHexNumber(
|
||||
particleEmitterConfiguration.getParticleRed2(),
|
||||
particleEmitterConfiguration.getParticleGreen2(),
|
||||
particleEmitterConfiguration.getParticleBlue2()
|
||||
),
|
||||
rgbOrHexToHexNumber(particleEmitterConfiguration.getParticleColor2()),
|
||||
1
|
||||
);
|
||||
this._pixiObject.moveTo(0, 0);
|
||||
@@ -91,11 +87,7 @@ export default class RenderedParticleEmitterInstance extends RenderedInstance {
|
||||
|
||||
this._pixiObject.lineStyle(0, 0x000000, 1);
|
||||
this._pixiObject.beginFill(
|
||||
rgbToHexNumber(
|
||||
particleEmitterConfiguration.getParticleRed1(),
|
||||
particleEmitterConfiguration.getParticleGreen1(),
|
||||
particleEmitterConfiguration.getParticleBlue1()
|
||||
)
|
||||
rgbOrHexToHexNumber(particleEmitterConfiguration.getParticleColor1())
|
||||
);
|
||||
this._pixiObject.drawCircle(0, 0, 8);
|
||||
this._pixiObject.endFill();
|
||||
|
@@ -7,6 +7,20 @@ import * as THREE from 'three';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
let transparentMaterial = null;
|
||||
const getTransparentMaterial = () => {
|
||||
if (!transparentMaterial)
|
||||
transparentMaterial = new THREE.MeshBasicMaterial({
|
||||
transparent: true,
|
||||
opacity: 0,
|
||||
// Set the alpha test to to ensure the faces behind are rendered
|
||||
// (no "back face culling" that would still be done if alphaTest is not set).
|
||||
alphaTest: 1,
|
||||
});
|
||||
|
||||
return transparentMaterial;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renderer for gd.SpriteObject
|
||||
*/
|
||||
@@ -50,15 +64,8 @@ export default class RenderedSprite3DInstance extends Rendered3DInstance {
|
||||
this._pixiContainer.addChild(this._pixiObject);
|
||||
|
||||
this.updateSprite();
|
||||
const material = this._pixiResourcesLoader.getThreeMaterial(
|
||||
project,
|
||||
this._sprite ? this._sprite.getImageName() : '',
|
||||
{
|
||||
useTransparentTexture: true,
|
||||
}
|
||||
);
|
||||
const geometry = new THREE.PlaneGeometry(1, -1);
|
||||
const threeObject = new THREE.Mesh(geometry, material);
|
||||
const threeObject = new THREE.Mesh(geometry, getTransparentMaterial());
|
||||
threeObject.rotation.order = 'ZYX';
|
||||
this._threeGroup.add(threeObject);
|
||||
this._threeObject = threeObject;
|
||||
@@ -177,11 +184,13 @@ export default class RenderedSprite3DInstance extends Rendered3DInstance {
|
||||
return true;
|
||||
}
|
||||
|
||||
updateTextureAndSprite(): void {
|
||||
async updateTextureAndSprite(): Promise<void> {
|
||||
this.updateSprite();
|
||||
const sprite = this._sprite;
|
||||
if (!sprite) return;
|
||||
|
||||
// Note that `getPIXITexture` could be refactored to return a promise
|
||||
// to make it nicer to use (no need to use "once('update')" pattern).
|
||||
const texture = this._pixiResourcesLoader.getPIXITexture(
|
||||
this._project,
|
||||
sprite.getImageName()
|
||||
@@ -196,7 +205,7 @@ export default class RenderedSprite3DInstance extends Rendered3DInstance {
|
||||
this._textureWidth = texture.width;
|
||||
this._textureHeight = texture.height;
|
||||
|
||||
const material = this._pixiResourcesLoader.getThreeMaterial(
|
||||
const material = await this._pixiResourcesLoader.getThreeMaterial(
|
||||
this._project,
|
||||
sprite.getImageName(),
|
||||
{
|
||||
|
@@ -158,13 +158,11 @@ const createField = (
|
||||
const extraInfos = property.getExtraInfo().toJSArray();
|
||||
// $FlowFixMe - assume the passed resource kind is always valid.
|
||||
const kind: ResourceKind = extraInfos[0] || '';
|
||||
// $FlowFixMe - assume the passed resource kind is always valid.
|
||||
const fallbackKind: ResourceKind = extraInfos[1] || '';
|
||||
|
||||
return {
|
||||
name,
|
||||
valueType: 'resource',
|
||||
resourceKind: kind,
|
||||
fallbackResourceKind: fallbackKind,
|
||||
getValue: (instance: Instance): string => {
|
||||
return getProperties(instance)
|
||||
.get(name)
|
||||
|
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
import * as React from 'react';
|
||||
import SemiControlledTextField from '../UI/SemiControlledTextField';
|
||||
import InlineCheckbox from '../UI/InlineCheckbox';
|
||||
@@ -13,11 +12,7 @@ import { MarkdownText } from '../UI/MarkdownText';
|
||||
import { rgbOrHexToRGBString } from '../Utils/ColorTransformer';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import InputAdornment from '@material-ui/core/InputAdornment';
|
||||
import { type MenuItemTemplate } from '../UI/Menu/Menu.flow';
|
||||
import {
|
||||
type ResourceKind,
|
||||
type ResourceManagementProps,
|
||||
} from '../ResourcesList/ResourceSource';
|
||||
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
|
||||
import {
|
||||
TextFieldWithButtonLayout,
|
||||
ResponsiveLineStackLayout,
|
||||
@@ -27,115 +22,34 @@ import RaisedButton from '../UI/RaisedButton';
|
||||
import UnsavedChangesContext, {
|
||||
type UnsavedChanges,
|
||||
} from '../MainFrame/UnsavedChangesContext';
|
||||
import { Column, Line, Spacer } from '../UI/Grid';
|
||||
import { Column, Line } from '../UI/Grid';
|
||||
import Text from '../UI/Text';
|
||||
import useForceUpdate from '../Utils/UseForceUpdate';
|
||||
import RaisedButtonWithSplitMenu from '../UI/RaisedButtonWithSplitMenu';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import Edit from '../UI/CustomSvgIcons/Edit';
|
||||
import {
|
||||
type Schema,
|
||||
type ValueField,
|
||||
type ActionButton,
|
||||
type SectionTitle,
|
||||
type ResourceField,
|
||||
} from '../CompactPropertiesEditor';
|
||||
|
||||
// Re-export the types.
|
||||
export type {
|
||||
Schema,
|
||||
ValueField,
|
||||
ActionButton,
|
||||
SectionTitle,
|
||||
ResourceField,
|
||||
Field,
|
||||
} from '../CompactPropertiesEditor';
|
||||
|
||||
// An "instance" here is the objects for which properties are shown
|
||||
export type Instance = Object; // This could be improved using generics.
|
||||
export type Instances = Array<Instance>;
|
||||
|
||||
// "Value" fields are fields displayed in the properties.
|
||||
export type ValueFieldCommonProperties = {|
|
||||
name: string,
|
||||
getLabel?: Instance => string,
|
||||
getDescription?: Instance => string,
|
||||
getExtraDescription?: Instance => string,
|
||||
disabled?: boolean | ((instances: Array<gdInitialInstance>) => boolean),
|
||||
onEditButtonBuildMenuTemplate?: (i18n: I18nType) => Array<MenuItemTemplate>,
|
||||
onEditButtonClick?: () => void,
|
||||
|};
|
||||
|
||||
// "Primitive" value fields are "simple" fields.
|
||||
export type PrimitiveValueField =
|
||||
| {|
|
||||
valueType: 'number',
|
||||
getValue: Instance => number,
|
||||
setValue: (instance: Instance, newValue: number) => void,
|
||||
getEndAdornment?: Instance => {|
|
||||
label: string,
|
||||
tooltipContent: React.Node,
|
||||
|},
|
||||
...ValueFieldCommonProperties,
|
||||
|}
|
||||
| {|
|
||||
valueType: 'string',
|
||||
getValue: Instance => string,
|
||||
setValue: (instance: Instance, newValue: string) => void,
|
||||
getChoices?: ?() => Array<{|
|
||||
value: string,
|
||||
label: string,
|
||||
labelIsUserDefined?: boolean,
|
||||
|}>,
|
||||
isHiddenWhenOnlyOneChoice?: boolean,
|
||||
...ValueFieldCommonProperties,
|
||||
|}
|
||||
| {|
|
||||
valueType: 'boolean',
|
||||
getValue: Instance => boolean,
|
||||
setValue: (instance: Instance, newValue: boolean) => void,
|
||||
...ValueFieldCommonProperties,
|
||||
|}
|
||||
| {|
|
||||
valueType: 'color',
|
||||
getValue: Instance => string,
|
||||
setValue: (instance: Instance, newValue: string) => void,
|
||||
...ValueFieldCommonProperties,
|
||||
|}
|
||||
| {|
|
||||
valueType: 'textarea',
|
||||
getValue: Instance => string,
|
||||
setValue: (instance: Instance, newValue: string) => void,
|
||||
...ValueFieldCommonProperties,
|
||||
|};
|
||||
|
||||
// "Resource" fields are showing a resource selector.
|
||||
type ResourceField = {|
|
||||
valueType: 'resource',
|
||||
resourceKind: ResourceKind,
|
||||
fallbackResourceKind?: ResourceKind,
|
||||
getValue: Instance => string,
|
||||
setValue: (instance: Instance, newValue: string) => void,
|
||||
...ValueFieldCommonProperties,
|
||||
|};
|
||||
|
||||
type SectionTitle = {|
|
||||
name: string,
|
||||
getValue?: Instance => string,
|
||||
nonFieldType: 'sectionTitle',
|
||||
defaultValue?: string,
|
||||
|};
|
||||
|
||||
type ActionButton = {|
|
||||
label: string,
|
||||
disabled: 'onValuesDifferent',
|
||||
getValue: Instance => string,
|
||||
nonFieldType: 'button',
|
||||
onClick: (instance: Instance) => void,
|
||||
|};
|
||||
|
||||
// A value field is a primitive or a resource.
|
||||
export type ValueField = PrimitiveValueField | ResourceField;
|
||||
|
||||
// A field can be a primitive, a resource or a list of fields
|
||||
export type Field =
|
||||
| PrimitiveValueField
|
||||
| ResourceField
|
||||
| SectionTitle
|
||||
| ActionButton
|
||||
| {|
|
||||
name: string,
|
||||
type: 'row' | 'column',
|
||||
title?: ?string,
|
||||
children: Array<Field>,
|
||||
|};
|
||||
|
||||
// The schema is the tree of all fields.
|
||||
export type Schema = Array<Field>;
|
||||
|
||||
type Props = {|
|
||||
onInstancesModified?: Instances => void,
|
||||
instances: Instances,
|
||||
@@ -556,7 +470,6 @@ const PropertiesEditor = ({
|
||||
project={project}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
resourceKind={field.resourceKind}
|
||||
fallbackResourceKind={field.fallbackResourceKind}
|
||||
resourceName={getFieldValue({
|
||||
instances,
|
||||
field,
|
||||
@@ -583,43 +496,13 @@ const PropertiesEditor = ({
|
||||
<ColumnStackLayout noMargin>{fields}</ColumnStackLayout>
|
||||
);
|
||||
|
||||
const renderSectionTitle = React.useCallback(
|
||||
(field: SectionTitle) => {
|
||||
const { getValue } = field;
|
||||
|
||||
let additionalText = null;
|
||||
|
||||
if (getValue) {
|
||||
let selectedInstancesValue = getFieldValue({
|
||||
instances,
|
||||
field,
|
||||
defaultValue: field.defaultValue || 'Multiple Values',
|
||||
});
|
||||
if (!!selectedInstancesValue) additionalText = selectedInstancesValue;
|
||||
}
|
||||
|
||||
if (!!additionalText) {
|
||||
return (
|
||||
<Line alignItems="baseline" key={`section-title-${field.name}`}>
|
||||
<Text displayInlineAsSpan>{field.name}</Text>
|
||||
<Spacer />
|
||||
<Text
|
||||
allowSelection
|
||||
displayInlineAsSpan
|
||||
size="body2"
|
||||
>{`- ${additionalText}`}</Text>
|
||||
</Line>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Line key={`section-title-${field.name}`}>
|
||||
<Text displayInlineAsSpan>{field.name}</Text>
|
||||
</Line>
|
||||
);
|
||||
},
|
||||
[instances]
|
||||
);
|
||||
const renderSectionTitle = React.useCallback((field: SectionTitle) => {
|
||||
return (
|
||||
<Line key={`section-title-${field.name}`}>
|
||||
<Text displayInlineAsSpan>{field.name}</Text>
|
||||
</Line>
|
||||
);
|
||||
}, []);
|
||||
|
||||
return renderContainer(
|
||||
schema.map(field => {
|
||||
|
@@ -89,7 +89,7 @@ const ResourcePropertiesEditor = React.forwardRef<
|
||||
{
|
||||
name: 'Resource name',
|
||||
valueType: 'string',
|
||||
disabled: true,
|
||||
disabled: () => true,
|
||||
getValue: (resource: gdResource) => resource.getName(),
|
||||
setValue: (resource: gdResource, newValue: string) =>
|
||||
resource.setName(newValue),
|
||||
|
@@ -222,6 +222,9 @@ export const CompactResourceSelectorWithThumbnail = ({
|
||||
onlyForStorageProvider === storageProvider.internalName
|
||||
);
|
||||
|
||||
const isResourceSetButInvalid =
|
||||
resourceName && !project.getResourcesManager().hasResource(resourceName);
|
||||
|
||||
return (
|
||||
<LineStackLayout noMargin expand id={idToUse.current}>
|
||||
{displayThumbnail && (
|
||||
@@ -237,7 +240,7 @@ export const CompactResourceSelectorWithThumbnail = ({
|
||||
className={classNames({
|
||||
[classes.container]: true,
|
||||
[classes.disabled]: false,
|
||||
[classes.errored]: false,
|
||||
[classes.errored]: isResourceSetButInvalid,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
@@ -245,7 +248,12 @@ export const CompactResourceSelectorWithThumbnail = ({
|
||||
[classes.compactResourceSelector]: true,
|
||||
})}
|
||||
>
|
||||
<input type="text" value={resourceName} />
|
||||
<input
|
||||
type="text"
|
||||
spellCheck={false}
|
||||
value={resourceName}
|
||||
onChange={e => onChange(e.currentTarget.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ElementWithMenu
|
||||
|
@@ -46,6 +46,7 @@ type Props = {|
|
||||
const ImageThumbnail = (props: Props) => {
|
||||
const { onContextMenu, resourcesLoader, resourceName, project } = props;
|
||||
const theme = React.useContext(GDevelopThemeContext);
|
||||
const [error, setError] = React.useState(false);
|
||||
|
||||
// Allow a long press to show the context menu
|
||||
const longTouchForContextMenuProps = useLongTouch(
|
||||
@@ -60,6 +61,8 @@ const ImageThumbnail = (props: Props) => {
|
||||
const normalBorderColor = theme.imagePreview.borderColor;
|
||||
const borderColor = props.selected
|
||||
? theme.palette.secondary
|
||||
: !!error
|
||||
? theme.message.error
|
||||
: normalBorderColor;
|
||||
|
||||
const containerStyle = {
|
||||
@@ -87,9 +90,16 @@ const ImageThumbnail = (props: Props) => {
|
||||
...styles.spriteThumbnailImage,
|
||||
maxWidth: props.size || 100,
|
||||
maxHeight: props.size || 100,
|
||||
display: error ? 'none' : undefined,
|
||||
}}
|
||||
alt={resourceName}
|
||||
src={resourcesLoader.getResourceFullUrl(project, resourceName, {})}
|
||||
onError={error => {
|
||||
setError(error);
|
||||
}}
|
||||
onLoad={() => {
|
||||
setError(false);
|
||||
}}
|
||||
/>
|
||||
{props.selectable && (
|
||||
<div style={styles.checkboxContainer}>
|
||||
|
@@ -30,6 +30,7 @@ export type SceneEditorsDisplayProps = {|
|
||||
objectsContainer: gdObjectsContainer,
|
||||
projectScopedContainersAccessor: ProjectScopedContainersAccessor,
|
||||
initialInstances: gdInitialInstancesContainer,
|
||||
lastSelectionType: 'instance' | 'object',
|
||||
instancesSelection: InstancesSelection,
|
||||
selectedLayer: string,
|
||||
onSelectInstances: (
|
||||
@@ -39,7 +40,8 @@ export type SceneEditorsDisplayProps = {|
|
||||
) => void,
|
||||
editInstanceVariables: (instance: ?gdInitialInstance) => void,
|
||||
editObjectByName: (objectName: string, initialTab?: ObjectEditorTab) => void,
|
||||
onEditObject: gdObject => void,
|
||||
editObjectInPropertiesPanel: (objectName: string) => void,
|
||||
onEditObject: (object: gdObject, initialTab: ?ObjectEditorTab) => void,
|
||||
selectedObjectFolderOrObjectsWithContext: ObjectFolderOrObjectWithContext[],
|
||||
onSelectLayer: (layerName: string) => void,
|
||||
editLayerEffects: (layer: ?gdLayer) => void,
|
||||
@@ -122,7 +124,7 @@ export type SceneEditorsDisplayProps = {|
|
||||
export type SceneEditorsDisplayInterface = {|
|
||||
getName: () => 'mosaic' | 'swipeableDrawer',
|
||||
forceUpdateInstancesList: () => void,
|
||||
forceUpdateInstancesPropertiesEditor: () => void,
|
||||
forceUpdatePropertiesEditor: () => void,
|
||||
forceUpdateObjectsList: () => void,
|
||||
forceUpdateObjectGroupsList: () => void,
|
||||
scrollObjectGroupsListToObjectGroup: (objectGroup: gdObjectGroup) => void,
|
||||
|
@@ -0,0 +1,121 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
import Paper from '../UI/Paper';
|
||||
import EmptyMessage from '../UI/EmptyMessage';
|
||||
import useForceUpdate from '../Utils/UseForceUpdate';
|
||||
import { CompactInstancePropertiesEditor } from '../InstancesEditor/CompactInstancePropertiesEditor';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { ProjectScopedContainersAccessor } from '../InstructionOrExpression/EventsScope';
|
||||
import { type UnsavedChanges } from '../MainFrame/UnsavedChangesContext';
|
||||
import { type HistoryHandler } from '../VariablesList/VariablesList';
|
||||
import { type TileMapTileSelection } from '../InstancesEditor/TileSetVisualizer';
|
||||
import { CompactObjectPropertiesEditor } from '../ObjectEditor/CompactObjectPropertiesEditor';
|
||||
import { type ObjectEditorTab } from '../ObjectEditor/ObjectEditorDialog';
|
||||
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
|
||||
|
||||
export const styles = {
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
flexDirection: 'column',
|
||||
},
|
||||
};
|
||||
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
resourceManagementProps: ResourceManagementProps,
|
||||
layout?: ?gdLayout,
|
||||
eventsFunctionsExtension: gdEventsFunctionsExtension | null,
|
||||
objectsContainer: gdObjectsContainer,
|
||||
globalObjectsContainer: gdObjectsContainer | null,
|
||||
layersContainer: gdLayersContainer,
|
||||
projectScopedContainersAccessor: ProjectScopedContainersAccessor,
|
||||
unsavedChanges?: ?UnsavedChanges,
|
||||
i18n: I18nType,
|
||||
historyHandler?: HistoryHandler,
|
||||
lastSelectionType: 'instance' | 'object',
|
||||
|
||||
// For objects:
|
||||
objects: Array<gdObject>,
|
||||
onEditObject: (object: gdObject, initialTab: ?ObjectEditorTab) => void,
|
||||
onUpdateBehaviorsSharedData: () => void,
|
||||
|
||||
// For instances:
|
||||
instances: Array<gdInitialInstance>,
|
||||
editObjectInPropertiesPanel: (objectName: string) => void,
|
||||
onInstancesModified?: (Array<gdInitialInstance>) => void,
|
||||
onGetInstanceSize: gdInitialInstance => [number, number, number],
|
||||
editInstanceVariables: gdInitialInstance => void,
|
||||
tileMapTileSelection: ?TileMapTileSelection,
|
||||
onSelectTileMapTile: (?TileMapTileSelection) => void,
|
||||
|};
|
||||
|
||||
export type InstanceOrObjectPropertiesEditorInterface = {|
|
||||
forceUpdate: () => void,
|
||||
|};
|
||||
|
||||
export const InstanceOrObjectPropertiesEditorContainer = React.forwardRef<
|
||||
Props,
|
||||
InstanceOrObjectPropertiesEditorInterface
|
||||
>((props, ref) => {
|
||||
const forceUpdate = useForceUpdate();
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
forceUpdate,
|
||||
}));
|
||||
|
||||
const {
|
||||
lastSelectionType,
|
||||
|
||||
// For objects:
|
||||
objects,
|
||||
onEditObject,
|
||||
resourceManagementProps,
|
||||
eventsFunctionsExtension,
|
||||
onUpdateBehaviorsSharedData,
|
||||
|
||||
// For instances:
|
||||
instances,
|
||||
editObjectInPropertiesPanel,
|
||||
onInstancesModified,
|
||||
onGetInstanceSize,
|
||||
editInstanceVariables,
|
||||
tileMapTileSelection,
|
||||
onSelectTileMapTile,
|
||||
...commonProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Paper background="dark" square style={styles.paper}>
|
||||
{!!instances.length && lastSelectionType === 'instance' ? (
|
||||
<CompactInstancePropertiesEditor
|
||||
instances={instances}
|
||||
editObjectInPropertiesPanel={editObjectInPropertiesPanel}
|
||||
onInstancesModified={onInstancesModified}
|
||||
onGetInstanceSize={onGetInstanceSize}
|
||||
editInstanceVariables={editInstanceVariables}
|
||||
tileMapTileSelection={tileMapTileSelection}
|
||||
onSelectTileMapTile={onSelectTileMapTile}
|
||||
{...commonProps}
|
||||
/>
|
||||
) : !!objects.length && lastSelectionType === 'object' ? (
|
||||
<CompactObjectPropertiesEditor
|
||||
objects={objects}
|
||||
onEditObject={onEditObject}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
eventsFunctionsExtension={eventsFunctionsExtension}
|
||||
onUpdateBehaviorsSharedData={onUpdateBehaviorsSharedData}
|
||||
{...commonProps}
|
||||
/>
|
||||
) : (
|
||||
<EmptyMessage>
|
||||
<Trans>
|
||||
Click on an instance on the canvas or an object in the list to
|
||||
display their properties.
|
||||
</Trans>
|
||||
</EmptyMessage>
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
});
|
@@ -28,9 +28,10 @@ import {
|
||||
type SceneEditorsDisplayProps,
|
||||
type SceneEditorsDisplayInterface,
|
||||
} from '../EditorsDisplay.flow';
|
||||
import CompactInstancePropertiesEditorContainer, {
|
||||
type CompactInstancePropertiesEditorInterface,
|
||||
} from '../../InstancesEditor/CompactInstancePropertiesEditor';
|
||||
import {
|
||||
InstanceOrObjectPropertiesEditorContainer,
|
||||
type InstanceOrObjectPropertiesEditorInterface,
|
||||
} from '../../SceneEditor/InstanceOrObjectPropertiesEditorContainer';
|
||||
|
||||
const initialMosaicEditorNodes = {
|
||||
direction: 'row',
|
||||
@@ -81,9 +82,11 @@ const MosaicEditorsDisplay = React.forwardRef<
|
||||
>((props, ref) => {
|
||||
const {
|
||||
project,
|
||||
resourceManagementProps,
|
||||
layout,
|
||||
eventsFunctionsExtension,
|
||||
eventsBasedObject,
|
||||
updateBehaviorsSharedData,
|
||||
layersContainer,
|
||||
globalObjectsContainer,
|
||||
objectsContainer,
|
||||
@@ -99,7 +102,7 @@ const MosaicEditorsDisplay = React.forwardRef<
|
||||
} = React.useContext(PreferencesContext);
|
||||
const selectedInstances = props.instancesSelection.getSelectedInstances();
|
||||
|
||||
const instancesPropertiesEditorRef = React.useRef<?CompactInstancePropertiesEditorInterface>(
|
||||
const instanceOrObjectPropertiesEditorRef = React.useRef<?InstanceOrObjectPropertiesEditorInterface>(
|
||||
null
|
||||
);
|
||||
const layersListRef = React.useRef<?LayersListInterface>(null);
|
||||
@@ -109,9 +112,10 @@ const MosaicEditorsDisplay = React.forwardRef<
|
||||
const editorMosaicRef = React.useRef<?EditorMosaicInterface>(null);
|
||||
const objectGroupsListRef = React.useRef<?ObjectGroupsListInterface>(null);
|
||||
|
||||
const forceUpdateInstancesPropertiesEditor = React.useCallback(() => {
|
||||
if (instancesPropertiesEditorRef.current)
|
||||
instancesPropertiesEditorRef.current.forceUpdate();
|
||||
// TODO: rename
|
||||
const forceUpdatePropertiesEditor = React.useCallback(() => {
|
||||
if (instanceOrObjectPropertiesEditorRef.current)
|
||||
instanceOrObjectPropertiesEditorRef.current.forceUpdate();
|
||||
}, []);
|
||||
const forceUpdateInstancesList = React.useCallback(() => {
|
||||
if (instancesListRef.current) instancesListRef.current.forceUpdate();
|
||||
@@ -170,7 +174,7 @@ const MosaicEditorsDisplay = React.forwardRef<
|
||||
return {
|
||||
getName: () => 'mosaic',
|
||||
forceUpdateInstancesList,
|
||||
forceUpdateInstancesPropertiesEditor,
|
||||
forceUpdatePropertiesEditor,
|
||||
forceUpdateObjectsList,
|
||||
forceUpdateObjectGroupsList,
|
||||
scrollObjectGroupsListToObjectGroup,
|
||||
@@ -220,26 +224,22 @@ const MosaicEditorsDisplay = React.forwardRef<
|
||||
(instances: Array<gdInitialInstance>, multiSelect: boolean) => {
|
||||
onSelectInstances(instances, multiSelect);
|
||||
forceUpdateInstancesList();
|
||||
forceUpdateInstancesPropertiesEditor();
|
||||
forceUpdatePropertiesEditor();
|
||||
},
|
||||
[
|
||||
forceUpdateInstancesList,
|
||||
forceUpdateInstancesPropertiesEditor,
|
||||
onSelectInstances,
|
||||
]
|
||||
[forceUpdateInstancesList, forceUpdatePropertiesEditor, onSelectInstances]
|
||||
);
|
||||
|
||||
const selectedObjectNames = props.selectedObjectFolderOrObjectsWithContext
|
||||
const selectedObjects = props.selectedObjectFolderOrObjectsWithContext
|
||||
.map(objectFolderOrObjectWithContext => {
|
||||
const { objectFolderOrObject } = objectFolderOrObjectWithContext;
|
||||
|
||||
if (!objectFolderOrObject) return null; // Protect ourselves from an unexpected null value.
|
||||
|
||||
if (objectFolderOrObject.isFolder()) return null;
|
||||
return objectFolderOrObject.getObject().getName();
|
||||
return objectFolderOrObject.getObject();
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
const selectedObjectNames = selectedObjects.map(object => object.getName());
|
||||
|
||||
const editors = {
|
||||
properties: {
|
||||
type: 'secondary',
|
||||
@@ -247,24 +247,30 @@ const MosaicEditorsDisplay = React.forwardRef<
|
||||
renderEditor: () => (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<CompactInstancePropertiesEditorContainer
|
||||
<InstanceOrObjectPropertiesEditorContainer
|
||||
i18n={i18n}
|
||||
project={project}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
layout={layout}
|
||||
eventsFunctionsExtension={eventsFunctionsExtension}
|
||||
onUpdateBehaviorsSharedData={updateBehaviorsSharedData}
|
||||
objectsContainer={objectsContainer}
|
||||
globalObjectsContainer={globalObjectsContainer}
|
||||
layersContainer={layersContainer}
|
||||
projectScopedContainersAccessor={projectScopedContainersAccessor}
|
||||
instances={selectedInstances}
|
||||
objects={selectedObjects}
|
||||
editInstanceVariables={props.editInstanceVariables}
|
||||
onEditObjectByName={props.editObjectByName}
|
||||
editObjectInPropertiesPanel={props.editObjectInPropertiesPanel}
|
||||
onEditObject={props.onEditObject}
|
||||
onInstancesModified={forceUpdateInstancesList}
|
||||
onGetInstanceSize={getInstanceSize}
|
||||
ref={instancesPropertiesEditorRef}
|
||||
ref={instanceOrObjectPropertiesEditorRef}
|
||||
unsavedChanges={props.unsavedChanges}
|
||||
historyHandler={props.historyHandler}
|
||||
tileMapTileSelection={props.tileMapTileSelection}
|
||||
onSelectTileMapTile={props.onSelectTileMapTile}
|
||||
lastSelectionType={props.lastSelectionType}
|
||||
/>
|
||||
)}
|
||||
</I18n>
|
||||
@@ -285,7 +291,7 @@ const MosaicEditorsDisplay = React.forwardRef<
|
||||
onEditLayer={props.editLayer}
|
||||
onRemoveLayer={props.onRemoveLayer}
|
||||
onLayerRenamed={props.onLayerRenamed}
|
||||
onCreateLayer={forceUpdateInstancesPropertiesEditor}
|
||||
onCreateLayer={forceUpdatePropertiesEditor}
|
||||
layersContainer={layersContainer}
|
||||
unsavedChanges={props.unsavedChanges}
|
||||
ref={layersListRef}
|
||||
|
@@ -31,3 +31,43 @@ export const cleanNonExistingObjectFolderOrObjectWithContexts = (
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export const getObjectFolderOrObjectWithContextFromObjectName = (
|
||||
globalObjectsContainer: ?gdObjectsContainer,
|
||||
objectsContainer: gdObjectsContainer,
|
||||
objectName: string
|
||||
): ObjectFolderOrObjectWithContext | null => {
|
||||
let foundObjectFolderObjectWithContext = null;
|
||||
if (globalObjectsContainer)
|
||||
mapVector(
|
||||
globalObjectsContainer.getAllObjectFolderOrObjects(),
|
||||
objectFolderOrObject => {
|
||||
if (
|
||||
!objectFolderOrObject.isFolder() &&
|
||||
objectFolderOrObject.getObject().getName() === objectName
|
||||
) {
|
||||
foundObjectFolderObjectWithContext = {
|
||||
objectFolderOrObject,
|
||||
global: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
if (objectsContainer)
|
||||
mapVector(
|
||||
objectsContainer.getAllObjectFolderOrObjects(),
|
||||
objectFolderOrObject => {
|
||||
if (
|
||||
!objectFolderOrObject.isFolder() &&
|
||||
objectFolderOrObject.getObject().getName() === objectName
|
||||
) {
|
||||
foundObjectFolderObjectWithContext = {
|
||||
objectFolderOrObject,
|
||||
global: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return foundObjectFolderObjectWithContext;
|
||||
};
|
||||
|
@@ -28,9 +28,10 @@ import {
|
||||
type SceneEditorsDisplayProps,
|
||||
} from '../EditorsDisplay.flow';
|
||||
import ErrorBoundary from '../../UI/ErrorBoundary';
|
||||
import CompactInstancePropertiesEditorContainer, {
|
||||
type CompactInstancePropertiesEditorInterface,
|
||||
} from '../../InstancesEditor/CompactInstancePropertiesEditor';
|
||||
import {
|
||||
InstanceOrObjectPropertiesEditorContainer,
|
||||
type InstanceOrObjectPropertiesEditorInterface,
|
||||
} from '../InstanceOrObjectPropertiesEditorContainer';
|
||||
|
||||
export const swipeableDrawerContainerId = 'swipeable-drawer-container';
|
||||
|
||||
@@ -57,9 +58,11 @@ const SwipeableDrawerEditorsDisplay = React.forwardRef<
|
||||
>((props, ref) => {
|
||||
const {
|
||||
project,
|
||||
resourceManagementProps,
|
||||
layout,
|
||||
eventsFunctionsExtension,
|
||||
eventsBasedObject,
|
||||
updateBehaviorsSharedData,
|
||||
layersContainer,
|
||||
globalObjectsContainer,
|
||||
objectsContainer,
|
||||
@@ -72,7 +75,7 @@ const SwipeableDrawerEditorsDisplay = React.forwardRef<
|
||||
const { values } = React.useContext(PreferencesContext);
|
||||
const screenType = useScreenType();
|
||||
|
||||
const instancesPropertiesEditorRef = React.useRef<?CompactInstancePropertiesEditorInterface>(
|
||||
const instanceOrObjectPropertiesEditorRef = React.useRef<?InstanceOrObjectPropertiesEditorInterface>(
|
||||
null
|
||||
);
|
||||
const layersListRef = React.useRef<?LayersListInterface>(null);
|
||||
@@ -105,9 +108,9 @@ const SwipeableDrawerEditorsDisplay = React.forwardRef<
|
||||
[selectedEditorId, drawerOpeningState]
|
||||
);
|
||||
|
||||
const forceUpdateInstancesPropertiesEditor = React.useCallback(() => {
|
||||
if (instancesPropertiesEditorRef.current)
|
||||
instancesPropertiesEditorRef.current.forceUpdate();
|
||||
const forceUpdatePropertiesEditor = React.useCallback(() => {
|
||||
if (instanceOrObjectPropertiesEditorRef.current)
|
||||
instanceOrObjectPropertiesEditorRef.current.forceUpdate();
|
||||
}, []);
|
||||
const forceUpdateInstancesList = React.useCallback(() => {
|
||||
if (instancesListRef.current) instancesListRef.current.forceUpdate();
|
||||
@@ -159,7 +162,7 @@ const SwipeableDrawerEditorsDisplay = React.forwardRef<
|
||||
return {
|
||||
getName: () => 'swipeableDrawer',
|
||||
forceUpdateInstancesList,
|
||||
forceUpdateInstancesPropertiesEditor,
|
||||
forceUpdatePropertiesEditor,
|
||||
forceUpdateObjectsList,
|
||||
forceUpdateObjectGroupsList,
|
||||
scrollObjectGroupsListToObjectGroup,
|
||||
@@ -209,26 +212,22 @@ const SwipeableDrawerEditorsDisplay = React.forwardRef<
|
||||
(instances: Array<gdInitialInstance>, multiSelect: boolean) => {
|
||||
onSelectInstances(instances, multiSelect, 'upperCenter');
|
||||
forceUpdateInstancesList();
|
||||
forceUpdateInstancesPropertiesEditor();
|
||||
forceUpdatePropertiesEditor();
|
||||
},
|
||||
[
|
||||
forceUpdateInstancesList,
|
||||
forceUpdateInstancesPropertiesEditor,
|
||||
onSelectInstances,
|
||||
]
|
||||
[forceUpdateInstancesList, forceUpdatePropertiesEditor, onSelectInstances]
|
||||
);
|
||||
|
||||
const selectedObjectNames = props.selectedObjectFolderOrObjectsWithContext
|
||||
const selectedObjects = props.selectedObjectFolderOrObjectsWithContext
|
||||
.map(objectFolderOrObjectWithContext => {
|
||||
const { objectFolderOrObject } = objectFolderOrObjectWithContext;
|
||||
|
||||
if (!objectFolderOrObject) return null; // Protect ourselves from an unexpected null value.
|
||||
|
||||
if (objectFolderOrObject.isFolder()) return null;
|
||||
return objectFolderOrObject.getObject().getName();
|
||||
return objectFolderOrObject.getObject();
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
const selectedObjectNames = selectedObjects.map(object => object.getName());
|
||||
|
||||
return (
|
||||
<FullSizeMeasurer>
|
||||
{({ width, height }) => (
|
||||
@@ -269,6 +268,7 @@ const SwipeableDrawerEditorsDisplay = React.forwardRef<
|
||||
}
|
||||
pauseRendering={!props.isActive}
|
||||
showObjectInstancesIn3D={values.use3DEditor}
|
||||
showBasicProfilingCounters={values.showBasicProfilingCounters}
|
||||
tileMapTileSelection={props.tileMapTileSelection}
|
||||
onSelectTileMapTile={props.onSelectTileMapTile}
|
||||
/>
|
||||
@@ -341,26 +341,34 @@ const SwipeableDrawerEditorsDisplay = React.forwardRef<
|
||||
{selectedEditorId === 'properties' && (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<CompactInstancePropertiesEditorContainer
|
||||
<InstanceOrObjectPropertiesEditorContainer
|
||||
i18n={i18n}
|
||||
project={project}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
layout={layout}
|
||||
eventsFunctionsExtension={eventsFunctionsExtension}
|
||||
onUpdateBehaviorsSharedData={updateBehaviorsSharedData}
|
||||
objectsContainer={objectsContainer}
|
||||
globalObjectsContainer={globalObjectsContainer}
|
||||
layersContainer={layersContainer}
|
||||
projectScopedContainersAccessor={
|
||||
projectScopedContainersAccessor
|
||||
}
|
||||
objects={selectedObjects}
|
||||
instances={selectedInstances}
|
||||
editInstanceVariables={props.editInstanceVariables}
|
||||
onEditObjectByName={props.editObjectByName}
|
||||
editObjectInPropertiesPanel={
|
||||
props.editObjectInPropertiesPanel
|
||||
}
|
||||
onEditObject={props.onEditObject}
|
||||
onInstancesModified={forceUpdateInstancesList}
|
||||
onGetInstanceSize={getInstanceSize}
|
||||
ref={instancesPropertiesEditorRef}
|
||||
ref={instanceOrObjectPropertiesEditorRef}
|
||||
unsavedChanges={props.unsavedChanges}
|
||||
historyHandler={props.historyHandler}
|
||||
tileMapTileSelection={props.tileMapTileSelection}
|
||||
onSelectTileMapTile={props.onSelectTileMapTile}
|
||||
lastSelectionType={props.lastSelectionType}
|
||||
/>
|
||||
)}
|
||||
</I18n>
|
||||
@@ -420,7 +428,7 @@ const SwipeableDrawerEditorsDisplay = React.forwardRef<
|
||||
onEditLayer={props.editLayer}
|
||||
onRemoveLayer={props.onRemoveLayer}
|
||||
onLayerRenamed={props.onLayerRenamed}
|
||||
onCreateLayer={forceUpdateInstancesPropertiesEditor}
|
||||
onCreateLayer={forceUpdatePropertiesEditor}
|
||||
layersContainer={layersContainer}
|
||||
unsavedChanges={props.unsavedChanges}
|
||||
ref={layersListRef}
|
||||
|
@@ -67,7 +67,10 @@ import {
|
||||
type ObjectFolderOrObjectWithContext,
|
||||
} from '../ObjectsList/EnumerateObjectFolderOrObject';
|
||||
import uniq from 'lodash/uniq';
|
||||
import { cleanNonExistingObjectFolderOrObjectWithContexts } from './ObjectFolderOrObjectsSelection';
|
||||
import {
|
||||
cleanNonExistingObjectFolderOrObjectWithContexts,
|
||||
getObjectFolderOrObjectWithContextFromObjectName,
|
||||
} from './ObjectFolderOrObjectsSelection';
|
||||
import {
|
||||
registerOnResourceExternallyChangedCallback,
|
||||
unregisterOnResourceExternallyChangedCallback,
|
||||
@@ -111,7 +114,6 @@ type Props = {|
|
||||
|
||||
getInitialInstancesEditorSettings: () => InstancesEditorSettings,
|
||||
|
||||
onEditObject?: ?(object: gdObject) => void,
|
||||
onOpenMoreSettings?: ?() => void,
|
||||
onOpenEvents: (sceneName: string) => void,
|
||||
onObjectEdited: (objectWithContext: ObjectWithContext) => void,
|
||||
@@ -161,6 +163,8 @@ type State = {|
|
||||
selectedLayer: string,
|
||||
|
||||
tileMapTileSelection: ?TileMapTileSelection,
|
||||
|
||||
lastSelectionType: 'instance' | 'object',
|
||||
|};
|
||||
|
||||
type CopyCutPasteOptions = {|
|
||||
@@ -215,6 +219,8 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
selectedObjectFolderOrObjectsWithContext: [],
|
||||
selectedLayer: BASE_LAYER_NAME,
|
||||
invisibleLayerOnWhichInstancesHaveJustBeenAdded: null,
|
||||
|
||||
lastSelectionType: 'instance',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -499,6 +505,22 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
this.editObject(globalObjectsContainer.getObject(objectName), initialTab);
|
||||
};
|
||||
|
||||
editObjectInPropertiesPanel = (objectName: string) => {
|
||||
const objectFolderOrObjectWithContext = getObjectFolderOrObjectWithContextFromObjectName(
|
||||
this.props.globalObjectsContainer,
|
||||
this.props.objectsContainer,
|
||||
objectName
|
||||
);
|
||||
if (!objectFolderOrObjectWithContext) return;
|
||||
|
||||
this.setState({
|
||||
selectedObjectFolderOrObjectsWithContext: [
|
||||
objectFolderOrObjectWithContext,
|
||||
],
|
||||
lastSelectionType: 'object',
|
||||
});
|
||||
};
|
||||
|
||||
_editObjectGroup = (group: ?gdObjectGroup) => {
|
||||
this.setState({ editedGroup: group, isCreatingNewGroup: false });
|
||||
};
|
||||
@@ -583,6 +605,7 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
|
||||
this.setState(
|
||||
{
|
||||
lastSelectionType: 'object',
|
||||
selectedObjectFolderOrObjectsWithContext,
|
||||
},
|
||||
() => {
|
||||
@@ -686,7 +709,10 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
_onInstancesSelected = (instances: Array<gdInitialInstance>) => {
|
||||
if (instances.length === 0) {
|
||||
this.setState(
|
||||
{ selectedObjectFolderOrObjectsWithContext: [] },
|
||||
{
|
||||
lastSelectionType: 'instance',
|
||||
selectedObjectFolderOrObjectsWithContext: [],
|
||||
},
|
||||
this.updateToolbar
|
||||
);
|
||||
return;
|
||||
@@ -702,6 +728,7 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
) {
|
||||
this.setState(
|
||||
{
|
||||
lastSelectionType: 'instance',
|
||||
selectedObjectFolderOrObjectsWithContext: [
|
||||
{
|
||||
objectFolderOrObject: globalObjectsContainer
|
||||
@@ -716,6 +743,7 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
} else if (objectsContainer.hasObjectNamed(objectName)) {
|
||||
this.setState(
|
||||
{
|
||||
lastSelectionType: 'instance',
|
||||
selectedObjectFolderOrObjectsWithContext: [
|
||||
{
|
||||
objectFolderOrObject: objectsContainer
|
||||
@@ -803,6 +831,7 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
|
||||
viewControls.centerViewOnLastInstance(instances, offset);
|
||||
}
|
||||
this.setState({ lastSelectionType: 'instance' });
|
||||
this.updateToolbar();
|
||||
};
|
||||
|
||||
@@ -1701,8 +1730,7 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
forceUpdatePropertiesEditor = () => {
|
||||
if (this.editorDisplay)
|
||||
this.editorDisplay.forceUpdateInstancesPropertiesEditor();
|
||||
if (this.editorDisplay) this.editorDisplay.forceUpdatePropertiesEditor();
|
||||
};
|
||||
|
||||
forceUpdateCustomObjectRenderedInstances = () => {
|
||||
@@ -1843,7 +1871,7 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
layersContainer={this.props.layersContainer}
|
||||
globalObjectsContainer={this.props.globalObjectsContainer}
|
||||
objectsContainer={this.props.objectsContainer}
|
||||
onEditObject={this.props.onEditObject || this.editObject}
|
||||
onEditObject={this.editObject}
|
||||
onEditObjectVariables={object => {
|
||||
this.editObject(object, 'variables');
|
||||
}}
|
||||
@@ -1873,6 +1901,7 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
editLayerEffects={this.editLayerEffects}
|
||||
editInstanceVariables={this.editInstanceVariables}
|
||||
editObjectByName={this.editObjectByName}
|
||||
editObjectInPropertiesPanel={this.editObjectInPropertiesPanel}
|
||||
selectedObjectFolderOrObjectsWithContext={
|
||||
selectedObjectFolderOrObjectsWithContext
|
||||
}
|
||||
@@ -1894,7 +1923,7 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
onRenameObjectGroup={this._onRenameObjectGroup}
|
||||
canObjectOrGroupBeGlobal={this.canObjectOrGroupBeGlobal}
|
||||
updateBehaviorsSharedData={this.updateBehaviorsSharedData}
|
||||
onEditObject={this.props.onEditObject || this.editObject}
|
||||
onEditObject={this.editObject}
|
||||
onRenameObjectFolderOrObjectWithContextFinish={
|
||||
this._onRenameObjectFolderOrObjectWithContextFinish
|
||||
}
|
||||
@@ -1954,6 +1983,7 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
}
|
||||
isActive={isActive}
|
||||
onOpenedEditorsChanged={this.updateToolbar}
|
||||
lastSelectionType={this.state.lastSelectionType}
|
||||
/>
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
|
@@ -0,0 +1,92 @@
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.compactSearchBar {
|
||||
border-radius: 4px;
|
||||
color: var(--theme-text-default-color);
|
||||
background-color: var(--theme-text-field-default-background-color);
|
||||
transition: box-shadow 0.1s;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0px;
|
||||
}
|
||||
|
||||
.container.disabled .compactSearchBar {
|
||||
color: var(--theme-text-field-disabled-color);
|
||||
}
|
||||
|
||||
.container.errored:not(.disabled) .compactSearchBar {
|
||||
border: none;
|
||||
outline: 1px solid var(--theme-text-field-default-error);
|
||||
}
|
||||
.container.errored:not(.disabled):hover .compactSearchBar {
|
||||
outline: 1px solid var(--theme-text-field-active-error);
|
||||
}
|
||||
.container.errored:not(.disabled):focus-within .compactSearchBar {
|
||||
outline: 1px solid var(--theme-text-field-active-error);
|
||||
}
|
||||
|
||||
.compactSearchBar::before {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
content: '';
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.container:not(.disabled):not(.errored):hover .compactSearchBar::before {
|
||||
border-bottom: 1px solid var(--theme-text-field-hover-border-color);
|
||||
}
|
||||
.container:not(.disabled):not(.errored):focus-within .compactSearchBar::before {
|
||||
border-bottom: 1px solid var(--theme-text-field-active-border-color);
|
||||
}
|
||||
|
||||
.compactSearchBar input {
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 2px 8px;
|
||||
padding-left: 22px;
|
||||
background-image: none;
|
||||
background-color: transparent;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-family: var(--gdevelop-modern-font-family);
|
||||
color: inherit;
|
||||
flex: 1;
|
||||
caret-color: var(--theme-text-field-active-caret-color);
|
||||
min-width: 0px;
|
||||
border-radius: inherit; /* Needed for InAppTutorialElementHighlighter to adapt its border-radius to the input container */
|
||||
}
|
||||
|
||||
.compactSearchBar input::placeholder {
|
||||
color: var(--theme-text-field-placeholder-color);
|
||||
}
|
||||
|
||||
.searchIconContainer {
|
||||
outline: 0;
|
||||
border: 0;
|
||||
position: absolute;
|
||||
left: 3px;
|
||||
top: 3px;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
display: flex;
|
||||
padding: 1px;
|
||||
border-radius: 4px;
|
||||
background-color: unset;
|
||||
}
|
||||
/* svg tag is needed to be first priority compared to Material UI Custom SVG icon classes*/
|
||||
svg.searchIcon {
|
||||
font-size: 15px;
|
||||
color: var(--theme-text-field-placeholder-color);
|
||||
transition: color 0.1s linear;
|
||||
}
|
64
newIDE/app/src/UI/CompactSearchBar/index.js
Normal file
64
newIDE/app/src/UI/CompactSearchBar/index.js
Normal file
@@ -0,0 +1,64 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { I18n } from '@lingui/react';
|
||||
import classNames from 'classnames';
|
||||
import classes from './CompactSearchBar.module.css';
|
||||
import { makeTimestampedId } from '../../Utils/TimestampedId';
|
||||
import Search from '../CustomSvgIcons/Search';
|
||||
import { type MessageDescriptor } from '../../Utils/i18n/MessageDescriptor.flow';
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
export type CompactSearchBarInterface = {|
|
||||
blur: () => void,
|
||||
|};
|
||||
|
||||
export type CompactSearchBarProps = {|
|
||||
value: string,
|
||||
onChange: (newValue: string) => void,
|
||||
id?: string,
|
||||
disabled?: boolean,
|
||||
errored?: boolean,
|
||||
placeholder?: MessageDescriptor,
|
||||
|};
|
||||
|
||||
const CompactSearchBar = React.forwardRef<
|
||||
CompactSearchBarProps,
|
||||
CompactSearchBarInterface
|
||||
>(({ value, onChange, id, disabled, errored, placeholder }, ref) => {
|
||||
const idToUse = React.useRef<string>(id || makeTimestampedId());
|
||||
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<div
|
||||
className={classNames({
|
||||
[classes.container]: true,
|
||||
[classes.disabled]: disabled,
|
||||
[classes.errored]: errored,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames({
|
||||
[classes.compactSearchBar]: true,
|
||||
})}
|
||||
>
|
||||
<div className={classes.searchIconContainer}>
|
||||
<Search className={classes.searchIcon} />
|
||||
</div>
|
||||
<input
|
||||
id={idToUse.current}
|
||||
// Type cannot be set to number in order to benefit from the mathematical parsing.
|
||||
type={'text'}
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
onChange={e => onChange(e.currentTarget.value)}
|
||||
placeholder={i18n._(placeholder || t`Search`)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</I18n>
|
||||
);
|
||||
});
|
||||
|
||||
export default CompactSearchBar;
|
@@ -61,6 +61,7 @@
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 2px 8px;
|
||||
padding-right: 22px;
|
||||
background-image: none;
|
||||
background-color: transparent;
|
||||
-webkit-box-shadow: none;
|
||||
|
@@ -1,14 +1,7 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
min-width: 0px;
|
||||
}
|
||||
|
||||
.container .error {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-family: var(--gdevelop-modern-font-family);
|
||||
color: var(--theme-error-color);
|
||||
padding: 2px 0px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
@@ -10,6 +10,9 @@ import {
|
||||
shouldValidate,
|
||||
} from '../KeyboardShortcuts/InteractionKeys';
|
||||
import { calculate } from '../../Utils/MathExpressionParser';
|
||||
import IconButton from '../IconButton';
|
||||
import Infinite from '../CustomSvgIcons/Infinite';
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
const getValueAsFloatIfValid = (valueAsString: string): number | null => {
|
||||
const valueAsFloat = parseFloat(valueAsString);
|
||||
@@ -18,6 +21,12 @@ const getValueAsFloatIfValid = (valueAsString: string): number | null => {
|
||||
return isValueAsFloatValid ? valueAsFloat : null;
|
||||
};
|
||||
|
||||
const styles = {
|
||||
icon: {
|
||||
fontSize: 18,
|
||||
},
|
||||
};
|
||||
|
||||
const updateAndReturnValueAsFloatIfValid = (
|
||||
valueAsString: string,
|
||||
valueToAddOrSubtract: number
|
||||
@@ -41,6 +50,7 @@ type Props = {|
|
||||
onChange: number => void,
|
||||
commitOnBlur?: boolean,
|
||||
disabled?: boolean,
|
||||
canBeUnlimitedUsingMinus1?: boolean,
|
||||
errored?: boolean,
|
||||
placeholder?: string,
|
||||
renderLeftIcon?: (className: string) => React.Node,
|
||||
@@ -50,14 +60,13 @@ type Props = {|
|
||||
onClickEndAdornment?: () => void,
|
||||
getValueFromDisplayedValue?: string => string,
|
||||
getDisplayedValueFromValue?: string => string,
|
||||
|
||||
errorText?: React.Node,
|
||||
|};
|
||||
|
||||
const CompactSemiControlledNumberField = ({
|
||||
value,
|
||||
onChange,
|
||||
errorText,
|
||||
placeholder,
|
||||
canBeUnlimitedUsingMinus1,
|
||||
commitOnBlur,
|
||||
getValueFromDisplayedValue,
|
||||
getDisplayedValueFromValue,
|
||||
@@ -145,12 +154,16 @@ const CompactSemiControlledNumberField = ({
|
||||
? getDisplayedValueFromValue(value.toString())
|
||||
: value.toString();
|
||||
|
||||
const isUnlimited = canBeUnlimitedUsingMinus1 && value === -1;
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<CompactTextField
|
||||
type="text"
|
||||
ref={textFieldRef}
|
||||
value={focused ? temporaryValue : stringValue}
|
||||
disabled={isUnlimited}
|
||||
placeholder={isUnlimited ? 'Unlimited' : placeholder}
|
||||
value={isUnlimited ? '' : focused ? temporaryValue : stringValue}
|
||||
onChange={onChangeValue}
|
||||
onFocus={event => {
|
||||
setFocused(true);
|
||||
@@ -225,7 +238,23 @@ const CompactSemiControlledNumberField = ({
|
||||
}}
|
||||
{...otherProps}
|
||||
/>
|
||||
{errorText && <div className={classes.error}>{errorText}</div>}
|
||||
{canBeUnlimitedUsingMinus1 && (
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
if (isUnlimited) {
|
||||
onChange(0);
|
||||
if (textFieldRef.current) textFieldRef.current.focus();
|
||||
} else {
|
||||
onChange(-1);
|
||||
}
|
||||
}}
|
||||
selected={isUnlimited}
|
||||
tooltip={isUnlimited ? t`Remove unlimited` : t`Set to unlimited`}
|
||||
>
|
||||
<Infinite style={styles.icon} />
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@@ -1,6 +1,8 @@
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.compactTextField {
|
||||
|
@@ -37,6 +37,7 @@ type OtherProps = {|
|
||||
|
||||
export type CompactTextFieldInterface = {|
|
||||
blur: () => void,
|
||||
focus: () => void,
|
||||
|};
|
||||
|
||||
export type CompactTextFieldProps = {|
|
||||
@@ -106,6 +107,9 @@ const CompactTextField = React.forwardRef<
|
||||
blur: () => {
|
||||
if (inputRef.current) inputRef.current.blur();
|
||||
},
|
||||
focus: () => {
|
||||
if (inputRef.current) inputRef.current.focus();
|
||||
},
|
||||
}));
|
||||
|
||||
const onWheelIfFocused = React.useCallback(
|
||||
|
@@ -1,8 +1,8 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.fullWidth {
|
||||
|
@@ -1,9 +1,24 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import Text from '../../UI/Text';
|
||||
import { MarkdownText } from '../../UI/MarkdownText';
|
||||
import { tooltipEnterDelay } from '../../UI/Tooltip';
|
||||
import classes from './CompactToggleField.module.css';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const styles = {
|
||||
label: {
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
lineHeight: '17px',
|
||||
maxHeight: 34, // 2 * lineHeight to limit to 2 lines.
|
||||
opacity: 0.7,
|
||||
},
|
||||
};
|
||||
type Props = {|
|
||||
label: string,
|
||||
markdownDescription?: ?string,
|
||||
id?: string,
|
||||
checked: boolean,
|
||||
onCheck: (newValue: boolean) => void,
|
||||
@@ -12,6 +27,9 @@ type Props = {|
|
||||
|};
|
||||
|
||||
export const CompactToggleField = (props: Props) => {
|
||||
const title = !props.markdownDescription
|
||||
? props.label
|
||||
: [props.label, ' - ', <MarkdownText source={props.markdownDescription} />];
|
||||
return (
|
||||
<label
|
||||
className={classNames({
|
||||
@@ -20,16 +38,10 @@ export const CompactToggleField = (props: Props) => {
|
||||
})}
|
||||
id={props.id}
|
||||
>
|
||||
<div
|
||||
className={classNames({
|
||||
[classes.toggleSwitch]: true,
|
||||
})}
|
||||
>
|
||||
<div className={classes.toggleSwitch}>
|
||||
<input
|
||||
type="checkbox"
|
||||
className={classNames({
|
||||
[classes.checkbox]: true,
|
||||
})}
|
||||
className={classes.checkbox}
|
||||
onChange={() => props.onCheck(!props.checked)}
|
||||
disabled={props.disabled}
|
||||
/>
|
||||
@@ -57,6 +69,27 @@ export const CompactToggleField = (props: Props) => {
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<Tooltip
|
||||
title={title}
|
||||
enterDelay={tooltipEnterDelay}
|
||||
placement="bottom"
|
||||
PopperProps={{
|
||||
modifiers: {
|
||||
offset: {
|
||||
enabled: true,
|
||||
/**
|
||||
* It does not seem possible to get the tooltip closer to the anchor
|
||||
* when positioned on top. So it is positioned on bottom with a negative offset.
|
||||
*/
|
||||
offset: '0,-20',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Text noMargin style={styles.label}>
|
||||
{props.label}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
11
newIDE/app/src/UI/CustomSvgIcons/Infinite.js
Normal file
11
newIDE/app/src/UI/CustomSvgIcons/Infinite.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import SvgIcon from '@material-ui/core/SvgIcon';
|
||||
|
||||
export default React.memo(props => (
|
||||
<SvgIcon {...props} width="17" height="16" viewBox="0 0 17 16" fill="none">
|
||||
<path
|
||||
d="M4.79997 4.8335C3.05107 4.8335 1.6333 6.25126 1.6333 8.00016C1.6333 9.74905 3.05107 11.1668 4.79997 11.1668C5.83632 11.1668 6.75639 10.6684 7.33329 9.90047C7.33892 9.89296 7.34435 9.8853 7.34955 9.87749L7.52731 9.61083C7.68048 9.38106 7.61838 9.07062 7.3886 8.91746C7.15883 8.76429 6.8484 8.82639 6.69524 9.05617L6.52569 9.31051C6.12908 9.83181 5.50371 10.1668 4.79997 10.1668C3.60335 10.1668 2.6333 9.19677 2.6333 8.00016C2.6333 6.80355 3.60335 5.8335 4.79997 5.8335C5.50372 5.8335 6.12903 6.1686 6.52564 6.69003L8.6504 9.87749C8.65561 9.8853 8.66103 9.89296 8.66666 9.90046C9.24354 10.6684 10.1637 11.1668 11.2 11.1668C12.9489 11.1668 14.3667 9.74905 14.3667 8.00016C14.3667 6.25126 12.9489 4.8335 11.2 4.8335C10.1637 4.8335 9.24354 5.33185 8.66666 6.09988C8.50081 6.32067 8.54536 6.6341 8.76615 6.79995C8.98695 6.96579 9.30038 6.92124 9.46623 6.70045C9.86235 6.17307 10.4915 5.8335 11.2 5.8335C12.3966 5.8335 13.3667 6.80355 13.3667 8.00016C13.3667 9.19677 12.3966 10.1668 11.2 10.1668C10.4962 10.1668 9.87085 9.83181 9.47427 9.31052L7.34936 6.12283C7.34288 6.1131 7.33611 6.10368 7.32908 6.09455C6.75198 5.32965 5.83393 4.8335 4.79997 4.8335Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</SvgIcon>
|
||||
));
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user