Compare commits

...

10 Commits

Author SHA1 Message Date
Florian Rival
d5f73bea0d Address some review comment 2022-10-04 22:46:55 +02:00
Florian Rival
32c69ab78a Fix typing 2022-10-04 22:46:55 +02:00
Florian Rival
3dd5e2c242 Refactor to have resourceManagementProps everywhere 2022-10-04 22:46:55 +02:00
Florian Rival
fbfcd3da5d [WIP] Allow to choose resources from the asset store in the desktop app 2022-10-04 22:42:15 +02:00
Florian Rival
0db30f02c9 Add support for opening onboarding directly on the web-app
https://editor.gdevelop.io/?initial-dialog=onboarding

Don't show in changelog
2022-10-04 16:03:12 +02:00
D8H
a852e91690 Add missing expression group icons (#4353) 2022-10-04 14:56:59 +02:00
AlexandreS
79d6281061 Add expression and condition to get highest z order of a layer (#4346) 2022-10-04 14:31:49 +02:00
AlexandreS
eba6b2540c Add price to assets home private asset packs thumbnails and display them in their dialog (#4350)
Don't show in changelog
2022-10-04 12:13:25 +02:00
D8H
0706a54305 Reorganize extensions categories (#4345) 2022-10-04 10:37:27 +02:00
D8H
d929fd6e48 Add a test on sprite hit-boxes after a camera displacement (#4349)
* Don't show in changelog
2022-10-03 14:17:01 +02:00
121 changed files with 1736 additions and 1453 deletions

View File

@@ -22,6 +22,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
"object or a position.",
"Florian Rival",
"Open source (MIT License)")
.SetCategory("Camera")
.SetExtensionHelpPath("/interface/scene-editor/layers-and-cameras");
extension.AddInstructionOrExpressionGroupMetadata(_("Layers and cameras"))
.SetIcon("res/conditions/camera24.png");
@@ -589,6 +590,19 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
.SetDefaultValue("\"\"")
.AddParameter("expression", _("New default Z order"));
extension
.AddExpressionAndCondition("number",
"LayerHighestZOrder",
_("Layer highest Z order"),
_("the highest Z order of objects in a layer"),
_("the highest Z order of objects in the layer _PARAM1_"),
"",
"res/conditions/layer.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("layer", _("Layer (base layer if empty)"), "", true)
.SetDefaultValue("\"\"")
.UseStandardParameters("number");
extension
.AddAction(
"SetLayerAmbientLightColor",

View File

@@ -21,7 +21,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsFileExtension(
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/all-features/storage")
.SetCategory("Device");
.SetCategory("Advanced");
extension.AddInstructionOrExpressionGroupMetadata(_("Storage"))
.SetIcon("res/conditions/fichier24.png");

View File

@@ -22,6 +22,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects/sprite");
extension.AddInstructionOrExpressionGroupMetadata(_("Sprite"))
.SetIcon("CppPlatform/Extensions/spriteicon.png");
gd::ObjectMetadata& obj =
extension

View File

@@ -20,6 +20,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsWindowExtension(
"these features can be applied.",
"Florian Rival",
"Open source (MIT License)")
.SetCategory("User interface")
.SetExtensionHelpPath("/all-features/window");
extension
.AddInstructionOrExpressionGroupMetadata(

View File

@@ -28,7 +28,7 @@ module.exports = {
'Arthur Pacaud (arthuro555)',
'MIT'
)
.setCategory('Device');
.setCategory('User interface');
extension
.addInstructionOrExpressionGroupMetadata(_('Advanced window management'))
.setIcon('res/actions/window24.png');

View File

@@ -17,6 +17,7 @@ void DeclareAnchorBehaviorExtension(gd::PlatformExtension& extension) {
_("Anchor objects to the window's bounds."),
"Victor Levasseur",
"Open source (MIT License)")
.SetCategory("User interface")
.SetExtensionHelpPath("/behaviors/anchor");
gd::BehaviorMetadata& aut = extension.AddBehavior(

View File

@@ -33,7 +33,10 @@ module.exports = {
'Todor Imreorov',
'Open source (MIT License)'
)
.setExtensionHelpPath('/objects/bbtext');
.setExtensionHelpPath('/objects/bbtext')
.setCategory('User interface');
extension.addInstructionOrExpressionGroupMetadata(_("BBCode Text Object"))
.setIcon("JsPlatform/Extensions/bbcode32.png");
var objectBBText = new gd.ObjectJsImplementation();
// $FlowExpectedError
@@ -168,7 +171,7 @@ module.exports = {
.addIncludeFile(
'Extensions/BBText/pixi-multistyle-text/dist/pixi-multistyle-text.umd.js'
)
.setCategoryFullName(_('Texts'));
.setCategoryFullName(_('User interface'));
/**
* Utility function to add both a setter and a getter to a property from a list.

View File

@@ -35,7 +35,10 @@ module.exports = {
'Aurélien Vivet',
'Open source (MIT License)'
)
.setExtensionHelpPath('/objects/bitmap_text');
.setExtensionHelpPath('/objects/bitmap_text')
.setCategory('User interface');
extension.addInstructionOrExpressionGroupMetadata(_("Bitmap Text"))
.setIcon("JsPlatform/Extensions/bitmapfont32.png");
const bitmapTextObject = new gd.ObjectJsImplementation();
// $FlowExpectedError
@@ -171,7 +174,7 @@ module.exports = {
.addIncludeFile(
'Extensions/BitmapText/bitmaptextruntimeobject-pixi-renderer.js'
)
.setCategoryFullName(_('Texts'));
.setCategoryFullName(_('User interface'));
object
.addExpressionAndConditionAndAction(

View File

@@ -20,6 +20,7 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
"or other short-lived objects."),
"Florian Rival",
"Open source (MIT License)")
.SetCategory("Game mechanic")
.SetExtensionHelpPath("/behaviors/destroyoutside");
gd::BehaviorMetadata& aut =

View File

@@ -31,7 +31,7 @@ module.exports = {
"Matthias Meike",
"Open source (MIT License)"
).setExtensionHelpPath("/all-features/device-sensors")
.setCategory('Device');
.setCategory('Input');
extension.addInstructionOrExpressionGroupMetadata(_("Device sensors"))
.setIcon("JsPlatform/Extensions/orientation_active32.png");

View File

@@ -35,7 +35,7 @@ module.exports = {
'Open source (MIT License)'
)
.setExtensionHelpPath('/all-features/device-vibration')
.setCategory('Device');
.setCategory('User interface');
extension.addInstructionOrExpressionGroupMetadata(_("Device vibration"))
.setIcon("JsPlatform/Extensions/vibration_start32.png");

View File

@@ -34,7 +34,7 @@ module.exports = {
'Open source (MIT License)'
)
.setExtensionHelpPath('/all-features/dialogue-tree')
.setCategory('Advanced');
.setCategory('Game mechanic');
extension
.addInstructionOrExpressionGroupMetadata(_('Dialogue Tree (experimental)'))
.setIcon('JsPlatform/Extensions/yarn32.png');

View File

@@ -20,6 +20,7 @@ void DeclareDraggableBehaviorExtension(gd::PlatformExtension& extension) {
"or disable the behavior when needed."),
"Florian Rival",
"Open source (MIT License)")
.SetCategory("User interface")
.SetExtensionHelpPath("/behaviors/draggable");
gd::BehaviorMetadata& aut = extension.AddBehavior(

View File

@@ -31,7 +31,9 @@ module.exports = {
'Lots of different effects to be used in your game.',
'Various contributors from PixiJS, PixiJS filters and GDevelop',
'MIT'
).setExtensionHelpPath('/interface/scene-editor/layer-effects');
)
.setCategory('Visual effect')
.setExtensionHelpPath('/interface/scene-editor/layer-effects');
// You can declare an effect here. Please order the effects by alphabetical order.
// This file is for common effects that are well-known/"battle-tested". If you have an

View File

@@ -34,7 +34,7 @@ module.exports = {
'Open source (MIT License)'
)
.setExtensionHelpPath('/all-features/filesystem')
.setCategory('Device');
.setCategory('Advanced');
extension
.addInstructionOrExpressionGroupMetadata(_('File system'))
.setIcon('JsPlatform/Extensions/filesystem_create_folder32.png');

View File

@@ -19,7 +19,7 @@ void DeclareInventoryExtension(gd::PlatformExtension& extension) {
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/all-features/inventory")
.SetCategory("Advanced");
.SetCategory("Game mechanic");
extension
.AddInstructionOrExpressionGroupMetadata(_("Inventories"))
.SetIcon("CppPlatform/Extensions/Inventoryicon.png");

View File

@@ -32,7 +32,8 @@ module.exports = {
'This provides a light object, and a behavior to mark other objects as being obstacles for the lights. This is a great way to create a special atmosphere to your game, along with effects, make it more realistic or to create gameplays based on lights.',
'Harsimran Virk',
'MIT'
);
)
.setCategory('Visual effect');
const lightObstacleBehavior = new gd.BehaviorJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
@@ -195,7 +196,7 @@ module.exports = {
.setIncludeFile('Extensions/Lighting/lightruntimeobject.js')
.addIncludeFile('Extensions/Lighting/lightruntimeobject-pixi-renderer.js')
.addIncludeFile('Extensions/Lighting/lightobstacleruntimebehavior.js')
.setCategoryFullName(_('Lights'));
.setCategoryFullName(_('Visual effect'));
object
.addAction(

View File

@@ -24,6 +24,8 @@ void DeclarePanelSpriteObjectExtension(gd::PlatformExtension& extension) {
"Victor Levasseur and Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects/panel_sprite");
extension.AddInstructionOrExpressionGroupMetadata(_("Panel Sprite (9-patch) Object"))
.SetIcon("CppPlatform/Extensions/PanelSpriteIcon.png");
gd::ObjectMetadata& obj =
extension

View File

@@ -25,7 +25,10 @@ void DeclareParticleSystemExtension(gd::PlatformExtension& extension) {
"explosions, magical effects, etc...",
"Florian Rival",
"Open source (MIT License)")
.SetCategory("Visual effect")
.SetExtensionHelpPath("/objects/particles_emitter");
extension.AddInstructionOrExpressionGroupMetadata(_("Particle system"))
.SetIcon("CppPlatform/Extensions/particleSystemicon.png");
// Declaration of all objects available
{
@@ -37,7 +40,7 @@ void DeclareParticleSystemExtension(gd::PlatformExtension& extension) {
_("Displays a large number of small particles to create visual "
"effects."),
"CppPlatform/Extensions/particleSystemicon.png")
.SetCategoryFullName(_("General"));
.SetCategoryFullName(_("Visual effect"));
// Declaration is too big to be compiled by GCC in one file, unless you have
// 4GB+ ram. :/

View File

@@ -21,7 +21,10 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"avoiding obstacles on the way.",
"Florian Rival",
"Open source (MIT License)")
.SetCategory("Movement")
.SetExtensionHelpPath("/behaviors/pathfinding");
extension.AddInstructionOrExpressionGroupMetadata(_("Pathfinding behavior"))
.SetIcon("CppPlatform/Extensions/AStaricon16.png");
{
gd::BehaviorMetadata& aut =

View File

@@ -34,7 +34,7 @@ module.exports = {
'MIT'
)
.setExtensionHelpPath('/behaviors/physics2')
.setCategory('Advanced');
.setCategory('Movement');
extension
.addInstructionOrExpressionGroupMetadata(_('Physics Engine 2.0'))
.setIcon('res/physics32.png');

View File

@@ -21,7 +21,10 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
"This is the old, deprecated physics engine. Prefer to use the Physics Engine 2.0.",
"Florian Rival",
"Open source (MIT License)")
.SetCategory("Movement")
.SetExtensionHelpPath("/behaviors/physics");
extension.AddInstructionOrExpressionGroupMetadata(_("Physics Engine (deprecated)"))
.SetIcon("res/physics16.png");
{
gd::BehaviorMetadata& aut = extension.AddBehavior(

View File

@@ -27,6 +27,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
"could be used.",
"Florian Rival",
"Open source (MIT License)")
.SetCategory("Movement")
.SetExtensionHelpPath("/behaviors/platformer");
extension.AddInstructionOrExpressionGroupMetadata(_("Platform behavior"))
.SetIcon("CppPlatform/Extensions/platformerobjecticon.png");

View File

@@ -20,6 +20,8 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
"Florian Rival and Aurélien Vivet",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects/shape_painter");
extension.AddInstructionOrExpressionGroupMetadata(_("Shape painter"))
.SetIcon("CppPlatform/Extensions/primitivedrawingicon.png");
gd::ObjectMetadata& obj =
extension

View File

@@ -34,7 +34,7 @@ module.exports = {
'Open source (MIT License)'
)
.setExtensionHelpPath('/all-features/screenshot')
.setCategory('Device');
.setCategory('Advanced');
extension
.addInstructionOrExpressionGroupMetadata(_('Screenshot'))
.setIcon('JsPlatform/Extensions/take_screenshot32.png');

View File

@@ -15,7 +15,7 @@ void DeclareSystemInfoExtension(gd::PlatformExtension& extension) {
_("Get information about the system and device running the game."),
"Florian Rival",
"Open source (MIT License)")
.SetCategory("Device");
.SetCategory("Advanced");
extension.AddInstructionOrExpressionGroupMetadata(_("System information"))
.SetIcon("CppPlatform/Extensions/systeminfoicon.png");

View File

@@ -18,6 +18,7 @@ void DeclareTextEntryObjectExtension(gd::PlatformExtension& extension) {
"entered with a keyboard by a player."),
"Florian Rival",
"Open source (MIT License)")
.SetCategory("User interface")
.SetExtensionHelpPath("/objects/text_entry");
gd::ObjectMetadata& obj =
@@ -27,7 +28,7 @@ void DeclareTextEntryObjectExtension(gd::PlatformExtension& extension) {
_("Invisible object used to get the text "
"entered with the keyboard."),
"CppPlatform/Extensions/textentry.png")
.SetCategoryFullName(_("Advanced"));
.SetCategoryFullName(_("User interface"));
obj.AddAction("String",
_("Text in memory"),

View File

@@ -31,7 +31,10 @@ module.exports = {
_('A text field the player can type text into.'),
'Florian Rival',
'MIT'
);
)
.setCategory('User interface');
extension.addInstructionOrExpressionGroupMetadata(_("Text Input"))
.setIcon("JsPlatform/Extensions/text_input.svg");
const textInputObject = new gd.ObjectJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating an object
@@ -277,7 +280,7 @@ module.exports = {
'JsPlatform/Extensions/text_input.svg',
textInputObject
)
.setCategoryFullName(_('Form control'))
.setCategoryFullName(_('User interface'))
.addUnsupportedBaseObjectCapability('effect')
.setIncludeFile('Extensions/TextInput/textinputruntimeobject.js')
.addIncludeFile(

View File

@@ -23,7 +23,10 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
"some indicators, menu buttons, dialogues..."),
"Florian Rival and Victor Levasseur",
"Open source (MIT License)")
.SetCategory("User interface")
.SetExtensionHelpPath("/objects/text");
extension.AddInstructionOrExpressionGroupMetadata(_("Text object"))
.SetIcon("CppPlatform/Extensions/texticon.png");
gd::ObjectMetadata& obj =
extension
@@ -31,7 +34,7 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
_("Text"),
_("Displays a text on the screen."),
"CppPlatform/Extensions/texticon.png")
.SetCategoryFullName(_("Texts"));
.SetCategoryFullName(_("User interface"));
obj.AddAction("String",
_("Modify the text"),

View File

@@ -858,7 +858,10 @@ module.exports = {
'Todor Imreorov',
'Open source (MIT License)'
)
.setCategory('Advanced')
.setExtensionHelpPath('/objects/tilemap');
extension.addInstructionOrExpressionGroupMetadata(_("Tilemap"))
.setIcon("JsPlatform/Extensions/tile_map.svg");
defineTileMap(extension, _, gd);
defineCollisionMask(extension, _, gd);

View File

@@ -121,10 +121,10 @@ describe('gdjs.TileMapCollisionMaskRuntimeObject', function () {
// TODO find a clean way to wait for the json to be read.
for (
let index = 0;
index < 200 && tileMap._collisionTileMap.getDimensionX() === 0;
index < 100 && tileMap._collisionTileMap.getDimensionX() === 0;
index++
) {
await delay(10);
await delay(20);
}
if (tileMap._collisionTileMap.getDimensionX() === 0) {
throw new Error('Timeout reading the tile map JSON file.');

View File

@@ -24,6 +24,8 @@ void DeclareTiledSpriteObjectExtension(gd::PlatformExtension& extension) {
"Victor Levasseur and Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects/tiled_sprite");
extension.AddInstructionOrExpressionGroupMetadata(_("Tiled Sprite Object"))
.SetIcon("CppPlatform/Extensions/TiledSpriteIcon.png");
gd::ObjectMetadata& obj =
extension

View File

@@ -20,7 +20,10 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
"keyboard or using events."),
"Florian Rival",
"Open source (MIT License)")
.SetCategory("Movement")
.SetExtensionHelpPath("/behaviors/topdown");
extension.AddInstructionOrExpressionGroupMetadata(_("Top-down movement"))
.SetIcon("CppPlatform/Extensions/topdownmovementicon16.png");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"TopDownMovementBehavior",

View File

@@ -73,7 +73,10 @@ module.exports = {
'Matthias Meike, Florian Rival',
'Open source (MIT License)'
)
.setCategory('Visual effect')
.setExtensionHelpPath('/behaviors/tween');
extension.addInstructionOrExpressionGroupMetadata(_("Tweening"))
.setIcon("JsPlatform/Extensions/tween_behavior32.png");
extension
.addExpression(

View File

@@ -28,12 +28,15 @@ module.exports = {
extension
.setExtensionInformation(
'Video',
'Video',
'Provides an object to display a video on the scene. The recommended file format is MPEG4, with H264 video codec and AAC audio codec, to maximize the support of the video on different platform and browsers.',
_('Video'),
_('Provides an object to display a video on the scene. The recommended file format is MPEG4, with H264 video codec and AAC audio codec, to maximize the support of the video on different platform and browsers.'),
'Aurélien Vivet',
'Open source (MIT License)'
)
.setCategory('User interface')
.setExtensionHelpPath('/objects/video');
extension.addInstructionOrExpressionGroupMetadata(_("Video"))
.setIcon("JsPlatform/Extensions/videoicon16.png");
var videoObject = new gd.ObjectJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating an object
@@ -133,7 +136,7 @@ module.exports = {
)
.setIncludeFile('Extensions/Video/videoruntimeobject.js')
.addIncludeFile('Extensions/Video/videoruntimeobject-pixi-renderer.js')
.setCategoryFullName(_('Multimedia'));
.setCategoryFullName(_('User interface'));
object
.addAction(

View File

@@ -128,6 +128,10 @@ CameraExtension::CameraExtension() {
GetAllExpressions()["LayerTimeScale"].SetFunctionName(
"gdjs.evtTools.camera.getLayerTimeScale");
GetAllConditions()["LayerHighestZOrder"].SetFunctionName(
"gdjs.evtTools.layer.getLayerHighestZOrder");
GetAllExpressions()["LayerHighestZOrder"].SetFunctionName(
"gdjs.evtTools.layer.getLayerHighestZOrder");
GetAllConditions()["LayerDefaultZOrder"].SetFunctionName(
"gdjs.evtTools.camera.getLayerDefaultZOrder");
GetAllActions()["SetLayerDefaultZOrder"].SetFunctionName(

View File

@@ -451,6 +451,7 @@ namespace gdjs {
};
/**
* @param runtimeScene The scene
* @param layerName The lighting layer with the ambient color.
* @param rgbColor The color, in RGB format ("128;200;255").
*/
@@ -477,6 +478,21 @@ namespace gdjs {
parseInt(colors[2], 10)
);
};
/**
* @param runtimeScene The scene
* @param layer The name of the layer
* @return the highest Z order of objects in the layer
*/
export const getLayerHighestZOrder = function (
runtimeScene: gdjs.RuntimeScene,
layer: string
): number {
if (!runtimeScene.hasLayer(layer)) {
return 0;
}
return runtimeScene.getLayer(layer).getHighestZOrder();
};
}
}
}

View File

@@ -15,6 +15,7 @@ namespace gdjs {
_zoomFactor: float = 1;
_timeScale: float = 1;
_defaultZOrder: integer = 0;
_highestZOrder: integer = 0;
_hidden: boolean;
_initialEffectsData: Array<EffectData>;
_cameraX: float;
@@ -74,7 +75,7 @@ namespace gdjs {
* Get the default Z order to be attributed to objects created on this layer
* (usually from events generated code).
*/
getDefaultZOrder(): float {
getDefaultZOrder(): integer {
return this._defaultZOrder;
}
@@ -86,6 +87,17 @@ namespace gdjs {
this._defaultZOrder = defaultZOrder;
}
/**
* Get the highest Z order occupied by the runtime objects on the layer
*/
getHighestZOrder(): integer {
return this._highestZOrder;
}
_setHighestZOrder(z: integer): void {
this._highestZOrder = z;
}
/**
* Called by the RuntimeScene whenever the game resolution size is changed.
* Updates the layer width/height and position.

View File

@@ -714,10 +714,13 @@ namespace gdjs {
}
this.zOrder = z;
const rendererObject = this.getRendererObject();
const theLayer = this._runtimeScene.getLayer(this.layer);
if (rendererObject) {
const theLayer = this._runtimeScene.getLayer(this.layer);
theLayer.getRenderer().changeRendererObjectZOrder(rendererObject, z);
}
if (z > theLayer.getHighestZOrder()) {
theLayer._setHighestZOrder(z);
}
}
/**

View File

@@ -732,6 +732,10 @@ namespace gdjs {
* Tool function filling _allInstancesList member with all the living object instances.
*/
_constructListOfAllInstances() {
for (const name in this._layers.items) {
this._layers.get(name)._setHighestZOrder(0);
}
let currentListSize = 0;
for (const name in this._instances.items) {
if (this._instances.items.hasOwnProperty(name)) {
@@ -739,10 +743,16 @@ namespace gdjs {
const oldSize = currentListSize;
currentListSize += list.length;
for (let j = 0, lenj = list.length; j < lenj; ++j) {
const instance = list[j];
if (oldSize + j < this._allInstancesList.length) {
this._allInstancesList[oldSize + j] = list[j];
this._allInstancesList[oldSize + j] = instance;
} else {
this._allInstancesList.push(list[j]);
this._allInstancesList.push(instance);
}
const layerName = instance.getLayer();
const zOrder = instance.getZOrder();
if (this.getLayer(layerName).getHighestZOrder() < zOrder) {
this.getLayer(layerName)._setHighestZOrder(zOrder);
}
}
}

View File

@@ -83,100 +83,165 @@ describe('gdjs.RuntimeScene integration tests', function () {
});
describe('Layers (using a Sprite object)', function () {
const runtimeGame = new gdjs.RuntimeGame({
variables: [],
// @ts-expect-error ts-migrate(2740) FIXME: Type '{ windowWidth: number; windowHeight: number;... Remove this comment to see the full error message
properties: { windowWidth: 800, windowHeight: 600 },
resources: { resources: [] },
});
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
runtimeScene.loadFromScene({
layers: [
{
name: '',
visibility: true,
cameras: [],
effects: [],
ambientLightColorR: 127,
ambientLightColorB: 127,
ambientLightColorG: 127,
isLightingLayer: false,
followBaseLayerCamera: false,
},
{
name: 'MyLayer',
visibility: true,
cameras: [],
effects: [],
ambientLightColorR: 127,
ambientLightColorB: 127,
ambientLightColorG: 127,
isLightingLayer: false,
followBaseLayerCamera: false,
},
],
variables: [],
r: 0,
v: 0,
b: 0,
mangledName: 'Scene1',
name: 'Scene1',
stopSoundsOnStartup: false,
title: '',
behaviorsSharedData: [],
objects: [
{
type: 'Sprite',
name: 'MyObject',
behaviors: [],
effects: [],
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ type: string; name: string; behaviors: nev... Remove this comment to see the full error message
animations: [],
updateIfNotVisible: false,
},
],
instances: [],
/** @type {gdjs.RuntimeGame} */
let runtimeGame;
/** @type {gdjs.RuntimeScene} */
let runtimeScene;
beforeEach(() => {
runtimeGame = new gdjs.RuntimeGame({
variables: [],
// @ts-expect-error ts-migrate(2740) FIXME: Type '{ windowWidth: number; windowHeight: number;... Remove this comment to see the full error message
properties: { windowWidth: 800, windowHeight: 600 },
resources: { resources: [] },
});
runtimeScene = new gdjs.RuntimeScene(runtimeGame);
runtimeScene.loadFromScene({
layers: [
{
name: '',
visibility: true,
cameras: [],
effects: [],
ambientLightColorR: 127,
ambientLightColorB: 127,
ambientLightColorG: 127,
isLightingLayer: false,
followBaseLayerCamera: false,
},
{
name: 'MyLayer',
visibility: true,
cameras: [],
effects: [],
ambientLightColorR: 127,
ambientLightColorB: 127,
ambientLightColorG: 127,
isLightingLayer: false,
followBaseLayerCamera: false,
},
],
variables: [],
r: 0,
v: 0,
b: 0,
mangledName: 'Scene1',
name: 'Scene1',
stopSoundsOnStartup: false,
title: '',
behaviorsSharedData: [],
objects: [
{
type: 'Sprite',
name: 'MyObject',
behaviors: [],
effects: [],
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ type: string; name: string; behaviors: nev... Remove this comment to see the full error message
animations: [],
updateIfNotVisible: false,
},
],
instances: [],
});
});
expect(runtimeScene.hasLayer('')).to.be(true);
expect(runtimeScene.hasLayer('MyLayer')).to.be(true);
expect(runtimeScene.hasLayer('MyOtherLayer')).to.be(false);
it('should handle objects on layers', () => {
expect(runtimeScene.hasLayer('')).to.be(true);
expect(runtimeScene.hasLayer('MyLayer')).to.be(true);
expect(runtimeScene.hasLayer('MyOtherLayer')).to.be(false);
const object1 = runtimeScene.createObject('MyObject');
const object2 = runtimeScene.createObject('MyObject');
const object3 = runtimeScene.createObject('MyObject');
if (!object1 || !object2 || !object3) {
throw new Error('object should have been created');
}
object2.setLayer('MyLayer');
const object1 = runtimeScene.createObject('MyObject');
const object2 = runtimeScene.createObject('MyObject');
const object3 = runtimeScene.createObject('MyObject');
if (!object1 || !object2 || !object3) {
throw new Error('object should have been created');
}
object2.setLayer('MyLayer');
runtimeScene.addLayer({
name: 'MyOtherLayer',
visibility: true,
cameras: [],
effects: [],
isLightingLayer: false,
followBaseLayerCamera: false,
ambientLightColorR: 128,
ambientLightColorG: 128,
ambientLightColorB: 128,
runtimeScene.addLayer({
name: 'MyOtherLayer',
visibility: true,
cameras: [],
effects: [],
isLightingLayer: false,
followBaseLayerCamera: false,
ambientLightColorR: 128,
ambientLightColorG: 128,
ambientLightColorB: 128,
});
expect(runtimeScene.hasLayer('')).to.be(true);
expect(runtimeScene.hasLayer('MyLayer')).to.be(true);
expect(runtimeScene.hasLayer('MyOtherLayer')).to.be(true);
object3.setLayer('MyOtherLayer');
expect(object1.getLayer()).to.be('');
expect(object2.getLayer()).to.be('MyLayer');
expect(object3.getLayer()).to.be('MyOtherLayer');
runtimeScene.removeLayer('MyLayer');
expect(object1.getLayer()).to.be('');
expect(object2.getLayer()).to.be('');
expect(object3.getLayer()).to.be('MyOtherLayer');
expect(runtimeScene.hasLayer('')).to.be(true);
expect(runtimeScene.hasLayer('MyLayer')).to.be(false);
expect(runtimeScene.hasLayer('MyOtherLayer')).to.be(true);
});
expect(runtimeScene.hasLayer('')).to.be(true);
expect(runtimeScene.hasLayer('MyLayer')).to.be(true);
expect(runtimeScene.hasLayer('MyOtherLayer')).to.be(true);
object3.setLayer('MyOtherLayer');
expect(object1.getLayer()).to.be('');
expect(object2.getLayer()).to.be('MyLayer');
expect(object3.getLayer()).to.be('MyOtherLayer');
it('should compute layers highest z orders correctly', () => {
expect(runtimeScene.hasLayer('')).to.be(true);
expect(runtimeScene.hasLayer('MyLayer')).to.be(true);
runtimeScene.removeLayer('MyLayer');
const object1 = runtimeScene.createObject('MyObject');
const object2 = runtimeScene.createObject('MyObject');
const object3 = runtimeScene.createObject('MyObject');
if (!object1 || !object2 || !object3) {
throw new Error('object should have been created');
}
object2.setLayer('MyLayer');
expect(object1.getLayer()).to.be('');
expect(object2.getLayer()).to.be('');
expect(object3.getLayer()).to.be('MyOtherLayer');
expect(runtimeScene.hasLayer('')).to.be(true);
expect(runtimeScene.hasLayer('MyLayer')).to.be(false);
expect(runtimeScene.hasLayer('MyOtherLayer')).to.be(true);
// Layers highest Z orders should stay at 0 if objects did not change their Z order
runtimeScene._updateObjectsPreRender();
expect(runtimeScene.getLayer('MyLayer').getHighestZOrder()).to.be(0);
expect(runtimeScene.getLayer('').getHighestZOrder()).to.be(0);
object2.setZOrder(8);
// Check highest Z order has been correctly updated for layers and they are stable
runtimeScene._updateObjectsPreRender();
expect(runtimeScene.getLayer('MyLayer').getHighestZOrder()).to.be(8);
expect(runtimeScene.getLayer('').getHighestZOrder()).to.be(0);
runtimeScene._updateObjectsPreRender();
expect(runtimeScene.getLayer('MyLayer').getHighestZOrder()).to.be(8);
expect(runtimeScene.getLayer('').getHighestZOrder()).to.be(0);
// Change default layer objects Z orders
object1.setZOrder(13);
object3.setZOrder(25);
runtimeScene._updateObjectsPreRender();
expect(runtimeScene.getLayer('MyLayer').getHighestZOrder()).to.be(8);
expect(runtimeScene.getLayer('').getHighestZOrder()).to.be(25);
// Move object in front of the highest
object1.setZOrder(30)
runtimeScene._updateObjectsPreRender();
expect(runtimeScene.getLayer('').getHighestZOrder()).to.be(30);
// Delete highest Z order object
runtimeScene.markObjectForDeletion(object1);
runtimeScene._updateObjectsPreRender();
expect(runtimeScene.getLayer('').getHighestZOrder()).to.be(25);
// Check highest z orders come back to 0 after objects deletion
runtimeScene.markObjectForDeletion(object2);
runtimeScene.markObjectForDeletion(object3);
runtimeScene._updateObjectsPreRender();
expect(runtimeScene.getLayer('MyLayer').getHighestZOrder()).to.be(0);
expect(runtimeScene.getLayer('').getHighestZOrder()).to.be(0);
});
});
});

View File

@@ -78,314 +78,357 @@ describe('gdjs.SpriteRuntimeObject (using a PixiJS RuntimeGame with assets)', fu
],
});
it('returns the size of the object from the texture', function() {
return gdjs.getPixiRuntimeGameWithAssets().then(runtimeGame => {
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
it('returns the size of the object from the texture', async () => {
const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets();
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
const object = makeSpriteRuntimeObjectWithCustomHitBox(runtimeScene);
const object = makeSpriteRuntimeObjectWithCustomHitBox(runtimeScene);
expect(object.getWidth()).to.be(64);
expect(object.getHeight()).to.be(64);
});
expect(object.getWidth()).to.be(64);
expect(object.getHeight()).to.be(64);
});
it('returns the object drawable X/Y', function() {
return gdjs.getPixiRuntimeGameWithAssets().then(runtimeGame => {
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
it('returns the object drawable X/Y', async () => {
const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets();
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
const object = makeSpriteRuntimeObjectWithCustomHitBox(runtimeScene);
const object = makeSpriteRuntimeObjectWithCustomHitBox(runtimeScene);
// Texture is shown on screen at -32;-16 because of the custom origin
expect(object.getDrawableX()).to.be(-32);
expect(object.getDrawableY()).to.be(-16);
// Texture is shown on screen at -32;-16 because of the custom origin
expect(object.getDrawableX()).to.be(-32);
expect(object.getDrawableY()).to.be(-16);
// Flipping is done relative to the center, so texture is shown on screen at 32;-16
// after vertical flip, as the center X position is 64 (on the very right of the texture):
object.flipX(true);
object.flipY(false);
expect(object.getDrawableX()).to.be(32);
expect(object.getDrawableY()).to.be(-16);
// Flipping is done relative to the center, so texture is shown on screen at 32;-16
// after vertical flip, as the center X position is 64 (on the very right of the texture):
object.flipX(true);
object.flipY(false);
expect(object.getDrawableX()).to.be(32);
expect(object.getDrawableY()).to.be(-16);
// Flipping is done relative to the center, so texture is shown on screen at 32;-18
// after vertical flip, as the center Y position is 31 (so new Y position is 2 pixels away from -16)
object.flipX(false);
object.flipY(true);
expect(object.getDrawableX()).to.be(-32);
expect(object.getDrawableY()).to.be(-18);
// Flipping is done relative to the center, so texture is shown on screen at 32;-18
// after vertical flip, as the center Y position is 31 (so new Y position is 2 pixels away from -16)
object.flipX(false);
object.flipY(true);
expect(object.getDrawableX()).to.be(-32);
expect(object.getDrawableY()).to.be(-18);
// Sanity check when flipping on both axes:
object.flipX(true);
object.flipY(true);
expect(object.getDrawableX()).to.be(32);
expect(object.getDrawableY()).to.be(-18);
});
// Sanity check when flipping on both axes:
object.flipX(true);
object.flipY(true);
expect(object.getDrawableX()).to.be(32);
expect(object.getDrawableY()).to.be(-18);
});
it('returns the object center X/Y', function() {
return gdjs.getPixiRuntimeGameWithAssets().then(runtimeGame => {
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
it('returns the object center X/Y', async () => {
const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets();
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
// Create an object with a custom hitbox
const object = makeSpriteRuntimeObjectWithCustomHitBox(runtimeScene);
// Create an object with a custom hitbox
const object = makeSpriteRuntimeObjectWithCustomHitBox(runtimeScene);
// getDrawableX/Y is checked in another test:
expect(object.getDrawableX()).to.be(-32);
expect(object.getDrawableY()).to.be(-16);
// getDrawableX/Y is checked in another test:
expect(object.getDrawableX()).to.be(-32);
expect(object.getDrawableY()).to.be(-16);
// Check that the center X and Y is returned relative to getDrawableX/Y:
expect(object.getCenterX()).to.be(64);
expect(object.getCenterY()).to.be(31);
// Check that the center X and Y is returned relative to getDrawableX/Y:
expect(object.getCenterX()).to.be(64);
expect(object.getCenterY()).to.be(31);
// Sanity test that center position in the scene coordinates is right.
// It's a common pattern in the game engine:
expect(object.getDrawableX() + object.getCenterX()).to.be(32);
expect(object.getDrawableY() + object.getCenterY()).to.be(15);
// Sanity test that center position in the scene coordinates is right.
// It's a common pattern in the game engine:
expect(object.getDrawableX() + object.getCenterX()).to.be(32);
expect(object.getDrawableY() + object.getCenterY()).to.be(15);
// Check that the center X and Y is always returned relative to the object texture origin.
object.flipX(true);
object.flipY(false);
expect(object.getCenterX()).to.be(0); // Center is at the very right of the texture, so after flipping is on the very left.
expect(object.getCenterY()).to.be(31);
// Check that the center X and Y is always returned relative to the object texture origin.
object.flipX(true);
object.flipY(false);
expect(object.getCenterX()).to.be(0); // Center is at the very right of the texture, so after flipping is on the very left.
expect(object.getCenterY()).to.be(31);
// As the center is the center for rotation and flipping, its position in the scene coordinates never changes:
expect(object.getDrawableX() + object.getCenterX()).to.be(32);
expect(object.getDrawableY() + object.getCenterY()).to.be(15);
// As the center is the center for rotation and flipping, its position in the scene coordinates never changes:
expect(object.getDrawableX() + object.getCenterX()).to.be(32);
expect(object.getDrawableY() + object.getCenterY()).to.be(15);
// Check that the center X and Y is always returned relative to the object texture origin.
object.flipX(false);
object.flipY(true);
expect(object.getCenterX()).to.be(64);
expect(object.getCenterY()).to.be(33); // Center point was 1 pixel above the texture center, so is now 1 pixel below
// Check that the center X and Y is always returned relative to the object texture origin.
object.flipX(false);
object.flipY(true);
expect(object.getCenterX()).to.be(64);
expect(object.getCenterY()).to.be(33); // Center point was 1 pixel above the texture center, so is now 1 pixel below
// As the center is the center for rotation and flipping, its position in the scene coordinates never changes:
expect(object.getDrawableX() + object.getCenterX()).to.be(32);
expect(object.getDrawableY() + object.getCenterY()).to.be(15);
// As the center is the center for rotation and flipping, its position in the scene coordinates never changes:
expect(object.getDrawableX() + object.getCenterX()).to.be(32);
expect(object.getDrawableY() + object.getCenterY()).to.be(15);
// Check that the center X and Y is always returned relative to the object texture origin.
object.flipX(true);
object.flipY(true);
expect(object.getCenterX()).to.be(0); // Center is at the very right of the texture, so after flipping is on the very left.
expect(object.getCenterY()).to.be(33); // Center point was 1 pixel above the texture center, so is now 1 pixel below
// Check that the center X and Y is always returned relative to the object texture origin.
object.flipX(true);
object.flipY(true);
expect(object.getCenterX()).to.be(0); // Center is at the very right of the texture, so after flipping is on the very left.
expect(object.getCenterY()).to.be(33); // Center point was 1 pixel above the texture center, so is now 1 pixel below
// As the center is the center for rotation and flipping, its position in the scene coordinates never changes:
expect(object.getDrawableX() + object.getCenterX()).to.be(32);
expect(object.getDrawableY() + object.getCenterY()).to.be(15);
// As the center is the center for rotation and flipping, its position in the scene coordinates never changes:
expect(object.getDrawableX() + object.getCenterX()).to.be(32);
expect(object.getDrawableY() + object.getCenterY()).to.be(15);
// As the center is the center for rotation and flipping, its position in the scene coordinates never changes:
object.setAngle(12.92);
expect(object.getDrawableX() + object.getCenterX()).to.be(32);
expect(object.getDrawableY() + object.getCenterY()).to.be(15);
// As the center is the center for rotation and flipping, its position in the scene coordinates never changes:
object.setAngle(12.92);
expect(object.getDrawableX() + object.getCenterX()).to.be(32);
expect(object.getDrawableY() + object.getCenterY()).to.be(15);
object.setAngle(-12345.67);
expect(object.getDrawableX() + object.getCenterX()).to.be(32);
expect(object.getDrawableY() + object.getCenterY()).to.be(15);
});
object.setAngle(-12345.67);
expect(object.getDrawableX() + object.getCenterX()).to.be(32);
expect(object.getDrawableY() + object.getCenterY()).to.be(15);
});
it('properly computes hitboxes and point positions (after flipping or rotation)', function() {
return gdjs.getPixiRuntimeGameWithAssets().then(runtimeGame => {
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
it('properly computes hitboxes and point positions (after flipping or rotation)', async () => {
const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets();
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
// Create an object with a custom hitbox
const object = makeSpriteRuntimeObjectWithCustomHitBox(runtimeScene);
// Create an object with a custom hitbox
const object = makeSpriteRuntimeObjectWithCustomHitBox(runtimeScene);
// Check the hitboxes without any rotation (only the non default origin
// which is at 32;16 is to be used).
expect(object.getHitBoxes()[0].vertices[0]).to.eql([12.5 - originPointX, 1 - originPointY]);
expect(object.getHitBoxes()[0].vertices[1]).to.eql([41.5 - originPointX, 2 - originPointY]);
expect(object.getHitBoxes()[0].vertices[2]).to.eql([55.5 - originPointX, 31 - originPointY]);
expect(object.getHitBoxes()[0].vertices[3]).to.eql([24.5 - originPointX, 30 - originPointY]);
// Check the hitboxes without any rotation (only the non default origin
// which is at 32;16 is to be used).
expect(object.getHitBoxes()[0].vertices[0]).to.eql([12.5 - originPointX, 1 - originPointY]);
expect(object.getHitBoxes()[0].vertices[1]).to.eql([41.5 - originPointX, 2 - originPointY]);
expect(object.getHitBoxes()[0].vertices[2]).to.eql([55.5 - originPointX, 31 - originPointY]);
expect(object.getHitBoxes()[0].vertices[3]).to.eql([24.5 - originPointX, 30 - originPointY]);
// Sanity check the center position
expect(object.getPointX("Center")).to.be(32);
expect(object.getPointY("Center")).to.be(15);
// Sanity check the center position
expect(object.getPointX("Center")).to.be(32);
expect(object.getPointY("Center")).to.be(15);
// Sanity check the origin position
expect(object.getPointX("Origin")).to.be(0);
expect(object.getPointY("Origin")).to.be(0);
// Sanity check the origin position
expect(object.getPointX("Origin")).to.be(0);
expect(object.getPointY("Origin")).to.be(0);
// Hitbox with rotation
object.setAngle(90);
expect(object.getHitBoxes()[0].vertices[0][0]).to.be.within(
61.9999,
62.0001
);
expect(object.getHitBoxes()[0].vertices[0][1]).to.be.within(
-36.5001,
-36.49999
);
expect(object.getHitBoxes()[0].vertices[2][0]).to.be.within(
31.999,
32.0001
);
expect(object.getHitBoxes()[0].vertices[2][1]).to.be.within(
6.4999,
6.5001
);
// Hitbox with rotation
object.setAngle(90);
expect(object.getHitBoxes()[0].vertices[0][0]).to.be.within(
61.9999,
62.0001
);
expect(object.getHitBoxes()[0].vertices[0][1]).to.be.within(
-36.5001,
-36.49999
);
expect(object.getHitBoxes()[0].vertices[2][0]).to.be.within(
31.999,
32.0001
);
expect(object.getHitBoxes()[0].vertices[2][1]).to.be.within(
6.4999,
6.5001
);
// Center is unchanged with rotation
expect(object.getPointX("Center")).to.be(32);
expect(object.getPointY("Center")).to.be(15);
// Center is unchanged with rotation
expect(object.getPointX("Center")).to.be(32);
expect(object.getPointY("Center")).to.be(15);
// Hitbox with flipping (X axis)
//
// On the X axis, points are like this (P = the first vertex of the hitboxes, O = origin, C = center):
// -20 -10 0 10 20 30 40 50 60 70 80 90
// |---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
// P (12.5) O (32) C (64)
//
// Object X position is 0, so the origin is at 0 in the scene coordinates:
// -20 -10 0 10 20 30 40 50 60 70 80 90
// |---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
// P (-19.5) O (0) C (32)
//
// Object is flipped on X axis, relative to the center, so points are like this now in the scene coordinates:
// -20 -10 0 10 20 30 40 50 60 70 80 90
// |---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
// C (32) O (64) P (83.5)
//
object.setAngle(0);
object.flipX(true);
expect(centerPointX - 12.5 + centerPointX - originPointX).to.be(83.5); // Sanity check of the first expected position
expect(object.getHitBoxes()[0].vertices[0]).to.eql([centerPointX - 12.5 + centerPointX - originPointX, 1 - originPointY]);
expect(object.getHitBoxes()[0].vertices[1]).to.eql([centerPointX - 41.5 + centerPointX - originPointX, 2 - originPointY]);
expect(object.getHitBoxes()[0].vertices[2]).to.eql([centerPointX - 55.5 + centerPointX - originPointX, 31 - originPointY]);
expect(object.getHitBoxes()[0].vertices[3]).to.eql([centerPointX - 24.5 + centerPointX - originPointX, 30 - originPointY]);
// Hitbox with flipping (X axis)
//
// On the X axis, points are like this (P = the first vertex of the hitboxes, O = origin, C = center):
// -20 -10 0 10 20 30 40 50 60 70 80 90
// |---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
// P (12.5) O (32) C (64)
//
// Object X position is 0, so the origin is at 0 in the scene coordinates:
// -20 -10 0 10 20 30 40 50 60 70 80 90
// |---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
// P (-19.5) O (0) C (32)
//
// Object is flipped on X axis, relative to the center, so points are like this now in the scene coordinates:
// -20 -10 0 10 20 30 40 50 60 70 80 90
// |---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
// C (32) O (64) P (83.5)
//
object.setAngle(0);
object.flipX(true);
expect(centerPointX - 12.5 + centerPointX - originPointX).to.be(83.5); // Sanity check of the first expected position
expect(object.getHitBoxes()[0].vertices[0]).to.eql([centerPointX - 12.5 + centerPointX - originPointX, 1 - originPointY]);
expect(object.getHitBoxes()[0].vertices[1]).to.eql([centerPointX - 41.5 + centerPointX - originPointX, 2 - originPointY]);
expect(object.getHitBoxes()[0].vertices[2]).to.eql([centerPointX - 55.5 + centerPointX - originPointX, 31 - originPointY]);
expect(object.getHitBoxes()[0].vertices[3]).to.eql([centerPointX - 24.5 + centerPointX - originPointX, 30 - originPointY]);
// Center is unchanged with flipping
expect(object.getPointX("Center")).to.be(32);
expect(object.getPointY("Center")).to.be(15);
// Center is unchanged with flipping
expect(object.getPointX("Center")).to.be(32);
expect(object.getPointY("Center")).to.be(15);
// Origin *point* is flipped
expect(object.getPointX("Origin")).to.be(64);
expect(object.getPointY("Origin")).to.be(0);
// Origin *point* is flipped
expect(object.getPointX("Origin")).to.be(64);
expect(object.getPointY("Origin")).to.be(0);
// Hitbox with flipping (X and Y axis)
//
// Same calculations as before for the point Y positions.
object.setAngle(0);
object.flipX(true);
object.flipY(true);
expect(centerPointY - 1 + centerPointY - originPointY).to.be(45); // Sanity check of the first expected position
expect(object.getHitBoxes()[0].vertices[0]).to.eql([centerPointX - 12.5 + centerPointX - originPointX, centerPointY - 1 + centerPointY - originPointY]);
expect(object.getHitBoxes()[0].vertices[1]).to.eql([centerPointX - 41.5 + centerPointX - originPointX, centerPointY - 2 + centerPointY - originPointY]);
expect(object.getHitBoxes()[0].vertices[2]).to.eql([centerPointX - 55.5 + centerPointX - originPointX, centerPointY - 31 + centerPointY - originPointY]);
expect(object.getHitBoxes()[0].vertices[3]).to.eql([centerPointX - 24.5 + centerPointX - originPointX, centerPointY - 30 + centerPointY - originPointY]);
// Hitbox with flipping (X and Y axis)
//
// Same calculations as before for the point Y positions.
object.setAngle(0);
object.flipX(true);
object.flipY(true);
expect(centerPointY - 1 + centerPointY - originPointY).to.be(45); // Sanity check of the first expected position
expect(object.getHitBoxes()[0].vertices[0]).to.eql([centerPointX - 12.5 + centerPointX - originPointX, centerPointY - 1 + centerPointY - originPointY]);
expect(object.getHitBoxes()[0].vertices[1]).to.eql([centerPointX - 41.5 + centerPointX - originPointX, centerPointY - 2 + centerPointY - originPointY]);
expect(object.getHitBoxes()[0].vertices[2]).to.eql([centerPointX - 55.5 + centerPointX - originPointX, centerPointY - 31 + centerPointY - originPointY]);
expect(object.getHitBoxes()[0].vertices[3]).to.eql([centerPointX - 24.5 + centerPointX - originPointX, centerPointY - 30 + centerPointY - originPointY]);
// Center is unchanged with flipping
expect(object.getPointX("Center")).to.be(32);
expect(object.getPointY("Center")).to.be(15);
// Center is unchanged with flipping
expect(object.getPointX("Center")).to.be(32);
expect(object.getPointY("Center")).to.be(15);
// Origin *point* is flipped
expect(object.getPointX("Origin")).to.be(64);
expect(object.getPointY("Origin")).to.be(30);
// Origin *point* is flipped
expect(object.getPointX("Origin")).to.be(64);
expect(object.getPointY("Origin")).to.be(30);
// Hitbox with flipping (X and Y axis) and rotation
object.setAngle(-90);
object.flipX(true);
object.flipY(true);
expect(object.getHitBoxes()[0].vertices[0]).to.eql([62, -36.5]);
expect(object.getHitBoxes()[0].vertices[1][0]).to.be(
61
);
expect(object.getHitBoxes()[0].vertices[1][1]).to.be.within(
-7.5,
-7.49
);
expect(object.getHitBoxes()[0].vertices[2]).to.eql([32, 6.5]);
expect(object.getHitBoxes()[0].vertices[3]).to.eql([33, -24.5]);
// Hitbox with flipping (X and Y axis) and rotation
object.setAngle(-90);
object.flipX(true);
object.flipY(true);
expect(object.getHitBoxes()[0].vertices[0]).to.eql([62, -36.5]);
expect(object.getHitBoxes()[0].vertices[1][0]).to.be(
61
);
expect(object.getHitBoxes()[0].vertices[1][1]).to.be.within(
-7.5,
-7.49
);
expect(object.getHitBoxes()[0].vertices[2]).to.eql([32, 6.5]);
expect(object.getHitBoxes()[0].vertices[3]).to.eql([33, -24.5]);
// Center is unchanged with flipping and rotation
expect(object.getPointX("Center")).to.be(32);
expect(object.getPointY("Center")).to.be(15);
// Center is unchanged with flipping and rotation
expect(object.getPointX("Center")).to.be(32);
expect(object.getPointY("Center")).to.be(15);
// Origin *point* is flipped and rotated
expect(object.getPointX("Origin")).to.be(47);
expect(object.getPointY("Origin")).to.be(-17);
});
// Origin *point* is flipped and rotated
expect(object.getPointX("Origin")).to.be(47);
expect(object.getPointY("Origin")).to.be(-17);
});
it('properly computes hitboxes and point positions after scaling', function() {
return gdjs.getPixiRuntimeGameWithAssets().then(runtimeGame => {
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
it('properly computes hitboxes and point positions after scaling', async () => {
const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets();
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
// Create an object with a custom hitbox
const object = makeSpriteRuntimeObjectWithCustomHitBox(runtimeScene);
// Create an object with a custom hitbox
const object = makeSpriteRuntimeObjectWithCustomHitBox(runtimeScene);
// Check the hitboxes without any rotation (only the non default origin
// which is at 32;16 is to be used).
expect(object.getHitBoxes()[0].vertices[0]).to.eql([12.5 - originPointX, 1 - originPointY]);
expect(object.getHitBoxes()[0].vertices[1]).to.eql([41.5 - originPointX, 2 - originPointY]);
expect(object.getHitBoxes()[0].vertices[2]).to.eql([55.5 - originPointX, 31 - originPointY]);
expect(object.getHitBoxes()[0].vertices[3]).to.eql([24.5 - originPointX, 30 - originPointY]);
// Check the hitboxes without any rotation (only the non default origin
// which is at 32;16 is to be used).
expect(object.getHitBoxes()[0].vertices[0]).to.eql([12.5 - originPointX, 1 - originPointY]);
expect(object.getHitBoxes()[0].vertices[1]).to.eql([41.5 - originPointX, 2 - originPointY]);
expect(object.getHitBoxes()[0].vertices[2]).to.eql([55.5 - originPointX, 31 - originPointY]);
expect(object.getHitBoxes()[0].vertices[3]).to.eql([24.5 - originPointX, 30 - originPointY]);
// Sanity check the center position
expect(object.getPointX("Center")).to.be(32);
expect(object.getPointY("Center")).to.be(15);
// Sanity check the center position
expect(object.getPointX("Center")).to.be(32);
expect(object.getPointY("Center")).to.be(15);
// Sanity check the origin position
expect(object.getPointX("Origin")).to.be(0);
expect(object.getPointY("Origin")).to.be(0);
// Sanity check the origin position
expect(object.getPointX("Origin")).to.be(0);
expect(object.getPointY("Origin")).to.be(0);
// Hitbox with 0.5 scaling (X and Y axis)
//
// On the X axis, points are like this (P = the first vertex of the hitboxes, O = origin, C = center):
// -20 -10 0 10 20 30 40 50 60 70 80 90
// |---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
// P (12.5) O (32) C (64)
//
// Object X position is 0, so the origin is at 0 in the scene coordinates:
// -20 -10 0 10 20 30 40 50 60 70 80 90
// |---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
// P (-19.5) O (0) C (32)
//
// Object is scaled, relative to the origin, so points are like this now in the scene coordinates:
// -20 -10 0 10 20 30 40 50 60 70 80 90
// |---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
// P (-9.75) O (0) C (16)
//
object.setAngle(0);
object.setScale(0.5);
object.flipX(false);
object.flipY(false);
expect((12.5 - originPointX)/2).to.be(-9.75); // Sanity check of the first expected position
expect(object.getHitBoxes()[0].vertices[0]).to.eql([(12.5 - originPointX)/2, (1 - originPointY)/2]);
expect(object.getHitBoxes()[0].vertices[1]).to.eql([(41.5 - originPointX)/2, (2 - originPointY)/2]);
expect(object.getHitBoxes()[0].vertices[2]).to.eql([(55.5 - originPointX)/2, (31 - originPointY)/2]);
expect(object.getHitBoxes()[0].vertices[3]).to.eql([(24.5 - originPointX)/2, (30 - originPointY)/2]);
// Hitbox with 0.5 scaling (X and Y axis)
//
// On the X axis, points are like this (P = the first vertex of the hitboxes, O = origin, C = center):
// -20 -10 0 10 20 30 40 50 60 70 80 90
// |---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
// P (12.5) O (32) C (64)
//
// Object X position is 0, so the origin is at 0 in the scene coordinates:
// -20 -10 0 10 20 30 40 50 60 70 80 90
// |---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
// P (-19.5) O (0) C (32)
//
// Object is scaled, relative to the origin, so points are like this now in the scene coordinates:
// -20 -10 0 10 20 30 40 50 60 70 80 90
// |---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
// P (-9.75) O (0) C (16)
//
object.setAngle(0);
object.setScale(0.5);
object.flipX(false);
object.flipY(false);
expect((12.5 - originPointX)/2).to.be(-9.75); // Sanity check of the first expected position
expect(object.getHitBoxes()[0].vertices[0]).to.eql([(12.5 - originPointX)/2, (1 - originPointY)/2]);
expect(object.getHitBoxes()[0].vertices[1]).to.eql([(41.5 - originPointX)/2, (2 - originPointY)/2]);
expect(object.getHitBoxes()[0].vertices[2]).to.eql([(55.5 - originPointX)/2, (31 - originPointY)/2]);
expect(object.getHitBoxes()[0].vertices[3]).to.eql([(24.5 - originPointX)/2, (30 - originPointY)/2]);
// Center is moved after scaling
expect(object.getPointX("Center")).to.be(16);
expect(object.getPointY("Center")).to.be(7.5);
// Center is moved after scaling
expect(object.getPointX("Center")).to.be(16);
expect(object.getPointY("Center")).to.be(7.5);
// Origin is unchanged after scaling
expect(object.getPointX("Origin")).to.be(0);
expect(object.getPointY("Origin")).to.be(0);
// Origin is unchanged after scaling
expect(object.getPointX("Origin")).to.be(0);
expect(object.getPointY("Origin")).to.be(0);
// Hitbox with 0.5 scaling (X and Y axis) and flipping (X axis)
// Hitbox with 0.5 scaling (X and Y axis) and flipping (X axis)
// Object is scaled, relative to the origin, and flipped on X axis so points are like this now in the scene coordinates:
// -20 -10 0 10 20 30 40 50 60 70 80 90
// |---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
// C (16) O (32) P (41.75)
//
object.setAngle(0);
object.setScale(0.5);
object.flipX(true);
object.flipY(false);
expect((centerPointX - 12.5 + centerPointX - originPointX)/2).to.be(41.75); // Sanity check of the first expected position
expect(object.getHitBoxes()[0].vertices[0]).to.eql([(centerPointX - 12.5 + centerPointX - originPointX)/2, (1 - originPointY)/2]);
expect(object.getHitBoxes()[0].vertices[1]).to.eql([(centerPointX - 41.5 + centerPointX - originPointX)/2, (2 - originPointY)/2]);
expect(object.getHitBoxes()[0].vertices[2]).to.eql([(centerPointX - 55.5 + centerPointX - originPointX)/2, (31 - originPointY)/2]);
expect(object.getHitBoxes()[0].vertices[3]).to.eql([(centerPointX - 24.5 + centerPointX - originPointX)/2, (30 - originPointY)/2]);
// Object is scaled, relative to the origin, and flipped on X axis so points are like this now in the scene coordinates:
// -20 -10 0 10 20 30 40 50 60 70 80 90
// |---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
// C (16) O (32) P (41.75)
//
object.setAngle(0);
object.setScale(0.5);
object.flipX(true);
object.flipY(false);
expect((centerPointX - 12.5 + centerPointX - originPointX)/2).to.be(41.75); // Sanity check of the first expected position
expect(object.getHitBoxes()[0].vertices[0]).to.eql([(centerPointX - 12.5 + centerPointX - originPointX)/2, (1 - originPointY)/2]);
expect(object.getHitBoxes()[0].vertices[1]).to.eql([(centerPointX - 41.5 + centerPointX - originPointX)/2, (2 - originPointY)/2]);
expect(object.getHitBoxes()[0].vertices[2]).to.eql([(centerPointX - 55.5 + centerPointX - originPointX)/2, (31 - originPointY)/2]);
expect(object.getHitBoxes()[0].vertices[3]).to.eql([(centerPointX - 24.5 + centerPointX - originPointX)/2, (30 - originPointY)/2]);
// Center is unchanged with flipping
expect(object.getPointX("Center")).to.be(16);
expect(object.getPointY("Center")).to.be(7.5);
// Center is unchanged with flipping
expect(object.getPointX("Center")).to.be(16);
expect(object.getPointY("Center")).to.be(7.5);
// Origin *point* is flipped
expect(object.getPointX("Origin")).to.be(32);
expect(object.getPointY("Origin")).to.be(0);
// Origin *point* is flipped
expect(object.getPointX("Origin")).to.be(32);
expect(object.getPointY("Origin")).to.be(0);
});
it('properly computes hitboxes and point positions after the layer camera has moved', async () => {
const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets();
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
runtimeScene.addLayer({
name: '',
visibility: true,
cameras: [],
effects: [],
ambientLightColorR: 0,
ambientLightColorG: 0,
ambientLightColorB: 0,
isLightingLayer: false,
followBaseLayerCamera: false,
});
const defaultLayer = runtimeScene.getLayer('');
// Create an object with a custom hitbox
const object = makeSpriteRuntimeObjectWithCustomHitBox(runtimeScene);
// Check the hitboxes and positions with default camera position
expect(object.getHitBoxes()[0].vertices[0]).to.eql([12.5 - originPointX, 1 - originPointY]);
expect(object.getHitBoxes()[0].vertices[1]).to.eql([41.5 - originPointX, 2 - originPointY]);
expect(object.getHitBoxes()[0].vertices[2]).to.eql([55.5 - originPointX, 31 - originPointY]);
expect(object.getHitBoxes()[0].vertices[3]).to.eql([24.5 - originPointX, 30 - originPointY]);
expect(object.getPointX("Center")).to.be(32);
expect(object.getPointY("Center")).to.be(15);
expect(object.getPointX("Origin")).to.be(0);
expect(object.getPointY("Origin")).to.be(0);
// Move the layer camera.
defaultLayer.setCameraX(2000);
defaultLayer.setCameraY(4000);
// The object hitboxes and positions stay the same.
expect(object.getHitBoxes()[0].vertices[0]).to.eql([12.5 - originPointX, 1 - originPointY]);
expect(object.getHitBoxes()[0].vertices[1]).to.eql([41.5 - originPointX, 2 - originPointY]);
expect(object.getHitBoxes()[0].vertices[2]).to.eql([55.5 - originPointX, 31 - originPointY]);
expect(object.getHitBoxes()[0].vertices[3]).to.eql([24.5 - originPointX, 30 - originPointY]);
expect(object.getPointX("Center")).to.be(32);
expect(object.getPointY("Center")).to.be(15);
expect(object.getPointX("Origin")).to.be(0);
expect(object.getPointY("Origin")).to.be(0);
});
});

View File

@@ -16,7 +16,7 @@ import EventsFunctionsExtensionsContext from '../EventsFunctionsExtensionsLoader
import { showErrorBox } from '../UI/Messages/MessageBox';
import LinearProgress from '../UI/LinearProgress';
import { AssetStoreContext } from './AssetStoreContext';
import { type OnFetchNewlyAddedResourcesFunction } from '../ProjectsStorage/ResourceFetcher';
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
type Props = {|
assetPack: AssetPack,
@@ -27,7 +27,7 @@ type Props = {|
project: gdProject,
objectsContainer: gdObjectsContainer,
onObjectAddedFromAsset: (object: gdObject) => void,
onFetchNewlyAddedResources: OnFetchNewlyAddedResourcesFunction,
resourceManagementProps: ResourceManagementProps,
|};
export const AssetPackDialog = ({
@@ -39,7 +39,7 @@ export const AssetPackDialog = ({
project,
objectsContainer,
onObjectAddedFromAsset,
onFetchNewlyAddedResources,
resourceManagementProps,
}: Props) => {
const missingAssetShortHeaders = assetShortHeaders.filter(
assetShortHeader => !addedAssetIds.includes(assetShortHeader.id)
@@ -82,7 +82,7 @@ export const AssetPackDialog = ({
});
});
await onFetchNewlyAddedResources();
await resourceManagementProps.onFetchNewlyAddedResources();
setAreAssetsBeingInstalled(false);
onAssetsAdded();
@@ -104,7 +104,7 @@ export const AssetPackDialog = ({
onObjectAddedFromAsset,
onAssetsAdded,
environment,
onFetchNewlyAddedResources,
resourceManagementProps,
]
);

View File

@@ -7,6 +7,7 @@ import GridList from '@material-ui/core/GridList';
import Paper from '@material-ui/core/Paper';
import { CorsAwareImage } from '../UI/CorsAwareImage';
import Text from '../UI/Text';
import PriceTag from '../UI/PriceTag';
import type { AssetPacks, AssetPack } from '../Utils/GDevelopServices/Asset';
import { type PrivateAssetPackListingData } from '../Utils/GDevelopServices/Shop';
import { shouldValidate } from '../UI/KeyboardShortcuts/InteractionKeys';
@@ -21,12 +22,19 @@ const cellSpacing = 2;
const styles = {
grid: { margin: '0 10px' },
priceTagContainer: {
position: 'absolute',
top: 10,
left: 10,
cursor: 'default',
},
previewImage: {
width: '100%',
// Prevent cumulative layout shift by enforcing
// the 16:9 ratio.
aspectRatio: '16 / 9',
objectFit: 'cover',
position: 'relative',
},
cardContainer: {
overflow: 'hidden',
@@ -150,6 +158,9 @@ const PrivateAssetPackTile = ({
src={assetPack.thumbnailUrls[0]}
alt={`Preview image of asset pack ${assetPack.name}`}
/>
<div style={styles.priceTagContainer}>
<PriceTag value={assetPack.prices[0].value} withOverlay />
</div>
<Column>
<Line justifyContent="space-between" noMargin>
<Text style={styles.packTitle} size="body2">

View File

@@ -15,12 +15,7 @@ import HelpButton from '../UI/HelpButton';
import { Column, Line } from '../UI/Grid';
import { Tabs, Tab } from '../UI/Tabs';
import { AssetStore } from '.';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../ResourcesList/ResourceSource';
import { type OnFetchNewlyAddedResourcesFunction } from '../ProjectsStorage/ResourceFetcher';
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
import {
sendAssetAddedToProject,
sendNewObjectCreated,
@@ -71,10 +66,7 @@ type Props = {|
project: gdProject,
layout: ?gdLayout,
objectsContainer: gdObjectsContainer,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
onFetchNewlyAddedResources: OnFetchNewlyAddedResourcesFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
onClose: () => void,
onCreateNewObject: (type: string) => void,
onObjectAddedFromAsset: gdObject => void,
@@ -84,10 +76,7 @@ export default function NewObjectDialog({
project,
layout,
objectsContainer,
resourceSources,
onChooseResource,
onFetchNewlyAddedResources,
resourceExternalEditors,
resourceManagementProps,
onClose,
onCreateNewObject,
onObjectAddedFromAsset,
@@ -180,7 +169,7 @@ export default function NewObjectDialog({
onObjectAddedFromAsset(object);
});
await onFetchNewlyAddedResources();
await resourceManagementProps.onFetchNewlyAddedResources();
} catch (error) {
console.error('Error while installing the asset:', error);
showErrorBox({
@@ -202,7 +191,7 @@ export default function NewObjectDialog({
onObjectAddedFromAsset,
openedAssetShortHeader,
environment,
onFetchNewlyAddedResources,
resourceManagementProps,
]
);
@@ -342,7 +331,7 @@ export default function NewObjectDialog({
project={project}
objectsContainer={objectsContainer}
onObjectAddedFromAsset={onObjectAddedFromAsset}
onFetchNewlyAddedResources={onFetchNewlyAddedResources}
resourceManagementProps={resourceManagementProps}
/>
)}
</>

View File

@@ -9,11 +9,16 @@ import {
import Text from '../UI/Text';
import { t, Trans } from '@lingui/macro';
import Dialog from '../UI/Dialog';
import PriceTag from '../UI/PriceTag';
import TextButton from '../UI/TextButton';
import AlertMessage from '../UI/AlertMessage';
import PlaceholderLoader from '../UI/PlaceholderLoader';
import { ResponsiveLineStackLayout } from '../UI/Layout';
import { Column, LargeSpacer, Line, Spacer } from '../UI/Grid';
import {
ColumnStackLayout,
ResponsiveLineStackLayout,
LineStackLayout,
} from '../UI/Layout';
import { Column, Line } from '../UI/Grid';
import {
getUserPublicProfile,
type UserPublicProfile,
@@ -22,7 +27,7 @@ import PublicProfileDialog from '../Profile/PublicProfileDialog';
import Link from '../UI/Link';
import Mark from '../UI/CustomSvgIcons/Mark';
import Cross from '../UI/CustomSvgIcons/Cross';
import { Paper } from '@material-ui/core';
import Paper from '@material-ui/core/Paper';
import ResponsiveImagesGallery from '../UI/ResponsiveImagesGallery';
import { useResponsiveWindowWidth } from '../UI/Reponsive/ResponsiveWindowMeasurer';
@@ -51,8 +56,12 @@ const contentTypeToMessageDescriptor = {
partial: t`Other`,
};
const styles = {
disabledText: { opacity: 0.6 },
};
const PrivateAssetPackDialog = ({
privateAssetPack: { id, name, description, sellerId },
privateAssetPack: { id, name, description, sellerId, prices },
onClose,
}: Props) => {
const [
@@ -123,9 +132,7 @@ const PrivateAssetPackDialog = ({
fullHeight
>
{errorText ? (
<AlertMessage kind="error">
<Text>{errorText}</Text>
</AlertMessage>
<AlertMessage kind="error">{errorText}</AlertMessage>
) : isFetchingDetails ? (
<>
<Text size="title">{name}</Text>
@@ -165,10 +172,11 @@ const PrivateAssetPackDialog = ({
variant="outlined"
style={{ padding: windowWidth === 'small' ? 20 : 30 }}
>
<Column noMargin>
<LargeSpacer /> {/* To be replaced by prices */}
<ColumnStackLayout noMargin useLargeSpacer>
<Line noMargin>
<PriceTag value={prices[0].value} />
</Line>
<Text noMargin>{assetPackDetails.longDescription}</Text>
<LargeSpacer />
<ResponsiveLineStackLayout noMargin noColumnMargin>
<Column noMargin expand>
<Text size="sub-title">
@@ -194,37 +202,40 @@ const PrivateAssetPackDialog = ({
<Text size="sub-title">
<Trans>Licensing</Trans>
</Text>
<Line noMargin alignItems="center">
<LineStackLayout noMargin alignItems="center">
<Mark fontSize="small" />
<Spacer />
<Text displayInlineAsSpan noMargin>
<Trans>Personal projects</Trans>
</Text>
</Line>
<Line noMargin alignItems="center">
</LineStackLayout>
<LineStackLayout noMargin alignItems="center">
<Mark fontSize="small" />
<Spacer />
<Text displayInlineAsSpan noMargin>
<Trans>Professional projects</Trans>
</Text>
</Line>
<Line noMargin alignItems="center">
</LineStackLayout>
<LineStackLayout noMargin alignItems="center">
<Mark fontSize="small" />
<Spacer />
<Text displayInlineAsSpan noMargin>
<Trans>Asset modification</Trans>
</Text>
</Line>
<Line noMargin alignItems="center">
<Cross fontSize="small" />
<Spacer />
<Text displayInlineAsSpan noMargin>
</LineStackLayout>
<LineStackLayout noMargin alignItems="center">
<Cross
fontSize="small"
style={styles.disabledText}
/>
<Text
displayInlineAsSpan
noMargin
style={styles.disabledText}
>
<Trans>Redistribution &amp; reselling</Trans>
</Text>
</Line>
</LineStackLayout>
</Column>
</ResponsiveLineStackLayout>
</Column>
</ColumnStackLayout>
</Paper>
</Column>
</ResponsiveLineStackLayout>

View File

@@ -34,6 +34,8 @@ import { AssetDetails } from './AssetDetails';
import PlaceholderLoader from '../UI/PlaceholderLoader';
import Home from '@material-ui/icons/Home';
import PrivateAssetPackDialog from './PrivateAssetPackDialog';
import PlaceholderError from '../UI/PlaceholderError';
import AlertMessage from '../UI/AlertMessage';
type Props = {|
project: gdProject,
@@ -265,9 +267,9 @@ export const AssetStore = ({ project }: Props) => {
)}
</Background>
)}
{isOnHomePage && !(assetPacks && privateAssetPacks) && (
<PlaceholderLoader />
)}
{isOnHomePage &&
!(assetPacks && privateAssetPacks) &&
!error && <PlaceholderLoader />}
{isOnHomePage &&
assetPacks &&
privateAssetPacks &&
@@ -300,6 +302,16 @@ export const AssetStore = ({ project }: Props) => {
}
/>
)}
{isOnHomePage && error && (
<PlaceholderError onRetry={fetchAssetsAndFilters}>
<AlertMessage kind="error">
<Trans>
An error occurred when fetching the asset store content.
Please try again later.
</Trans>
</AlertMessage>
</PlaceholderError>
)}
{openedAssetShortHeader && (
<AssetDetails
project={project}

View File

@@ -1,9 +1,5 @@
// @flow
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../../ResourcesList/ResourceSource';
/**
* The props given to any behavior editor
@@ -12,7 +8,5 @@ export type BehaviorEditorProps = {|
behavior: gdBehavior,
project: gdProject,
object: gdObject,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
|};

View File

@@ -292,9 +292,7 @@ const Physics2Editor = (props: Props) => {
</Trans>
}
project={props.project}
resourceSources={props.resourceSources}
onChooseResource={props.onChooseResource}
resourceExternalEditors={props.resourceExternalEditors}
resourceManagementProps={props.resourceManagementProps}
resourcesLoader={resourcesLoader}
resourceKind={'image'}
initialResourceName={''}

View File

@@ -14,11 +14,7 @@ import BehaviorsEditorService from './BehaviorsEditorService';
import Window from '../Utils/Window';
import { Column, Line } from '../UI/Grid';
import RaisedButton from '../UI/RaisedButton';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
import DismissableTutorialMessage from '../Hints/DismissableTutorialMessage';
import { ColumnStackLayout } from '../UI/Layout';
import useForceUpdate from '../Utils/UseForceUpdate';
@@ -41,9 +37,7 @@ type Props = {|
object: gdObject,
onUpdateBehaviorsSharedData: () => void,
onSizeUpdated?: ?() => void,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
|};
const BehaviorsEditor = (props: Props) => {
@@ -256,10 +250,8 @@ const BehaviorsEditor = (props: Props) => {
behavior={behavior}
project={project}
object={object}
resourceSources={props.resourceSources}
onChooseResource={props.onChooseResource}
resourceExternalEditors={
props.resourceExternalEditors
resourceManagementProps={
props.resourceManagementProps
}
/>
</Line>

View File

@@ -26,11 +26,7 @@ import {
type EnumeratedEffectMetadata,
setEffectDefaultParameters,
} from './EnumerateEffects';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
import ScrollView from '../UI/ScrollView';
import { EmptyPlaceholder } from '../UI/EmptyPlaceholder';
import {
@@ -59,9 +55,7 @@ const styles = {
type Props = {|
project: gdProject,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
effectsContainer: gdEffectsContainer,
onEffectsUpdated: () => void,
target: 'object' | 'layer',
@@ -367,14 +361,8 @@ export default function EffectsList(props: Props) {
effectMetadata.parametersSchema
}
project={props.project}
resourceSources={
props.resourceSources
}
onChooseResource={
props.onChooseResource
}
resourceExternalEditors={
props.resourceExternalEditors
resourceManagementProps={
props.resourceManagementProps
}
renderExtraDescriptionText={
showEffectParameterNames

View File

@@ -12,13 +12,11 @@ import type { ObjectWithContext } from '../ObjectsList/EnumerateObjects';
import Window from '../Utils/Window';
import ObjectEditorDialog from '../ObjectEditor/ObjectEditorDialog';
import { type ObjectEditorTab } from '../ObjectEditor/ObjectEditorDialog';
import { type OnFetchNewlyAddedResourcesFunction } from '../ProjectsStorage/ResourceFetcher';
const gd: libGDevelop = global.gd;
type Props = {|
project: gdProject,
onFetchNewlyAddedResources: OnFetchNewlyAddedResourcesFunction,
globalObjectsContainer: gdObjectsContainer,
eventsFunctionsExtension: gdEventsFunctionsExtension,
eventsBasedObject: gdEventsBasedObject,
@@ -212,9 +210,12 @@ export default class EventBasedObjectChildrenEditor extends React.Component<
objectsContainer={eventsBasedObject}
layout={null}
// TODO EBO Allow to use project resources as place holders
resourceSources={[]}
resourceExternalEditors={[]}
onChooseResource={() => Promise.resolve([])}
resourceManagementProps={{
resourceSources: [],
resourceExternalEditors: [],
onChooseResource: async () => [],
onFetchNewlyAddedResources: async () => {},
}}
selectedObjectNames={this.state.selectedObjectNames}
onEditObject={this.editObject}
onDeleteObject={this._onDeleteObject(i18n)}
@@ -243,9 +244,6 @@ export default class EventBasedObjectChildrenEditor extends React.Component<
launchProjectDataOnlyPreview: () => {},
launchProjectWithLoadingScreenPreview: () => {},
}}
onFetchNewlyAddedResources={
this.props.onFetchNewlyAddedResources
}
/>
</Line>
{this.state.editedObjectWithContext && (
@@ -254,9 +252,12 @@ export default class EventBasedObjectChildrenEditor extends React.Component<
object={this.state.editedObjectWithContext.object}
initialTab={this.state.editedObjectInitialTab}
project={project}
resourceSources={[]}
resourceExternalEditors={[]}
onChooseResource={() => Promise.resolve([])}
resourceManagementProps={{
resourceSources: [],
resourceExternalEditors: [],
onChooseResource: async () => [],
onFetchNewlyAddedResources: async () => {},
}}
onComputeAllVariableNames={() => {
return [];
// TODO EBO Find undeclared variables in the parent events.

View File

@@ -4,12 +4,10 @@ import * as React from 'react';
import Dialog, { DialogPrimaryButton } from '../UI/Dialog';
import EventsBasedObjectEditor from './index';
import HelpButton from '../UI/HelpButton';
import { type OnFetchNewlyAddedResourcesFunction } from '../ProjectsStorage/ResourceFetcher';
type Props = {|
onApply: () => void,
project: gdProject,
onFetchNewlyAddedResources: OnFetchNewlyAddedResourcesFunction,
globalObjectsContainer: gdObjectsContainer,
eventsFunctionsExtension: gdEventsFunctionsExtension,
eventsBasedObject: gdEventsBasedObject,
@@ -55,7 +53,6 @@ export default class EventsBasedObjectEditorDialog extends React.Component<
>
<EventsBasedObjectEditor
project={project}
onFetchNewlyAddedResources={this.props.onFetchNewlyAddedResources}
globalObjectsContainer={globalObjectsContainer}
eventsFunctionsExtension={eventsFunctionsExtension}
eventsBasedObject={eventsBasedObject}

View File

@@ -12,16 +12,13 @@ import EventsBasedObjectPropertiesEditor from './EventsBasedObjectPropertiesEdit
import EventBasedObjectChildrenEditor from './EventBasedObjectChildrenEditor';
import { ColumnStackLayout } from '../UI/Layout';
import { Line } from '../UI/Grid';
import { type OnFetchNewlyAddedResourcesFunction } from '../ProjectsStorage/ResourceFetcher';
import { showWarningBox } from '../UI/Messages/MessageBox';
const gd: libGDevelop = global.gd;
type TabName = 'configuration' | 'properties' | 'children';
type Props = {|
project: gdProject,
onFetchNewlyAddedResources: OnFetchNewlyAddedResourcesFunction,
globalObjectsContainer: gdObjectsContainer,
eventsFunctionsExtension: gdEventsFunctionsExtension,
eventsBasedObject: gdEventsBasedObject,
@@ -164,7 +161,6 @@ export default class EventsBasedObjectEditor extends React.Component<
{currentTab === 'children' && (
<EventBasedObjectChildrenEditor
project={project}
onFetchNewlyAddedResources={this.props.onFetchNewlyAddedResources}
globalObjectsContainer={globalObjectsContainer}
eventsFunctionsExtension={eventsFunctionsExtension}
eventsBasedObject={eventsBasedObject}

View File

@@ -224,10 +224,6 @@ export const ExtensionOptionsEditor = ({
text: 'Camera',
value: 'Camera',
},
{
text: 'Device',
value: 'Device',
},
{
text: 'Input',
value: 'Input',

View File

@@ -19,11 +19,7 @@ import OptionsEditorDialog from './OptionsEditorDialog';
import { showWarningBox } from '../UI/Messages/MessageBox';
import EventsBasedBehaviorEditorDialog from '../EventsBasedBehaviorEditor/EventsBasedBehaviorEditorDialog';
import EventsBasedObjectEditorDialog from '../EventsBasedObjectEditor/EventsBasedObjectEditorDialog';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
import BehaviorMethodSelectorDialog from './BehaviorMethodSelectorDialog';
import ObjectMethodSelectorDialog from './ObjectMethodSelectorDialog';
import ExtensionFunctionSelectorDialog from './ExtensionFunctionSelectorDialog';
@@ -47,7 +43,6 @@ import PreferencesContext from '../MainFrame/Preferences/PreferencesContext';
import { ParametersIndexOffsets } from '../EventsFunctionsExtensionsLoader';
import { sendEventsExtractedAsFunction } from '../Utils/Analytics/EventSender';
import Window from '../Utils/Window';
import { type OnFetchNewlyAddedResourcesFunction } from '../ProjectsStorage/ResourceFetcher';
const gd: libGDevelop = global.gd;
const isDev = Window.isDev();
@@ -56,10 +51,7 @@ type Props = {|
project: gdProject,
eventsFunctionsExtension: gdEventsFunctionsExtension,
setToolbar: (?React.Node) => void,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
onFetchNewlyAddedResources: OnFetchNewlyAddedResourcesFunction,
resourceManagementProps: ResourceManagementProps,
openInstructionOrExpression: (
extension: gdPlatformExtension,
type: string
@@ -1095,9 +1087,7 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
events={selectedEventsFunction.getEvents()}
onOpenExternalEvents={() => {}}
onOpenLayout={() => {}}
resourceSources={this.props.resourceSources}
onChooseResource={this.props.onChooseResource}
resourceExternalEditors={this.props.resourceExternalEditors}
resourceManagementProps={this.props.resourceManagementProps}
openInstructionOrExpression={
this.props.openInstructionOrExpression
}
@@ -1488,7 +1478,6 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
{editedEventsBasedObject && this._globalObjectsContainer && (
<EventsBasedObjectEditorDialog
project={project}
onFetchNewlyAddedResources={this.props.onFetchNewlyAddedResources}
globalObjectsContainer={this._globalObjectsContainer}
eventsFunctionsExtension={eventsFunctionsExtension}
eventsBasedObject={editedEventsBasedObject}

View File

@@ -2,11 +2,7 @@
import * as React from 'react';
import InlinePopover from './InlinePopover';
import ParameterRenderingService from './ParameterRenderingService';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
import { type EventsScope } from '../InstructionOrExpression/EventsScope.flow';
import { setupInstructionParameters } from '../InstructionOrExpression/SetupInstructionParameters';
import { getObjectParameterIndex } from '../InstructionOrExpression/EnumerateInstructions';
@@ -29,9 +25,7 @@ type Props = {|
anchorEl: ?any,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
|};
type State = {|
@@ -164,9 +158,7 @@ export default class InlineParameterEditor extends React.Component<
ref={field => (this._field = field)}
parameterRenderingService={ParameterRenderingService}
isInline
resourceSources={this.props.resourceSources}
onChooseResource={this.props.onChooseResource}
resourceExternalEditors={this.props.resourceExternalEditors}
resourceManagementProps={this.props.resourceManagementProps}
/>
</InlinePopover>
);

View File

@@ -5,11 +5,7 @@ import * as React from 'react';
import Dialog, { DialogPrimaryButton } from '../../UI/Dialog';
import FlatButton from '../../UI/FlatButton';
import InstructionEditor from '.';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../../ResourcesList/ResourceSource';
import { type EventsScope } from '../../InstructionOrExpression/EventsScope.flow';
type Props = {|
@@ -19,9 +15,7 @@ type Props = {|
objectsContainer: gdObjectsContainer,
instruction: gdInstruction,
isCondition: boolean,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
style?: Object,
isNewInstruction: boolean,
onCancel: () => void,

View File

@@ -10,11 +10,7 @@ import { mapFor } from '../../Utils/MapFor';
import EmptyMessage from '../../UI/EmptyMessage';
import ParameterRenderingService from '../ParameterRenderingService';
import HelpButton from '../../UI/HelpButton';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../../ResourcesList/ResourceSource';
import { Column, Line, Spacer } from '../../UI/Grid';
import AlertMessage from '../../UI/AlertMessage';
import DismissableAlertMessage from '../../UI/DismissableAlertMessage';
@@ -74,9 +70,7 @@ type Props = {|
instruction: gdInstruction,
isCondition: boolean,
focusOnMount?: boolean,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
style?: Object,
openInstructionOrExpression: (
extension: gdPlatformExtension,
@@ -116,9 +110,7 @@ const InstructionParametersEditor = React.forwardRef<
focusOnMount,
style,
openInstructionOrExpression,
resourceSources,
onChooseResource,
resourceExternalEditors,
resourceManagementProps,
},
ref
) => {
@@ -373,9 +365,7 @@ const InstructionParametersEditor = React.forwardRef<
objectsContainer={objectsContainer}
key={i}
parameterRenderingService={ParameterRenderingService}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
ref={field => {
if (isFirstVisibleParameterField) {
firstVisibleField.current = field;

View File

@@ -4,11 +4,7 @@ import { Trans } from '@lingui/macro';
import * as React from 'react';
import Dialog, { DialogPrimaryButton } from '../../UI/Dialog';
import FlatButton from '../../UI/FlatButton';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../../ResourcesList/ResourceSource';
import InstructionParametersEditor, {
type InstructionParametersEditorInterface,
} from './InstructionParametersEditor';
@@ -57,9 +53,7 @@ type Props = {|
objectsContainer: gdObjectsContainer,
instruction: gdInstruction,
isCondition: boolean,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
style?: Object,
isNewInstruction: boolean,
onCancel: () => void,
@@ -102,9 +96,7 @@ export default function NewInstructionEditorDialog({
isNewInstruction,
scope,
onSubmit,
resourceSources,
onChooseResource,
resourceExternalEditors,
resourceManagementProps,
openInstructionOrExpression,
}: Props) {
const forceUpdate = useForceUpdate();
@@ -262,9 +254,7 @@ export default function NewInstructionEditorDialog({
objectName={chosenObjectName}
isCondition={isCondition}
instruction={instruction}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
openInstructionOrExpression={openInstructionOrExpression}
ref={instructionParametersEditor}
focusOnMount={!!instructionType}

View File

@@ -2,11 +2,7 @@
import { Trans } from '@lingui/macro';
import Popover from '@material-ui/core/Popover';
import * as React from 'react';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../../ResourcesList/ResourceSource';
import {
useNewInstructionEditor,
getInstructionMetadata,
@@ -42,9 +38,7 @@ type Props = {|
objectsContainer: gdObjectsContainer,
instruction: gdInstruction,
isCondition: boolean,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
style?: Object,
anchorEl: ?HTMLElement,
isNewInstruction: boolean,

View File

@@ -5,11 +5,7 @@ import InstructionSelector from './InstructionOrExpressionSelector/InstructionSe
import InstructionParametersEditor, {
type InstructionParametersEditorInterface,
} from './InstructionParametersEditor';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../../ResourcesList/ResourceSource';
import { type EventsScope } from '../../InstructionOrExpression/EventsScope.flow';
const styles = {
@@ -31,9 +27,7 @@ type Props = {|
objectsContainer: gdObjectsContainer,
instruction: gdInstruction,
isCondition: boolean,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
style?: Object,
openInstructionOrExpression: (
extension: gdPlatformExtension,
@@ -83,9 +77,7 @@ export default class InstructionEditor extends React.Component<Props, State> {
objectsContainer={objectsContainer}
isCondition={isCondition}
instruction={instruction}
resourceSources={this.props.resourceSources}
onChooseResource={this.props.onChooseResource}
resourceExternalEditors={this.props.resourceExternalEditors}
resourceManagementProps={this.props.resourceManagementProps}
openInstructionOrExpression={this.props.openInstructionOrExpression}
ref={instructionParametersEditor =>
(this._instructionParametersEditor = instructionParametersEditor)

View File

@@ -17,14 +17,9 @@ export default class AudioResourceField extends Component<
}
render() {
if (
!this.props.resourceSources ||
!this.props.onChooseResource ||
!this.props.resourceExternalEditors ||
!this.props.project
) {
if (!this.props.resourceManagementProps || !this.props.project) {
console.error(
'Missing project, resourceSources, onChooseResource or resourceExternalEditors for AudioResourceField'
'Missing project or resourceManagementProps for AudioResourceField'
);
return null;
}
@@ -33,9 +28,7 @@ export default class AudioResourceField extends Component<
<ResourceSelector
margin={this.props.isInline ? 'none' : 'dense'}
project={this.props.project}
resourceSources={this.props.resourceSources}
onChooseResource={this.props.onChooseResource}
resourceExternalEditors={this.props.resourceExternalEditors}
resourceManagementProps={this.props.resourceManagementProps}
resourcesLoader={ResourcesLoader}
resourceKind="audio"
fullWidth

View File

@@ -17,14 +17,9 @@ export default class FontResourceField extends Component<
}
render() {
if (
!this.props.resourceSources ||
!this.props.onChooseResource ||
!this.props.resourceExternalEditors ||
!this.props.project
) {
if (!this.props.resourceManagementProps || !this.props.project) {
console.error(
'Missing project, resourceSources, onChooseResource or resourceExternalEditors for BitmapFontResourceField'
'Missing project or resourceManagementProps for BitmapFontResourceField'
);
return null;
}
@@ -33,9 +28,7 @@ export default class FontResourceField extends Component<
<ResourceSelector
margin={this.props.isInline ? 'none' : 'dense'}
project={this.props.project}
resourceSources={this.props.resourceSources}
onChooseResource={this.props.onChooseResource}
resourceExternalEditors={this.props.resourceExternalEditors}
resourceManagementProps={this.props.resourceManagementProps}
resourcesLoader={ResourcesLoader}
resourceKind="bitmapFont"
fullWidth

View File

@@ -17,14 +17,9 @@ export default class BitmapFontResourceField extends Component<
}
render() {
if (
!this.props.resourceSources ||
!this.props.onChooseResource ||
!this.props.resourceExternalEditors ||
!this.props.project
) {
if (!this.props.resourceManagementProps || !this.props.project) {
console.error(
'Missing project, resourceSources, onChooseResource or resourceExternalEditors for FontResourceField'
'Missing project or resourceManagementProps for FontResourceField'
);
return null;
}
@@ -33,9 +28,7 @@ export default class BitmapFontResourceField extends Component<
<ResourceSelector
margin={this.props.isInline ? 'none' : 'dense'}
project={this.props.project}
resourceSources={this.props.resourceSources}
onChooseResource={this.props.onChooseResource}
resourceExternalEditors={this.props.resourceExternalEditors}
resourceManagementProps={this.props.resourceManagementProps}
resourcesLoader={ResourcesLoader}
resourceKind="font"
fullWidth

View File

@@ -23,14 +23,9 @@ const ImageResourceField = React.forwardRef<
focus,
}));
if (
!props.resourceSources ||
!props.onChooseResource ||
!props.resourceExternalEditors ||
!props.project
) {
if (!props.resourceManagementProps || !props.project) {
console.error(
'Missing project, resourceSources, onChooseResource or resourceExternalEditors for ImageResourceField'
'Missing project or resourceManagementProps for ImageResourceField'
);
return null;
}
@@ -39,9 +34,7 @@ const ImageResourceField = React.forwardRef<
<ResourceSelector
margin={props.isInline ? 'none' : 'dense'}
project={props.project}
resourceSources={props.resourceSources}
onChooseResource={props.onChooseResource}
resourceExternalEditors={props.resourceExternalEditors}
resourceManagementProps={props.resourceManagementProps}
resourcesLoader={ResourcesLoader}
resourceKind="image"
fullWidth

View File

@@ -17,14 +17,9 @@ export default class JsonResourceField extends Component<
}
render() {
if (
!this.props.resourceSources ||
!this.props.onChooseResource ||
!this.props.resourceExternalEditors ||
!this.props.project
) {
if (!this.props.resourceManagementProps || !this.props.project) {
console.error(
'Missing project, resourceSources, onChooseResource or resourceExternalEditors for JsonResourceField'
'Missing project or resourceManagementProps for JsonResourceField'
);
return null;
}
@@ -33,9 +28,7 @@ export default class JsonResourceField extends Component<
<ResourceSelector
margin={this.props.isInline ? 'none' : 'dense'}
project={this.props.project}
resourceSources={this.props.resourceSources}
onChooseResource={this.props.onChooseResource}
resourceExternalEditors={this.props.resourceExternalEditors}
resourceManagementProps={this.props.resourceManagementProps}
resourcesLoader={ResourcesLoader}
resourceKind="json"
fullWidth

View File

@@ -1,9 +1,5 @@
// @flow
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../../ResourcesList/ResourceSource';
import { type EventsScope } from '../../InstructionOrExpression/EventsScope.flow';
import { type MessageDescriptor } from '../../Utils/i18n/MessageDescriptor.flow';
@@ -27,9 +23,7 @@ type CommonProps = {|
isInline?: boolean,
onRequestClose?: () => void,
onApply?: () => void,
resourceSources?: Array<ResourceSource>,
onChooseResource?: ChooseResourceFunction,
resourceExternalEditors?: Array<ResourceExternalEditor>,
resourceManagementProps?: ResourceManagementProps,
// Pass the ParameterRenderingService to allow to render nested parameters
parameterRenderingService?: ParameterRenderingServiceType,

View File

@@ -17,14 +17,9 @@ export default class VideoResourceField extends Component<
}
render() {
if (
!this.props.resourceSources ||
!this.props.onChooseResource ||
!this.props.resourceExternalEditors ||
!this.props.project
) {
if (!this.props.resourceManagementProps || !this.props.project) {
console.error(
'Missing project, resourceSources, onChooseResource or resourceExternalEditors for VideoResourceField'
'Missing project or resourceManagementProps for VideoResourceField'
);
return null;
}
@@ -33,9 +28,7 @@ export default class VideoResourceField extends Component<
<ResourceSelector
margin={this.props.isInline ? 'none' : 'dense'}
project={this.props.project}
resourceSources={this.props.resourceSources}
onChooseResource={this.props.onChooseResource}
resourceExternalEditors={this.props.resourceExternalEditors}
resourceManagementProps={this.props.resourceManagementProps}
resourcesLoader={ResourcesLoader}
resourceKind="video"
fullWidth

View File

@@ -57,11 +57,7 @@ import EventsContextAnalyzerDialog, {
toEventsContextResult,
} from './EventsContextAnalyzerDialog';
import SearchPanel, { type SearchPanelInterface } from './SearchPanel';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
import EventsSearcher, {
type ReplaceInEventsInputs,
type SearchInEventsInputs,
@@ -121,9 +117,7 @@ type Props = {|
onOpenSettings?: ?() => void,
onOpenExternalEvents: string => void,
onOpenLayout: string => void,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
openInstructionOrExpression: (
extension: gdPlatformExtension,
type: string
@@ -1578,9 +1572,7 @@ export class EventsSheetComponentWithoutHandle extends React.Component<
ensureSingleOnceInstructions(instrsList);
if (this._eventsTree) this._eventsTree.forceEventsUpdate();
}}
resourceSources={this.props.resourceSources}
onChooseResource={this.props.onChooseResource}
resourceExternalEditors={this.props.resourceExternalEditors}
resourceManagementProps={this.props.resourceManagementProps}
openInstructionOrExpression={(extension, type) => {
this.closeInstructionEditor();
this.props.openInstructionOrExpression(extension, type);
@@ -1648,9 +1640,7 @@ export class EventsSheetComponentWithoutHandle extends React.Component<
globalObjectsContainer,
objectsContainer,
preferences,
resourceSources,
onChooseResource,
resourceExternalEditors,
resourceManagementProps,
onCreateEventsFunction,
tutorials,
} = this.props;
@@ -1807,9 +1797,7 @@ export class EventsSheetComponentWithoutHandle extends React.Component<
if (this._searchPanel)
this._searchPanel.markSearchResultsDirty();
}}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
/>
<ContextMenu
ref={eventContextMenu =>

View File

@@ -273,13 +273,16 @@ describe('EnumerateExpressions', () => {
});
// Check that some behavior expressions are there
expect(generalTreeNode).toHaveProperty('Platform behavior');
const movementTreeNode: TreeNode<EnumeratedExpressionMetadata> =
// $FlowFixMe
allExpressionsTree['Movement'];
expect(movementTreeNode).toHaveProperty('Platform behavior');
// $FlowFixMe
expect(generalTreeNode['Platform behavior']).toMatchObject({
expect(movementTreeNode['Platform behavior']).toMatchObject({
Options: {
'Maximum horizontal speed': {
displayedName: 'Maximum horizontal speed',
fullGroupName: 'General/Platform behavior/Options',
fullGroupName: 'Movement/Platform behavior/Options',
iconFilename: 'CppPlatform/Extensions/platformerobjecticon.png',
isPrivate: false,
name: 'MaxSpeed',

View File

@@ -14,11 +14,7 @@ import { useSerializableObjectCancelableEditor } from '../Utils/SerializableObje
import DismissableAlertMessage from '../UI/DismissableAlertMessage';
import Text from '../UI/Text';
import useForceUpdate from '../Utils/UseForceUpdate';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
import HotReloadPreviewButton, {
type HotReloadPreviewButtonProps,
} from '../HotReload/HotReloadPreviewButton';
@@ -30,9 +26,7 @@ const gd: libGDevelop = global.gd;
type Props = {|
project: gdProject,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
layer: gdLayer,
initialInstances: gdInitialInstancesContainer,
@@ -200,9 +194,7 @@ const LayerEditorDialog = (props: Props) => {
<EffectsList
target="layer"
project={props.project}
resourceSources={props.resourceSources}
onChooseResource={props.onChooseResource}
resourceExternalEditors={props.resourceExternalEditors}
resourceManagementProps={props.resourceManagementProps}
effectsContainer={layer.getEffects()}
onEffectsUpdated={
forceUpdate /*Force update to ensure dialog is properly positioned*/

View File

@@ -8,11 +8,7 @@ import LayerRow from './LayerRow';
import BackgroundColorRow from './BackgroundColorRow';
import { Column, Line } from '../UI/Grid';
import Add from '@material-ui/icons/Add';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
import { type UnsavedChanges } from '../MainFrame/UnsavedChangesContext';
import ScrollView from '../UI/ScrollView';
import { FullSizeMeasurer } from '../UI/FullSizeMeasurer';
@@ -112,9 +108,7 @@ const SortableLayersListBody = SortableContainer(LayersListBody);
type Props = {|
project: gdProject,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
layersContainer: gdLayout,
onEditLayerEffects: (layer: ?gdLayer) => void,
onEditLayer: (layer: ?gdLayer) => void,

View File

@@ -1,17 +1,12 @@
// @flow
import * as React from 'react';
import { type UnsavedChanges } from '../UnsavedChangesContext';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../../ResourcesList/ResourceSource';
import { type ResourceManagementProps } from '../../ResourcesList/ResourceSource';
import type { StorageProvider } from '../../ProjectsStorage';
import { type PreviewDebuggerServer } from '../../Export/PreviewLauncher.flow';
import { type HotReloadPreviewButtonProps } from '../../HotReload/HotReloadPreviewButton';
import { type ResourceExternalEditor } from '../../ResourcesList/ResourceExternalEditor.flow';
import { type FileMetadataAndStorageProviderName } from '../../ProjectsStorage';
import { type ExampleShortHeader } from '../../Utils/GDevelopServices/Example';
import { type OnFetchNewlyAddedResourcesFunction } from '../../ProjectsStorage/ResourceFetcher';
export type EditorContainerExtraProps = {|
// Events function extension editor
@@ -32,10 +27,7 @@ export type RenderEditorContainerProps = {|
extraEditorProps: ?EditorContainerExtraProps,
// Resources:
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
onFetchNewlyAddedResources: OnFetchNewlyAddedResourcesFunction,
resourceManagementProps: ResourceManagementProps,
unsavedChanges: ?UnsavedChanges,

View File

@@ -83,9 +83,7 @@ export class EventsEditorContainer extends React.Component<RenderEditorContainer
ref={editor => (this.editor = editor)}
setToolbar={this.props.setToolbar}
onOpenLayout={this.props.onOpenLayout}
resourceSources={this.props.resourceSources}
onChooseResource={this.props.onChooseResource}
resourceExternalEditors={this.props.resourceExternalEditors}
resourceManagementProps={this.props.resourceManagementProps}
openInstructionOrExpression={this.props.openInstructionOrExpression}
onCreateEventsFunction={this.onCreateEventsFunction}
onBeginCreateEventsFunction={this.onBeginCreateEventsFunction}

View File

@@ -133,10 +133,7 @@ export class EventsFunctionsExtensionEditorContainer extends React.Component<Ren
project={project}
eventsFunctionsExtension={eventsFunctionsExtension}
setToolbar={this.props.setToolbar}
resourceSources={this.props.resourceSources}
onChooseResource={this.props.onChooseResource}
resourceExternalEditors={this.props.resourceExternalEditors}
onFetchNewlyAddedResources={this.props.onFetchNewlyAddedResources}
resourceManagementProps={this.props.resourceManagementProps}
openInstructionOrExpression={this.props.openInstructionOrExpression}
onCreateEventsFunction={this.props.onCreateEventsFunction}
initiallyFocusedFunctionName={initiallyFocusedFunctionName}

View File

@@ -143,9 +143,7 @@ export class ExternalEventsEditorContainer extends React.Component<
ref={editor => (this.editor = editor)}
setToolbar={this.props.setToolbar}
onOpenLayout={this.props.onOpenLayout}
resourceSources={this.props.resourceSources}
onChooseResource={this.props.onChooseResource}
resourceExternalEditors={this.props.resourceExternalEditors}
resourceManagementProps={this.props.resourceManagementProps}
openInstructionOrExpression={this.props.openInstructionOrExpression}
onCreateEventsFunction={this.onCreateEventsFunction}
onBeginCreateEventsFunction={this.onBeginCreateEventsFunction}

View File

@@ -165,10 +165,7 @@ export class ExternalLayoutEditorContainer extends React.Component<
{layout && (
<SceneEditor
setToolbar={this.props.setToolbar}
resourceSources={this.props.resourceSources}
onChooseResource={this.props.onChooseResource}
resourceExternalEditors={this.props.resourceExternalEditors}
onFetchNewlyAddedResources={this.props.onFetchNewlyAddedResources}
resourceManagementProps={this.props.resourceManagementProps}
unsavedChanges={this.props.unsavedChanges}
hotReloadPreviewButtonProps={this.props.hotReloadPreviewButtonProps}
ref={editor => (this.editor = editor)}

View File

@@ -50,8 +50,7 @@ export class ResourcesEditorContainer extends React.Component<RenderEditorContai
setToolbar={this.props.setToolbar}
onDeleteResource={this.props.onDeleteResource}
onRenameResource={this.props.onRenameResource}
resourceSources={this.props.resourceSources}
onChooseResource={this.props.onChooseResource}
resourceManagementProps={this.props.resourceManagementProps}
ref={editor => (this.editor = editor)}
project={project}
/>

View File

@@ -83,10 +83,7 @@ export class SceneEditorContainer extends React.Component<RenderEditorContainerP
return (
<SceneEditor
setToolbar={this.props.setToolbar}
resourceSources={this.props.resourceSources}
onChooseResource={this.props.onChooseResource}
resourceExternalEditors={this.props.resourceExternalEditors}
onFetchNewlyAddedResources={this.props.onFetchNewlyAddedResources}
resourceManagementProps={this.props.resourceManagementProps}
unsavedChanges={this.props.unsavedChanges}
ref={editor => (this.editor = editor)}
project={project}

View File

@@ -515,6 +515,9 @@ const MainFrame = (props: Props) => {
if (initialDialog === 'subscription') {
openSubscriptionDialog(true);
}
if (initialDialog === 'onboarding') {
openOnboardingDialog(true);
}
},
[initialDialog]
);
@@ -2177,17 +2180,18 @@ const MainFrame = (props: Props) => {
return true;
};
const onChooseResource: ChooseResourceFunction = (
options: ChooseResourceOptions
) => {
return new Promise(resolve => {
setChooseResourceOptions(options);
const onResourceChosenSetter: () => (
Promise<Array<gdResource>> | Array<gdResource>
) => void = () => resolve;
setOnResourceChosen(onResourceChosenSetter);
});
};
const onChooseResource: ChooseResourceFunction = React.useCallback(
(options: ChooseResourceOptions) => {
return new Promise(resolve => {
setChooseResourceOptions(options);
const onResourceChosenSetter: () => (
Promise<Array<gdResource>> | Array<gdResource>
) => void = () => resolve;
setOnResourceChosen(onResourceChosenSetter);
});
},
[setOnResourceChosen, setChooseResourceOptions]
);
const setElectronUpdateStatus = (updateStatus: ElectronUpdateStatus) => {
setState(state => ({ ...state, updateStatus }));
@@ -2423,6 +2427,21 @@ const MainFrame = (props: Props) => {
),
});
const resourceManagementProps = React.useMemo(
() => ({
resourceSources,
onChooseResource,
resourceExternalEditors,
onFetchNewlyAddedResources,
}),
[
resourceSources,
onChooseResource,
resourceExternalEditors,
onFetchNewlyAddedResources,
]
);
const showLoader = isLoadingProject || previewLoading;
return (
@@ -2519,9 +2538,7 @@ const MainFrame = (props: Props) => {
freezeUpdate={!projectManagerOpen}
unsavedChanges={unsavedChanges}
hotReloadPreviewButtonProps={hotReloadPreviewButtonProps}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
/>
)}
{!state.currentProject && (
@@ -2600,10 +2617,7 @@ const MainFrame = (props: Props) => {
openEventsEditor: true,
openSceneEditor: false,
}),
resourceSources: props.resourceSources,
onChooseResource,
resourceExternalEditors,
onFetchNewlyAddedResources,
resourceManagementProps,
onCreateEventsFunction,
openInstructionOrExpression,
unsavedChanges: unsavedChanges,
@@ -2710,9 +2724,7 @@ const MainFrame = (props: Props) => {
open
onApply={() => openPlatformSpecificAssetsDialog(false)}
onClose={() => openPlatformSpecificAssetsDialog(false)}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
/>
)}
{!!renderPreviewLauncher &&

View File

@@ -32,9 +32,7 @@ const CustomObjectPropertiesEditor = (props: Props) => {
const {
objectConfiguration,
project,
resourceSources,
onChooseResource,
resourceExternalEditors,
resourceManagementProps,
unsavedChanges,
} = props;
@@ -91,9 +89,7 @@ const CustomObjectPropertiesEditor = (props: Props) => {
schema={propertiesSchema}
instances={[customObjectConfiguration]}
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
/>
{eventBasedObject &&
mapFor(0, eventBasedObject.getObjectsCount(), i => {
@@ -163,10 +159,8 @@ const CustomObjectPropertiesEditor = (props: Props) => {
<EditorComponent
objectConfiguration={childObjectConfiguration}
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={
resourceExternalEditors
resourceManagementProps={
resourceManagementProps
}
onSizeUpdated={
forceUpdate /*Force update to ensure dialog is properly positionned*/

View File

@@ -1,9 +1,5 @@
// @flow
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../../ResourcesList/ResourceSource';
import { type UnsavedChanges } from '../../MainFrame/UnsavedChangesContext';
/**
@@ -12,9 +8,7 @@ import { type UnsavedChanges } from '../../MainFrame/UnsavedChangesContext';
export type EditorProps = {|
objectConfiguration: gdObjectConfiguration,
project: gdProject,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
onSizeUpdated: () => void,
objectName: string,
unsavedChanges?: UnsavedChanges,

View File

@@ -22,9 +22,7 @@ const ObjectPropertiesEditor = (props: Props) => {
const {
objectConfiguration,
project,
resourceSources,
onChooseResource,
resourceExternalEditors,
resourceManagementProps,
unsavedChanges,
} = props;
@@ -79,9 +77,7 @@ const ObjectPropertiesEditor = (props: Props) => {
schema={propertiesSchema}
instances={[objectConfigurationAsGd]}
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
/>
</React.Fragment>
) : (

View File

@@ -17,9 +17,7 @@ export default class PanelSpriteEditor extends React.Component<
const {
objectConfiguration,
project,
resourceSources,
onChooseResource,
resourceExternalEditors,
resourceManagementProps,
} = this.props;
const panelSpriteConfiguration = gd.asPanelSpriteConfiguration(
objectConfiguration
@@ -29,9 +27,7 @@ export default class PanelSpriteEditor extends React.Component<
<ColumnStackLayout>
<ResourceSelectorWithThumbnail
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
resourceKind="image"
resourceName={panelSpriteConfiguration.getTexture()}
onChange={resourceName => {

View File

@@ -27,9 +27,7 @@ export default class ParticleEmitterEditor extends React.Component<
const {
objectConfiguration,
project,
resourceSources,
onChooseResource,
resourceExternalEditors,
resourceManagementProps,
} = this.props;
const particleEmitterConfiguration = gd.asParticleEmitterConfiguration(
objectConfiguration
@@ -119,11 +117,9 @@ export default class ParticleEmitterEditor extends React.Component<
gd.ParticleEmitterObject.Quad && (
<ResourceSelectorWithThumbnail
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceManagementProps={resourceManagementProps}
resourceKind="image"
resourceName={particleEmitterConfiguration.getParticleTexture()}
resourceExternalEditors={resourceExternalEditors}
onChange={resourceName => {
particleEmitterConfiguration.setParticleTexture(resourceName);
this.forceUpdate();

View File

@@ -1,6 +1,6 @@
// @flow
import { Trans } from '@lingui/macro';
import { type I18n as I18nType } from '@lingui/core';
import React, { Component } from 'react';
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
import { mapFor } from '../../../Utils/MapFor';
@@ -19,12 +19,13 @@ import {
import ResourcesLoader from '../../../ResourcesLoader';
import {
type ResourceSource,
type ChooseResourceFunction,
type ResourceManagementProps,
} from '../../../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../../../ResourcesList/ResourceExternalEditor.flow';
import { applyResourceDefaults } from '../../../ResourcesList/ResourceUtils';
import FlatButton from '../../../UI/FlatButton';
import ThemeConsumer from '../../../UI/Theme/ThemeConsumer';
import ElementWithMenu from '../../../UI/Menu/ElementWithMenu';
const gd: libGDevelop = global.gd;
const path = require('path');
@@ -45,26 +46,47 @@ const styles = {
},
};
const AddSpriteButton = SortableElement(({ displayHint, onAdd }) => {
return (
<ThemeConsumer>
{muiTheme => (
<div
style={{
...thumbnailContainerStyle,
backgroundColor: muiTheme.list.itemsBackgroundColor,
}}
>
<FlatButton
onClick={onAdd}
label={<Trans>Add</Trans>}
leftIcon={<Add />}
/>
</div>
)}
</ThemeConsumer>
);
});
type AddSpriteButtonProps = {|
onAdd: (resourceSource: ResourceSource) => void,
resourceSources: Array<ResourceSource>,
|};
const AddSpriteButton = SortableElement(
({ onAdd, resourceSources }: AddSpriteButtonProps) => {
return (
<ThemeConsumer>
{muiTheme => (
<div
style={{
...thumbnailContainerStyle,
backgroundColor: muiTheme.list.itemsBackgroundColor,
}}
>
<ElementWithMenu
element={
<FlatButton
onClick={() => {
/* Will be replaced by ElementWithMenu. */
}}
label={<Trans>Add</Trans>}
leftIcon={<Add />}
/>
}
buildMenuTemplate={(i18n: I18nType) =>
resourceSources
.filter(source => source.kind === 'image')
.map(source => ({
label: i18n._(source.displayName),
click: () => onAdd(source),
}))
}
/>
</div>
)}
</ThemeConsumer>
);
}
);
const SortableSpriteThumbnail = SortableElement(
({ sprite, project, resourcesLoader, selected, onSelect, onContextMenu }) => {
@@ -89,6 +111,7 @@ const SortableList = SortableContainer(
project,
resourcesLoader,
onAddSprite,
resourceSources,
selectedSprites,
onSelectSprite,
onSpriteContextMenu,
@@ -118,6 +141,7 @@ const SortableList = SortableContainer(
disabled
index={spritesCount}
onAdd={onAddSprite}
resourceSources={resourceSources}
/>,
]}
</div>
@@ -152,9 +176,7 @@ type Props = {|
direction: gdDirection,
project: gdProject,
resourcesLoader: typeof ResourcesLoader,
resourceSources: Array<ResourceSource>,
resourceExternalEditors: Array<ResourceExternalEditor>,
onChooseResource: ChooseResourceFunction,
resourceManagementProps: ResourceManagementProps,
onSpriteContextMenu: (x: number, y: number, sprite: gdSprite) => void,
selectedSprites: {
[number]: boolean,
@@ -178,51 +200,43 @@ export default class SpritesList extends Component<Props, void> {
this.forceUpdate();
};
onAddSprite = () => {
const {
resourceSources,
onChooseResource,
project,
direction,
} = this.props;
if (!resourceSources) return;
const sources = resourceSources.filter(source => source.kind === 'image');
if (!sources.length) return;
onAddSprite = async (resourceSource: ResourceSource) => {
const { resourceManagementProps, project, direction } = this.props;
const {
allDirectionSpritesHaveSameCollisionMasks,
allDirectionSpritesHaveSamePoints,
} = checkDirectionPointsAndCollisionsMasks(direction);
onChooseResource({
// Should be updated once new sources are introduced in the desktop app.
// Search for "sources[0]" in the codebase for other places like this.
initialSourceName: sources[0].name,
const resources = await resourceManagementProps.onChooseResource({
initialSourceName: resourceSource.name,
multiSelection: true,
resourceKind: 'image',
}).then(resources => {
resources.forEach(resource => {
applyResourceDefaults(project, resource);
project.getResourcesManager().addResource(resource);
const sprite = new gd.Sprite();
sprite.setImageName(resource.getName());
if (allDirectionSpritesHaveSamePoints) {
copySpritePoints(direction.getSprite(0), sprite);
}
if (allDirectionSpritesHaveSameCollisionMasks) {
copySpritePolygons(direction.getSprite(0), sprite);
}
direction.addSprite(sprite);
sprite.delete();
});
// Important, we are responsible for deleting the resources that were given to us.
// Otherwise we have a memory leak, as calling addResource is making a copy of the resource.
resources.forEach(resource => resource.delete());
this.forceUpdate();
});
resources.forEach(resource => {
applyResourceDefaults(project, resource);
project.getResourcesManager().addResource(resource);
const sprite = new gd.Sprite();
sprite.setImageName(resource.getName());
if (allDirectionSpritesHaveSamePoints) {
copySpritePoints(direction.getSprite(0), sprite);
}
if (allDirectionSpritesHaveSameCollisionMasks) {
copySpritePolygons(direction.getSprite(0), sprite);
}
direction.addSprite(sprite);
sprite.delete();
});
// Important, we are responsible for deleting the resources that were given to us.
// Otherwise we have a memory leak, as calling addResource is making a copy of the resource.
resources.forEach(resource => resource.delete());
this.forceUpdate();
await resourceManagementProps.onFetchNewlyAddedResources();
};
editWith = (externalEditor: ResourceExternalEditor) => {
@@ -324,7 +338,9 @@ export default class SpritesList extends Component<Props, void> {
direction={this.props.direction}
resourcesLoader={this.props.resourcesLoader}
project={this.props.project}
resourceExternalEditors={this.props.resourceExternalEditors}
resourceExternalEditors={
this.props.resourceManagementProps.resourceExternalEditors
}
onEditWith={this.editWith}
/>
</MiniToolbar>
@@ -334,6 +350,7 @@ export default class SpritesList extends Component<Props, void> {
project={this.props.project}
onSortEnd={this.onSortEnd}
onAddSprite={this.onAddSprite}
resourceSources={this.props.resourceManagementProps.resourceSources}
selectedSprites={this.props.selectedSprites}
onSelectSprite={this.props.onSelectSprite}
onSpriteContextMenu={this.props.onSpriteContextMenu}

View File

@@ -29,11 +29,7 @@ import {
duplicateSpritesInAnimation,
} from './Utils/SpriteObjectHelper';
import { type EditorProps } from '../EditorProps.flow';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../../../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../../../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../../../ResourcesList/ResourceSource';
import { Column } from '../../../UI/Grid';
import { ResponsiveLineStackLayout } from '../../../UI/Layout';
import ScrollView from '../../../UI/ScrollView';
@@ -48,9 +44,7 @@ type AnimationProps = {|
animation: gdAnimation,
id: number,
project: gdProject,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
onRemove: () => void,
resourcesLoader: typeof ResourcesLoader,
onSpriteContextMenu: (x: number, y: number, sprite: gdSprite) => void,
@@ -72,10 +66,8 @@ class Animation extends React.Component<AnimationProps, void> {
animation,
id,
project,
resourceSources,
onRemove,
onChooseResource,
resourceExternalEditors,
resourceManagementProps,
resourcesLoader,
onSpriteContextMenu,
selectedSprites,
@@ -113,9 +105,7 @@ class Animation extends React.Component<AnimationProps, void> {
key={i}
project={project}
resourcesLoader={resourcesLoader}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
onSpriteContextMenu={onSpriteContextMenu}
selectedSprites={selectedSprites}
onSelectSprite={onSelectSprite}
@@ -144,9 +134,7 @@ const SortableAnimationsList = SortableContainer(
onChangeAnimationName,
project,
resourcesLoader,
resourceSources,
onChooseResource,
resourceExternalEditors,
resourceManagementProps,
extraBottomTools,
onSpriteContextMenu,
selectedSprites,
@@ -168,9 +156,7 @@ const SortableAnimationsList = SortableContainer(
animation={animation}
project={project}
resourcesLoader={resourcesLoader}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
onRemove={() => onRemoveAnimation(i)}
onChangeName={newName => onChangeAnimationName(i, newName)}
onSpriteContextMenu={onSpriteContextMenu}
@@ -191,9 +177,7 @@ const SortableAnimationsList = SortableContainer(
type AnimationsListContainerProps = {|
spriteConfiguration: gdSpriteObject,
project: gdProject,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
resourcesLoader: typeof ResourcesLoader,
extraBottomTools: React.Node,
onSizeUpdated: () => void,
@@ -343,9 +327,7 @@ class AnimationsListContainer extends React.Component<
selectedSprites={this.state.selectedSprites}
onSelectSprite={this.selectSprite}
resourcesLoader={this.props.resourcesLoader}
resourceSources={this.props.resourceSources}
resourceExternalEditors={this.props.resourceExternalEditors}
onChooseResource={this.props.onChooseResource}
resourceManagementProps={this.props.resourceManagementProps}
useDragHandle
lockAxis="y"
axis="y"
@@ -389,9 +371,7 @@ class AnimationsListContainer extends React.Component<
export default function SpriteEditor({
objectConfiguration,
project,
resourceSources,
onChooseResource,
resourceExternalEditors,
resourceManagementProps,
onSizeUpdated,
objectName,
}: EditorProps) {
@@ -409,9 +389,7 @@ export default function SpriteEditor({
<AnimationsListContainer
spriteConfiguration={spriteConfiguration}
resourcesLoader={ResourcesLoader}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
project={project}
objectName={objectName}
onSizeUpdated={onSizeUpdated}

View File

@@ -32,9 +32,7 @@ export default class TextEditor extends React.Component<EditorProps, void> {
const {
objectConfiguration,
project,
resourceSources,
onChooseResource,
resourceExternalEditors,
resourceManagementProps,
} = this.props;
const textObjectConfiguration = gd.asTextObjectConfiguration(
objectConfiguration
@@ -104,9 +102,7 @@ export default class TextEditor extends React.Component<EditorProps, void> {
<ResourceSelector
margin="none"
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
resourcesLoader={ResourcesLoader}
resourceKind="font"
fullWidth

View File

@@ -16,9 +16,7 @@ export default class TiledSpriteEditor extends React.Component<
const {
objectConfiguration,
project,
resourceSources,
onChooseResource,
resourceExternalEditors,
resourceManagementProps,
} = this.props;
const tiledSpriteConfiguration = gd.asTiledSpriteConfiguration(
objectConfiguration
@@ -28,11 +26,9 @@ export default class TiledSpriteEditor extends React.Component<
<ColumnStackLayout>
<ResourceSelectorWithThumbnail
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceManagementProps={resourceManagementProps}
resourceKind="image"
resourceName={tiledSpriteConfiguration.getTexture()}
resourceExternalEditors={resourceExternalEditors}
onChange={resourceName => {
tiledSpriteConfiguration.setTexture(resourceName);
this.forceUpdate();

View File

@@ -12,11 +12,7 @@ import { useSerializableObjectCancelableEditor } from '../Utils/SerializableObje
import SemiControlledTextField from '../UI/SemiControlledTextField';
import { Column, Line } from '../UI/Grid';
import { type EditorProps } from './Editors/EditorProps.flow';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
import { type UnsavedChanges } from '../MainFrame/UnsavedChangesContext';
import useForceUpdate from '../Utils/UseForceUpdate';
import HotReloadPreviewButton, {
@@ -48,9 +44,7 @@ type Props = {|
// Passed down to object editors:
project: gdProject,
onComputeAllVariableNames: () => Array<string>,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
unsavedChanges?: UnsavedChanges,
onUpdateBehaviorsSharedData: () => void,
initialTab: ?ObjectEditorTab,
@@ -209,9 +203,7 @@ const InnerDialog = (props: InnerDialogProps) => {
<EditorComponent
objectConfiguration={props.object.getConfiguration()}
project={props.project}
resourceSources={props.resourceSources}
onChooseResource={props.onChooseResource}
resourceExternalEditors={props.resourceExternalEditors}
resourceManagementProps={props.resourceManagementProps}
onSizeUpdated={
forceUpdate /*Force update to ensure dialog is properly positionned*/
}
@@ -223,9 +215,7 @@ const InnerDialog = (props: InnerDialogProps) => {
<BehaviorsEditor
object={props.object}
project={props.project}
resourceSources={props.resourceSources}
onChooseResource={props.onChooseResource}
resourceExternalEditors={props.resourceExternalEditors}
resourceManagementProps={props.resourceManagementProps}
onSizeUpdated={
forceUpdate /*Force update to ensure dialog is properly positionned*/
}
@@ -260,9 +250,7 @@ const InnerDialog = (props: InnerDialogProps) => {
<EffectsList
target="object"
project={props.project}
resourceSources={props.resourceSources}
onChooseResource={props.onChooseResource}
resourceExternalEditors={props.resourceExternalEditors}
resourceManagementProps={props.resourceManagementProps}
effectsContainer={props.object.getEffects()}
onEffectsUpdated={
forceUpdate /*Force update to ensure dialog is properly positionned*/

View File

@@ -41,13 +41,8 @@ import {
import { type UnsavedChanges } from '../MainFrame/UnsavedChangesContext';
import { type HotReloadPreviewButtonProps } from '../HotReload/HotReloadPreviewButton';
import { useScreenType } from '../UI/Reponsive/ScreenTypeMeasurer';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
import { type OnFetchNewlyAddedResourcesFunction } from '../ProjectsStorage/ResourceFetcher';
import { getInstanceCountInLayoutForObject } from '../Utils/Layout';
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
const gd: libGDevelop = global.gd;
const styles = {
@@ -106,11 +101,8 @@ type Props = {|
project: gdProject,
layout: ?gdLayout,
objectsContainer: gdObjectsContainer,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
onFetchNewlyAddedResources: OnFetchNewlyAddedResourcesFunction,
onSelectAllInstancesOfObjectInLayout?: string => void,
resourceManagementProps: ResourceManagementProps,
onDeleteObject: (
objectWithContext: ObjectWithContext,
cb: (boolean) => void
@@ -639,11 +631,8 @@ export default class ObjectsList extends React.Component<Props, State> {
project,
layout,
objectsContainer,
resourceSources,
onChooseResource,
resourceExternalEditors,
resourceManagementProps,
selectedObjectTags,
onFetchNewlyAddedResources,
} = this.props;
const { searchText, tagEditedObject } = this.state;
@@ -737,10 +726,7 @@ export default class ObjectsList extends React.Component<Props, State> {
project={project}
layout={layout}
objectsContainer={objectsContainer}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
onFetchNewlyAddedResources={onFetchNewlyAddedResources}
resourceManagementProps={resourceManagementProps}
/>
)}
{tagEditedObject && (

View File

@@ -2,6 +2,7 @@
import { Trans } from '@lingui/macro';
import { t } from '@lingui/macro';
import { I18n } from '@lingui/react';
import { type I18n as I18nType } from '@lingui/core';
import * as React from 'react';
import FlatButton from '../UI/FlatButton';
@@ -12,14 +13,14 @@ import ResourcesLoader from '../ResourcesLoader';
import ResourceSelectorWithThumbnail from '../ResourcesList/ResourceSelectorWithThumbnail';
import {
type ResourceSource,
type ChooseResourceFunction,
type ResourceManagementProps,
} from '../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
import { resizeImage, isResizeSupported } from './ImageResizer';
import { showErrorBox } from '../UI/Messages/MessageBox';
import optionalRequire from '../Utils/OptionalRequire';
import Text from '../UI/Text';
import { ColumnStackLayout } from '../UI/Layout';
import ElementWithMenu from '../UI/Menu/ElementWithMenu';
const path = optionalRequire('path');
const gd: libGDevelop = global.gd;
@@ -28,9 +29,7 @@ type Props = {|
open: boolean,
onClose: Function,
onApply: Function,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
|};
type State = {|
@@ -109,123 +108,132 @@ export default class PlatformSpecificAssetsDialog extends React.Component<
}
}
_generateFromFile = () => {
const { project, resourceSources, onChooseResource } = this.props;
_generateFromFile = (resourceSource: ResourceSource) => {
const { project, resourceManagementProps } = this.props;
const sources = resourceSources.filter(source => source.kind === 'image');
if (!sources.length) return;
if (!resourceSource.name.startsWith('local-file-opener')) {
throw new Error('Only local files are supported for generating icons.');
}
onChooseResource({
// Should be updated once new sources are introduced in the desktop app.
// Search for "sources[0]" in the codebase for other places like this.
initialSourceName: sources[0].name,
multiSelection: false,
resourceKind: 'image',
}).then(resources => {
if (!resources.length || !path) {
return;
}
const resourcesManager = project.getResourcesManager();
const projectPath = path.dirname(project.getProjectFile());
const fullPath = path.resolve(projectPath, resources[0].getFile());
// Important, we are responsible for deleting the resources that were given to us.
// Otherwise we have a memory leak.
resources.forEach(resource => resource.delete());
Promise.all([
...desktopSizes.map(size =>
resizeImage(
fullPath,
path.join(projectPath, `desktop-icon-${size}.png`),
{
width: size,
height: size,
}
)
),
...androidSizes.map(size =>
resizeImage(
fullPath,
path.join(projectPath, `android-icon-${size}.png`),
{
width: size,
height: size,
}
)
),
resizeImage(
fullPath,
path.join(projectPath, 'android-windowSplashScreenAnimatedIcon.png'),
{
width: androidWindowSplashScreenAnimatedIconRecommendedSize,
height: androidWindowSplashScreenAnimatedIconRecommendedSize,
transparentBorderSize:
androidWindowSplashScreenAnimatedIconRecommendedSize / 6,
}
),
...iosSizes.map(size =>
resizeImage(
fullPath,
path.join(projectPath, `ios-icon-${size}.png`),
{
width: size,
height: size,
}
)
),
]).then(results => {
if (results.indexOf(false) !== -1) {
showErrorBox({
message: 'Some icons could not be generated!',
rawError: undefined,
errorId: 'icon-generation-error',
doNotReport: true,
});
resourceManagementProps
.onChooseResource({
initialSourceName: resourceSource.name,
multiSelection: false,
resourceKind: 'image',
})
.then(resources => {
if (!resources.length || !path) {
return;
}
// Add resources to the game
const allResourcesNames = [
...desktopSizes.map(size => `desktop-icon-${size}.png`),
...androidSizes.map(size => `android-icon-${size}.png`),
'android-windowSplashScreenAnimatedIcon.png',
...iosSizes.map(size => `ios-icon-${size}.png`),
];
allResourcesNames.forEach(resourceName => {
if (!resourcesManager.hasResource(resourceName)) {
const imageResource = new gd.ImageResource();
imageResource.setFile(resourceName);
imageResource.setName(resourceName);
const resourcesManager = project.getResourcesManager();
const projectPath = path.dirname(project.getProjectFile());
const fullPath = path.resolve(projectPath, resources[0].getFile());
resourcesManager.addResource(imageResource);
// Important, we are responsible for deleting the resources that were given to us.
// Otherwise we have a memory leak.
resources.forEach(resource => resource.delete());
// Important, we are responsible for deleting the resources that we created
// Otherwise we have a memory leak, as calling addResource is making a copy of the resource.
imageResource.delete();
} else {
resourcesManager.getResource(resourceName).setFile(resourceName);
Promise.all([
...desktopSizes.map(size =>
resizeImage(
fullPath,
path.join(projectPath, `desktop-icon-${size}.png`),
{
width: size,
height: size,
}
)
),
...androidSizes.map(size =>
resizeImage(
fullPath,
path.join(projectPath, `android-icon-${size}.png`),
{
width: size,
height: size,
}
)
),
resizeImage(
fullPath,
path.join(
projectPath,
'android-windowSplashScreenAnimatedIcon.png'
),
{
width: androidWindowSplashScreenAnimatedIconRecommendedSize,
height: androidWindowSplashScreenAnimatedIconRecommendedSize,
transparentBorderSize:
androidWindowSplashScreenAnimatedIconRecommendedSize / 6,
}
),
...iosSizes.map(size =>
resizeImage(
fullPath,
path.join(projectPath, `ios-icon-${size}.png`),
{
width: size,
height: size,
}
)
),
]).then(results => {
if (results.indexOf(false) !== -1) {
showErrorBox({
message: 'Some icons could not be generated!',
rawError: undefined,
errorId: 'icon-generation-error',
doNotReport: true,
});
return;
}
});
// Make sure the resources are (re)loaded.
ResourcesLoader.burstUrlsCacheForResources(project, allResourcesNames);
setTimeout(() => {
this.setState({
desktopIconResourceNames: desktopSizes.map(
size => `desktop-icon-${size}.png`
),
androidIconResourceNames: androidSizes.map(
size => `android-icon-${size}.png`
),
androidWindowSplashScreenAnimatedIconResourceName:
'android-windowSplashScreenAnimatedIcon.png',
iosIconResourceNames: iosSizes.map(size => `ios-icon-${size}.png`),
// Add resources to the game
const allResourcesNames = [
...desktopSizes.map(size => `desktop-icon-${size}.png`),
...androidSizes.map(size => `android-icon-${size}.png`),
'android-windowSplashScreenAnimatedIcon.png',
...iosSizes.map(size => `ios-icon-${size}.png`),
];
allResourcesNames.forEach(resourceName => {
if (!resourcesManager.hasResource(resourceName)) {
const imageResource = new gd.ImageResource();
imageResource.setFile(resourceName);
imageResource.setName(resourceName);
resourcesManager.addResource(imageResource);
// Important, we are responsible for deleting the resources that we created
// Otherwise we have a memory leak, as calling addResource is making a copy of the resource.
imageResource.delete();
} else {
resourcesManager.getResource(resourceName).setFile(resourceName);
}
});
}, 200 /* Let a bit of time so that image files can be found */);
// Make sure the resources are (re)loaded.
ResourcesLoader.burstUrlsCacheForResources(
project,
allResourcesNames
);
setTimeout(() => {
this.setState({
desktopIconResourceNames: desktopSizes.map(
size => `desktop-icon-${size}.png`
),
androidIconResourceNames: androidSizes.map(
size => `android-icon-${size}.png`
),
androidWindowSplashScreenAnimatedIconResourceName:
'android-windowSplashScreenAnimatedIcon.png',
iosIconResourceNames: iosSizes.map(
size => `ios-icon-${size}.png`
),
});
}, 200 /* Let a bit of time so that image files can be found */);
});
});
});
};
onApply = () => {
@@ -287,12 +295,7 @@ export default class PlatformSpecificAssetsDialog extends React.Component<
onClick={this.onApply}
/>,
];
const {
project,
resourceSources,
onChooseResource,
resourceExternalEditors,
} = this.props;
const { project, resourceManagementProps } = this.props;
const {
thumbnailResourceName,
desktopIconResourceNames,
@@ -310,29 +313,29 @@ export default class PlatformSpecificAssetsDialog extends React.Component<
onApply={this.onApply}
>
<ColumnStackLayout noMargin>
<Text size="sub-title">
<Trans>Liluo.io thumbnail</Trans>
</Text>
<ResourceSelectorWithThumbnail
floatingLabelText={`Liluo.io thumbnail (1920x1080 px)`}
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceKind="image"
resourceName={thumbnailResourceName}
onChange={resourceName => {
this.setState({
thumbnailResourceName: resourceName,
});
}}
/>
<Line justifyContent="center">
<Line justifyContent="center" noMargin>
{isResizeSupported() ? (
<RaisedButton
primary
label={<Trans>Generate icons from a file</Trans>}
onClick={this._generateFromFile}
<ElementWithMenu
element={
<RaisedButton
primary
label={<Trans>Generate icons from a file</Trans>}
onClick={() => {
/* Will be replaced by ElementWithMenu */
}}
/>
}
buildMenuTemplate={(i18n: I18nType) =>
resourceManagementProps.resourceSources
.filter(source => source.kind === 'image')
.filter(source =>
source.name.startsWith('local-file-opener')
)
.map(source => ({
label: i18n._(source.displayName),
click: () => this._generateFromFile(source),
}))
}
/>
) : (
<Text>
@@ -343,6 +346,21 @@ export default class PlatformSpecificAssetsDialog extends React.Component<
</Text>
)}
</Line>
<Text size="sub-title">
<Trans>Liluo.io thumbnail</Trans>
</Text>
<ResourceSelectorWithThumbnail
floatingLabelText={`Liluo.io thumbnail (1920x1080 px)`}
project={project}
resourceManagementProps={resourceManagementProps}
resourceKind="image"
resourceName={thumbnailResourceName}
onChange={resourceName => {
this.setState({
thumbnailResourceName: resourceName,
});
}}
/>
<Text size="sub-title">
<Trans>Desktop (Windows, macOS and Linux) icon</Trans>
</Text>
@@ -351,9 +369,7 @@ export default class PlatformSpecificAssetsDialog extends React.Component<
key={size}
floatingLabelText={`Desktop icon (${size}x${size} px)`}
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
resourceKind="image"
resourceName={desktopIconResourceNames[index]}
onChange={resourceName => {
@@ -373,9 +389,7 @@ export default class PlatformSpecificAssetsDialog extends React.Component<
<ResourceSelectorWithThumbnail
floatingLabelText={`Android 12+ splashscreen icon (576x576 px)`}
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
resourceKind="image"
resourceName={androidWindowSplashScreenAnimatedIconResourceName}
onChange={resourceName => {
@@ -394,9 +408,7 @@ export default class PlatformSpecificAssetsDialog extends React.Component<
key={size}
floatingLabelText={`Android icon (${size}x${size} px)`}
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
resourceKind="image"
resourceName={androidIconResourceNames[index]}
onChange={resourceName => {
@@ -416,11 +428,9 @@ export default class PlatformSpecificAssetsDialog extends React.Component<
key={size}
floatingLabelText={`iOS icon (${size}x${size} px)`}
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceManagementProps={resourceManagementProps}
resourceKind="image"
resourceName={iosIconResourceNames[index]}
resourceExternalEditors={resourceExternalEditors}
onChange={resourceName => {
const newIcons = [...iosIconResourceNames];
newIcons[index] = resourceName;

View File

@@ -14,11 +14,7 @@ import {
} from '../Utils/ColorTransformer';
import useForceUpdate from '../Utils/UseForceUpdate';
import ResourceSelectorWithThumbnail from '../ResourcesList/ResourceSelectorWithThumbnail';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
import SelectField from '../UI/SelectField';
import SelectOption from '../UI/SelectOption';
import Text from '../UI/Text';
@@ -29,18 +25,14 @@ type Props = {
// For resources:
project: gdProject,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
};
export const LoadingScreenEditor = ({
loadingScreen,
onChangeSubscription,
project,
resourceSources,
onChooseResource,
resourceExternalEditors,
resourceManagementProps,
}: Props) => {
const subscriptionChecker = React.useRef<?SubscriptionChecker>(null);
const forceUpdate = useForceUpdate();
@@ -56,9 +48,7 @@ export const LoadingScreenEditor = ({
<ResourceSelectorWithThumbnail
floatingLabelText={<Trans>Background image</Trans>}
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
resourceKind="image"
resourceName={loadingScreen.getBackgroundImageResourceName()}
onChange={resourceName => {

View File

@@ -27,11 +27,7 @@ import AlertMessage from '../UI/AlertMessage';
import { GameRegistration } from '../GameDashboard/GameRegistration';
import { Tab, Tabs } from '../UI/Tabs';
import { LoadingScreenEditor } from './LoadingScreenEditor';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
import {
type HotReloadPreviewButtonProps,
NewPreviewIcon,
@@ -49,9 +45,7 @@ type Props = {|
hotReloadPreviewButtonProps?: ?HotReloadPreviewButtonProps,
// For resources:
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
|};
type ProjectProperties = {|
@@ -597,9 +591,7 @@ function ProjectPropertiesDialog(props: Props) {
props.onChangeSubscription();
}}
project={project}
resourceSources={props.resourceSources}
onChooseResource={props.onChooseResource}
resourceExternalEditors={props.resourceExternalEditors}
resourceManagementProps={props.resourceManagementProps}
/>
)}
</Dialog>

View File

@@ -47,11 +47,7 @@ import ProjectManagerCommands from './ProjectManagerCommands';
import { type HotReloadPreviewButtonProps } from '../HotReload/HotReloadPreviewButton';
import { type ExtensionShortHeader } from '../Utils/GDevelopServices/Extension';
import EventsRootVariablesFinder from '../Utils/EventsRootVariablesFinder';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
import InstalledExtensionDetails from './InstalledExtensionDetails';
import {
Item,
@@ -111,9 +107,7 @@ type Props = {|
onInstallExtension: ExtensionShortHeader => void,
// For resources:
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
resourceManagementProps: ResourceManagementProps,
|};
type State = {|
@@ -1112,9 +1106,7 @@ export default class ProjectManager extends React.Component<Props, State> {
onApply={this.props.onSaveProjectProperties}
onPropertiesApplied={this._onProjectPropertiesApplied}
onChangeSubscription={this.props.onChangeSubscription}
resourceSources={this.props.resourceSources}
onChooseResource={this.props.onChooseResource}
resourceExternalEditors={this.props.resourceExternalEditors}
resourceManagementProps={this.props.resourceManagementProps}
hotReloadPreviewButtonProps={
this.props.hotReloadPreviewButtonProps
}

View File

@@ -1,5 +1,6 @@
// @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,13 +14,11 @@ import ColorField from '../UI/ColorField';
import { MarkdownText } from '../UI/MarkdownText';
import { rgbOrHexToRGBString } from '../Utils/ColorTransformer';
import FormHelperText from '@material-ui/core/FormHelperText';
import { type MenuItemTemplate } from '../UI/Menu/Menu.flow';
import {
type ResourceKind,
type ResourceSource,
type ChooseResourceFunction,
type ResourceManagementProps,
} from '../ResourcesList/ResourceSource';
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
import {
TextFieldWithButtonLayout,
ResponsiveLineStackLayout,
@@ -32,6 +31,7 @@ import UnsavedChangesContext, {
import { Line, Spacer } from '../UI/Grid';
import Text from '../UI/Text';
import useForceUpdate from '../Utils/UseForceUpdate';
import ElementWithMenu from '../UI/Menu/ElementWithMenu';
// An "instance" here is the objects for which properties are shown
export type Instance = Object; // This could be improved using generics.
@@ -44,7 +44,7 @@ export type ValueFieldCommonProperties = {|
getDescription?: Instance => string,
getExtraDescription?: Instance => string,
disabled?: boolean | ((instances: Array<gdInitialInstance>) => boolean),
onEditButtonClick?: Instance => void,
onEditButtonBuildMenuTemplate?: (i18n: I18nType) => Array<MenuItemTemplate>,
|};
// "Primitive" value fields are "simple" fields.
@@ -141,9 +141,7 @@ type Props = {|
// Optional context:
project?: ?gdProject,
resourceSources?: ?Array<ResourceSource>,
onChooseResource?: ?ChooseResourceFunction,
resourceExternalEditors?: ?Array<ResourceExternalEditor>,
resourceManagementProps?: ?ResourceManagementProps,
|};
const styles = {
@@ -230,9 +228,7 @@ const PropertiesEditor = ({
renderExtraDescriptionText,
unsavedChanges,
project,
resourceSources,
onChooseResource,
resourceExternalEditors,
resourceManagementProps,
}: Props) => {
const forceUpdate = useForceUpdate();
@@ -358,7 +354,7 @@ const PropertiesEditor = ({
/>
);
} else {
const { onEditButtonClick, setValue } = field;
const { onEditButtonBuildMenuTemplate, setValue } = field;
return (
<TextFieldWithButtonLayout
key={field.name}
@@ -378,14 +374,21 @@ const PropertiesEditor = ({
/>
)}
renderButton={style =>
onEditButtonClick ? (
<RaisedButton
style={style}
primary
disabled={instances.length !== 1}
icon={<Edit />}
label={<Trans>Edit</Trans>}
onClick={() => onEditButtonClick(instances[0])}
onEditButtonBuildMenuTemplate ? (
<ElementWithMenu
element={
<RaisedButton
style={style}
primary
disabled={instances.length !== 1}
icon={<Edit />}
label={<Trans>Edit</Trans>}
onClick={() => {
/* Will be replaced by ElementWithMenu */
}}
/>
}
buildMenuTemplate={onEditButtonBuildMenuTemplate}
/>
) : null
}
@@ -479,14 +482,9 @@ const PropertiesEditor = ({
);
const renderResourceField = (field: ResourceField) => {
if (
!project ||
!resourceSources ||
!onChooseResource ||
!resourceExternalEditors
) {
if (!project || !resourceManagementProps) {
console.error(
'You tried to display a resource field in a PropertiesEditor that does not support display resources. If you need to display resources, pass additional props (project, resourceSources, onChooseResource, resourceExternalEditors).'
'You tried to display a resource field in a PropertiesEditor that does not support display resources. If you need to display resources, pass additional props (project, resourceManagementProps).'
);
return null;
}
@@ -496,9 +494,7 @@ const PropertiesEditor = ({
<ResourceSelector
key={field.name}
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
resourcesLoader={ResourcesLoader}
resourceKind={field.resourceKind}
fullWidth
@@ -582,9 +578,7 @@ const PropertiesEditor = ({
{unsavedChanges => (
<PropertiesEditor
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
schema={field.children}
instances={instances}
mode="row"
@@ -613,9 +607,7 @@ const PropertiesEditor = ({
{unsavedChanges => (
<PropertiesEditor
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceExternalEditors={resourceExternalEditors}
resourceManagementProps={resourceManagementProps}
schema={field.children}
instances={instances}
mode="column"

View File

@@ -1,5 +1,6 @@
// @flow
import { Trans } from '@lingui/macro';
import { type I18n as I18nType } from '@lingui/core';
import * as React from 'react';
import Background from '../../UI/Background';
@@ -12,7 +13,7 @@ import { type Schema } from '../../PropertiesEditor';
import {
type ResourceSource,
type ChooseResourceFunction,
type ResourceManagementProps,
} from '../../ResourcesList/ResourceSource';
const styles = {
@@ -29,8 +30,7 @@ type Props = {|
resourcesLoader: typeof ResourcesLoader,
resources: Array<gdResource>,
onResourcePathUpdated: () => void,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceManagementProps: ResourceManagementProps,
|};
export default class ResourcePropertiesEditor extends React.Component<
@@ -52,7 +52,13 @@ export default class ResourcePropertiesEditor extends React.Component<
getValue: (resource: gdResource) => resource.getFile(),
setValue: (resource: gdResource, newValue: string) =>
resource.setFile(newValue),
onEditButtonClick: () => this._chooseResourcePath(),
onEditButtonBuildMenuTemplate: (i18n: I18nType) =>
this.props.resourceManagementProps.resourceSources
.filter(source => source.kind === this.props.resources[0].getKind())
.map(source => ({
label: i18n._(source.displayName),
click: () => this._chooseResourcePath(source),
})),
},
];
@@ -68,35 +74,30 @@ export default class ResourcePropertiesEditor extends React.Component<
);
}
_chooseResourcePath = () => {
_chooseResourcePath = async (resourceSource: ResourceSource) => {
const {
resources,
onResourcePathUpdated,
onChooseResource,
resourceSources,
resourceManagementProps,
} = this.props;
const resource = resources[0];
const sources = resourceSources.filter(
source => source.kind === resource.getKind()
);
if (!sources.length) return;
onChooseResource({
// Should be updated once new sources are introduced in the desktop app.
// Search for "sources[0]" in the codebase for other places like this.
initialSourceName: sources[0].name,
multiSelection: true,
const newResources = await resourceManagementProps.onChooseResource({
initialSourceName: resourceSource.name,
multiSelection: false,
resourceKind: resource.getKind(),
}).then(resources => {
if (!resources.length) return; // No path was chosen by the user.
resource.setFile(resources[0].getFile());
// Important, we are responsible for deleting the resources that were given to us.
// Otherwise we have a memory leak.
resources.forEach(resource => resource.delete());
onResourcePathUpdated();
this.forceUpdate();
});
if (!newResources.length) return; // No path was chosen by the user.
resource.setFile(newResources[0].getFile());
// Important, we are responsible for deleting the resources that were given to us.
// Otherwise we have a memory leak.
newResources.forEach(resource => resource.delete());
onResourcePathUpdated();
this.forceUpdate();
await resourceManagementProps.onFetchNewlyAddedResources();
};
_renderResourcesProperties() {

View File

@@ -13,8 +13,7 @@ import optionalRequire from '../Utils/OptionalRequire';
import Window from '../Utils/Window';
import PreferencesContext from '../MainFrame/Preferences/PreferencesContext';
import {
type ResourceSource,
type ChooseResourceFunction,
type ResourceManagementProps,
type ResourceKind,
} from '../ResourcesList/ResourceSource';
import { getResourceFilePathStatus } from '../ResourcesList/ResourceUtils';
@@ -47,8 +46,7 @@ type Props = {|
newName: string,
cb: (boolean) => void
) => void,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceManagementProps: ResourceManagementProps,
|};
const initialMosaicEditorNodes = {
@@ -207,12 +205,7 @@ export default class ResourcesEditor extends React.Component<Props, State> {
};
render() {
const {
project,
onRenameResource,
onChooseResource,
resourceSources,
} = this.props;
const { project, onRenameResource, resourceManagementProps } = this.props;
const { selectedResource } = this.state;
const editors = {
@@ -233,8 +226,7 @@ export default class ResourcesEditor extends React.Component<Props, State> {
this._resourcesList.checkMissingPaths();
}
}}
onChooseResource={onChooseResource}
resourceSources={resourceSources}
resourceManagementProps={resourceManagementProps}
/>
),
},

View File

@@ -1,30 +1,74 @@
// @flow
import { t } from '@lingui/macro';
import { t, Trans } from '@lingui/macro';
import * as React from 'react';
import {
type ChooseResourceOptions,
type ChooseResourceProps,
type ResourceSourceComponentProps,
type ResourceSource,
allResourceKindsAndMetadata,
} from './ResourceSource';
import { ResourceStore } from '../AssetStore/ResourceStore';
import { isPathInProjectFolder, copyAllToProjectFolder } from './ResourceUtils';
import optionalRequire from '../Utils/OptionalRequire';
import Window from '../Utils/Window';
import { Line } from '../UI/Grid';
import RaisedButton from '../UI/RaisedButton';
const remote = optionalRequire('@electron/remote');
const dialog = remote ? remote.dialog : null;
const path = optionalRequire('path');
type ResourceStoreChooserProps = {
options: ChooseResourceOptions,
onChooseResources: (resources: Array<gdResource>) => void,
createNewResource: () => gdResource,
};
const ResourceStoreChooser = ({
options,
onChooseResources,
createNewResource,
}: ResourceStoreChooserProps) => {
return (
<ResourceStore
onChoose={resource => {
const chosenResourceUrl = resource.url;
const newResource = createNewResource();
newResource.setFile(chosenResourceUrl);
newResource.setName(path.basename(chosenResourceUrl));
newResource.setOrigin('gdevelop-asset-store', chosenResourceUrl);
onChooseResources([newResource]);
}}
resourceKind={options.resourceKind}
/>
);
};
const localResourceSources: Array<ResourceSource> = [
...allResourceKindsAndMetadata.map(({ kind, createNewResource }) => ({
name: `resource-store-${kind}`,
displayName: t`Choose from asset store`,
displayTab: 'standalone',
kind,
renderComponent: (props: ResourceSourceComponentProps) => (
<ResourceStoreChooser
createNewResource={createNewResource}
onChooseResources={props.onChooseResources}
options={props.options}
key={`resource-store-${kind}`}
/>
),
})),
...allResourceKindsAndMetadata.map(
({ kind, displayName, fileExtensions, createNewResource }) => ({
name: 'local-file-opener-' + kind,
displayName: t`Choose a file`,
displayTab: 'import',
kind,
selectResourcesHeadless: async ({
({ kind, displayName, fileExtensions, createNewResource }) => {
const selectLocalFileResources = async ({
i18n,
getLastUsedPath,
setLastUsedPath,
project,
options,
}) => {
}: ChooseResourceProps) => {
if (!dialog)
throw new Error('Electron dialog not supported in this environment.');
@@ -72,9 +116,43 @@ const localResourceSources: Array<ResourceSource> = [
return newResource;
});
},
renderComponent: () => null,
})
};
return {
name: 'local-file-opener-' + kind,
displayName: t`Choose a file`,
displayTab: 'import',
kind,
selectResourcesHeadless: selectLocalFileResources,
renderComponent: (props: ResourceSourceComponentProps) => (
<Line justifyContent="center">
<RaisedButton
primary
label={
props.options.multiSelection ? (
<Trans>Choose one or more files</Trans>
) : (
<Trans>Choose a file</Trans>
)
}
onClick={async () => {
const resources = await selectLocalFileResources({
i18n: props.i18n,
project: props.project,
fileMetadata: props.fileMetadata,
getStorageProvider: props.getStorageProvider,
getLastUsedPath: props.getLastUsedPath,
setLastUsedPath: props.setLastUsedPath,
options: props.options,
});
props.onChooseResources(resources);
}}
/>
</Line>
),
};
}
),
];

Some files were not shown because too many files have changed in this diff Show More