Compare commits

..

18 Commits

Author SHA1 Message Date
Davy Hélard
249775ee2f Remove unused code. 2025-08-09 18:46:04 +02:00
Davy Hélard
6bcc810489 Fix async task clearing. 2025-08-09 18:40:12 +02:00
Davy Hélard
2a763f18dd Clear the context. 2025-08-09 18:26:55 +02:00
Davy Hélard
b401a4cb08 Avoid to allocate a new context for behaviors and objects 2025-08-09 18:26:55 +02:00
Davy Hélard
b2b0540f93 Fix a regression on ActionWithOperator 2025-08-09 18:26:55 +02:00
Davy Hélard
be59a629f0 Format 2025-08-09 18:26:54 +02:00
Davy Hélard
a40b36d52e Fix tests 2025-08-09 18:26:54 +02:00
Davy Hélard
cc12ab662d Avoid to allocate a new context 2025-08-09 18:26:54 +02:00
Davy Hélard
0b8d6986ff Generate classes for event function contexts 2025-08-09 18:26:53 +02:00
D8H
fbb985833f Optimize JavaScript events (avoid a list copy) (#7775)
Only show in developer changelog
2025-08-09 17:58:12 +02:00
D8H
1b3734ff6b Add the unit for 3D angle parameter descriptions (#7774) 2025-08-08 17:13:06 +02:00
D8H
6288b30ac3 Add an alert when editing the default variant of an extension from the store (#7773) 2025-08-08 16:39:50 +02:00
D8H
ee435f7081 Fix the tint action of Sprite to handle floating point color components (#7772) 2025-08-08 15:57:19 +02:00
Gleb Volkov
d75b4eb2a9 Add debug flag to GDJS build script (#7771) 2025-08-08 14:17:26 +02:00
Florian Rival
5eeb505807 Fix tests
Don't show in changelog
2025-08-08 11:36:33 +02:00
Florian Rival
30566e35ce Fix changing language not reloading courses in this language 2025-08-08 10:14:57 +02:00
Florian Rival
e058b7f295 Add line height property for Text objects (#7769) 2025-08-07 18:47:02 +02:00
Florian Rival
902a30a9f8 Update capability user-friendly name and fix test
Don't show in changelog
2025-08-07 16:55:52 +02:00
55 changed files with 997 additions and 842 deletions

View File

@@ -18,19 +18,19 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("AnimatableCapability",
_("Objects with animations"),
_("Actions and conditions for objects having animations (sprite, 3D models...)."),
_("Animate objects."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Animatable capability"))
extension.AddInstructionOrExpressionGroupMetadata(_("Objects with animations"))
.SetIcon("res/actions/animation24.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Animations and images"))
.SetIcon("res/actions/animation24.png");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"AnimatableBehavior",
_("Animatable capability"),
_("Objects with animations"),
"Animation",
_("Actions and conditions for objects having animations (sprite, 3D models...).."),
"",

View File

@@ -18,7 +18,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("EffectCapability",
_("Effect capability"),
_("Objects with effects"),
_("Actions/conditions to enable/disable and change parameters of visual effects applied on objects."),
"Florian Rival",
"Open source (MIT License)")
@@ -28,7 +28,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
gd::BehaviorMetadata& aut = extension.AddBehavior(
"EffectBehavior",
_("Effect capability"),
_("Objects with effects"),
"Effect",
_("Actions/conditions to enable/disable and change parameters of visual effects applied on objects."),
"",

View File

@@ -18,7 +18,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsFlippableExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("FlippableCapability",
_("Flippable capability"),
_("Flippable objects"),
_("Actions/conditions for objects which can be flipped horizontally or vertically."),
"Florian Rival",
"Open source (MIT License)")
@@ -28,7 +28,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsFlippableExtension(
gd::BehaviorMetadata& aut = extension.AddBehavior(
"FlippableBehavior",
_("Flippable capability"),
_("Flippable objects"),
"Flippable",
_("Actions/conditions for objects which can be flipped horizontally or vertically."),
"",

View File

@@ -18,13 +18,13 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsOpacityExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("OpacityCapability",
_("Opacity capability"),
_("Objects with opacity"),
_("Action/condition/expression to change or "
"check the opacity of an object (0-255)."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Opacity capability"))
extension.AddInstructionOrExpressionGroupMetadata(_("Objects with opacity"))
.SetIcon("res/actions/opacity24.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Visibility"))
.SetIcon("res/actions/opacity24.png");
@@ -32,7 +32,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsOpacityExtension(
gd::BehaviorMetadata& aut =
extension
.AddBehavior("OpacityBehavior",
_("Opacity capability"),
_("Objects with opacity"),
"Opacity",
_("Action/condition/expression to change or check the "
"opacity of an object (0-255)."),

View File

@@ -18,7 +18,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsResizableExtension(
extension
.SetExtensionInformation(
"ResizableCapability",
_("Resizable capability"),
_("Resizable objects"),
_("Change or compare the size (width/height) of an object which can "
"be resized (i.e: most objects)."),
"Florian Rival",
@@ -30,7 +30,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsResizableExtension(
gd::BehaviorMetadata &aut =
extension
.AddBehavior("ResizableBehavior",
_("Resizable capability"),
_("Resizable objects"),
"Resizable",
_("Change or compare the size (width/height) of an "
"object which can be resized (i.e: most objects)."),

View File

@@ -18,13 +18,13 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("ScalableCapability",
_("Scalable capability"),
_("Scalable objects"),
_("Actions/conditions/expression to change or "
"check the scale of an object (default: 1)."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Scalable capability"))
extension.AddInstructionOrExpressionGroupMetadata(_("Scalable objects"))
.SetIcon("res/actions/scale24_black.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Size")).SetIcon(
"res/actions/scale24_black.png");
@@ -32,7 +32,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
gd::BehaviorMetadata& aut =
extension
.AddBehavior("ScalableBehavior",
_("Scalable capability"),
_("Scalable objects"),
"Scale",
_("Actions/conditions/expression to change or check the "
"scale of an object (default: 1)."),

View File

@@ -18,17 +18,17 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTextContainerExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("TextContainerCapability",
_("Text capability"),
_("Objects containing a text"),
_("Allows an object to contain a text, usually shown on screen, that can be modified."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Text capability"))
extension.AddInstructionOrExpressionGroupMetadata(_("Objects containing a text"))
.SetIcon("res/conditions/text24_black.png");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"TextContainerBehavior",
_("Text capability"),
_("Objects containing a text"),
"Text",
_("Allows an object to contain a text, usually shown on screen, that can be modified."),
"",

View File

@@ -235,6 +235,9 @@ class GD_CORE_API EventsFunction {
/**
* \brief Return the parameters of the function that are used in the events.
*
* \warning `ActionWithOperator` function are muted by this function. Make sure
* to use the right functions container to avoid strange side effects.
*
* \note During code/extension generation, new parameters are added
* to the generated function, like "runtimeScene" and "eventsFunctionContext".

View File

@@ -162,7 +162,12 @@ module.exports = {
)
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setFunctionName('setRotationX')
.setGetter('getRotationX');
@@ -178,7 +183,12 @@ module.exports = {
)
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setFunctionName('setRotationY')
.setGetter('getRotationY');
@@ -196,7 +206,7 @@ module.exports = {
)
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setFunctionName('turnAroundX');
@@ -214,7 +224,7 @@ module.exports = {
)
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setFunctionName('turnAroundY');
@@ -232,7 +242,7 @@ module.exports = {
)
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setFunctionName('turnAroundZ');
}
@@ -594,7 +604,12 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D model'), 'Model3DObject', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setHidden()
.setFunctionName('setRotationX')
.setGetter('getRotationX');
@@ -611,7 +626,12 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D model'), 'Model3DObject', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setHidden()
.setFunctionName('setRotationY')
.setGetter('getRotationY');
@@ -630,7 +650,7 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D model'), 'Model3DObject', false)
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setHidden()
.setFunctionName('turnAroundX');
@@ -649,7 +669,7 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D model'), 'Model3DObject', false)
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setHidden()
.setFunctionName('turnAroundY');
@@ -668,7 +688,7 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D model'), 'Model3DObject', false)
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setHidden()
.setFunctionName('turnAroundZ');
@@ -1493,7 +1513,12 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D cube'), 'Cube3DObject', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setFunctionName('setRotationX')
.setHidden()
.setGetter('getRotationX');
@@ -1510,7 +1535,12 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D cube'), 'Cube3DObject', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setFunctionName('setRotationY')
.setHidden()
.setGetter('getRotationY');

View File

@@ -109,17 +109,6 @@ module.exports = {
.setLabel(_('Visible on start'))
.setGroup(_('Appearance'));
if (!objectContent.lineHeight) {
objectContent.lineHeight = 0;
}
objectProperties
.getOrCreate('lineHeight')
.setValue(objectContent.lineHeight.toString())
.setType('number')
.setLabel(_('Line height'))
.setDescription(_('Line height for multiline text (0 for automatic)'))
.setGroup(_('Appearance'));
return objectProperties;
};
objectBBText.content = {
@@ -131,7 +120,6 @@ module.exports = {
fontFamily: 'Arial',
align: 'left',
verticalTextAlignment: 'top',
lineHeight: 0,
};
objectBBText.updateInitialInstanceProperty = function (
@@ -406,19 +394,6 @@ module.exports = {
expressionLabel: _('Get the wrapping width'),
expressionDescription: _('Get the wrapping width'),
},
{
functionName: 'LineHeight',
iconPath: 'res/actions/characterSize24.png',
type: 'number',
instructionLabel: _('Line height'),
paramLabel: _('Line height (0 for automatic)'),
conditionDescription: _('Compare the line height of the text.'),
conditionSentence: _('the line height'),
actionDescription: _('Set line height'),
actionSentence: _('the line height'),
expressionLabel: _('Get the line height'),
expressionDescription: _('Get the line height'),
},
];
addSettersAndGettersToObject(object, setterAndGetterProperties, 'BBText');

View File

@@ -102,17 +102,6 @@ namespace gdjs {
this._pixiObject.dirty = true;
}
updateStyle(): void {
//@ts-ignore Private member usage.
if (this._object._lineHeight > 0) {
this._pixiObject.textStyles.default.lineHeight = this._object._lineHeight;
} else {
// Remove lineHeight to use automatic
delete this._pixiObject.textStyles.default.lineHeight;
}
this._pixiObject.dirty = true;
}
updatePosition(): void {
if (this._object.isWrapping() && this._pixiObject.width !== 0) {
const alignmentX =

View File

@@ -20,8 +20,6 @@ namespace gdjs {
/** Alignment of the text: "left", "center" or "right" */
align: 'left' | 'center' | 'right';
verticalTextAlignment: 'top' | 'center' | 'bottom';
/** Line height for multiline text */
lineHeight: float;
};
};
export type BBTextObjectData = ObjectData & BBTextObjectDataType;
@@ -37,7 +35,6 @@ namespace gdjs {
align: string;
vta: string;
hidden: boolean;
lh: float;
};
export type BBTextObjectNetworkSyncData = ObjectNetworkSyncData &
@@ -64,7 +61,6 @@ namespace gdjs {
_textAlign: string;
_verticalTextAlignment: string;
_lineHeight: float;
_renderer: gdjs.BBTextRuntimeObjectRenderer;
@@ -91,7 +87,6 @@ namespace gdjs {
this._textAlign = objectData.content.align;
this._verticalTextAlignment =
objectData.content.verticalTextAlignment || 'top';
this._lineHeight = objectData.content.lineHeight || 0;
this.hidden = !objectData.content.visible;
this._renderer = new gdjs.BBTextRuntimeObjectRenderer(
@@ -147,9 +142,6 @@ namespace gdjs {
newObjectData.content.verticalTextAlignment
);
}
if (oldObjectData.content.lineHeight !== newObjectData.content.lineHeight) {
this.setLineHeight(newObjectData.content.lineHeight || 0);
}
return true;
}
@@ -166,7 +158,6 @@ namespace gdjs {
align: this._textAlign,
vta: this._verticalTextAlignment,
hidden: this.hidden,
lh: this._lineHeight,
};
}
@@ -205,9 +196,6 @@ namespace gdjs {
if (this.hidden !== undefined) {
this.hide(networkSyncData.hidden);
}
if (this._lineHeight !== undefined) {
this.setLineHeight(networkSyncData.lh);
}
}
override extraInitializationFromInitialInstance(
@@ -409,23 +397,6 @@ namespace gdjs {
: 0)
);
}
/**
* Get line height of the BBText object.
* @return line height in pixels (0 for automatic)
*/
getLineHeight(): number {
return this._lineHeight;
}
/**
* Set line height of the BBText object.
* @param value line height in pixels (0 for automatic)
*/
setLineHeight(value: float): void {
this._lineHeight = value;
this._renderer.updateStyle();
}
}
// @ts-ignore
gdjs.registerObject('BBText::BBText', gdjs.BBTextRuntimeObject);

View File

@@ -110,17 +110,6 @@ module.exports = {
.setLabel(_('Font tint'))
.setGroup(_('Font'));
if (!objectContent.lineHeight) {
objectContent.lineHeight = 0;
}
objectProperties
.getOrCreate('lineHeight')
.setValue(objectContent.lineHeight.toString())
.setType('number')
.setLabel(_('Line height'))
.setDescription(_('Line height for multiline text (0 for automatic)'))
.setGroup(_('Appearance'));
return objectProperties;
};
bitmapTextObject.content = {
@@ -133,7 +122,6 @@ module.exports = {
textureAtlasResourceName: '',
align: 'left',
verticalTextAlignment: 'top',
lineHeight: 0,
};
bitmapTextObject.updateInitialInstanceProperty = function (
@@ -425,21 +413,6 @@ module.exports = {
.setFunctionName('setWrappingWidth')
.setGetter('getWrappingWidth');
object
.addExpressionAndConditionAndAction(
'number',
'LineHeight',
_('Line height'),
_('the line height, in pixels, for multiline text'),
_('the line height'),
'',
'res/actions/characterSize24.png'
)
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions().setDescription(_('Line height (0 for automatic)')))
.setFunctionName('setLineHeight')
.setGetter('getLineHeight');
return extension;
},

View File

@@ -96,17 +96,6 @@ namespace gdjs {
this._pixiObject.dirty = true;
}
updateStyle(): void {
// Note: PIXI.BitmapText doesn't have built-in lineHeight support like PIXI.Text
// The lineHeight would need to be handled at a higher level during text layout
// For now, we just mark the object as dirty to trigger a re-render
if (this._object._lineHeight > 0) {
// Custom line height handling would go here
// This is a placeholder for future implementation
}
this._pixiObject.dirty = true;
}
/**
* Get the tint of the bitmap object as a "R;G;B" string.
* @returns the tint of bitmap object in "R;G;B" format.

View File

@@ -20,8 +20,6 @@ namespace gdjs {
/** Alignment of the text. */
align: 'left' | 'center' | 'right';
verticalTextAlignment: 'top' | 'center' | 'bottom';
/** Line height for multiline text */
lineHeight: float;
};
};
export type BitmapTextObjectData = ObjectData & BitmapTextObjectDataType;
@@ -37,7 +35,6 @@ namespace gdjs {
wwidth: float;
align: string;
vta: string;
lh: float;
};
export type BitmapTextObjectNetworkSyncData = ObjectNetworkSyncData &
@@ -69,7 +66,6 @@ namespace gdjs {
_wrappingWidth: float;
_textAlign: string;
_verticalTextAlignment: string;
_lineHeight: float;
_renderer: gdjs.BitmapTextRuntimeObjectPixiRenderer;
@@ -96,7 +92,6 @@ namespace gdjs {
this._textAlign = objectData.content.align;
this._verticalTextAlignment =
objectData.content.verticalTextAlignment || 'top';
this._lineHeight = objectData.content.lineHeight;
this._renderer = new gdjs.BitmapTextRuntimeObjectRenderer(
this,
@@ -156,9 +151,6 @@ namespace gdjs {
newObjectData.content.verticalTextAlignment
);
}
if (oldObjectData.content.lineHeight !== newObjectData.content.lineHeight) {
this.setLineHeight(newObjectData.content.lineHeight);
}
return true;
}
@@ -176,7 +168,6 @@ namespace gdjs {
wwidth: this._wrappingWidth,
align: this._textAlign,
vta: this._verticalTextAlignment,
lh: this.getLineHeight(),
};
}
@@ -215,9 +206,6 @@ namespace gdjs {
if (this._verticalTextAlignment !== undefined) {
this.setVerticalTextAlignment(networkSyncData.vta);
}
if (this._lineHeight !== undefined) {
this.setLineHeight(networkSyncData.lh);
}
}
/**
@@ -452,23 +440,6 @@ namespace gdjs {
: 0)
);
}
/**
* Get line height of the bitmap text object.
* @return line height in pixels (0 for automatic)
*/
getLineHeight(): number {
return this._lineHeight;
}
/**
* Set line height of the bitmap text object.
* @param value line height in pixels (0 for automatic)
*/
setLineHeight(value: float): void {
this._lineHeight = value;
this._renderer.updateStyle();
}
}
gdjs.registerObject(
'BitmapText::BitmapTextObject',

View File

@@ -389,26 +389,15 @@ namespace gdjs {
this.updatePosition();
}
setColor(rgbColor): void {
const colors = rgbColor.split(';');
if (colors.length < 3) {
return;
}
this._centerSprite.tint = gdjs.rgbToHexNumber(
parseInt(colors[0], 10),
parseInt(colors[1], 10),
parseInt(colors[2], 10)
);
setColor(rgbOrHexColor: string): void {
const tint = gdjs.rgbOrHexStringToNumber(rgbOrHexColor);
this._centerSprite.tint = tint;
for (
let borderCounter = 0;
borderCounter < this._borderSprites.length;
borderCounter++
) {
this._borderSprites[borderCounter].tint = gdjs.rgbToHexNumber(
parseInt(colors[0], 10),
parseInt(colors[1], 10),
parseInt(colors[2], 10)
);
this._borderSprites[borderCounter].tint = tint;
}
this._spritesContainer.cacheAsBitmap = false;
}
@@ -416,11 +405,11 @@ namespace gdjs {
getColor() {
const rgb = new PIXI.Color(this._centerSprite.tint).toRgbArray();
return (
Math.floor(rgb[0] * 255) +
Math.round(rgb[0] * 255) +
';' +
Math.floor(rgb[1] * 255) +
Math.round(rgb[1] * 255) +
';' +
Math.floor(rgb[2] * 255)
Math.round(rgb[2] * 255)
);
}

View File

@@ -235,18 +235,6 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Blur radius")));
obj.AddExpressionAndConditionAndAction("number", "LineHeight",
_("Line height"),
_("the line height of the text"),
_("the line height"),
_("Font"),
"res/actions/characterSize24.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Line height (0 for automatic)")));
obj.AddAction("SetSmooth",
_("Smoothing"),
_("Activate or deactivate text smoothing."),
@@ -461,6 +449,16 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"), "Text")
.UseStandardParameters("number", gd::ParameterOptions::MakeNewOptions());
obj.AddExpressionAndConditionAndAction("number",
"LineHeight",
_("Line height"),
_("the line height of a text object"),
_("the line height"),
"",
"res/actions/font24.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardParameters("number", gd::ParameterOptions::MakeNewOptions());
// Support for deprecated "Size" actions/conditions:
obj.AddDuplicatedAction("Size", "Text::SetFontSize").SetHidden();
obj.AddDuplicatedCondition("Size", "Text::FontSize").SetHidden();

View File

@@ -96,6 +96,14 @@ class TextObjectJsExtension : public gd::PlatformExtension {
.SetFunctionName("setOutlineThickness")
.SetGetter("getOutlineThickness");
GetAllExpressionsForObject("TextObject::Text")["LineHeight"]
.SetFunctionName("getLineHeight");
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::LineHeight"]
.SetFunctionName("getLineHeight");
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetLineHeight"]
.SetFunctionName("setLineHeight")
.SetGetter("getLineHeight");
GetAllActionsForObject("TextObject::Text")["TextObject::ShowShadow"]
.SetFunctionName("showShadow");
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::IsShadowEnabled"]
@@ -135,14 +143,6 @@ class TextObjectJsExtension : public gd::PlatformExtension {
.SetFunctionName("setShadowBlurRadius")
.SetGetter("getShadowBlurRadius");
GetAllExpressionsForObject("TextObject::Text")["LineHeight"]
.SetFunctionName("getLineHeight");
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::LineHeight"]
.SetFunctionName("getLineHeight");
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetLineHeight"]
.SetFunctionName("setLineHeight")
.SetGetter("getLineHeight");
// Deprecated actions/conditions (use "FontSize"/"SetFontSize" instead):
GetAllActionsForObject("TextObject::Text")["TextObject::Size"]
.SetFunctionName("setCharacterSize")

View File

@@ -20,6 +20,7 @@ using namespace std;
TextObject::TextObject()
: text("Text"),
characterSize(20),
lineHeight(0),
fontName(""),
smoothed(true),
bold(false),
@@ -36,8 +37,7 @@ TextObject::TextObject()
shadowOpacity(127),
shadowAngle(90),
shadowDistance(4),
shadowBlurRadius(2),
lineHeight(0) {}
shadowBlurRadius(2) {}
TextObject::~TextObject() {};
@@ -51,6 +51,10 @@ bool TextObject::UpdateProperty(const gd::String& propertyName,
characterSize = newValue.To<double>();
return true;
}
if (propertyName == "lineHeight") {
lineHeight = newValue.To<double>();
return true;
}
if (propertyName == "font") {
fontName = newValue;
return true;
@@ -111,10 +115,6 @@ bool TextObject::UpdateProperty(const gd::String& propertyName,
shadowBlurRadius = newValue.To<double>();
return true;
}
if (propertyName == "lineHeight") {
lineHeight = newValue.To<double>();
return true;
}
return false;
}
@@ -134,6 +134,13 @@ std::map<gd::String, gd::PropertyDescriptor> TextObject::GetProperties() const {
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
.SetGroup(_("Font"));
objectProperties["lineHeight"]
.SetValue(gd::String::From(lineHeight))
.SetType("number")
.SetLabel(_("Line height"))
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
.SetGroup(_("Font"));
objectProperties["font"]
.SetValue(fontName)
.SetType("resource")
@@ -260,15 +267,6 @@ std::map<gd::String, gd::PropertyDescriptor> TextObject::GetProperties() const {
.SetAdvanced()
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
objectProperties["lineHeight"]
.SetValue(gd::String::From(lineHeight))
.SetType("number")
.SetLabel(_("Line height"))
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
.SetGroup(_("Font"))
.SetDescription(_("Line height for multiline text (0 for automatic)"))
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
return objectProperties;
}
@@ -285,6 +283,7 @@ void TextObject::DoUnserializeFrom(gd::Project& project,
SetCharacterSize(content.GetChild("characterSize", 0, "CharacterSize")
.GetValue()
.GetInt());
SetLineHeight(content.GetDoubleAttribute("lineHeight", 0));
smoothed = content.GetBoolAttribute("smoothed");
bold = content.GetBoolAttribute("bold");
italic = content.GetBoolAttribute("italic");
@@ -318,7 +317,6 @@ void TextObject::DoUnserializeFrom(gd::Project& project,
SetShadowAngle(content.GetIntAttribute("shadowAngle", 90));
SetShadowDistance(content.GetIntAttribute("shadowDistance", 4));
SetShadowBlurRadius(content.GetIntAttribute("shadowBlurRadius", 2));
SetLineHeight(content.GetIntAttribute("lineHeight", 0));
}
}
@@ -354,6 +352,7 @@ void TextObject::DoSerializeTo(gd::SerializerElement& element) const {
content.AddChild("textAlignment").SetValue(GetTextAlignment());
content.AddChild("verticalTextAlignment").SetValue(GetVerticalTextAlignment());
content.AddChild("characterSize").SetValue(GetCharacterSize());
content.AddChild("lineHeight").SetValue(GetLineHeight());
content.AddChild("color").SetValue(GetColor());
content.SetAttribute("smoothed", smoothed);
@@ -371,7 +370,6 @@ void TextObject::DoSerializeTo(gd::SerializerElement& element) const {
content.SetAttribute("shadowAngle", shadowAngle);
content.SetAttribute("shadowDistance", shadowDistance);
content.SetAttribute("shadowBlurRadius", shadowBlurRadius);
content.SetAttribute("lineHeight", lineHeight);
}
void TextObject::ExposeResources(gd::ArbitraryResourceWorker& worker) {

View File

@@ -49,6 +49,12 @@ class GD_EXTENSION_API TextObject : public gd::ObjectConfiguration {
*/
inline double GetCharacterSize() const { return characterSize; };
/** \brief Change the line height. */
inline void SetLineHeight(double value) { lineHeight = value; };
/** \brief Get the line height. */
inline double GetLineHeight() const { return lineHeight; };
/** \brief Return the name of the font resource used for the text.
*/
inline const gd::String& GetFontName() const { return fontName; };
@@ -113,9 +119,6 @@ class GD_EXTENSION_API TextObject : public gd::ObjectConfiguration {
void SetShadowBlurRadius(double value) { shadowBlurRadius = value; };
double GetShadowBlurRadius() const { return shadowBlurRadius; };
void SetLineHeight(double value) { lineHeight = value; };
double GetLineHeight() const { return lineHeight; };
private:
virtual void DoUnserializeFrom(gd::Project& project,
const gd::SerializerElement& element) override;
@@ -123,6 +126,7 @@ class GD_EXTENSION_API TextObject : public gd::ObjectConfiguration {
gd::String text;
double characterSize;
double lineHeight;
gd::String fontName;
bool smoothed;
bool bold, italic, underlined;
@@ -140,5 +144,4 @@ class GD_EXTENSION_API TextObject : public gd::ObjectConfiguration {
double shadowAngle;
double shadowDistance;
double shadowBlurRadius;
double lineHeight;
};

View File

@@ -64,9 +64,6 @@ namespace gdjs {
style.wordWrap = this._object._wrapping;
style.wordWrapWidth = this._object._wrappingWidth;
style.breakWords = true;
if (this._object._lineHeight > 0) {
style.lineHeight = this._object._lineHeight;
}
style.stroke = gdjs.rgbToHexNumber(
this._object._outlineColor[0],
this._object._outlineColor[1],
@@ -89,6 +86,7 @@ namespace gdjs {
? style.dropShadowDistance + style.dropShadowBlur
: 0;
style.padding = Math.ceil(this._object._padding + extraPaddingForShadow);
style.lineHeight = this._object._lineHeight;
// Prevent spikey outlines by adding a miter limit
style.miterLimit = 3;

View File

@@ -22,6 +22,8 @@ namespace gdjs {
text: string;
textAlignment: string;
verticalTextAlignment: string;
/** The line height */
lineHeight: float;
isOutlineEnabled: boolean;
outlineThickness: float;
@@ -34,7 +36,6 @@ namespace gdjs {
shadowDistance: float;
shadowAngle: float;
shadowBlurRadius: float;
lineHeight: float;
};
};
@@ -103,10 +104,11 @@ namespace gdjs {
_shadowAngle: float;
_shadowBlur: float;
_lineHeight: float;
_padding: integer = 5;
_str: string;
_renderer: gdjs.TextRuntimeObjectRenderer;
_lineHeight: float = 0;
// We can store the scale as nothing else can change it.
_scaleX: number = 1;
@@ -142,7 +144,6 @@ namespace gdjs {
this._shadowDistance = content.shadowDistance;
this._shadowBlur = content.shadowBlurRadius;
this._shadowAngle = content.shadowAngle;
this._lineHeight = content.lineHeight || 0;
this._renderer = new gdjs.TextRuntimeObjectRenderer(
@@ -216,7 +217,7 @@ namespace gdjs {
if (oldContent.shadowBlurRadius !== newContent.shadowBlurRadius) {
this.setShadowBlurRadius(newContent.shadowBlurRadius);
}
if (oldContent.lineHeight !== newContent.lineHeight) {
if ((oldContent.lineHeight || 0) !== (newContent.lineHeight || 0)) {
this.setLineHeight(newContent.lineHeight || 0);
}
return true;
@@ -246,8 +247,8 @@ namespace gdjs {
shd: this._shadowDistance,
sha: this._shadowAngle,
shb: this._shadowBlur,
pad: this._padding,
lh: this._lineHeight,
pad: this._padding,
};
}
@@ -321,12 +322,12 @@ namespace gdjs {
if (networkSyncData.shb !== undefined) {
this.setShadowBlurRadius(networkSyncData.shb);
}
if (networkSyncData.pad !== undefined) {
this.setPadding(networkSyncData.pad);
}
if (networkSyncData.lh !== undefined) {
this.setLineHeight(networkSyncData.lh);
}
if (networkSyncData.pad !== undefined) {
this.setPadding(networkSyncData.pad);
}
}
override getRendererObject() {
@@ -456,6 +457,22 @@ namespace gdjs {
this._renderer.updateStyle();
}
/**
* Get the line height of the text.
*/
getLineHeight(): float {
return this._lineHeight;
}
/**
* Set the line height of the text.
* @param value The new line height for the text.
*/
setLineHeight(value: float): void {
this._lineHeight = value;
this._renderer.updateStyle();
}
/**
* Set the name of the resource to use for the font.
* @param fontResourceName The name of the font resource.
@@ -585,16 +602,10 @@ namespace gdjs {
/**
* Change the text color.
* @param colorString color as a "R;G;B" string, for example: "255;0;0"
* @param rgbOrHexColor color as a "R;G;B" string, for example: "255;0;0"
*/
setColor(colorString: string): void {
const color = colorString.split(';');
if (color.length < 3) {
return;
}
this._color[0] = parseInt(color[0], 10);
this._color[1] = parseInt(color[1], 10);
this._color[2] = parseInt(color[2], 10);
setColor(rgbOrHexColor: string): void {
this._color = gdjs.rgbOrHexToRGBColor(rgbOrHexColor);
this._useGradient = false;
this._renderer.updateStyle();
}
@@ -703,18 +714,12 @@ namespace gdjs {
/**
* Set the outline for the text object.
* @param str color as a "R;G;B" string, for example: "255;0;0"
* @param rgbOrHexColor color as a "R;G;B" string, for example: "255;0;0"
* @param thickness thickness of the outline (0 = disabled)
* @deprecated Prefer independent setters.
*/
setOutline(str: string, thickness: number): void {
const color = str.split(';');
if (color.length < 3) {
return;
}
this._outlineColor[0] = parseInt(color[0], 10);
this._outlineColor[1] = parseInt(color[1], 10);
this._outlineColor[2] = parseInt(color[2], 10);
setOutline(rgbOrHexColor: string, thickness: number): void {
this._outlineColor = gdjs.rgbOrHexToRGBColor(rgbOrHexColor);
this._outlineThickness = thickness;
this._renderer.updateStyle();
}
@@ -756,25 +761,19 @@ namespace gdjs {
/**
* Set the shadow for the text object.
* @param str color as a "R;G;B" string, for example: "255;0;0"
* @param rgbOrHexColor color as a "R;G;B" string, for example: "255;0;0"
* @param distance distance between the shadow and the text, in pixels.
* @param blur amount of shadow blur, in pixels.
* @param angle shadow offset direction, in degrees.
* @deprecated Prefer independent setters.
*/
setShadow(
str: string,
rgbOrHexColor: string,
distance: number,
blur: integer,
angle: float
): void {
const color = str.split(';');
if (color.length < 3) {
return;
}
this._shadowColor[0] = parseInt(color[0], 10);
this._shadowColor[1] = parseInt(color[1], 10);
this._shadowColor[2] = parseInt(color[2], 10);
this._shadowColor = gdjs.rgbOrHexToRGBColor(rgbOrHexColor);
this._shadowDistance = distance;
this._shadowBlur = blur;
this._shadowAngle = angle;
@@ -887,38 +886,18 @@ namespace gdjs {
strThirdColor: string,
strFourthColor: string
): void {
const colorFirst = strFirstColor.split(';');
const colorSecond = strSecondColor.split(';');
const colorThird = strThirdColor.split(';');
const colorFourth = strFourthColor.split(';');
this._gradient = [];
if (colorFirst.length == 3) {
this._gradient.push([
parseInt(colorFirst[0], 10),
parseInt(colorFirst[1], 10),
parseInt(colorFirst[2], 10),
]);
if (strFirstColor) {
this._gradient.push(gdjs.rgbOrHexToRGBColor(strFirstColor));
}
if (colorSecond.length == 3) {
this._gradient.push([
parseInt(colorSecond[0], 10),
parseInt(colorSecond[1], 10),
parseInt(colorSecond[2], 10),
]);
if (strSecondColor) {
this._gradient.push(gdjs.rgbOrHexToRGBColor(strSecondColor));
}
if (colorThird.length == 3) {
this._gradient.push([
parseInt(colorThird[0], 10),
parseInt(colorThird[1], 10),
parseInt(colorThird[2], 10),
]);
if (strThirdColor) {
this._gradient.push(gdjs.rgbOrHexToRGBColor(strThirdColor));
}
if (colorFourth.length == 3) {
this._gradient.push([
parseInt(colorFourth[0], 10),
parseInt(colorFourth[1], 10),
parseInt(colorFourth[2], 10),
]);
if (strFourthColor) {
this._gradient.push(gdjs.rgbOrHexToRGBColor(strFourthColor));
}
this._gradientType = strGradientType;
this._useGradient = this._gradient.length > 1 ? true : false;
@@ -941,23 +920,6 @@ namespace gdjs {
this._padding = value;
this._renderer.updateStyle();
}
/**
* Get line height of the text object.
* @return line height in pixels (0 for automatic)
*/
getLineHeight(): number {
return this._lineHeight;
}
/**
* Set line height of the text object.
* @param value line height in pixels (0 for automatic)
*/
setLineHeight(value: float): void {
this._lineHeight = value;
this._renderer.updateStyle();
}
}
gdjs.registerObject('TextObject::Text', gdjs.TextRuntimeObject);
}

View File

@@ -92,28 +92,18 @@ namespace gdjs {
-this._object._yOffset % this._tiledSprite.texture.height;
}
setColor(rgbColor: string): void {
const colors = rgbColor.split(';');
if (colors.length < 3) {
return;
}
this._tiledSprite.tint =
'0x' +
gdjs.rgbToHex(
parseInt(colors[0], 10),
parseInt(colors[1], 10),
parseInt(colors[2], 10)
);
setColor(rgbOrHexColor: string): void {
this._tiledSprite.tint = gdjs.rgbOrHexStringToNumber(rgbOrHexColor);
}
getColor() {
const rgb = new PIXI.Color(this._tiledSprite.tint).toRgbArray();
return (
Math.floor(rgb[0] * 255) +
Math.round(rgb[0] * 255) +
';' +
Math.floor(rgb[1] * 255) +
Math.round(rgb[1] * 255) +
';' +
Math.floor(rgb[2] * 255)
Math.round(rgb[2] * 255)
);
}

View File

@@ -145,6 +145,7 @@ describe('gdjs.TweenRuntimeBehavior', () => {
shadowDistance: 4,
shadowAngle: 90,
shadowBlurRadius: 2,
lineHeight: 0,
},
});
runtimeScene.addObject(object);

View File

@@ -145,6 +145,7 @@ describe('gdjs.TweenRuntimeBehavior', () => {
shadowDistance: 4,
shadowAngle: 90,
shadowBlurRadius: 2,
lineHeight: 0,
},
});
runtimeScene.addObject(object);

View File

@@ -1326,11 +1326,11 @@ namespace gdjs {
lightness
);
owner.setColor(
Math.floor(rgbFromHslColor[0]) +
Math.round(rgbFromHslColor[0]) +
';' +
Math.floor(rgbFromHslColor[1]) +
Math.round(rgbFromHslColor[1]) +
';' +
Math.floor(rgbFromHslColor[2])
Math.round(rgbFromHslColor[2])
);
};
} else {
@@ -1439,12 +1439,11 @@ namespace gdjs {
if (!isColorable(this.owner)) return;
const owner = this.owner;
const rgbFromColor: string[] = owner.getColor().split(';');
if (rgbFromColor.length < 3) return;
const rgbFromColor = gdjs.rgbOrHexToRGBColor(owner.getColor());
const hslFromColor = gdjs.evtTools.tween.rgbToHsl(
parseFloat(rgbFromColor[0]),
parseFloat(rgbFromColor[1]),
parseFloat(rgbFromColor[2])
rgbFromColor[0],
rgbFromColor[1],
rgbFromColor[2]
);
const toH = animateHue ? toHue : hslFromColor[0];
@@ -1474,11 +1473,11 @@ namespace gdjs {
);
owner.setColor(
Math.floor(rgbFromHslColor[0]) +
Math.round(rgbFromHslColor[0]) +
';' +
Math.floor(rgbFromHslColor[1]) +
Math.round(rgbFromHslColor[1]) +
';' +
Math.floor(rgbFromHslColor[2])
Math.round(rgbFromHslColor[2])
);
},

View File

@@ -40,6 +40,7 @@ gd::String EventsCodeGenerator::GenerateEventsListCompleteFunctionCode(
gdjs::EventsCodeGenerator& codeGenerator,
gd::String fullyQualifiedFunctionName,
gd::String functionArgumentsCode,
gd::String contextClassCode,
gd::String functionPreEventsCode,
const gd::EventsList& events,
gd::String functionPostEventsCode,
@@ -81,6 +82,7 @@ gd::String EventsCodeGenerator::GenerateEventsListCompleteFunctionCode(
globalDeclarations +
globalObjectLists + "\n\n" +
codeGenerator.GetCustomCodeOutsideMain() + "\n\n" +
contextClassCode + "\n\n" +
fullyQualifiedFunctionName + " = function(" +
functionArgumentsCode +
") {\n" +
@@ -112,6 +114,7 @@ gd::String EventsCodeGenerator::GenerateLayoutCode(
codeGenerator,
codeGenerator.GetCodeNamespaceAccessor() + "func",
"runtimeScene",
"",
"runtimeScene.getOnceTriggers().startNewFrame();\n",
scene.GetEvents(),
"",
@@ -145,16 +148,40 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionCode(
gd::DiagnosticReport diagnosticReport;
codeGenerator.SetDiagnosticReport(&diagnosticReport);
gd::String output = GenerateEventsListCompleteFunctionCode(
codeGenerator, codeGenerator.GetCodeNamespaceAccessor() + "func",
codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
auto parameterList = codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
eventsFunction.GetParametersForEvents(
eventsFunctionsExtension.GetEventsFunctions()),
0, true),
0, true);
gd::String output = GenerateEventsListCompleteFunctionCode(
codeGenerator, codeGenerator.GetCodeNamespaceAccessor() + "func",
parameterList,
codeGenerator.GenerateFreeEventsFunctionContext(
eventsFunctionsExtension, eventsFunction,
"runtimeScene.getOnceTriggers()"),
eventsFunction.GetEvents(), "",
"runtimeScene.getOnceTriggers()") +
codeGenerator.GetCodeNamespaceAccessor() + "context = null;\n" + //
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted = false;\n",
"if (!" + codeGenerator.GetCodeNamespaceAccessor() + "context) " +
codeGenerator.GetCodeNamespaceAccessor() + "context = new " +
codeGenerator.GetCodeNamespaceAccessor() + "Context(" +
parameterList + ");\n"
"const eventsFunctionContext = " +
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted ? new " +
codeGenerator.GetCodeNamespaceAccessor() + "Context(" +
parameterList + ") : " + codeGenerator.GetCodeNamespaceAccessor() +
"context;\n" + //
"if (!" + codeGenerator.GetCodeNamespaceAccessor() +
"isExecuted) {\n" + codeGenerator.GetCodeNamespaceAccessor() +
"context.reinitialize(" + parameterList + ");\n" + //
codeGenerator.GetCodeNamespaceAccessor() +
"isExecuted = true;\n"
"}\n",
eventsFunction.GetEvents(),
"if (eventsFunctionContext === " +
codeGenerator.GetCodeNamespaceAccessor() + "context) " +
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted = false;\n",
codeGenerator.GenerateEventsFunctionReturn(eventsFunction));
// TODO: the editor should pass the diagnostic report and display it to the
@@ -201,44 +228,59 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
gd::DiagnosticReport diagnosticReport;
codeGenerator.SetDiagnosticReport(&diagnosticReport);
auto contextParameterList = codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
eventsFunction.GetParametersForEvents(
eventsBasedBehavior.GetEventsFunctions()),
2, true) + ", that";
// Generate the code setting up the context of the function.
gd::String fullPreludeCode =
preludeCode + "\n" + "var that = this;\n" +
preludeCode + "\n" + "const that = this;\n" +
// runtimeScene is supposed to be always accessible, read
// it from the behavior.
// TODO: this should be renamed to "instanceContainer" and have the code generation
// adapted for this (rely less on `gdjs.RuntimeScene`, and more on `RuntimeInstanceContainer`).
"var runtimeScene = this._runtimeScene;\n" +
// By convention of Behavior Events Function, the object is accessible
// as a parameter called "Object", and thisObjectList is an array
// containing it (for faster access, without having to go through the
// hashmap).
"var thisObjectList = [this.owner];\n" +
"var Object = Hashtable.newFrom({Object: thisObjectList});\n" +
// By convention of Behavior Events Function, the behavior is accessible
// as a parameter called "Behavior".
"var Behavior = this.name;\n" +
codeGenerator.GenerateBehaviorEventsFunctionContext(
eventsFunctionsExtension,
eventsBasedBehavior,
eventsFunction,
onceTriggersVariable,
// Pass the names of the parameters considered as the current
// object and behavior parameters:
"Object",
"Behavior");
// TODO: this should be renamed to "instanceContainer" and have the code
// generation adapted for this (rely less on `gdjs.RuntimeScene`, and more
// on `RuntimeInstanceContainer`).
"const runtimeScene = this._runtimeScene;\n" +
"if (!" + codeGenerator.GetCodeNamespaceAccessor() + "context) " +
codeGenerator.GetCodeNamespaceAccessor() + "context = new " +
codeGenerator.GetCodeNamespaceAccessor() + "Context(" +
contextParameterList + ");\n"
"const eventsFunctionContext = " +
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted ? new " +
codeGenerator.GetCodeNamespaceAccessor() + "Context(" +
contextParameterList + ") : " + codeGenerator.GetCodeNamespaceAccessor() +
"context;\n" + //
"if (!" + codeGenerator.GetCodeNamespaceAccessor() +
"isExecuted) {\n" + codeGenerator.GetCodeNamespaceAccessor() +
"context.reinitialize(" + contextParameterList + ");\n" + //
codeGenerator.GetCodeNamespaceAccessor() +
"isExecuted = true;\n"
"}\n";
gd::String output = GenerateEventsListCompleteFunctionCode(
codeGenerator,
fullyQualifiedFunctionName,
auto parameterList =
codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
eventsFunction.GetParametersForEvents(
eventsBasedBehavior.GetEventsFunctions()),
2,
false),
2, false);
gd::String output = GenerateEventsListCompleteFunctionCode(
codeGenerator, fullyQualifiedFunctionName, parameterList,
codeGenerator.GenerateBehaviorEventsFunctionContext(
eventsFunctionsExtension, eventsBasedBehavior, eventsFunction,
onceTriggersVariable,
// Pass the names of the parameters considered as the current
// object and behavior parameters:
"Object", "Behavior") +
codeGenerator.GetCodeNamespaceAccessor() + "context = null;\n" + //
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted = false;\n",
fullPreludeCode,
eventsFunction.GetEvents(),
"",
"if (eventsFunctionContext === " +
codeGenerator.GetCodeNamespaceAccessor() + "context) {" +
codeGenerator.GetCodeNamespaceAccessor() + "context.clear();\n" +
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted = false;\n"
"};\n",
codeGenerator.GenerateEventsFunctionReturn(eventsFunction));
// TODO: the editor should pass the diagnostic report and display it to the
@@ -286,53 +328,56 @@ gd::String EventsCodeGenerator::GenerateObjectEventsFunctionCode(
gd::DiagnosticReport diagnosticReport;
codeGenerator.SetDiagnosticReport(&diagnosticReport);
auto contextParameterList = codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
eventsFunction.GetParametersForEvents(
eventsBasedObject.GetEventsFunctions()),
1, true) + ", that";
// Generate the code setting up the context of the function.
gd::String fullPreludeCode =
preludeCode + "\n" + "var that = this;\n" +
preludeCode + "\n" + "const that = this;\n" +
// runtimeScene is supposed to be always accessible, read
// it from the object.
// TODO: this should be renamed to "instanceContainer" and have the code generation
// adapted for this (rely less on `gdjs.RuntimeScene`, and more on `RuntimeInstanceContainer`).
"var runtimeScene = this._instanceContainer;\n" +
// By convention of Object Events Function, the object is accessible
// as a parameter called "Object", and thisObjectList is an array
// containing it (for faster access, without having to go through the
// hashmap).
"var thisObjectList = [this];\n" +
"var Object = Hashtable.newFrom({Object: thisObjectList});\n";
"const runtimeScene = this._instanceContainer;\n"
"if (!" + codeGenerator.GetCodeNamespaceAccessor() + "context) " +
codeGenerator.GetCodeNamespaceAccessor() + "context = new " +
codeGenerator.GetCodeNamespaceAccessor() + "Context(" +
contextParameterList + ");\n"
"const eventsFunctionContext = " +
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted ? new " +
codeGenerator.GetCodeNamespaceAccessor() + "Context(" +
contextParameterList + ") : " + codeGenerator.GetCodeNamespaceAccessor() +
"context;\n" + //
"if (!" + codeGenerator.GetCodeNamespaceAccessor() +
"isExecuted) {\n" + codeGenerator.GetCodeNamespaceAccessor() +
"context.reinitialize(" + contextParameterList + ");\n" + //
codeGenerator.GetCodeNamespaceAccessor() +
"isExecuted = true;\n"
"}\n";
// Add child-objects
for (auto& childObject : eventsBasedObject.GetObjects().GetObjects()) {
// child-object are never picked because they are not parameters.
const auto& childName = ManObjListName(childObject->GetName());
fullPreludeCode += "var this" + childName +
"List = [...runtimeScene.getObjects(" +
ConvertToStringExplicit(childObject->GetName()) +
")];\n" + "var " + childName + " = Hashtable.newFrom({" +
ConvertToStringExplicit(childObject->GetName()) +
": this" + childName + "List});\n";
}
fullPreludeCode += codeGenerator.GenerateObjectEventsFunctionContext(
eventsFunctionsExtension,
eventsBasedObject,
eventsFunction,
onceTriggersVariable,
// Pass the names of the parameters considered as the current
// object and behavior parameters:
"Object");
gd::String output = GenerateEventsListCompleteFunctionCode(
codeGenerator,
fullyQualifiedFunctionName,
auto parameterList =
codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
// TODO EBO use constants for firstParameterIndex
eventsFunction.GetParametersForEvents(
eventsBasedObject.GetEventsFunctions()),
1,
false),
fullPreludeCode,
eventsFunction.GetEvents(),
1, false);
gd::String output = GenerateEventsListCompleteFunctionCode(
codeGenerator, fullyQualifiedFunctionName, parameterList,
codeGenerator.GenerateObjectEventsFunctionContext(
eventsFunctionsExtension, eventsBasedObject, eventsFunction,
onceTriggersVariable,
// Pass the names of the parameters considered as the current
// object and behavior parameters:
"Object") +
codeGenerator.GetCodeNamespaceAccessor() + "context = null;\n" + //
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted = false;\n",
fullPreludeCode, eventsFunction.GetEvents(),
"if (eventsFunctionContext === " +
codeGenerator.GetCodeNamespaceAccessor() + "context) " +
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted = false;\n" +
endingCode,
codeGenerator.GenerateEventsFunctionReturn(eventsFunction));
@@ -376,6 +421,33 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionParameterDeclarationsList(
return declaration;
}
gd::String EventsCodeGenerator::GenerateEventsFunctionParametersToAttribues(
const gd::ParameterMetadataContainer &parameters, int firstParameterIndex,
bool addsSceneParameter) {
gd::String declaration =
addsSceneParameter ? " this.runtimeScene = runtimeScene;\n" : "";
// By convention, the first two arguments of a behavior events function
// are the object and the behavior, which are not passed to the called
// function in the generated JS code.
for (size_t i = firstParameterIndex; i < parameters.GetParametersCount(); ++i) {
const auto &parameter = parameters.GetParameter(i);
if (parameter.GetValueTypeMetadata().IsObject() ||
parameter.GetValueTypeMetadata().IsBehavior()) {
continue;
}
gd::String parameterMangledName =
parameter.GetName().empty()
? "_"
: EventsCodeNameMangler::GetMangledName(parameter.GetName());
declaration +=
" this." + parameterMangledName + " = " + parameterMangledName + ";\n";
}
declaration +=
" this.parentEventsFunctionContext = parentEventsFunctionContext;\n";
return declaration;
}
gd::String EventsCodeGenerator::GenerateFreeEventsFunctionContext(
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsFunction& eventsFunction,
@@ -410,17 +482,17 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionContext(
// optimized getter for it (bypassing "Object" hashmap, and directly return
// the array containing it).
if (!thisObjectName.empty()) {
objectsGettersMap +=
ConvertToStringExplicit(thisObjectName) + ": " + thisObjectName + "\n";
objectArraysMap +=
ConvertToStringExplicit(thisObjectName) + ": thisObjectList\n";
objectsGettersMap += " " +
ConvertToStringExplicit(thisObjectName) + ": " + thisObjectName;
objectArraysMap += " " +
ConvertToStringExplicit(thisObjectName) + ": thisObjectList";
}
if (!thisBehaviorName.empty()) {
// If we have a behavior considered as the current behavior ("this")
// (usually called Behavior in behavior events function), generate a
// slightly more optimized getter for it.
behaviorNamesMap += ConvertToStringExplicit(thisBehaviorName) + ": " +
behaviorNamesMap += " " + ConvertToStringExplicit(thisBehaviorName) + ": " +
thisBehaviorName + "\n";
// Add required behaviors from properties
@@ -437,11 +509,40 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionContext(
gd::String comma = behaviorNamesMap.empty() ? "" : ", ";
behaviorNamesMap +=
comma + ConvertToStringExplicit(propertyDescriptor.GetName()) +
": this._get" + propertyDescriptor.GetName() + "()\n";
": that._get" + propertyDescriptor.GetName() + "()\n";
}
}
}
gd::String constructorAdditionalCode =
"this.that = that;\n"
" const thisObjectList = [that.owner];\n";
if (!thisObjectName.empty()) {
constructorAdditionalCode += " const " + thisObjectName +
" = Hashtable.newFrom({ " + thisObjectName +
": thisObjectList });\n";
}
if (!thisBehaviorName.empty()) {
constructorAdditionalCode +=
" const " + thisBehaviorName + " = that.name;\n";
}
gd::String reinitializeAdditionalCode = "";
gd::String clearAdditionalCode = "";
if (!thisObjectName.empty()) {
reinitializeAdditionalCode += " this._objectArraysMap[" +
ConvertToStringExplicit(thisObjectName) +
"][0] = that.owner;\n";
clearAdditionalCode += " this._objectArraysMap[" +
ConvertToStringExplicit(thisObjectName) +
"][0] = null;\n";
}
if (!thisBehaviorName.empty()) {
reinitializeAdditionalCode += " this._behaviorNamesMap[" +
ConvertToStringExplicit(thisBehaviorName) +
"] = that.name;\n";
}
return GenerateEventsFunctionContext(eventsFunctionsExtension,
eventsBasedBehavior.GetEventsFunctions(),
eventsFunction,
@@ -449,6 +550,9 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionContext(
objectsGettersMap,
objectArraysMap,
behaviorNamesMap,
constructorAdditionalCode,
reinitializeAdditionalCode,
clearAdditionalCode,
thisObjectName,
thisBehaviorName);
}
@@ -470,24 +574,69 @@ gd::String EventsCodeGenerator::GenerateObjectEventsFunctionContext(
// optimized getter for it (bypassing "Object" hashmap, and directly return
// the array containing it).
if (!thisObjectName.empty()) {
objectsGettersMap +=
ConvertToStringExplicit(thisObjectName) + ": " + thisObjectName + "\n";
objectArraysMap +=
ConvertToStringExplicit(thisObjectName) + ": thisObjectList\n";
objectsGettersMap += " " +
ConvertToStringExplicit(thisObjectName) + ": " + thisObjectName;
objectArraysMap += " " +
ConvertToStringExplicit(thisObjectName) + ": thisObjectList";
// Add child-objects
for (auto& childObject : eventsBasedObject.GetObjects().GetObjects()) {
const auto& childName = ManObjListName(childObject->GetName());
// child-object are never picked because they are not parameters.
objectsGettersMap += ", " +
objectsGettersMap += ",\n " +
ConvertToStringExplicit(childObject->GetName()) +
": " + childName + "\n";
objectArraysMap += ", " +
": " + childName;
objectArraysMap += ",\n " +
ConvertToStringExplicit(childObject->GetName()) +
": this" + childName + "List\n";
": this" + childName + "List";
}
}
// By convention of Object Events Function, the object is accessible
// as a parameter called "Object", and thisObjectList is an array
// containing it (for faster access, without having to go through the
// hashmap).
gd::String constructorAdditionalCode = "this.that = that;\n"
" const thisObjectList = [that];\n";
if (!thisObjectName.empty()) {
constructorAdditionalCode += " const " + thisObjectName +
" = Hashtable.newFrom({" + thisObjectName +
": thisObjectList});\n";
}
// Add child-objects
for (auto& childObject : eventsBasedObject.GetObjects().GetObjects()) {
// child-object are never picked because they are not parameters.
const auto& childName = ManObjListName(childObject->GetName());
constructorAdditionalCode +=
" const this" + childName + "List = [...runtimeScene.getObjects(" +
ConvertToStringExplicit(childObject->GetName()) + ")];\n" + //
" const " + childName + " = Hashtable.newFrom({" +
ConvertToStringExplicit(childObject->GetName()) + ": this" + childName +
"List});\n";
}
gd::String reinitializeAdditionalCode = "";
gd::String clearAdditionalCode = "";
if (!thisObjectName.empty()) {
reinitializeAdditionalCode += " this._objectArraysMap[" +
ConvertToStringExplicit(thisObjectName) +
"][0] = that;\n";
clearAdditionalCode += " this._objectArraysMap[" +
ConvertToStringExplicit(thisObjectName) +
"][0] = null;\n";
}
// Add child-objects
for (auto& childObject : eventsBasedObject.GetObjects().GetObjects()) {
// child-object are never picked because they are not parameters.
const auto& childName = ManObjListName(childObject->GetName());
reinitializeAdditionalCode +=
" gdjs.copyArray(runtimeScene.getObjects(" +
ConvertToStringExplicit(childObject->GetName()) +
"), "
"this._objectArraysMap[" +
ConvertToStringExplicit(childObject->GetName()) + "]);\n";
}
return GenerateEventsFunctionContext(eventsFunctionsExtension,
eventsBasedObject.GetEventsFunctions(),
eventsFunction,
@@ -495,6 +644,9 @@ gd::String EventsCodeGenerator::GenerateObjectEventsFunctionContext(
objectsGettersMap,
objectArraysMap,
behaviorNamesMap,
constructorAdditionalCode,
reinitializeAdditionalCode,
clearAdditionalCode,
thisObjectName);
}
@@ -506,6 +658,9 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
gd::String& objectsGettersMap,
gd::String& objectArraysMap,
gd::String& behaviorNamesMap,
const gd::String& constructorAdditionalCode,
const gd::String& reinitializeAdditionalCode,
const gd::String& clearAdditionalCode,
const gd::String& thisObjectName,
const gd::String& thisBehaviorName) {
const auto& extensionName = eventsFunctionsExtension.GetName();
@@ -526,6 +681,11 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
// Conditions/expressions are available to deal with them in events.
gd::String argumentsGetters;
gd::String reinitializeObjectsMap;
gd::String reinitializeArraysMap;
gd::String reinitializeBehaviorNamesMap;
gd::String clearObjectsMap;
gd::String clearArraysMap;
for (const auto& parameterPtr : parameters.GetInternalVector()) {
const auto& parameter = *parameterPtr;
@@ -541,13 +701,26 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
// Generate map that will be used to get the lists of objects passed
// as parameters (either as objects lists or array).
gd::String comma = objectsGettersMap.empty() ? "" : ", ";
gd::String comma = objectsGettersMap.empty() ? "" : ",\n ";
objectsGettersMap += comma +
ConvertToStringExplicit(parameter.GetName()) + ": " +
parameterMangledName + "\n";
parameterMangledName;
objectArraysMap += comma + ConvertToStringExplicit(parameter.GetName()) +
": gdjs.objectsListsToArray(" + parameterMangledName +
")\n";
")";
reinitializeObjectsMap += " this._objectsMap[" +
ConvertToStringExplicit(parameter.GetName()) +
"] = " + parameterMangledName + ";\n";
reinitializeArraysMap +=
" gdjs.objectsListsToArray(" + parameterMangledName +
", this._objectArraysMap[" +
ConvertToStringExplicit(parameter.GetName()) + "]);\n";
clearObjectsMap += " this._objectsMap[" +
ConvertToStringExplicit(parameter.GetName()) +
"] = null;\n";
clearArraysMap += " this._objectArraysMap[" +
ConvertToStringExplicit(parameter.GetName()) +
"].length = 0;\n";
} else if (gd::ParameterMetadata::IsBehavior(parameter.GetType())) {
if (parameter.GetName() == thisBehaviorName) {
continue;
@@ -555,114 +728,171 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
// Generate map that will be used to transform from behavior name used in
// function to the "real" behavior name from the caller.
gd::String comma = behaviorNamesMap.empty() ? "" : ", ";
gd::String comma = behaviorNamesMap.empty() ? "" : ",\n";
behaviorNamesMap += comma + ConvertToStringExplicit(parameter.GetName()) +
": " + parameterMangledName + "\n";
": " + parameterMangledName;
reinitializeBehaviorNamesMap +=
"this._behaviorNamesMap[" +
ConvertToStringExplicit(parameter.GetName()) +
"] = " + parameterMangledName + ";\n";
} else {
argumentsGetters +=
"if (argName === " + ConvertToStringExplicit(parameter.GetName()) +
") return " + parameterMangledName + ";\n";
" if (argName === " + ConvertToStringExplicit(parameter.GetName()) +
") return this." + parameterMangledName + ";\n";
}
}
const gd::String async = eventsFunction.IsAsync()
? " task: new gdjs.ManuallyResolvableTask(),\n"
? " this.task = new gdjs.ManuallyResolvableTask(),\n"
: "";
const gd::String clearAsync = eventsFunction.IsAsync()
? " this.task = null,\n"
: "";
const int firstParameterIndex =
(thisObjectName.empty() ? 0 : 1) + (thisBehaviorName.empty() ? 0 : 1);
auto parameterList =
GenerateEventsFunctionParameterDeclarationsList(
eventsFunction.GetParametersForEvents(eventsFunctionsContainer),
firstParameterIndex, true) +
(thisObjectName.empty() && thisBehaviorName.empty() ? "" : ", that");
return gd::String("var eventsFunctionContext = {\n") +
// The async task, if there is one
async +
return GetCodeNamespaceAccessor() + "Context = class {\n"
"constructor(" + parameterList + ") {\n" +
GenerateEventsFunctionParametersToAttribues(
eventsFunction.GetParametersForEvents(
eventsFunctionsContainer),
firstParameterIndex, true) +
constructorAdditionalCode +
// The async task, if there is one
async +
// The object name to parameter map:
" _objectsMap: {\n" + objectsGettersMap +
"},\n"
" this._objectsMap = {\n" +
objectsGettersMap + "\n"
" };\n"
// The object name to arrays map:
" _objectArraysMap: {\n" +
objectArraysMap +
"},\n"
" this._objectArraysMap = {\n" +
objectArraysMap + "\n"
" };\n"
// The behavior name to parameter map:
" _behaviorNamesMap: {\n" +
behaviorNamesMap +
"},\n"
" globalVariablesForExtension: "
" this._behaviorNamesMap = {\n" +
behaviorNamesMap + "\n"
" };\n"
" this.globalVariablesForExtension = "
"runtimeScene.getGame().getVariablesForExtension(" +
ConvertToStringExplicit(extensionName) + "),\n" +
" sceneVariablesForExtension: "
ConvertToStringExplicit(extensionName) + ");\n" +
" this.sceneVariablesForExtension = "
"runtimeScene.getScene().getVariablesForExtension(" +
ConvertToStringExplicit(extensionName) + "),\n" +
ConvertToStringExplicit(extensionName) + ");\n"
// The local variables stack:
" localVariables: [],\n"
" this.localVariables = [];\n"
"}\n"
"reinitialize(" + parameterList + ") {\n" +
GenerateEventsFunctionParametersToAttribues(
eventsFunction.GetParametersForEvents(
eventsFunctionsContainer),
firstParameterIndex, true) +
// The async task, if there is one
async +
// The object name to parameter map:
reinitializeObjectsMap +
// The object name to arrays map:
reinitializeArraysMap +
// The behavior name to parameter map:
reinitializeBehaviorNamesMap +
// globalVariablesForExtension stays the same.
" this.sceneVariablesForExtension = "
"runtimeScene.getScene().getVariablesForExtension(" +
ConvertToStringExplicit(extensionName) + ");\n" +
reinitializeAdditionalCode +
"}\n"
"clear() {\n" +
" this.runtimeScene = null;\n"
" this.parentEventsFunctionContext = null;\n"
// globalVariablesForExtension stays the same.
" this.sceneVariablesForExtension = null;\n" +
// The async task, if there is one
clearAsync +
// The object name to parameter map:
clearObjectsMap +
// The object name to arrays map:
clearArraysMap +
clearAdditionalCode +
"}\n"
// Function that will be used to query objects, when a new object list
// is needed by events. We assume it's used a lot by the events
// generated code, so we cache the arrays in a map.
" getObjects: function(objectName) {\n" +
" return eventsFunctionContext._objectArraysMap[objectName] || "
"[];\n" +
" },\n" +
" getObjects(objectName) {\n"
" return this._objectArraysMap[objectName] || [];\n"
" }\n"
// Function that can be used in JS code to get the lists of objects
// and filter/alter them (not actually used in events).
" getObjectsLists: function(objectName) {\n" +
" return eventsFunctionContext._objectsMap[objectName] || null;\n"
" },\n" +
" getObjectsLists(objectName) {\n"
" return this._objectsMap[objectName] || null;\n"
" }\n"
// Function that will be used to query behavior name (as behavior name
// can be different between the parameter name vs the actual behavior
// name passed as argument).
" getBehaviorName: function(behaviorName) {\n" +
" getBehaviorName(behaviorName) {\n"
// TODO EBO Handle behavior name collision between parameters and
// children
" return eventsFunctionContext._behaviorNamesMap[behaviorName] || "
" return this._behaviorNamesMap[behaviorName] || "
"behaviorName;\n"
" },\n" +
" }\n"
// Creator function that will be used to create new objects. We
// need to check if the function was given the context of the calling
// function (parentEventsFunctionContext). If this is the case, use it
// to create the new object as the object names used in the function
// are not the same as the objects available in the scene.
" createObject: function(objectName) {\n"
" const objectsList = "
"eventsFunctionContext._objectsMap[objectName];\n" +
" createObject(objectName) {\n"
" const objectsList = this._objectsMap[objectName];\n"
// TODO: we could speed this up by storing a map of object names, but
// the cost of creating/storing it for each events function might not
// be worth it.
" if (objectsList) {\n" +
" const object = parentEventsFunctionContext ?\n" +
" if (objectsList) {\n"
" const object = this.parentEventsFunctionContext ?\n"
" "
"parentEventsFunctionContext.createObject(objectsList.firstKey()) "
":\n" +
" runtimeScene.createObject(objectsList.firstKey());\n" +
"this.parentEventsFunctionContext.createObject(objectsList.firstKey()) "
":\n"
" this.runtimeScene.createObject(objectsList.firstKey());\n"
// Add the new instance to object lists
" if (object) {\n" +
" objectsList.get(objectsList.firstKey()).push(object);\n" +
" "
"eventsFunctionContext._objectArraysMap[objectName].push(object);\n" +
" }\n" + " return object;" + " }\n" +
" if (object) {\n"
" objectsList.get(objectsList.firstKey()).push(object);\n"
" this._objectArraysMap[objectName].push(object);\n"
" }\n"
" return object;\n"
" }\n"
// Unknown object, don't create anything:
" return null;\n" +
" },\n"
" return null;\n"
" }\n"
// Function to count instances on the scene. We need it here because
// it needs the objects map to get the object names of the parent
// context.
" getInstancesCountOnScene: function(objectName) {\n"
" const objectsList = "
"eventsFunctionContext._objectsMap[objectName];\n" +
" let count = 0;\n" + " if (objectsList) {\n" +
" for(const objectName in objectsList.items)\n" +
" count += parentEventsFunctionContext ?\n" +
"parentEventsFunctionContext.getInstancesCountOnScene(objectName) "
":\n" +
" runtimeScene.getInstancesCountOnScene(objectName);\n" +
" }\n" + " return count;\n" +
" },\n"
" getInstancesCountOnScene(objectName) {\n"
" const objectsList = this._objectsMap[objectName];\n"
" let count = 0;\n"
" if (objectsList) {\n"
" for(const objectName in objectsList.items)\n"
" count += this.parentEventsFunctionContext ?\n"
" this.parentEventsFunctionContext.getInstancesCountOnScene(objectName) :\n"
" this.runtimeScene.getInstancesCountOnScene(objectName);\n"
" }\n"
" return count;\n"
" }\n"
// Allow to get a layer directly from the context for convenience:
" getLayer: function(layerName) {\n"
" return runtimeScene.getLayer(layerName);\n"
" },\n"
" getLayer(layerName) {\n"
" return this.runtimeScene.getLayer(layerName);\n"
" }\n"
// Getter for arguments that are not objects
" getArgument: function(argName) {\n" +
argumentsGetters + " return \"\";\n" + " },\n" +
" getArgument(argName) {\n" + argumentsGetters + //
" return \"\";\n"
" }\n"
// Expose OnceTriggers (will be pointing either to the runtime scene
// ones, or the ones from the behavior):
" getOnceTriggers: function() { return " + onceTriggersVariable +
"; }\n" + "};\n";
" getOnceTriggers() { return this." + onceTriggersVariable + "; }\n"
"}\n";
}
gd::String EventsCodeGenerator::GenerateEventsFunctionReturn(

View File

@@ -375,6 +375,7 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
gdjs::EventsCodeGenerator& codeGenerator,
gd::String fullyQualifiedFunctionName,
gd::String functionArgumentsCode,
gd::String contextClassCode,
gd::String functionPreEventsCode,
const gd::EventsList& events,
gd::String functionPostEventsCode,
@@ -409,6 +410,16 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
int firstParameterIndex,
bool addsSceneParameter);
/**
* \brief Generate the affectation from parameters to class attributes.
*
* \note runtimeScene is always added as the first parameter, and
* parentEventsFunctionContext as the last parameter.
*/
gd::String GenerateEventsFunctionParametersToAttribues(
const gd::ParameterMetadataContainer &parameters, int firstParameterIndex,
bool addsSceneParameter);
/**
* \brief Generate the "eventsFunctionContext" object that allow a free
* function to provides access objects, object creation and access to
@@ -475,6 +486,9 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
gd::String &objectsGettersMap,
gd::String &objectArraysMap,
gd::String &behaviorNamesMap,
const gd::String &constructorAdditionalCode = "",
const gd::String &reinitializeAdditionalCode = "",
const gd::String& clearAdditionalCode = "",
const gd::String &thisObjectName = "",
const gd::String &thisBehaviorName = "");
};

View File

@@ -841,13 +841,23 @@ CommonInstructionsExtension::CommonInstructionsExtension() {
event.GetParameterObjects(),
parentContext.GetCurrentObject());
callingCode += "var objects = [];\n";
for (unsigned int i = 0; i < realObjects.size(); ++i) {
parentContext.ObjectsListNeeded(realObjects[i]);
if (realObjects.size() == 1) {
parentContext.ObjectsListNeeded(realObjects[0]);
callingCode +=
"objects.push.apply(objects," +
codeGenerator.GetObjectListName(realObjects[i], parentContext) +
");\n";
"const objects = " +
codeGenerator.GetObjectListName(realObjects[0], parentContext) +
";\n";
} else {
// Groups are rarely used in JS events so it's fine to make
// allocations.
callingCode += "const objects = [];\n";
for (unsigned int i = 0; i < realObjects.size(); ++i) {
parentContext.ObjectsListNeeded(realObjects[i]);
callingCode += "objects.push.apply(objects," +
codeGenerator.GetObjectListName(realObjects[i],
parentContext) +
");\n";
}
}
}

View File

@@ -26,6 +26,28 @@ The game engine is in the _Runtime_ folder. If you want to work on the engine di
- To launch type checking with TypeScript, run `npm install` and `npm run check-types` in `GDJS` folder.
#### Building GDJS Runtime
To build the GDJS Runtime, run `npm run build` in the `GDJS` folder.
**Build Options:**
- **Production build (default)**: `npm run build` - builds with minification enabled
- **Debug build**: `npm run build -- --debug` - builds without minification for easier debugging
- **Custom output path**: `npm run build -- --out=/path/to/output` - specify custom output directory
**Examples:**
```bash
# Standard production build
npm run build
# Debug build for development (no minification)
npm run build -- --debug
# Debug build with custom output path
npm run build -- --debug --out=./debug-build
```
### GDJS Platform (exporters, code generation...)
Check the [GDJS Platform](https://docs.gdevelop.io/GDJS%20Documentation/index.html) documentation or the [full GDevelop developers documentation](https://docs.gdevelop.io/).

View File

@@ -462,12 +462,12 @@ namespace gdjs {
/**
* @param instanceContainer the container owning the layer
* @param layerName The lighting layer with the ambient color.
* @param rgbColor The color, in RGB format ("128;200;255").
* @param rgbOrHexColor The color, in RGB format ("128;200;255").
*/
export const setLayerAmbientLightColor = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
layerName: string,
rgbColor: string
rgbOrHexColor: string
) {
if (
!instanceContainer.hasLayer(layerName) ||
@@ -475,17 +475,10 @@ namespace gdjs {
) {
return;
}
const colors = rgbColor.split(';');
if (colors.length < 3) {
return;
}
const color = gdjs.rgbOrHexToRGBColor(rgbOrHexColor);
return instanceContainer
.getLayer(layerName)
.setClearColor(
parseInt(colors[0], 10),
parseInt(colors[1], 10),
parseInt(colors[2], 10)
);
.setClearColor(color[0], color[1], color[2]);
};
}
}

View File

@@ -24,19 +24,12 @@ namespace gdjs {
export const setBackgroundColor = function (
runtimeScene: gdjs.RuntimeScene,
rgbColor: string
rgbOrHexColor: string
) {
const colors = rgbColor.split(';');
if (colors.length < 3) {
return;
}
const color = gdjs.rgbOrHexToRGBColor(rgbOrHexColor);
runtimeScene
.getScene()
.setBackgroundColor(
parseInt(colors[0]),
parseInt(colors[1]),
parseInt(colors[2])
);
.setBackgroundColor(color[0], color[1], color[2]);
};
export const getElapsedTimeInSeconds = function (

View File

@@ -12,7 +12,6 @@ namespace gdjs {
const logger = new gdjs.Logger('Engine runtime');
const hexStringRegex = /^(#{0,1}[A-Fa-f0-9]{6})$/;
const shorthandHexStringRegex = /^(#{0,1}[A-Fa-f0-9]{3})$/;
const rgbStringRegex = /^(\d{1,3};\d{1,3};\d{1,3})/;
/**
* Contains functions used by events (this is a convention only, functions can actually
@@ -105,9 +104,9 @@ namespace gdjs {
export const rgbOrHexToRGBColor = function (
value: string
): [number, number, number] {
const rgbColor = extractRGBString(value);
if (rgbColor) {
const splitValue = rgbColor.split(';');
// TODO Add a `result` parameter to allow to reuse the returned array.
if (!value.startsWith('#')) {
const splitValue = value.split(';');
// If a RGB string is provided, return the RGB object.
if (splitValue.length === 3) {
return [
@@ -145,11 +144,11 @@ namespace gdjs {
* @param b Blue
*/
export const rgbToHexNumber = function (
r: integer,
g: integer,
b: integer
r: float,
g: float,
b: float
): integer {
return (r << 16) + (g << 8) + b;
return (Math.round(r) << 16) + (Math.round(g) << 8) + Math.round(b);
};
/**
@@ -192,12 +191,6 @@ namespace gdjs {
return matches[0];
};
export const extractRGBString = (str: string): string | null => {
const matches = str.match(rgbStringRegex);
if (!matches) return null;
return matches[0];
};
/**
* Get a random integer between 0 and max.
* @param max The maximum value (inclusive).
@@ -509,18 +502,21 @@ namespace gdjs {
* @returns {Array}
*/
export const objectsListsToArray = function (
objectsLists: Hashtable<RuntimeObject>
objectsLists: Hashtable<RuntimeObject>,
result: Array<RuntimeObject> = []
): Array<RuntimeObject> {
var lists = gdjs.staticArray(gdjs.objectsListsToArray);
const lists = gdjs.staticArray(gdjs.objectsListsToArray);
objectsLists.values(lists);
var result: Array<RuntimeObject> = [];
for (var i = 0; i < lists.length; ++i) {
var arr = lists[i];
for (var k = 0; k < arr.length; ++k) {
result.push(arr[k]);
let resultIndex = 0;
for (let i = 0; i < lists.length; ++i) {
const arr = lists[i];
for (let k = 0; k < arr.length; ++k) {
result[resultIndex] = arr[k];
resultIndex++;
}
}
result.length = resultIndex;
return result;
};
@@ -531,8 +527,8 @@ namespace gdjs {
* @param dst The destination array
*/
export const copyArray = function <T>(src: Array<T>, dst: Array<T>): void {
var len = src.length;
for (var i = 0; i < len; ++i) {
const len = src.length;
for (let i = 0; i < len; ++i) {
dst[i] = src[i];
}
dst.length = len;

View File

@@ -140,18 +140,18 @@ namespace gdjs {
this._sprite.visible = !this._object.hidden;
}
setColor(rgbOrHexColor): void {
setColor(rgbOrHexColor: string): void {
this._sprite.tint = gdjs.rgbOrHexStringToNumber(rgbOrHexColor);
}
getColor() {
const rgb = new PIXI.Color(this._sprite.tint).toRgbArray();
return (
Math.floor(rgb[0] * 255) +
Math.round(rgb[0] * 255) +
';' +
Math.floor(rgb[1] * 255) +
Math.round(rgb[1] * 255) +
';' +
Math.floor(rgb[2] * 255)
Math.round(rgb[2] * 255)
);
}

View File

@@ -7,6 +7,8 @@ const {
} = require('./lib/runtime-files-list');
const args = require('minimist')(process.argv.slice(2), {
string: ['out'],
boolean: ['debug'],
default: { debug: false }
});
const fs = require('fs').promises;
@@ -52,7 +54,7 @@ shell.mkdir('-p', bundledOutPath);
return build({
sourcemap: true,
entryPoints: [inPath],
minify: true,
minify: !args.debug,
outfile: renameBuiltFile(outPath),
}).catch(() => {
// Error is already logged by esbuild.

View File

@@ -36,11 +36,11 @@ describe('gdjs', function () {
expect(gdjs.rgbOrHexToRGBColor('255;255;300')).to.eql([255, 255, 255]);
expect(gdjs.rgbOrHexToRGBColor('999;12;6')).to.eql([255, 12, 6]);
});
it('should cut rgb values if string too long', function () {
it('should cap rgb values', function () {
expect(gdjs.rgbOrHexToRGBColor('255;255;200456')).to.eql([
255,
255,
200,
255,
]);
});
it('should return components for black if unrecognized input', function () {
@@ -48,7 +48,6 @@ describe('gdjs', function () {
expect(gdjs.rgbOrHexToRGBColor('19819830803')).to.eql([0, 0, 0]);
expect(gdjs.rgbOrHexToRGBColor('Infinity')).to.eql([0, 0, 0]);
expect(gdjs.rgbOrHexToRGBColor('-4564')).to.eql([0, 0, 0]);
expect(gdjs.rgbOrHexToRGBColor('9999;12;6')).to.eql([0, 0, 0]);
});
});
});

View File

@@ -3728,6 +3728,8 @@ interface TextObject {
[Const, Ref] DOMString GetText();
void SetCharacterSize(double size);
double GetCharacterSize();
void SetLineHeight(double value);
double GetLineHeight();
void SetFontName([Const] DOMString string);
[Const, Ref] DOMString GetFontName();
boolean IsBold();

View File

@@ -239,7 +239,8 @@ function generateCompiledEventsForSerializedEventsBasedExtension(
gd,
serializedEventsFunctionsExtension,
gdjs,
runtimeScene
runtimeScene,
options
) {
const project = new gd.ProjectHelper.createNewGDJSProject();
const extension = project.insertNewEventsFunctionsExtension(
@@ -294,7 +295,8 @@ function generateCompiledEventsForSerializedEventsBasedExtension(
project,
extension,
behavior,
gdjs
gdjs,
options
);
}

View File

@@ -531,7 +531,7 @@ describe('libGD.js - GDJS related tests', function () {
// Check that the context for the events function is here...
expect(code).toMatch('function(runtimeScene, eventsFunctionContext)');
expect(code).toMatch('var eventsFunctionContext =');
expect(code).toMatch('const eventsFunctionContext =');
// Check that the parameters, with the (optional) context of the parent function,
// are all here
@@ -544,8 +544,8 @@ describe('libGD.js - GDJS related tests', function () {
expect(code).toMatch('"MySprite": MySprite');
// ...and arguments should be able to get queried too:
expect(code).toMatch('if (argName === "MyNumber") return MyNumber;');
expect(code).toMatch('if (argName === "MyString") return MyString;');
expect(code).toMatch('if (argName === "MyNumber") return this.MyNumber;');
expect(code).toMatch('if (argName === "MyString") return this.MyString;');
// GetArgumentAsString("MyString") should be generated code to query and cast as a string
// the argument
@@ -635,7 +635,7 @@ describe('libGD.js - GDJS related tests', function () {
// Check that the context for the events function is here...
expect(code).toMatch('function(runtimeScene, eventsFunctionContext)');
expect(code).toMatch('var eventsFunctionContext =');
expect(code).toMatch('const eventsFunctionContext =');
// Check that the parameters, with the (optional) context of the parent function,
// are all here

View File

@@ -80,7 +80,7 @@ describe('libGD.js - GDJS Behavior Code Generation integration tests', function
eventsFunctionsExtension,
eventsBasedBehavior,
gdjs,
{logCode: false}
{ logCode: false }
);
project.delete();

View File

@@ -3578,7 +3578,8 @@ describe('libGD.js - GDJS Async Code Generation integration tests', function ()
gd,
require('./extensions/EBAsyncAction.json'),
gdjs,
runtimeScene
runtimeScene,
{ logCode: true }
);
const {
@@ -3618,7 +3619,8 @@ describe('libGD.js - GDJS Async Code Generation integration tests', function ()
gd,
require('./extensions/EBAsyncAction.json'),
gdjs,
runtimeScene
runtimeScene,
{ logCode: false }
);
const {

View File

@@ -12,7 +12,7 @@ describe('libGD.js - GDJS Object Code Generation integration tests', function ()
});
describe('SceneInstancesCount', () => {
const prepareCompiledEvents = () => {
const prepareCompiledEvents = (options = {}) => {
const eventsSerializerElement = gd.Serializer.fromJSObject([
{
type: 'BuiltinCommonInstructions::Standard',
@@ -62,7 +62,8 @@ describe('libGD.js - GDJS Object Code Generation integration tests', function ()
const runCompiledEvents = generateCompiledEventsForEventsFunction(
gd,
project,
eventsFunction
eventsFunction,
options.logCode
);
eventsFunction.delete();
@@ -71,7 +72,7 @@ describe('libGD.js - GDJS Object Code Generation integration tests', function ()
};
it('counts instances from the scene in a function, when no instances are passed as parameters', () => {
const { runCompiledEvents } = prepareCompiledEvents();
const { runCompiledEvents } = prepareCompiledEvents({ logCode: false });
const { gdjs, runtimeScene } = makeMinimalGDJSMock();
runtimeScene.getOnceTriggers().startNewFrame();
@@ -106,7 +107,7 @@ describe('libGD.js - GDJS Object Code Generation integration tests', function ()
});
it('counts instances from the scene in a function, when some instances are passed as parameters', () => {
const { runCompiledEvents } = prepareCompiledEvents();
const { runCompiledEvents } = prepareCompiledEvents({ logCode: false });
const { gdjs, runtimeScene } = makeMinimalGDJSMock();
runtimeScene.getOnceTriggers().startNewFrame();

View File

@@ -737,7 +737,7 @@ describe('libGD.js - GDJS Code Generation integration tests', function () {
},
],
{
logCode: true,
logCode: false,
}
);
expect(runtimeScene.getVariables().has('Counter')).toBe(true);
@@ -787,7 +787,7 @@ describe('libGD.js - GDJS Code Generation integration tests', function () {
},
],
{
logCode: true,
logCode: false,
}
);
expect(runtimeScene.getVariables().has('Counter')).toBe(true);
@@ -882,7 +882,7 @@ describe('libGD.js - GDJS Code Generation integration tests', function () {
},
],
{
logCode: true,
logCode: false,
}
);
expect(runtimeScene.getVariables().has('Counter')).toBe(true);

View File

@@ -64,7 +64,7 @@ describe('libGD.js object serialization', function() {
obj.delete();
expect(jsonObject).toBe(
'{"bold":false,"italic":false,"smoothed":true,"underlined":false,"string":"Text of the object, with 官话 characters","font":"","textAlignment":"left","characterSize":20.0,"color":{"b":0,"g":0,"r":0},"content":{"bold":false,"isOutlineEnabled":false,"isShadowEnabled":false,"italic":false,"outlineColor":"255;255;255","outlineThickness":2.0,"shadowAngle":90.0,"shadowBlurRadius":2.0,"shadowColor":"0;0;0","shadowDistance":4.0,"shadowOpacity":127.0,"smoothed":true,"underlined":false,"text":"Text of the object, with 官话 characters","font":"","textAlignment":"left","verticalTextAlignment":"top","characterSize":20.0,"color":"0;0;0"}}'
'{"bold":false,"italic":false,"smoothed":true,"underlined":false,"string":"Text of the object, with 官话 characters","font":"","textAlignment":"left","characterSize":20.0,"color":{"b":0,"g":0,"r":0},"content":{"bold":false,"isOutlineEnabled":false,"isShadowEnabled":false,"italic":false,"outlineColor":"255;255;255","outlineThickness":2.0,"shadowAngle":90.0,"shadowBlurRadius":2.0,"shadowColor":"0;0;0","shadowDistance":4.0,"shadowOpacity":127.0,"smoothed":true,"underlined":false,"text":"Text of the object, with 官话 characters","font":"","textAlignment":"left","verticalTextAlignment":"top","characterSize":20.0,"lineHeight":0.0,"color":"0;0;0"}}'
);
});
});

View File

@@ -2758,6 +2758,8 @@ export class TextObject extends ObjectConfiguration {
getText(): string;
setCharacterSize(size: number): void;
getCharacterSize(): number;
setLineHeight(value: number): void;
getLineHeight(): number;
setFontName(string: string): void;
getFontName(): string;
isBold(): boolean;

View File

@@ -5,6 +5,8 @@ declare class gdTextObject extends gdObjectConfiguration {
getText(): string;
setCharacterSize(size: number): void;
getCharacterSize(): number;
setLineHeight(value: number): void;
getLineHeight(): number;
setFontName(string: string): void;
getFontName(): string;
isBold(): boolean;

View File

@@ -19,6 +19,7 @@ import { type ExtensionItemConfigurationAttribute } from '../EventsFunctionsExte
import SelectField from '../UI/SelectField';
import SelectOption from '../UI/SelectOption';
import Window from '../Utils/Window';
import ScrollView from '../UI/ScrollView';
const gd: libGDevelop = global.gd;
@@ -67,150 +68,154 @@ export default function EventsBasedBehaviorEditor({
return (
<I18n>
{({ i18n }) => (
<ColumnStackLayout expand noMargin>
<DismissableAlertMessage
identifier="events-based-behavior-explanation"
kind="info"
>
<Trans>
This is the configuration of your behavior. Make sure to choose a
proper internal name as it's hard to change it later. Enter a
description explaining what the behavior is doing to the object.
</Trans>
</DismissableAlertMessage>
<TextField
floatingLabelText={<Trans>Internal Name</Trans>}
value={eventsBasedBehavior.getName()}
disabled
fullWidth
/>
<SemiControlledTextField
commitOnBlur
floatingLabelText={<Trans>Name displayed in editor</Trans>}
value={eventsBasedBehavior.getFullName()}
onChange={text => {
eventsBasedBehavior.setFullName(text);
onChange();
}}
fullWidth
/>
<SemiControlledTextField
commitOnBlur
floatingLabelText={<Trans>Description</Trans>}
helperMarkdownText={i18n._(
t`Explain what the behavior is doing to the object. Start with a verb when possible.`
)}
value={eventsBasedBehavior.getDescription()}
onChange={text => {
eventsBasedBehavior.setDescription(text);
onChange();
}}
multiline
fullWidth
rows={3}
/>
<ObjectTypeSelector
floatingLabelText={
<Trans>Object on which this behavior can be used</Trans>
}
project={project}
value={eventsBasedBehavior.getObjectType()}
onChange={(objectType: string) => {
eventsBasedBehavior.setObjectType(objectType);
onChange();
}}
allowedObjectTypes={
allObjectTypes.length === 0
? undefined /* Allow anything as the behavior is not used */
: allObjectTypes.length === 1
? [
'',
allObjectTypes[0],
] /* Allow only the type of the objects using the behavior */
: [
'',
] /* More than one type of object are using the behavior. Only "any object" can be used on this behavior */
}
/>
{allObjectTypes.length > 1 && (
<AlertMessage kind="info">
<Trans>
This behavior is being used by multiple types of objects. Thus,
you can't restrict its usage to any particular object type. All
the object types using this behavior are listed here:
{allObjectTypes.join(', ')}
</Trans>
</AlertMessage>
)}
{isDev && (
<SelectField
floatingLabelText={
<Trans>Visibility in quick customization dialog</Trans>
}
value={eventsBasedBehavior.getQuickCustomizationVisibility()}
onChange={(e, i, valueString: string) => {
// $FlowFixMe
const value: QuickCustomization_Visibility = valueString;
eventsBasedBehavior.setQuickCustomizationVisibility(value);
onChange();
}}
fullWidth
>
<SelectOption
value={gd.QuickCustomization.Default}
label={t`Default (visible)`}
/>
<SelectOption
value={gd.QuickCustomization.Visible}
label={t`Always visible`}
/>
<SelectOption
value={gd.QuickCustomization.Hidden}
label={t`Hidden`}
/>
</SelectField>
)}
<Checkbox
label={<Trans>Private</Trans>}
checked={eventsBasedBehavior.isPrivate()}
onCheck={(e, checked) => {
eventsBasedBehavior.setPrivate(checked);
if (onConfigurationUpdated) onConfigurationUpdated('isPrivate');
onChange();
}}
tooltipOrHelperText={
eventsBasedBehavior.isPrivate() ? (
<Trans>
This behavior won't be visible in the scene and events
editors.
</Trans>
) : (
<Trans>
This behavior will be visible in the scene and events editors.
</Trans>
)
}
/>
{eventsBasedBehavior
.getEventsFunctions()
.getEventsFunctionsCount() === 0 && (
<ScrollView>
<ColumnStackLayout expand noMargin>
<DismissableAlertMessage
identifier="empty-events-based-behavior-explanation"
identifier="events-based-behavior-explanation"
kind="info"
>
<Trans>
Once you're done, start adding some functions to the behavior.
Then, test the behavior by adding it to an object in a scene.
This is the configuration of your behavior. Make sure to choose
a proper internal name as it's hard to change it later. Enter a
description explaining what the behavior is doing to the object.
</Trans>
</DismissableAlertMessage>
)}
<Line noMargin>
<HelpButton
key="help"
helpPagePath="/behaviors/events-based-behaviors"
<TextField
floatingLabelText={<Trans>Internal Name</Trans>}
value={eventsBasedBehavior.getName()}
disabled
fullWidth
/>
</Line>
</ColumnStackLayout>
<SemiControlledTextField
commitOnBlur
floatingLabelText={<Trans>Name displayed in editor</Trans>}
value={eventsBasedBehavior.getFullName()}
onChange={text => {
eventsBasedBehavior.setFullName(text);
onChange();
}}
fullWidth
/>
<SemiControlledTextField
commitOnBlur
floatingLabelText={<Trans>Description</Trans>}
helperMarkdownText={i18n._(
t`Explain what the behavior is doing to the object. Start with a verb when possible.`
)}
value={eventsBasedBehavior.getDescription()}
onChange={text => {
eventsBasedBehavior.setDescription(text);
onChange();
}}
multiline
fullWidth
rows={3}
/>
<ObjectTypeSelector
floatingLabelText={
<Trans>Object on which this behavior can be used</Trans>
}
project={project}
value={eventsBasedBehavior.getObjectType()}
onChange={(objectType: string) => {
eventsBasedBehavior.setObjectType(objectType);
onChange();
}}
allowedObjectTypes={
allObjectTypes.length === 0
? undefined /* Allow anything as the behavior is not used */
: allObjectTypes.length === 1
? [
'',
allObjectTypes[0],
] /* Allow only the type of the objects using the behavior */
: [
'',
] /* More than one type of object are using the behavior. Only "any object" can be used on this behavior */
}
/>
{allObjectTypes.length > 1 && (
<AlertMessage kind="info">
<Trans>
This behavior is being used by multiple types of objects.
Thus, you can't restrict its usage to any particular object
type. All the object types using this behavior are listed
here:
{allObjectTypes.join(', ')}
</Trans>
</AlertMessage>
)}
{isDev && (
<SelectField
floatingLabelText={
<Trans>Visibility in quick customization dialog</Trans>
}
value={eventsBasedBehavior.getQuickCustomizationVisibility()}
onChange={(e, i, valueString: string) => {
// $FlowFixMe
const value: QuickCustomization_Visibility = valueString;
eventsBasedBehavior.setQuickCustomizationVisibility(value);
onChange();
}}
fullWidth
>
<SelectOption
value={gd.QuickCustomization.Default}
label={t`Default (visible)`}
/>
<SelectOption
value={gd.QuickCustomization.Visible}
label={t`Always visible`}
/>
<SelectOption
value={gd.QuickCustomization.Hidden}
label={t`Hidden`}
/>
</SelectField>
)}
<Checkbox
label={<Trans>Private</Trans>}
checked={eventsBasedBehavior.isPrivate()}
onCheck={(e, checked) => {
eventsBasedBehavior.setPrivate(checked);
if (onConfigurationUpdated) onConfigurationUpdated('isPrivate');
onChange();
}}
tooltipOrHelperText={
eventsBasedBehavior.isPrivate() ? (
<Trans>
This behavior won't be visible in the scene and events
editors.
</Trans>
) : (
<Trans>
This behavior will be visible in the scene and events
editors.
</Trans>
)
}
/>
{eventsBasedBehavior
.getEventsFunctions()
.getEventsFunctionsCount() === 0 && (
<DismissableAlertMessage
identifier="empty-events-based-behavior-explanation"
kind="info"
>
<Trans>
Once you're done, start adding some functions to the behavior.
Then, test the behavior by adding it to an object in a scene.
</Trans>
</DismissableAlertMessage>
)}
<Line noMargin>
<HelpButton
key="help"
helpPagePath="/behaviors/events-based-behaviors"
/>
</Line>
</ColumnStackLayout>
</ScrollView>
)}
</I18n>
);

View File

@@ -72,6 +72,7 @@ export default function EventsBasedObjectEditorPanel({
</Line>
{currentTab === 'configuration' && (
<EventsBasedObjectEditor
eventsFunctionsExtension={eventsFunctionsExtension}
eventsBasedObject={eventsBasedObject}
unsavedChanges={unsavedChanges}
onOpenCustomObjectEditor={onOpenCustomObjectEditor}

View File

@@ -6,7 +6,8 @@ import * as React from 'react';
import TextField from '../UI/TextField';
import SemiControlledTextField from '../UI/SemiControlledTextField';
import DismissableAlertMessage from '../UI/DismissableAlertMessage';
import { ColumnStackLayout } from '../UI/Layout';
import AlertMessage from '../UI/AlertMessage';
import { ColumnStackLayout, LineStackLayout } from '../UI/Layout';
import useForceUpdate from '../Utils/UseForceUpdate';
import Checkbox from '../UI/Checkbox';
import HelpButton from '../UI/HelpButton';
@@ -14,12 +15,15 @@ import { Line } from '../UI/Grid';
import { type UnsavedChanges } from '../MainFrame/UnsavedChangesContext';
import RaisedButton from '../UI/RaisedButton';
import Window from '../Utils/Window';
import ScrollView from '../UI/ScrollView';
import { Column } from '../UI/Grid';
const gd: libGDevelop = global.gd;
const isDev = Window.isDev();
type Props = {|
eventsFunctionsExtension: gdEventsFunctionsExtension,
eventsBasedObject: gdEventsBasedObject,
onOpenCustomObjectEditor: () => void,
unsavedChanges?: ?UnsavedChanges,
@@ -29,6 +33,7 @@ type Props = {|
|};
export default function EventsBasedObjectEditor({
eventsFunctionsExtension,
eventsBasedObject,
onOpenCustomObjectEditor,
unsavedChanges,
@@ -47,135 +52,155 @@ export default function EventsBasedObjectEditor({
);
return (
<ColumnStackLayout expand noMargin>
<DismissableAlertMessage
identifier="events-based-object-explanation"
kind="info"
>
<Trans>
This is the configuration of your object. Make sure to choose a proper
internal name as it's hard to change it later.
</Trans>
</DismissableAlertMessage>
<TextField
floatingLabelText={<Trans>Internal Name</Trans>}
value={eventsBasedObject.getName()}
disabled
fullWidth
/>
<SemiControlledTextField
commitOnBlur
floatingLabelText={<Trans>Name displayed in editor</Trans>}
value={eventsBasedObject.getFullName()}
onChange={text => {
eventsBasedObject.setFullName(text);
onChange();
}}
fullWidth
/>
<SemiControlledTextField
commitOnBlur
floatingLabelText={<Trans>Description</Trans>}
floatingLabelFixed
translatableHintText={t`The description of the object should explain what the object is doing, and, briefly, how to use it.`}
value={eventsBasedObject.getDescription()}
onChange={text => {
eventsBasedObject.setDescription(text);
onChange();
}}
multiline
fullWidth
rows={3}
/>
<SemiControlledTextField
commitOnBlur
floatingLabelText={<Trans>Default name for created objects</Trans>}
value={
eventsBasedObject.getDefaultName() || eventsBasedObject.getName()
}
onChange={newName => {
eventsBasedObject.setDefaultName(gd.Project.getSafeName(newName));
onChange();
}}
fullWidth
/>
<Checkbox
label={<Trans>Use 3D rendering</Trans>}
checked={eventsBasedObject.isRenderedIn3D()}
onCheck={(e, checked) => {
eventsBasedObject.markAsRenderedIn3D(checked);
onChange();
}}
/>
<Checkbox
label={<Trans>Has animations</Trans>}
checked={eventsBasedObject.isAnimatable()}
onCheck={(e, checked) => {
eventsBasedObject.markAsAnimatable(checked);
onChange();
}}
/>
<Checkbox
label={<Trans>Contains text</Trans>}
checked={eventsBasedObject.isTextContainer()}
onCheck={(e, checked) => {
eventsBasedObject.markAsTextContainer(checked);
onChange();
}}
/>
<Checkbox
label={<Trans>Expand inner area with parent</Trans>}
checked={eventsBasedObject.isInnerAreaFollowingParentSize()}
onCheck={(e, checked) => {
eventsBasedObject.markAsInnerAreaFollowingParentSize(checked);
onChange();
onEventsBasedObjectChildrenEdited(eventsBasedObject);
}}
/>
{isDev && (
<ScrollView>
<ColumnStackLayout expand noMargin>
<DismissableAlertMessage
identifier="events-based-object-explanation"
kind="info"
>
<Trans>
This is the configuration of your object. Make sure to choose a
proper internal name as it's hard to change it later.
</Trans>
</DismissableAlertMessage>
<TextField
floatingLabelText={<Trans>Internal Name</Trans>}
value={eventsBasedObject.getName()}
disabled
fullWidth
/>
<SemiControlledTextField
commitOnBlur
floatingLabelText={<Trans>Name displayed in editor</Trans>}
value={eventsBasedObject.getFullName()}
onChange={text => {
eventsBasedObject.setFullName(text);
onChange();
}}
fullWidth
/>
<SemiControlledTextField
commitOnBlur
floatingLabelText={<Trans>Description</Trans>}
floatingLabelFixed
translatableHintText={t`The description of the object should explain what the object is doing, and, briefly, how to use it.`}
value={eventsBasedObject.getDescription()}
onChange={text => {
eventsBasedObject.setDescription(text);
onChange();
}}
multiline
fullWidth
rows={3}
/>
<SemiControlledTextField
commitOnBlur
floatingLabelText={<Trans>Default name for created objects</Trans>}
value={
eventsBasedObject.getDefaultName() || eventsBasedObject.getName()
}
onChange={newName => {
eventsBasedObject.setDefaultName(gd.Project.getSafeName(newName));
onChange();
}}
fullWidth
/>
<Checkbox
label={<Trans>Use legacy renderer</Trans>}
checked={eventsBasedObject.isUsingLegacyInstancesRenderer()}
label={<Trans>Use 3D rendering</Trans>}
checked={eventsBasedObject.isRenderedIn3D()}
onCheck={(e, checked) => {
eventsBasedObject.makAsUsingLegacyInstancesRenderer(checked);
eventsBasedObject.markAsRenderedIn3D(checked);
onChange();
}}
/>
<Checkbox
label={<Trans>Has animations</Trans>}
checked={eventsBasedObject.isAnimatable()}
onCheck={(e, checked) => {
eventsBasedObject.markAsAnimatable(checked);
onChange();
}}
/>
<Checkbox
label={<Trans>Contains text</Trans>}
checked={eventsBasedObject.isTextContainer()}
onCheck={(e, checked) => {
eventsBasedObject.markAsTextContainer(checked);
onChange();
}}
/>
<Checkbox
label={<Trans>Expand inner area with parent</Trans>}
checked={eventsBasedObject.isInnerAreaFollowingParentSize()}
onCheck={(e, checked) => {
eventsBasedObject.markAsInnerAreaFollowingParentSize(checked);
onChange();
onEventsBasedObjectChildrenEdited(eventsBasedObject);
}}
/>
)}
<Checkbox
label={<Trans>Private</Trans>}
checked={eventsBasedObject.isPrivate()}
onCheck={(e, checked) => {
eventsBasedObject.setPrivate(checked);
onChange();
onEventsBasedObjectChildrenEdited(eventsBasedObject);
}}
tooltipOrHelperText={
eventsBasedObject.isPrivate() ? (
<Trans>
This object won't be visible in the scene and events editors.
</Trans>
) : (
<Trans>
This object will be visible in the scene and events editors.
</Trans>
)
}
/>
<Line noMargin justifyContent="center">
<RaisedButton
label={<Trans>Open visual editor for the object</Trans>}
primary
onClick={onOpenCustomObjectEditor}
{isDev && (
<Checkbox
label={<Trans>Use legacy renderer</Trans>}
checked={eventsBasedObject.isUsingLegacyInstancesRenderer()}
onCheck={(e, checked) => {
eventsBasedObject.makAsUsingLegacyInstancesRenderer(checked);
onChange();
onEventsBasedObjectChildrenEdited(eventsBasedObject);
}}
/>
)}
<Checkbox
label={<Trans>Private</Trans>}
checked={eventsBasedObject.isPrivate()}
onCheck={(e, checked) => {
eventsBasedObject.setPrivate(checked);
onChange();
onEventsBasedObjectChildrenEdited(eventsBasedObject);
}}
tooltipOrHelperText={
eventsBasedObject.isPrivate() ? (
<Trans>
This object won't be visible in the scene and events editors.
</Trans>
) : (
<Trans>
This object will be visible in the scene and events editors.
</Trans>
)
}
/>
</Line>
<Line noMargin>
<HelpButton
key="help"
helpPagePath="/objects/custom-objects-prefab-template"
/>
</Line>
</ColumnStackLayout>
{eventsFunctionsExtension.getOriginName() ===
'gdevelop-extension-store' ? (
<AlertMessage
kind="error"
renderRightButton={() => (
<RaisedButton
label={<Trans>Edit the default variant</Trans>}
primary
onClick={onOpenCustomObjectEditor}
/>
)}
>
<Trans>
The default variant is erased when the extension is updated.
</Trans>
</AlertMessage>
) : (
<Line noMargin justifyContent="center">
<RaisedButton
label={<Trans>Open visual editor for the object</Trans>}
primary
onClick={onOpenCustomObjectEditor}
/>
</Line>
)}
<Line noMargin>
<HelpButton
key="help"
helpPagePath="/objects/custom-objects-prefab-template"
/>
</Line>
</ColumnStackLayout>
</ScrollView>
);
}

View File

@@ -33,7 +33,7 @@ describe('EnumerateInstructions', () => {
expect.objectContaining({
displayedName: 'Animation finished',
fullGroupName:
'General Animatable capability Animations and images',
'General Objects with animations Animations and images',
type: 'AnimatableCapability::AnimatableBehavior::HasAnimationEnded',
})
);

View File

@@ -545,6 +545,20 @@ const useCourses = () => {
]
);
React.useEffect(
() => {
if (language) {
console.info(
`Resetting course chapters cache as language changed to ${language}.`
);
setChaptersByCourseIdByUserId(() => ({
'': noCourseChapters,
}));
}
},
[language]
);
// This callback will change (triggering re-renders)
// anytime the chapters are fetched for a course for a user.
const getCourseChapters = React.useCallback(

View File

@@ -89,24 +89,6 @@ export default class TextEditor extends React.Component<EditorProps, void> {
this.forceUpdate();
}}
/>
<MiniToolbarText>
<Trans>Line height:</Trans>
</MiniToolbarText>
<SemiControlledTextField
commitOnBlur
id="text-object-line-height"
type="number"
margin="none"
style={styles.sizeTextField}
value={textObjectConfiguration.getLineHeight()}
onChange={value => {
textObjectConfiguration.setLineHeight(
parseInt(value, 10) || 0
);
this.forceUpdate();
}}
placeholder="Auto"
/>
<MiniToolbarText>
<Trans>Color:</Trans>
</MiniToolbarText>
@@ -389,6 +371,21 @@ export default class TextEditor extends React.Component<EditorProps, void> {
}}
/>
</Column>
<Text size="block-title" noMargin>
<Trans>Multiline</Trans>
</Text>
<Line noMargin>
<SemiControlledTextField
floatingLabelText={<Trans>Line height</Trans>}
type="number"
fullWidth
value={textObjectConfiguration.getLineHeight()}
onChange={value => {
textObjectConfiguration.setLineHeight(parseFloat(value) || 0);
this.forceUpdate();
}}
/>
</Line>
</ColumnStackLayout>
);
}

View File

@@ -98,6 +98,7 @@ export default class RenderedTextInstance extends RenderedInstance {
textObjectConfiguration.isItalic() !== this._isItalic ||
textObjectConfiguration.isBold() !== this._isBold ||
textObjectConfiguration.getCharacterSize() !== this._characterSize ||
textObjectConfiguration.getLineHeight() !== this._lineHeight ||
textObjectConfiguration.getTextAlignment() !== this._textAlignment ||
textObjectConfiguration.getVerticalTextAlignment() !==
this._verticalTextAlignment ||
@@ -114,12 +115,12 @@ export default class RenderedTextInstance extends RenderedInstance {
textObjectConfiguration.getShadowBlurRadius() !==
this._shadowBlurRadius ||
this._instance.hasCustomSize() !== this._wrapping ||
(this.getCustomWidth() !== this._wrappingWidth && this._wrapping) ||
textObjectConfiguration.getLineHeight() !== this._lineHeight
(this.getCustomWidth() !== this._wrappingWidth && this._wrapping)
) {
this._isItalic = textObjectConfiguration.isItalic();
this._isBold = textObjectConfiguration.isBold();
this._characterSize = textObjectConfiguration.getCharacterSize();
this._lineHeight = textObjectConfiguration.getLineHeight();
this._textAlignment = textObjectConfiguration.getTextAlignment();
this._verticalTextAlignment = textObjectConfiguration.getVerticalTextAlignment();
this._color = textObjectConfiguration.getColor();
@@ -134,7 +135,6 @@ export default class RenderedTextInstance extends RenderedInstance {
this._shadowColor = textObjectConfiguration.getShadowColor();
this._shadowOpacity = textObjectConfiguration.getShadowOpacity();
this._shadowBlurRadius = textObjectConfiguration.getShadowBlurRadius();
this._lineHeight = textObjectConfiguration.getLineHeight();
this._wrapping = this._instance.hasCustomSize();
this._wrappingWidth = this.getCustomWidth();
@@ -170,14 +170,12 @@ export default class RenderedTextInstance extends RenderedInstance {
style.fontSize = Math.max(1, this._characterSize);
style.fontStyle = this._isItalic ? 'italic' : 'normal';
style.fontWeight = this._isBold ? 'bold' : 'normal';
style.lineHeight = this._lineHeight !== 0 ? this._lineHeight : undefined;
style.fill = rgbStringToHexNumber(this._color);
style.wordWrap = this._wrapping;
style.wordWrapWidth = this._wrappingWidth <= 1 ? 1 : this._wrappingWidth;
style.breakWords = true;
style.align = this._textAlignment;
if (this._lineHeight > 0) {
style.lineHeight = this._lineHeight;
}
style.stroke = rgbStringToHexNumber(this._outlineColor);
style.strokeThickness = this._isOutlineEnabled

View File

@@ -104,6 +104,7 @@ export class ExtensionTreeViewItemContent implements TreeViewItemContent {
if (oldName === newName) {
return;
}
this.eventsFunctionsExtension.setOrigin('', '');
this.props.onRenameEventsFunctionsExtension(oldName, newName);
}
@@ -219,6 +220,9 @@ export class ExtensionTreeViewItemContent implements TreeViewItemContent {
project
);
newEventsFunctionsExtension.setName(newName); // Unserialization has overwritten the name.
if (newName !== name) {
newEventsFunctionsExtension.setOrigin('', '');
}
this._onProjectItemModified();
this.props.onReloadEventsFunctionsExtensions();

View File

@@ -17,6 +17,7 @@ export default {
export const Default = () => (
<EventsBasedObjectEditor
eventsFunctionsExtension={testProject.testEventsFunctionsExtension}
eventsBasedObject={testProject.testEventsBasedObject}
onOpenCustomObjectEditor={action('onOpenCustomObjectEditor')}
onEventsBasedObjectChildrenEdited={action(