Compare commits

...

133 Commits

Author SHA1 Message Date
Alex
8c817a4674 Use top bar on mobile to signify version is checkout 2023-12-15 14:10:16 +01:00
Alex
cacd81f233 Add margin to avoid misclicks 2023-12-15 14:09:59 +01:00
AlexandreSi
061da1f02d Remove unused 'saved' status 2023-12-15 12:02:37 +01:00
AlexandreSi
5202a61b0f Prettier 2023-12-15 11:24:59 +01:00
AlexandreSi
3e32f7278e Quit navigating history when checking out latest save 2023-12-15 11:24:59 +01:00
AlexandreSi
3a70315388 Store latest versions instead of just its id 2023-12-15 11:15:47 +01:00
AlexandreSi
5b5db025eb Display bottom border on toolbar when a version is checked out 2023-12-15 10:52:27 +01:00
AlexandreSi
9abad68125 Simplify typing 2023-12-15 10:43:49 +01:00
AlexandreSi
256ce50d98 Add OpenedVersionStatusChip to toolbar 2023-12-15 09:37:49 +01:00
AlexandreSi
5ce68cd257 Store whole version when checking out a version 2023-12-15 09:30:36 +01:00
AlexandreSi
e122c38c19 Reduce padding so that it fits better in the toolbar 2023-12-15 09:05:59 +01:00
AlexandreSi
05a8f084dc Fix loading error 2023-12-15 09:01:34 +01:00
AlexandreSi
930316e121 Add history icon 2023-12-15 09:01:26 +01:00
AlexandreSi
90681c75cd Improve context menu actions 2023-12-15 08:55:50 +01:00
AlexandreSi
1140b1cd37 Create OpenedVersionStatusChip component for the toolbar 2023-12-14 17:55:06 +01:00
AlexandreSi
f992c14893 Display Latest save chip and indicator 2023-12-14 15:37:57 +01:00
AlexandreSi
093df26ba5 Add Status indicator to sub rows 2023-12-14 15:14:42 +01:00
AlexandreSi
5731fe8d2e Highlight correct part of day collapse 2023-12-14 14:46:18 +01:00
AlexandreSi
93b2c7cc5f Display named versions in collapsed day row 2023-12-14 14:22:36 +01:00
AlexandreSi
225a6e199a Remove the Changes Saved chip state when saving 2023-12-14 12:04:47 +01:00
AlexandreSi
c3e587b09e Prevent checking out previous version if unsaved changes in project 2023-12-13 17:36:48 +01:00
AlexandreSi
aa300b2441 Unfold most recent day initially 2023-12-13 17:25:03 +01:00
AlexandreSi
fd8eb03de3 Update current filemetadata lastModifiedDate when saving cloud project name 2023-12-13 17:11:40 +01:00
AlexandreSi
07d45c6be0 Add loader when renaming is ongoing 2023-12-13 17:07:34 +01:00
AlexandreSi
4cb08642b6 Do not ask for confirmation when switching version and there are unsaved changes 2023-12-13 17:07:34 +01:00
AlexandreSi
7685aaf90c Conserve VersionHistory component state across version checking out 2023-12-13 17:07:34 +01:00
AlexandreSi
79983841b7 Add confirmation dialog before restoring cloud project previous version 2023-12-13 17:07:34 +01:00
AlexandreSi
b69e9df277 Improve placeholder messages 2023-12-13 17:07:34 +01:00
AlexandreSi
5945289a2d Add menu items to show version history 2023-12-13 17:07:34 +01:00
AlexandreSi
d4170e324a Disable project sharing when restoring a project 2023-12-13 17:07:34 +01:00
AlexandreSi
6ff69014fc Make the restoration of versions work 2023-12-13 17:07:34 +01:00
AlexandreSi
18898c9a73 Prevent closing of drawer when hitting Esc to cancel version labelling 2023-12-13 17:07:34 +01:00
AlexandreSi
7f347be5df Memoize version history component 2023-12-13 17:07:34 +01:00
AlexandreSi
fd9390e30f Improve fallback state in case of error 2023-12-13 17:07:34 +01:00
AlexandreSi
48143cc8b3 Fix flow 2023-12-13 17:07:34 +01:00
AlexandreSi
d07a463513 Implement version checkout 2023-12-13 17:07:34 +01:00
AlexandreSi
ad209ad035 Add possibility to specify restored from version id 2023-12-13 17:07:34 +01:00
AlexandreSi
38466874e7 Fix possibility to remove version label 2023-12-13 17:07:34 +01:00
AlexandreSi
148f6fc092 Add possibility to rename a version 2023-12-13 17:07:34 +01:00
AlexandreSi
6e5fad024c Update visual feedbacks on rows 2023-12-13 17:07:34 +01:00
AlexandreSi
7be4e7d462 Update version history on project save 2023-12-13 17:07:34 +01:00
AlexandreSi
3b78dafa86 Stabilize the number of version listing API calls 2023-12-13 17:07:34 +01:00
AlexandreSi
add039e90c Implement version pagination 2023-12-13 17:07:34 +01:00
AlexandreSi
100fb7c862 Start implementing version history in editor 2023-12-13 17:07:34 +01:00
AlexandreSi
0547fbf360 Update icon 2023-12-13 17:07:34 +01:00
AlexandreSi
49d19f1ef7 Improve story 2023-12-13 17:07:34 +01:00
AlexandreSi
aa7bc0cf6c Add outline to unsaved changes versions row 2023-12-13 17:07:34 +01:00
AlexandreSi
ba51819c91 Add status chip to project version row 2023-12-13 17:07:34 +01:00
AlexandreSi
43fdc6a872 Use all horizontal space to display label textfield 2023-12-13 17:07:34 +01:00
AlexandreSi
33b442d7e3 Add anonymous avatar when project version has no user 2023-12-13 17:07:33 +01:00
AlexandreSi
232dab4488 Change no more version message UI 2023-12-13 17:07:33 +01:00
AlexandreSi
e3daeaead5 Make whole day row clickable 2023-12-13 17:07:33 +01:00
AlexandreSi
6526b92e88 Add hover effect on project version row 2023-12-13 17:07:33 +01:00
AlexandreSi
755b5cc87e extract project version row in dedicated file 2023-12-13 17:07:33 +01:00
AlexandreSi
daf0852f52 Extract named versions from collapse 2023-12-13 17:07:33 +01:00
AlexandreSi
b540d9e0bd Also rename restored from versions when possible 2023-12-13 17:07:33 +01:00
AlexandreSi
cddd20103c Add fallback anonymous mention if no author 2023-12-13 17:07:33 +01:00
AlexandreSi
84256c3364 Fix restored from version display 2023-12-13 17:07:33 +01:00
AlexandreSi
57134a36c1 Improve date display when long version label 2023-12-13 17:07:33 +01:00
AlexandreSi
62577c9a9a Mention restored from version when applicable 2023-12-13 17:07:33 +01:00
AlexandreSi
f354de912f Add props to checkout version 2023-12-13 17:07:33 +01:00
AlexandreSi
a1e701829d Add flag to mean that all versions were loaded 2023-12-13 17:07:33 +01:00
AlexandreSi
68141bfd87 Add button to load more versions 2023-12-13 17:07:33 +01:00
AlexandreSi
193a743a3f Limit label length 2023-12-13 17:07:33 +01:00
AlexandreSi
6915f41080 Add keyboard shortcuts when editing version label 2023-12-13 17:07:33 +01:00
AlexandreSi
0c4c46a529 Rename version on text field blur 2023-12-13 17:07:33 +01:00
AlexandreSi
3f3da17c72 Add context menu to version history component 2023-12-13 17:07:33 +01:00
AlexandreSi
352ccf00e4 Collapse version under day row 2023-12-13 17:07:33 +01:00
AlexandreSi
f6b8768e07 Group versions by day 2023-12-13 17:07:33 +01:00
AlexandreSi
c76b49bdc2 Start version history component 2023-12-13 17:07:33 +01:00
AlexandreSi
bdca1add48 Refactor User service api client 2023-12-13 17:07:33 +01:00
AlexandreSi
d00a93dedc Update typing 2023-12-13 17:07:33 +01:00
AlexandreS
ac82be800b Fix: update file metadata when saving cloud project triggers new commit (#6074) 2023-12-13 16:50:50 +01:00
Aurélien Vivet
2c92ae4042 Fix typo (#6075) 2023-12-13 16:35:36 +01:00
Clément Pasteau
465e934605 Bump version to 5.3.184 (#6072) 2023-12-13 14:34:04 +01:00
github-actions[bot]
248ba7675e Update translations [skip ci] (#6070)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2023-12-13 14:19:15 +01:00
AlexandreS
fd2b59ba45 Revert "Use python3 to update bindings (#6068)" (#6071)
This reverts commit 6edf63e98f.

Only show in developer changelog
2023-12-13 12:01:14 +01:00
Clément Pasteau
be54236ece Fix using which from key event when not defined (#6069)
Do not show in changelog
2023-12-13 11:59:57 +01:00
AlexandreS
6edf63e98f Use python3 to update bindings (#6068)
Only show in developer changelog
2023-12-13 11:36:44 +01:00
github-actions[bot]
034f1ad9cc Update translations [skip ci] (#6057)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2023-12-12 15:42:47 +01:00
Florian Rival
94b8c31ac2 Allow to increment position of points of sprites by 0.5 in the editor 2023-12-12 15:11:21 +01:00
Florian Rival
57d1241e2d Allow to increment position of vertices of collision masks by 0.5 2023-12-12 15:08:50 +01:00
github-actions[bot]
eb4708ca87 Update translations [skip ci] (#6051)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2023-12-11 14:47:52 +01:00
D8H
fac710780b Fix a flash of 1 black frame the first time a scene is started (#6048) 2023-12-11 14:41:27 +01:00
github-actions[bot]
d28aac325a Update translations [skip ci] (#6033)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2023-12-11 14:18:26 +01:00
Clément Pasteau
306b341ee5 Education premium users now also have a dedicated Discord channel (#6047) 2023-12-11 14:17:09 +01:00
D8H
1d8e04cb78 Allow to set 3D models dimensions with a scaling factor (#6044)
- It allows to keep consistent dimensions for a set of 3D models
2023-12-11 12:13:17 +01:00
Clément Pasteau
3b2855de59 Fix a possible crash when closing an app window (#6046) 2023-12-11 10:29:10 +01:00
AlexandreS
d5c2982b2d Increase home page mobile menu icon sizes (#6034) 2023-12-08 10:58:05 +01:00
Clément Pasteau
315b7387b3 Bump version to 5.3.183 (#6032) 2023-12-07 16:21:12 +01:00
Clément Pasteau
f4f92566f4 Remove showing GDevelop templates first + increase visibility (#6031)
Do not show in changelog
2023-12-07 15:56:22 +01:00
github-actions[bot]
7b72d4e080 Update translations [skip ci] (#6030)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2023-12-07 15:56:06 +01:00
D8H
ae96ebf79c Fix heavy assets failing to download on slow connections (#6024) 2023-12-07 15:36:27 +01:00
D8H
be813a0271 Start games without any loading screen if assets are ready before half the fade-in (#6025)
- This only works if the watermark is enabled.
- It allows players to start a game a 2nd time almost instantly.
2023-12-07 15:21:30 +01:00
github-actions[bot]
b306d80915 Update translations [skip ci] (#6020)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2023-12-07 11:56:18 +01:00
AlexandreS
9baed02aa1 Improve project list refreshing UI/UX (#6026) 2023-12-07 11:43:29 +01:00
Clément Pasteau
1b4c5c1b1c Improve export copy & logic (#6023)
Do not show in changelog
2023-12-06 15:13:30 +01:00
Arthur Pacaud (arthuro555)
4f04190614 Add automatic generation of TypeScript types for Core (in addition to existing Flow types) (#5429)
Only show in developer changelog
2023-12-05 18:47:20 +01:00
AlexandreS
d3134ecde9 Bump newIDE version (#6019) 2023-12-05 17:13:32 +01:00
github-actions[bot]
61ed7ffa16 Update translations [skip ci] (#6015)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-12-05 16:43:36 +01:00
Clément Pasteau
8be1961d3f Throw if wrong response from project api (#6014)
Do not show in changelog
2023-12-05 09:59:00 +01:00
github-actions[bot]
112c306610 Update translations [skip ci] (#6001)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2023-12-05 09:44:27 +01:00
D8H
423f15b513 Add actions to tween effect properties (#5993) 2023-12-04 15:25:40 +01:00
D8H
a9c89b14c3 Use a drop-down list for object effect parameters (#6005) 2023-12-04 14:39:12 +01:00
TRP
bd898463f5 Add an action to draw a torus with the shape painter object (#5981)
- Thanks @trp02
2023-12-04 14:11:41 +01:00
D8H
ed4635664c Fix 3D light rotation angle calculus when Y is the top (#6004)
- In order to get back the same light result as before, 27° should be subtracted to the rotation angle.
2023-12-04 12:48:56 +01:00
Clément Pasteau
b6f25db40c Throw if badges or achievements not loading properly (#6002)
Do not show in changelog
2023-12-04 12:21:11 +01:00
Florian Rival
f78662be5f Reduce the number of tutorial progress analytics events (#5992)
Don't show in changelog
2023-12-04 11:24:37 +01:00
github-actions[bot]
7aae35a029 Update translations [skip ci] (#5980)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2023-12-04 11:18:46 +01:00
D8H
6c323614ef Fix object type declaration for 3D capabilities actions (#6000)
- Don't show in changelog
2023-12-04 11:03:35 +01:00
AlexandreS
eb4170df20 Fix suggestion to add missing object variable that is used in events (#5999) 2023-12-04 10:40:29 +01:00
D8H
8830bb93ae Force sounds to download even when "preload in cache" is unchecked (#5984)
- Don't show in changelog
2023-12-01 16:30:11 +01:00
Florian Rival
87fa0a39ac Fix warning 2023-12-01 15:29:19 +01:00
Florian Rival
a9cc911ca8 Use JSX for rendering in game debugger message (#5983)
Only show in developer changelog
2023-12-01 09:57:21 +01:00
AlexandreS
d3fe6cf532 Remove unused project version get api call (#5982)
Don't show in changelog
2023-12-01 08:16:43 +01:00
Florian Rival
e2c40ff205 Improve structure variables completion (#5978)
* When a structure variable name is entered in an expression, completions will also be provided for children variables.
2023-11-30 13:47:19 +01:00
github-actions[bot]
970d04b0df Update translations [skip ci] (#5972)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2023-11-30 09:26:31 +01:00
D8H
8c5076443c Fix property name case in action sentences (#5977) 2023-11-29 15:29:04 +01:00
D8H
3744e98065 Use the new syntax when generating expressions and actions for properties (#5976) 2023-11-29 15:20:38 +01:00
AlexandreS
b6a1332124 Add game dashboard on home page and project manager (#5963) 2023-11-29 11:12:29 +01:00
Clément Pasteau
0251997703 Prevent loading announcements in state if response is not an array (#5975)
Do not show in changelog
2023-11-29 10:34:50 +01:00
D8H
57faa9fb4a Move community tier extensions in their own index section (#5973)
- Don't show in changelog.
2023-11-28 20:34:06 +01:00
D8H
ba95f66ccd Fix LDtk tile map resources export with the fast loading (#5951)
- Don't show in changelog
2023-11-28 14:31:47 +01:00
github-actions[bot]
9465873dbd Update translations [skip ci] (#5943)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2023-11-28 14:17:13 +01:00
Clément Pasteau
b5a9fe4fe1 Update fling game with new trigger (#5971)
Do not show in changelog
2023-11-28 14:16:43 +01:00
D8H
6531e2e970 Use relative links in extensions reference pages (#5970)
Don't show in changelog
2023-11-28 12:59:39 +01:00
Florian Rival
4f900c9451 Enable auto-completion for structure and array variables when writing an expression (#5960) 2023-11-28 11:30:26 +01:00
Clément Pasteau
f97f267a96 Put the Fling Game tutorial back (#5967)
* It was unintentionally removed!
2023-11-28 11:19:07 +01:00
Clément Pasteau
6cd8f54869 Fix possible crash in the New Object Dialog (#5968) 2023-11-28 09:51:07 +01:00
Clément Pasteau
9718fb788e Better wording for Discord role perks (#5966)
Do not show in changelog
2023-11-27 18:40:32 +01:00
Clément Pasteau
38651edf3e Discord username can now be added to one's Profile, allowing to claim a role on GDevelop's server (#5962)
* If you have a Gold or Startup subscription, head down to your profile, to claim access to a premium channel on Discord, where you can discuss together and ask for support.
2023-11-27 16:54:27 +01:00
Florian Rival
d34f1a654f Improve navigation for extensions in the documentation (#5950) 2023-11-24 11:43:47 +01:00
D8H
5abc74b66b Remove only (#5946)
Don't show in changelog
2023-11-23 19:05:08 +01:00
337 changed files with 20925 additions and 10161 deletions

View File

@@ -277,8 +277,11 @@ class GD_CORE_API ExpressionParser2 {
std::unique_ptr<VariableNode> Variable(const gd::String &name, gd::ExpressionParserLocation nameLocation) {
auto variable = gd::make_unique<VariableNode>(name);
variable->child = VariableAccessorOrVariableBracketAccessor();
variable->child->parent = variable.get();
if (CheckIfChar(IsOpeningSquareBracket) || CheckIfChar(IsDot)) {
variable->child = VariableAccessorOrVariableBracketAccessor();
variable->child->parent = variable.get();
}
variable->location = ExpressionParserLocation(
nameLocation.GetStartPosition(), GetCurrentPosition());
@@ -302,8 +305,12 @@ class GD_CORE_API ExpressionParser2 {
"bracket for each opening bracket."));
}
SkipIfChar(IsClosingSquareBracket);
child->child = VariableAccessorOrVariableBracketAccessor();
child->child->parent = child.get();
SkipAllWhitespaces();
if (CheckIfChar(IsOpeningSquareBracket) || CheckIfChar(IsDot)) {
child->child = VariableAccessorOrVariableBracketAccessor();
child->child->parent = child.get();
}
child->location =
ExpressionParserLocation(childStartPosition, GetCurrentPosition());
@@ -315,8 +322,15 @@ class GD_CORE_API ExpressionParser2 {
auto identifierAndLocation = ReadIdentifierName(/*allowDeprecatedSpacesInName=*/ false);
auto child =
gd::make_unique<VariableAccessorNode>(identifierAndLocation.name);
child->child = VariableAccessorOrVariableBracketAccessor();
child->child->parent = child.get();
if (identifierAndLocation.name.empty()) {
child->diagnostic = RaiseSyntaxError(_("A name should be entered after the dot."));
}
SkipAllWhitespaces();
if (CheckIfChar(IsOpeningSquareBracket) || CheckIfChar(IsDot)) {
child->child = VariableAccessorOrVariableBracketAccessor();
child->child->parent = child.get();
}
child->nameLocation = identifierAndLocation.location;
child->dotLocation = dotLocation;
child->location =
@@ -325,7 +339,11 @@ class GD_CORE_API ExpressionParser2 {
return std::move(child);
}
return std::move(gd::make_unique<VariableAccessorOrVariableBracketAccessorNode>());
// Should never happen, unless a node called this function without checking if the current character
// was a dot or an opening bracket - this means there is an error in the grammar.
auto unrecognisedNode = gd::make_unique<VariableAccessorOrVariableBracketAccessorNode>();
unrecognisedNode->diagnostic = RaiseSyntaxError(_("A dot or bracket was expected here."));
return std::move(unrecognisedNode);
}
std::unique_ptr<FunctionCallNode> FreeFunction(
@@ -361,18 +379,24 @@ class GD_CORE_API ExpressionParser2 {
const auto &childIdentifierNameLocation =
childIdentifierAndLocation.location;
std::unique_ptr<gd::ExpressionParserError> emptyNameError = childIdentifierName.empty() ?
RaiseSyntaxError(_("A name should be entered after the dot.")) : nullptr;
SkipAllWhitespaces();
if (IsNamespaceSeparator()) {
ExpressionParserLocation namespaceSeparatorLocation =
SkipNamespaceSeparator();
SkipAllWhitespaces();
return BehaviorFunction(parentIdentifier,
auto behaviorFunction = BehaviorFunction(parentIdentifier,
childIdentifierName,
parentIdentifierLocation,
parentIdentifierDotLocation,
childIdentifierNameLocation,
namespaceSeparatorLocation);
if (emptyNameError) behaviorFunction->diagnostic = std::move(emptyNameError);
return std::move(behaviorFunction);
} else if (CheckIfChar(IsOpeningParenthesis)) {
ExpressionParserLocation openingParenthesisLocation = SkipChar();
@@ -381,7 +405,7 @@ class GD_CORE_API ExpressionParser2 {
childIdentifierName);
auto parametersNode = Parameters(function.get(), parentIdentifier);
function->parameters = std::move(parametersNode.parameters),
function->diagnostic = std::move(parametersNode.diagnostic);
function->diagnostic = emptyNameError ? std::move(emptyNameError) : std::move(parametersNode.diagnostic);
function->location = ExpressionParserLocation(
parentIdentifierLocation.GetStartPosition(), GetCurrentPosition());
@@ -394,6 +418,8 @@ class GD_CORE_API ExpressionParser2 {
return std::move(function);
} else if (CheckIfChar(IsDot) || CheckIfChar(IsOpeningSquareBracket)) {
auto variable = gd::make_unique<VariableNode>(parentIdentifier);
variable->diagnostic = std::move(emptyNameError);
auto child =
gd::make_unique<VariableAccessorNode>(childIdentifierName);
child->child = VariableAccessorOrVariableBracketAccessor();
@@ -419,6 +445,7 @@ class GD_CORE_API ExpressionParser2 {
node->identifierNameLocation = parentIdentifierLocation;
node->identifierNameDotLocation = parentIdentifierDotLocation;
node->childIdentifierNameLocation = childIdentifierNameLocation;
node->diagnostic = std::move(emptyNameError);
return std::move(node);
}

View File

@@ -8,6 +8,7 @@
#include <memory>
#include <vector>
#include "GDCore/Events/Parsers/ExpressionParser2Node.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
namespace gd {
@@ -43,6 +44,11 @@ class GD_CORE_API ExpressionParser2NodePrinter
*/
const gd::String& GetOutput() { return output; };
static gd::String PrintStringLiteral(const gd::String& str) {
return "\"" +
str.FindAndReplace("\\", "\\\\").FindAndReplace("\"", "\\\"") + "\"";
}
protected:
void OnVisitSubExpressionNode(SubExpressionNode& node) override {
output += "(";
@@ -69,10 +75,7 @@ class GD_CORE_API ExpressionParser2NodePrinter
}
void OnVisitNumberNode(NumberNode& node) override { output += node.number; }
void OnVisitTextNode(TextNode& node) override {
output +=
"\"" +
node.text.FindAndReplace("\\", "\\\\").FindAndReplace("\"", "\\\"") +
"\"";
output += PrintStringLiteral(node.text);
}
void OnVisitVariableNode(VariableNode& node) override {
output += node.name;
@@ -97,8 +100,8 @@ class GD_CORE_API ExpressionParser2NodePrinter
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
if (!node.behaviorFunctionName.empty()) {
output +=
node.objectName + "." + node.objectFunctionOrBehaviorName + "::" + node.behaviorFunctionName;
output += node.objectName + "." + node.objectFunctionOrBehaviorName +
"::" + node.behaviorFunctionName;
} else {
output += node.objectName + "." + node.objectFunctionOrBehaviorName;
}

View File

@@ -171,8 +171,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
_("Preload a scene resources as soon as possible in background."),
_("Preload scene _PARAM1_ in background"),
"",
"res/actions/replaceScene24.png",
"res/actions/replaceScene.png")
"res/actions/hourglass_black.svg",
"res/actions/hourglass_black.svg")
.SetHelpPath("/all-features/resources-loading")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("sceneName", _("Name of the new scene"))
@@ -184,7 +184,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
_("The progress of resources loading in background for a scene (between 0 and 1)."),
_("_PARAM0_ loading progress"),
_(""),
"res/actions/replaceScene24.png")
"res/actions/hourglass_black.svg")
.SetHelpPath("/all-features/resources-loading")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("sceneName", _("Scene name"))
@@ -197,8 +197,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
_("Check if scene resources have finished to load in background."),
_("Scene _PARAM1_ was preloaded in background"),
"",
"res/actions/replaceScene24.png",
"res/actions/replaceScene.png")
"res/actions/hourglass_black.svg",
"res/actions/hourglass_black.svg")
.SetHelpPath("/all-features/resources-loading")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("sceneName", _("Scene name"))

View File

@@ -5,6 +5,7 @@
*/
#include "ExpressionMetadata.h"
#include "GDCore/CommonTools.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/String.h"
namespace gd {
@@ -46,16 +47,14 @@ gd::ExpressionMetadata& ExpressionMetadata::AddParameter(
// For objects/behavior, the supplementary information
// parameter is an object/behavior type...
((gd::ParameterMetadata::IsObject(type) ||
gd::ParameterMetadata::IsBehavior(type))
// Prefix with the namespace if it's not already there.
&& !(supplementaryInformation.rfind(extensionNamespace, 0) == 0))
? (supplementaryInformation.empty()
? ""
: extensionNamespace +
supplementaryInformation //... so prefix it with the extension
// namespace.
)
: supplementaryInformation); // Otherwise don't change anything
gd::ParameterMetadata::IsBehavior(type))
// Prefix with the namespace if it's not already there.
&& (supplementaryInformation.find(
PlatformExtension::GetNamespaceSeparator()) != gd::String::npos)
? supplementaryInformation
: (supplementaryInformation.empty()
? ""
: extensionNamespace + supplementaryInformation)));
// TODO: Assert against supplementaryInformation === "emsc" (when running with
// Emscripten), and warn about a missing argument when calling addParameter.

View File

@@ -8,6 +8,7 @@
#include <algorithm>
#include "GDCore/CommonTools.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Tools/Log.h"
@@ -64,17 +65,14 @@ InstructionMetadata& InstructionMetadata::AddParameter(
// For objects/behavior, the supplementary information
// parameter is an object/behavior type...
((gd::ParameterMetadata::IsObject(type) ||
gd::ParameterMetadata::IsBehavior(type))
// Prefix with the namespace if it's not already there.
&& !(supplementaryInformation.rfind(extensionNamespace, 0) == 0))
? (supplementaryInformation.empty()
? ""
: extensionNamespace +
supplementaryInformation //... so prefix it with the
// extension
// namespace.
)
: supplementaryInformation); // Otherwise don't change anything
gd::ParameterMetadata::IsBehavior(type))
// Prefix with the namespace if it's not already there.
&& (supplementaryInformation.find(
PlatformExtension::GetNamespaceSeparator()) != gd::String::npos)
? supplementaryInformation
: (supplementaryInformation.empty()
? ""
: extensionNamespace + supplementaryInformation)));
// TODO: Assert against supplementaryInformation === "emsc" (when running with
// Emscripten), and warn about a missing argument when calling addParameter.
@@ -190,7 +188,7 @@ InstructionMetadata::UseStandardRelationalOperatorParameters(
gd::String templateSentence = _("<subject> of _PARAM0_ <operator> <value>");
sentence =
templateSentence.FindAndReplace("<subject>", sentence)
templateSentence.FindAndReplace("<subject>", sentence.CapitalizeFirstLetter())
.FindAndReplace(
"<operator>",
"_PARAM" + gd::String::From(operatorParamIndex) + "_")
@@ -200,7 +198,7 @@ InstructionMetadata::UseStandardRelationalOperatorParameters(
gd::String templateSentence = _("<subject> <operator> <value>");
sentence =
templateSentence.FindAndReplace("<subject>", sentence)
templateSentence.FindAndReplace("<subject>", sentence.CapitalizeFirstLetter())
.FindAndReplace(
"<operator>",
"_PARAM" + gd::String::From(operatorParamIndex) + "_")

View File

@@ -264,8 +264,11 @@ class GD_CORE_API PlatformExtension {
*
* \param name The name of the behavior
* \param fullname The user friendly name of the behavior
* \param defaultName The default name of behavior instances
* \param description The user friendly description of the behavior
* \param group The behavior category label
* \param icon The icon of the behavior.
* \param className The name of the class implementing the behavior
* \param instance An instance of the behavior that
* will be used to create the behavior
* \param sharedDatasInstance Optional
@@ -288,6 +291,7 @@ class GD_CORE_API PlatformExtension {
* \param name The name of the behavior
* \param fullname The user friendly name of the behavior
* \param description The user friendly description of the behavior
* \param group The behavior category label
* \param icon The icon of the behavior.
*/
gd::BehaviorMetadata& AddEventsBasedBehavior(

View File

@@ -136,7 +136,7 @@ class GD_CORE_API ExpressionVariableReplacer
auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
if (!objectNameToUseForVariableAccessor.empty()) {
if (objectsContainersList.HasVariablesContainer(
if (objectsContainersList.HasObjectOrGroupVariablesContainer(
objectNameToUseForVariableAccessor, targetVariablesContainer)) {
// The node represents an object variable, and this object variables are
// the target. Do the replacement or removals:
@@ -177,7 +177,7 @@ class GD_CORE_API ExpressionVariableReplacer
GetPotentialNewName(node.identifierName),
[&]() {
// This represents an object.
if (objectsContainersList.HasVariablesContainer(
if (objectsContainersList.HasObjectOrGroupVariablesContainer(
node.identifierName, targetVariablesContainer)) {
// The node represents an object variable, and this object variables
// are the target. Do the replacement or removals:

View File

@@ -11,13 +11,16 @@
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Events/Parsers/ExpressionParser2Node.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Events/Parsers/GrammarTerminals.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/Extensions/Metadata/ValueTypeMetadata.h"
#include "GDCore/IDE/Events/ExpressionNodeLocationFinder.h"
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
#include "GDCore/IDE/Events/ExpressionVariableOwnerFinder.h"
#include "GDCore/IDE/Events/ExpressionVariableParentFinder.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Project/Variable.h"
@@ -486,47 +489,56 @@ class GD_CORE_API ExpressionCompletionFinder
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainers, rootType, node);
// Only attempt to complete with the children of the variable
// if it's the last child (no more `.AnotherVariable` written after).
bool eagerlyCompleteIfExactMatch = node.child == nullptr;
if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
if (type == "globalvar") {
if (type == "globalvar" || type == "scenevar") {
const auto* variablesContainer =
projectScopedContainers.GetVariablesContainersList()
.GetTopMostVariablesContainer();
type == "globalvar"
? projectScopedContainers.GetVariablesContainersList()
.GetTopMostVariablesContainer()
: projectScopedContainers.GetVariablesContainersList()
.GetBottomMostVariablesContainer();
if (variablesContainer) {
AddCompletionsForVariablesMatchingSearch(
*variablesContainer, node.name, node.nameLocation);
}
} else if (type == "scenevar") {
const auto* variablesContainer =
projectScopedContainers.GetVariablesContainersList()
.GetBottomMostVariablesContainer();
if (variablesContainer) {
AddCompletionsForVariablesMatchingSearch(
*variablesContainer, node.name, node.nameLocation);
AddCompletionsForVariablesMatchingSearch(*variablesContainer,
node.name,
node.nameLocation,
eagerlyCompleteIfExactMatch);
}
} else if (type == "objectvar") {
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
platform,
objectsContainersList,
// Variable fields doesn't use expression completion,
// so the object will be found inside the expression itself.
"",
node);
platform, objectsContainersList, rootObjectName, node);
AddCompletionsForObjectOrGroupVariablesMatchingSearch(
objectsContainersList, objectName, node.name, node.nameLocation);
objectsContainersList,
objectName,
node.name,
node.nameLocation,
eagerlyCompleteIfExactMatch);
}
} else {
AddCompletionsForObjectsAndVariablesMatchingSearch(
node.name, type, node.nameLocation);
node.name, type, node.nameLocation, eagerlyCompleteIfExactMatch);
}
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
// No completions
VariableAndItsParent variableAndItsParent =
gd::ExpressionVariableParentFinder::GetLastParentOfNode(
platform, projectScopedContainers, node);
// If no child, we're at the end of a variable (like `GrandChild` in
// `Something.Child.GrandChild`) so we can complete eagerly children if we
// can.
gd::String eagerlyCompleteForVariableName =
node.child == nullptr ? node.name : "";
AddCompletionsForChildrenVariablesOf(variableAndItsParent,
node.nameLocation,
eagerlyCompleteForVariableName);
}
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override {
// No completions
}
VariableBracketAccessorNode& node) override {}
void OnVisitIdentifierNode(IdentifierNode& node) override {
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
@@ -537,45 +549,81 @@ class GD_CORE_API ExpressionCompletionFinder
AddCompletionsForObjectMatchingSearch(
node.identifierName, type, node.location);
} else if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
if (type == "globalvar") {
if (type == "globalvar" || type == "scenevar") {
const auto* variablesContainer =
projectScopedContainers.GetVariablesContainersList()
.GetTopMostVariablesContainer();
type == "globalvar"
? projectScopedContainers.GetVariablesContainersList()
.GetTopMostVariablesContainer()
: projectScopedContainers.GetVariablesContainersList()
.GetBottomMostVariablesContainer();
if (variablesContainer) {
AddCompletionsForVariablesMatchingSearch(*variablesContainer,
node.identifierName,
node.identifierNameLocation);
}
} else if (type == "scenevar") {
const auto* variablesContainer =
projectScopedContainers.GetVariablesContainersList()
.GetBottomMostVariablesContainer();
if (variablesContainer) {
AddCompletionsForVariablesMatchingSearch(*variablesContainer,
node.identifierName,
node.identifierNameLocation);
if (IsCaretOn(node.identifierNameDotLocation) ||
IsCaretOn(node.childIdentifierNameLocation)) {
// Complete a potential child variable:
if (variablesContainer->Has(node.identifierName)) {
AddCompletionsForChildrenVariablesOf(
&variablesContainer->Get(node.identifierName),
node.childIdentifierNameLocation,
node.childIdentifierName);
}
} else {
// Complete a root variable of the scene or project.
// Don't attempt to complete children variables if there is
// already a dot written (`MyVariable.`).
bool eagerlyCompleteIfPossible =
!node.identifierNameDotLocation.IsValid();
AddCompletionsForVariablesMatchingSearch(
*variablesContainer,
node.identifierName,
node.identifierNameLocation,
eagerlyCompleteIfPossible);
}
}
} else if (type == "objectvar") {
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
platform,
objectsContainersList,
// Variable fields doesn't use expression completion,
// so the object will be found inside the expression itself.
"",
node);
platform, objectsContainersList, rootObjectName, node);
AddCompletionsForObjectOrGroupVariablesMatchingSearch(
objectsContainersList,
objectName,
node.identifierName,
node.identifierNameLocation);
if (IsCaretOn(node.identifierNameDotLocation) ||
IsCaretOn(node.childIdentifierNameLocation)) {
// Complete a potential child variable:
const auto* variablesContainer =
objectsContainersList.GetObjectOrGroupVariablesContainer(
objectName);
if (variablesContainer &&
variablesContainer->Has(node.identifierName)) {
AddCompletionsForChildrenVariablesOf(
&variablesContainer->Get(node.identifierName),
node.childIdentifierNameLocation,
node.childIdentifierName);
}
} else {
// Complete a root variable of the object.
// Don't attempt to complete children variables if there is
// already a dot written (`MyVariable.`).
bool eagerlyCompleteIfPossible =
!node.identifierNameDotLocation.IsValid();
AddCompletionsForObjectOrGroupVariablesMatchingSearch(
objectsContainersList,
objectName,
node.identifierName,
node.identifierNameLocation,
eagerlyCompleteIfPossible);
}
}
} else {
// Object function, behavior name, variable, object variable.
if (IsCaretOn(node.identifierNameLocation)) {
// Is this the proper position?
// Don't attempt to complete children variables if there is
// already a dot written (`MyVariable.`).
bool eagerlyCompleteIfPossible =
!node.identifierNameDotLocation.IsValid();
AddCompletionsForAllIdentifiersMatchingSearch(
node.identifierName, type, node.identifierNameLocation);
node.identifierName,
type,
node.identifierNameLocation,
eagerlyCompleteIfPossible);
if (!node.identifierNameDotLocation.IsValid()) {
completions.push_back(
ExpressionCompletionDescription::ForExpressionWithPrefix(
@@ -586,27 +634,57 @@ class GD_CORE_API ExpressionCompletionFinder
}
} else if (IsCaretOn(node.identifierNameDotLocation) ||
IsCaretOn(node.childIdentifierNameLocation)) {
const gd::String& objectName = node.identifierName;
// Might be:
// - An object variable, object behavior or object expression.
// - Or a variable with a child.
projectScopedContainers.MatchIdentifierWithName<void>(
node.identifierName,
[&]() {
// This is an object.
const gd::String& objectName = node.identifierName;
AddCompletionsForObjectOrGroupVariablesMatchingSearch(
objectsContainersList,
objectName,
node.childIdentifierName,
node.childIdentifierNameLocation,
true);
// Might be an object variable, object behavior or object expression:
AddCompletionsForObjectOrGroupVariablesMatchingSearch(
objectsContainersList,
objectName,
node.childIdentifierName,
node.childIdentifierNameLocation);
completions.push_back(
ExpressionCompletionDescription::ForBehaviorWithPrefix(
node.childIdentifierName,
node.childIdentifierNameLocation.GetStartPosition(),
node.childIdentifierNameLocation.GetEndPosition(),
objectName));
completions.push_back(
ExpressionCompletionDescription::ForExpressionWithPrefix(
type,
node.childIdentifierName,
node.childIdentifierNameLocation.GetStartPosition(),
node.childIdentifierNameLocation.GetEndPosition(),
objectName));
completions.push_back(
ExpressionCompletionDescription::ForBehaviorWithPrefix(
node.childIdentifierName,
node.childIdentifierNameLocation.GetStartPosition(),
node.childIdentifierNameLocation.GetEndPosition(),
objectName));
completions.push_back(
ExpressionCompletionDescription::ForExpressionWithPrefix(
type,
node.childIdentifierName,
node.childIdentifierNameLocation.GetStartPosition(),
node.childIdentifierNameLocation.GetEndPosition(),
objectName));
},
[&]() {
// This is a variable.
VariableAndItsParent variableAndItsParent =
gd::ExpressionVariableParentFinder::GetLastParentOfNode(
platform, projectScopedContainers, node);
AddCompletionsForChildrenVariablesOf(
variableAndItsParent,
node.childIdentifierNameLocation,
node.childIdentifierName);
},
[&]() {
// Ignore properties here.
// There is no support for "children" of properties.
},
[&]() {
// Ignore parameters here.
// There is no support for "children" of parameters.
},
[&]() {
// Ignore unrecognised identifiers here.
});
}
}
}
@@ -736,7 +814,8 @@ class GD_CORE_API ExpressionCompletionFinder
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainers, rootType, node);
AddCompletionsForAllIdentifiersMatchingSearch(node.text, type, node.location);
AddCompletionsForAllIdentifiersMatchingSearch(
node.text, type, node.location);
completions.push_back(
ExpressionCompletionDescription::ForExpressionWithPrefix(
type,
@@ -755,10 +834,96 @@ class GD_CORE_API ExpressionCompletionFinder
(inclusive && searchedPosition <= location.GetEndPosition())));
}
/**
* A slightly less strict check than `gd::Project::IsNameSafe` as child
* variables can be completed even if they start with a number.
*/
bool IsIdentifierSafe(const gd::String& name) {
if (name.empty()) return false;
for (auto character : name) {
if (!GrammarTerminals::IsAllowedInIdentifier(character)) {
return false;
}
}
return true;
}
void AddCompletionsForChildrenVariablesOf(
VariableAndItsParent variableAndItsParent,
const ExpressionParserLocation& location,
gd::String eagerlyCompleteForVariableName = "") {
if (variableAndItsParent.parentVariable) {
AddCompletionsForChildrenVariablesOf(variableAndItsParent.parentVariable,
location,
eagerlyCompleteForVariableName);
} else if (variableAndItsParent.parentVariablesContainer) {
AddCompletionsForVariablesMatchingSearch(
*variableAndItsParent.parentVariablesContainer, "", location);
}
}
void AddCompletionsForChildrenVariablesOf(
const gd::Variable* variable,
const ExpressionParserLocation& location,
gd::String eagerlyCompleteForVariableName = "") {
if (!variable) return;
if (variable->GetType() == gd::Variable::Structure) {
for (const auto& name : variable->GetAllChildrenNames()) {
if (!IsIdentifierSafe(name)) continue;
const auto& childVariable = variable->GetChild(name);
ExpressionCompletionDescription description(
ExpressionCompletionDescription::Variable,
location.GetStartPosition(),
location.GetEndPosition());
description.SetCompletion(name);
description.SetVariableType(childVariable.GetType());
completions.push_back(description);
if (name == eagerlyCompleteForVariableName) {
AddEagerCompletionForVariableChildren(childVariable, name, location);
}
}
} else {
// TODO: we could do a "comment only completion" to indicate that nothing
// can/should be completed?
}
}
void AddEagerCompletionForVariableChildren(
const gd::Variable& variable,
const gd::String& variableName,
const ExpressionParserLocation& location) {
if (variable.GetType() == gd::Variable::Structure) {
gd::String prefix = variableName + ".";
for (const auto& name : variable.GetAllChildrenNames()) {
gd::String completion =
IsIdentifierSafe(name)
? (prefix + name)
: (variableName + "[" +
gd::ExpressionParser2NodePrinter::PrintStringLiteral(name) +
"]");
const auto& childVariable = variable.GetChild(name);
ExpressionCompletionDescription description(
ExpressionCompletionDescription::Variable,
location.GetStartPosition(),
location.GetEndPosition());
description.SetCompletion(completion);
description.SetVariableType(childVariable.GetType());
completions.push_back(description);
}
}
}
void AddCompletionsForVariablesMatchingSearch(
const gd::VariablesContainer& variablesContainer,
const gd::String& search,
const ExpressionParserLocation& location) {
const ExpressionParserLocation& location,
bool eagerlyCompleteIfExactMatch = false) {
variablesContainer.ForEachVariableMatchingSearch(
search,
[&](const gd::String& variableName, const gd::Variable& variable) {
@@ -769,6 +934,11 @@ class GD_CORE_API ExpressionCompletionFinder
description.SetCompletion(variableName);
description.SetVariableType(variable.GetType());
completions.push_back(description);
if (eagerlyCompleteIfExactMatch && variableName == search) {
AddEagerCompletionForVariableChildren(
variable, variableName, location);
}
});
}
@@ -776,7 +946,8 @@ class GD_CORE_API ExpressionCompletionFinder
const gd::ObjectsContainersList& objectsContainersList,
const gd::String& objectOrGroupName,
const gd::String& search,
const ExpressionParserLocation& location) {
const ExpressionParserLocation& location,
bool eagerlyCompleteIfExactMatch) {
objectsContainersList.ForEachObjectOrGroupVariableMatchingSearch(
objectOrGroupName,
search,
@@ -788,6 +959,11 @@ class GD_CORE_API ExpressionCompletionFinder
description.SetCompletion(variableName);
description.SetVariableType(variable.GetType());
completions.push_back(description);
if (eagerlyCompleteIfExactMatch && variableName == search) {
AddEagerCompletionForVariableChildren(
variable, variableName, location);
}
});
}
@@ -795,25 +971,27 @@ class GD_CORE_API ExpressionCompletionFinder
const gd::String& search,
const gd::String& type,
const ExpressionParserLocation& location) {
projectScopedContainers.GetObjectsContainersList().ForEachNameMatchingSearch(
search,
[&](const gd::String& name,
const gd::ObjectConfiguration* objectConfiguration) {
ExpressionCompletionDescription description(
ExpressionCompletionDescription::Object,
location.GetStartPosition(),
location.GetEndPosition());
description.SetObjectConfiguration(objectConfiguration);
description.SetCompletion(name);
description.SetType(type);
completions.push_back(description);
});
projectScopedContainers.GetObjectsContainersList()
.ForEachNameMatchingSearch(
search,
[&](const gd::String& name,
const gd::ObjectConfiguration* objectConfiguration) {
ExpressionCompletionDescription description(
ExpressionCompletionDescription::Object,
location.GetStartPosition(),
location.GetEndPosition());
description.SetObjectConfiguration(objectConfiguration);
description.SetCompletion(name);
description.SetType(type);
completions.push_back(description);
});
}
void AddCompletionsForObjectsAndVariablesMatchingSearch(
const gd::String& search,
const gd::String& type,
const ExpressionParserLocation& location) {
const ExpressionParserLocation& location,
bool eagerlyCompleteIfExactMatch) {
projectScopedContainers.ForEachIdentifierMatchingSearch(
search,
[&](const gd::String& objectName,
@@ -835,6 +1013,11 @@ class GD_CORE_API ExpressionCompletionFinder
description.SetCompletion(variableName);
description.SetVariableType(variable.GetType());
completions.push_back(description);
if (eagerlyCompleteIfExactMatch && variableName == search) {
AddEagerCompletionForVariableChildren(
variable, variableName, location);
}
},
[&](const gd::NamedPropertyDescriptor& property) {
// Ignore properties here.
@@ -845,7 +1028,7 @@ class GD_CORE_API ExpressionCompletionFinder
}
void AddCompletionsForAllIdentifiersMatchingSearch(const gd::String& search,
const gd::String& type) {
const gd::String& type) {
AddCompletionsForAllIdentifiersMatchingSearch(
search,
type,
@@ -855,7 +1038,8 @@ class GD_CORE_API ExpressionCompletionFinder
void AddCompletionsForAllIdentifiersMatchingSearch(
const gd::String& search,
const gd::String& type,
const ExpressionParserLocation& location) {
const ExpressionParserLocation& location,
bool eagerlyCompleteIfExactMatch = false) {
projectScopedContainers.ForEachIdentifierMatchingSearch(
search,
[&](const gd::String& objectName,
@@ -877,6 +1061,11 @@ class GD_CORE_API ExpressionCompletionFinder
description.SetCompletion(variableName);
description.SetVariableType(variable.GetType());
completions.push_back(description);
if (eagerlyCompleteIfExactMatch && variableName == search) {
AddEagerCompletionForVariableChildren(
variable, variableName, location);
}
},
[&](const gd::NamedPropertyDescriptor& property) {
ExpressionCompletionDescription description(
@@ -904,11 +1093,14 @@ class GD_CORE_API ExpressionCompletionFinder
const gd::String& rootType_,
size_t searchedPosition_,
gd::ExpressionNode* maybeParentNodeAtLocation_)
: platform(platform_),
: searchedPosition(searchedPosition_),
maybeParentNodeAtLocation(maybeParentNodeAtLocation_),
platform(platform_),
projectScopedContainers(projectScopedContainers_),
rootType(rootType_),
searchedPosition(searchedPosition_),
maybeParentNodeAtLocation(maybeParentNodeAtLocation_){};
rootObjectName("") // Always empty, might be changed if variable fields
// in the editor are changed to use completion.
{};
std::vector<ExpressionCompletionDescription> completions;
size_t searchedPosition;
@@ -917,6 +1109,7 @@ class GD_CORE_API ExpressionCompletionFinder
const gd::Platform& platform;
const gd::ProjectScopedContainers& projectScopedContainers;
const gd::String rootType;
const gd::String rootObjectName;
};
} // namespace gd

View File

@@ -263,11 +263,17 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(
}
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
RaiseError("invalid_function_name",
if (function.functionName.empty()) {
RaiseError("invalid_function_name",
_("Enter the name of the function to call."),
function.location);
} else {
RaiseError("invalid_function_name",
_("Cannot find an expression with this name: ") +
function.functionName + "\n" +
_("Double check that you've not made any typo in the name."),
function.location);
}
return returnType;
}

View File

@@ -0,0 +1,370 @@
/*
* GDevelop Core
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include <memory>
#include <vector>
#include "GDCore/Events/Parsers/ExpressionParser2Node.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Project/Variable.h"
#include "GDCore/Tools/Localization.h"
namespace gd {
class Expression;
class ObjectsContainer;
class Platform;
class ParameterMetadata;
class ExpressionMetadata;
} // namespace gd
namespace gd {
/**
* \brief Contains a variables container or a variable. Useful
* to refer to the parent of a variable (which can be a VariablesContainer
* or another Variable).
*/
struct VariableAndItsParent {
const gd::VariablesContainer* parentVariablesContainer;
const gd::Variable* parentVariable;
};
/**
* \brief Find the last parent (i.e: the variables container) of a node representing a variable.
*
* Useful for completions, to know which variables can be entered in a node.
*
* \see gd::ExpressionParser2
*/
class GD_CORE_API ExpressionVariableParentFinder
: public ExpressionParser2NodeWorker {
public:
static VariableAndItsParent GetLastParentOfNode(
const gd::Platform& platform,
const gd::ProjectScopedContainers& projectScopedContainers,
gd::ExpressionNode& node) {
gd::ExpressionVariableParentFinder typeFinder(
platform, projectScopedContainers);
node.Visit(typeFinder);
return typeFinder.variableAndItsParent;
}
virtual ~ExpressionVariableParentFinder(){};
protected:
ExpressionVariableParentFinder(
const gd::Platform& platform_,
const gd::ProjectScopedContainers& projectScopedContainers_)
: platform(platform_),
projectScopedContainers(projectScopedContainers_),
variableNode(nullptr),
thisIsALegacyPrescopedVariable(false),
bailOutBecauseEmptyVariableName(false),
legacyPrescopedVariablesContainer(nullptr){};
void OnVisitSubExpressionNode(SubExpressionNode& node) override {}
void OnVisitOperatorNode(OperatorNode& node) override {}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {}
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
if (variableNode != nullptr) {
// This is not possible
return;
}
variableNode = &node;
// Check if the parent is a function call, in which we might be dealing
// with a legacy pre-scoped variable parameter:
if (node.parent) node.parent->Visit(*this);
if (thisIsALegacyPrescopedVariable) {
// The node represents a variable name, and the variables container
// containing it was identified in the FunctionCallNode.
childVariableNames.insert(childVariableNames.begin(), node.name);
if (legacyPrescopedVariablesContainer)
variableAndItsParent = WalkUntilLastParent(
*legacyPrescopedVariablesContainer, childVariableNames);
} else {
// Otherwise, the identifier is to be interpreted as usual:
// it can be an object (on which a variable is accessed),
// or a variable.
projectScopedContainers.MatchIdentifierWithName<void>(
node.name,
[&]() {
// This is an object.
const auto* variablesContainer =
projectScopedContainers.GetObjectsContainersList()
.GetObjectOrGroupVariablesContainer(node.name);
if (variablesContainer)
variableAndItsParent =
WalkUntilLastParent(*variablesContainer, childVariableNames);
},
[&]() {
// This is a variable.
if (projectScopedContainers.GetVariablesContainersList().Has(
node.name)) {
variableAndItsParent = WalkUntilLastParent(
projectScopedContainers.GetVariablesContainersList().Get(
node.name),
childVariableNames);
}
},
[&]() {
// Ignore properties here.
// There is no support for "children" of properties.
},
[&]() {
// Ignore parameters here.
// There is no support for "children" of parameters.
},
[&]() {
// Ignore unrecognised identifiers here.
});
}
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
if (node.name.empty() && node.child) {
// A variable accessor should always have a name if it has a child (i.e: another accessor).
// While the parser may have generated an empty name,
// flag this so we avoid finding a wrong parent (and so, run the risk of giving
// wrong autocompletions).
bailOutBecauseEmptyVariableName = true;
}
childVariableNames.insert(childVariableNames.begin(), node.name);
if (node.parent) node.parent->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
if (variableNode != nullptr) {
// This is not possible
return;
}
// This node is not necessarily a variable node.
// It will be checked when visiting the FunctionCallNode, just after.
variableNode = &node;
// Check if the parent is a function call, in which we might be dealing
// with a legacy pre-scoped variable parameter:
if (node.parent) node.parent->Visit(*this);
if (thisIsALegacyPrescopedVariable) {
// The identifier represents a variable name, and the variables container
// containing it was identified in the FunctionCallNode.
if (!node.childIdentifierName.empty())
childVariableNames.insert(childVariableNames.begin(),
node.childIdentifierName);
childVariableNames.insert(childVariableNames.begin(),
node.identifierName);
if (legacyPrescopedVariablesContainer)
variableAndItsParent = WalkUntilLastParent(
*legacyPrescopedVariablesContainer, childVariableNames);
} else {
// Otherwise, the identifier is to be interpreted as usual:
// it can be an object (on which a variable is accessed),
// or a variable.
projectScopedContainers.MatchIdentifierWithName<void>(
node.identifierName,
[&]() {
// This is an object.
if (!node.childIdentifierName.empty())
childVariableNames.insert(childVariableNames.begin(),
node.childIdentifierName);
const auto* variablesContainer =
projectScopedContainers.GetObjectsContainersList()
.GetObjectOrGroupVariablesContainer(node.identifierName);
if (variablesContainer)
variableAndItsParent =
WalkUntilLastParent(*variablesContainer, childVariableNames);
},
[&]() {
// This is a variable.
if (!node.childIdentifierName.empty())
childVariableNames.insert(childVariableNames.begin(),
node.childIdentifierName);
if (projectScopedContainers.GetVariablesContainersList().Has(
node.identifierName)) {
variableAndItsParent = WalkUntilLastParent(
projectScopedContainers.GetVariablesContainersList().Get(
node.identifierName),
childVariableNames);
}
},
[&]() {
// Ignore properties here.
// There is no support for "children" of properties.
},
[&]() {
// Ignore parameters here.
// There is no support for "children" of properties.
},
[&]() {
// Ignore unrecognised identifiers here.
});
}
}
void OnVisitEmptyNode(EmptyNode& node) override {}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {}
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override {
// Add a child with an empty name, which will be interpreted as
// "take the first child/item of the structure/array".
childVariableNames.insert(childVariableNames.begin(), "");
if (node.parent) node.parent->Visit(*this);
}
void OnVisitFunctionCallNode(FunctionCallNode& functionCall) override {
if (variableNode == nullptr) {
return;
}
int parameterIndex = -1;
for (int i = 0; i < functionCall.parameters.size(); i++) {
if (functionCall.parameters.at(i).get() == variableNode) {
parameterIndex = i;
break;
}
}
if (parameterIndex < 0) {
return;
}
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
const gd::ParameterMetadata* parameterMetadata =
MetadataProvider::GetFunctionCallParameterMetadata(
platform, objectsContainersList, functionCall, parameterIndex);
if (parameterMetadata == nullptr) return; // Unexpected
// Support for legacy pre-scoped variables:
if (parameterMetadata->GetValueTypeMetadata().IsLegacyPreScopedVariable()) {
if (parameterMetadata->GetType() == "objectvar") {
// Legacy convention where a "objectvar"
// parameter represents a variable of the object represented by the
// previous "object" parameter. The object on which the function is
// called is returned if no previous parameters are objects.
gd::String objectName = functionCall.objectName;
for (int previousIndex = parameterIndex - 1; previousIndex >= 0;
previousIndex--) {
const gd::ParameterMetadata* previousParameterMetadata =
MetadataProvider::GetFunctionCallParameterMetadata(
platform, objectsContainersList, functionCall, previousIndex);
if (previousParameterMetadata != nullptr &&
gd::ParameterMetadata::IsObject(
previousParameterMetadata->GetType())) {
auto previousParameterNode =
functionCall.parameters[previousIndex].get();
IdentifierNode* objectNode =
dynamic_cast<IdentifierNode*>(previousParameterNode);
objectName = objectNode->identifierName;
break;
}
}
legacyPrescopedVariablesContainer =
projectScopedContainers.GetObjectsContainersList()
.GetObjectOrGroupVariablesContainer(objectName);
thisIsALegacyPrescopedVariable = true;
} else if (parameterMetadata->GetType() == "scenevar") {
legacyPrescopedVariablesContainer =
projectScopedContainers.GetVariablesContainersList()
.GetBottomMostVariablesContainer();
thisIsALegacyPrescopedVariable = true;
} else if (parameterMetadata->GetType() == "globalvar") {
legacyPrescopedVariablesContainer =
projectScopedContainers.GetVariablesContainersList()
.GetTopMostVariablesContainer();
thisIsALegacyPrescopedVariable = true;
}
} else {
thisIsALegacyPrescopedVariable = false;
legacyPrescopedVariablesContainer = nullptr;
}
}
private:
VariableAndItsParent WalkUntilLastParent(
const gd::Variable& variable,
const std::vector<gd::String>& childVariableNames,
size_t startIndex = 0) {
if (bailOutBecauseEmptyVariableName)
return {}; // Do not even attempt to find the parent if we had an issue when visiting nodes.
const gd::Variable* currentVariable = &variable;
// Walk until size - 1 as we want the last parent.
for (size_t index = startIndex; index + 1 < childVariableNames.size();
++index) {
const gd::String& childName = childVariableNames[index];
if (childName.empty()) {
if (currentVariable->GetChildrenCount() == 0) {
// The array or structure is empty, we can't walk through it - there
// is no "parent".
return {};
}
if (currentVariable->GetType() == gd::Variable::Array) {
currentVariable = &currentVariable->GetAtIndex(0);
} else {
currentVariable =
currentVariable->GetAllChildren().begin()->second.get();
}
} else {
if (!currentVariable->HasChild(childName)) {
// Non existing child - there is no "parent".
return {};
}
currentVariable = &currentVariable->GetChild(childName);
}
}
// Return the last parent of the chain of variables (so not the last variable
// but the one before it).
return {.parentVariable = currentVariable};
}
VariableAndItsParent WalkUntilLastParent(
const gd::VariablesContainer& variablesContainer,
const std::vector<gd::String>& childVariableNames) {
if (bailOutBecauseEmptyVariableName)
return {}; // Do not even attempt to find the parent if we had an issue when visiting nodes.
if (childVariableNames.empty())
return {}; // There is no "parent" to the variables container itself.
const gd::String& firstChildName = *childVariableNames.begin();
const gd::Variable* variable = variablesContainer.Has(firstChildName) ?
&variablesContainer.Get(firstChildName) : nullptr;
if (childVariableNames.size() == 1 || !variable)
return {// Only one child: the parent is the variables container itself.
.parentVariablesContainer = &variablesContainer};
return WalkUntilLastParent(*variable, childVariableNames, 1);
}
gd::ExpressionNode* variableNode;
std::vector<gd::String> childVariableNames;
bool thisIsALegacyPrescopedVariable;
bool bailOutBecauseEmptyVariableName;
const gd::VariablesContainer* legacyPrescopedVariablesContainer;
VariableAndItsParent variableAndItsParent;
const gd::Platform& platform;
const gd::ProjectScopedContainers& projectScopedContainers;
};
} // namespace gd

View File

@@ -63,14 +63,10 @@ void ArbitraryResourceWorker::ExposeBitmapFont(gd::String& bitmapFontName){
};
void ArbitraryResourceWorker::ExposeAudio(gd::String& audioName) {
for (auto resources : GetResources()) {
if (!resources) continue;
if (resources->HasResource(audioName) &&
resources->GetResource(audioName).GetKind() == "audio") {
// Nothing to do, the audio is a reference to a proper resource.
return;
}
if (resourcesManager->HasResource(audioName) &&
resourcesManager->GetResource(audioName).GetKind() == "audio") {
// Nothing to do, the audio is a reference to a proper resource.
return;
}
// For compatibility with older projects (where events were referring to files
@@ -80,14 +76,10 @@ void ArbitraryResourceWorker::ExposeAudio(gd::String& audioName) {
};
void ArbitraryResourceWorker::ExposeFont(gd::String& fontName) {
for (auto resources : GetResources()) {
if (!resources) continue;
if (resources->HasResource(fontName) &&
resources->GetResource(fontName).GetKind() == "font") {
// Nothing to do, the font is a reference to a proper resource.
return;
}
if (resourcesManager->HasResource(fontName) &&
resourcesManager->GetResource(fontName).GetKind() == "font") {
// Nothing to do, the font is a reference to a proper resource.
return;
}
// For compatibility with older projects (where events were referring to files
@@ -96,12 +88,7 @@ void ArbitraryResourceWorker::ExposeFont(gd::String& fontName) {
ExposeFile(fontName);
};
void ArbitraryResourceWorker::ExposeResources(
gd::ResourcesManager* resourcesManager) {
if (!resourcesManager) return;
resourcesManagers.push_back(resourcesManager);
void ArbitraryResourceWorker::ExposeResources() {
std::vector<gd::String> resources = resourcesManager->GetAllResourceNames();
for (std::size_t i = 0; i < resources.size(); i++) {
if (resourcesManager->GetResource(resources[i]).UseFile())
@@ -110,9 +97,6 @@ void ArbitraryResourceWorker::ExposeResources(
}
void ArbitraryResourceWorker::ExposeEmbeddeds(gd::String& resourceName) {
if (resourcesManagers.empty()) return;
gd::ResourcesManager* resourcesManager = resourcesManagers[0];
gd::Resource& resource = resourcesManager->GetResource(resourceName);
if (!resource.GetMetadata().empty()) {
@@ -176,6 +160,7 @@ void ArbitraryResourceWorker::ExposeResourceWithType(
}
if (resourceType == "tilemap") {
ExposeTilemap(resourceName);
ExposeEmbeddeds(resourceName);
return;
}
if (resourceType == "tileset") {
@@ -184,6 +169,7 @@ void ArbitraryResourceWorker::ExposeResourceWithType(
}
if (resourceType == "json") {
ExposeJson(resourceName);
ExposeEmbeddeds(resourceName);
return;
}
if (resourceType == "video") {
@@ -243,10 +229,12 @@ bool ResourceWorkerInEventsWorker::DoVisitInstruction(gd::Instruction& instructi
} else if (parameterMetadata.GetType() == "jsonResource") {
gd::String updatedParameterValue = parameterValue;
worker.ExposeJson(updatedParameterValue);
worker.ExposeEmbeddeds(updatedParameterValue);
instruction.SetParameter(parameterIndex, updatedParameterValue);
} else if (parameterMetadata.GetType() == "tilemapResource") {
gd::String updatedParameterValue = parameterValue;
worker.ExposeTilemap(updatedParameterValue);
worker.ExposeEmbeddeds(updatedParameterValue);
instruction.SetParameter(parameterIndex, updatedParameterValue);
} else if (parameterMetadata.GetType() == "tilesetResource") {
gd::String updatedParameterValue = parameterValue;

View File

@@ -42,8 +42,9 @@ namespace gd {
* \ingroup IDE
*/
class GD_CORE_API ArbitraryResourceWorker {
public:
ArbitraryResourceWorker(){};
public:
ArbitraryResourceWorker(gd::ResourcesManager &resourcesManager_)
: resourcesManager(&resourcesManager_){};
virtual ~ArbitraryResourceWorker();
/**
@@ -52,7 +53,7 @@ class GD_CORE_API ArbitraryResourceWorker {
* first to ensure that resources are known so that images, shaders & audio
* can make reference to them.
*/
void ExposeResources(gd::ResourcesManager *resourcesManager);
void ExposeResources();
/**
* \brief Expose a resource from a given type.
@@ -122,11 +123,6 @@ class GD_CORE_API ArbitraryResourceWorker {
*/
virtual void ExposeEmbeddeds(gd::String &resourceName);
protected:
const std::vector<gd::ResourcesManager *> &GetResources() {
return resourcesManagers;
};
private:
/**
* \brief Expose a resource: resources that have a file are
@@ -134,7 +130,7 @@ class GD_CORE_API ArbitraryResourceWorker {
*/
void ExposeResource(gd::Resource &resource);
std::vector<gd::ResourcesManager *> resourcesManagers;
gd::ResourcesManager * resourcesManager;
};
/**

View File

@@ -6,7 +6,7 @@
namespace gd {
void ObjectsUsingResourceCollector::DoVisitObject(gd::Object& object) {
gd::ResourceNameMatcher resourceNameMatcher(resourceName);
gd::ResourceNameMatcher resourceNameMatcher(*resourcesManager, resourceName);
object.GetConfiguration().ExposeResources(resourceNameMatcher);
if (resourceNameMatcher.AnyResourceMatches()) {

View File

@@ -4,8 +4,7 @@
* reserved. This project is released under the MIT License.
*/
#ifndef ProjectObjectsUsingResourceCollector_H
#define ProjectObjectsUsingResourceCollector_H
#pragma once
#include <vector>
@@ -21,9 +20,10 @@ namespace gd {
class GD_CORE_API ObjectsUsingResourceCollector
: public ArbitraryObjectsWorker {
public:
ObjectsUsingResourceCollector(const gd::String& resourceName_)
: resourceName(resourceName_){};
public:
ObjectsUsingResourceCollector(gd::ResourcesManager &resourcesManager_,
const gd::String &resourceName_)
: resourcesManager(&resourcesManager_), resourceName(resourceName_){};
virtual ~ObjectsUsingResourceCollector();
std::vector<gd::String>& GetObjectNames() { return objectNames; }
@@ -33,12 +33,16 @@ class GD_CORE_API ObjectsUsingResourceCollector
std::vector<gd::String> objectNames;
gd::String resourceName;
gd::ResourcesManager *resourcesManager;
};
class GD_CORE_API ResourceNameMatcher : public ArbitraryResourceWorker {
public:
ResourceNameMatcher(const gd::String& resourceName_)
: resourceName(resourceName_), matchesResourceName(false){};
public:
ResourceNameMatcher(gd::ResourcesManager &resourcesManager,
const gd::String &resourceName_)
: resourceName(resourceName_),
matchesResourceName(false), gd::ArbitraryResourceWorker(
resourcesManager){};
virtual ~ResourceNameMatcher(){};
bool AnyResourceMatches() { return matchesResourceName; }
@@ -85,5 +89,3 @@ class GD_CORE_API ResourceNameMatcher : public ArbitraryResourceWorker {
};
}; // namespace gd
#endif // ProjectObjectsUsingResourceCollector_H

View File

@@ -18,8 +18,9 @@ namespace gd {
std::vector<gd::String> ProjectResourcesAdder::GetAllUseless(
gd::Project& project, const gd::String& resourceType) {
std::vector<gd::String> unusedResources;
// Search for resources used in the project
gd::ResourcesInUseHelper resourcesInUse;
gd::ResourcesInUseHelper resourcesInUse(project.GetResourcesManager());
gd::ResourceExposer::ExposeWholeProjectResources(project, resourcesInUse);
std::set<gd::String>& usedResources = resourcesInUse.GetAll(resourceType);

View File

@@ -25,8 +25,29 @@ bool ProjectResourcesCopier::CopyAllResourcesTo(
bool updateOriginalProject,
bool preserveAbsoluteFilenames,
bool preserveDirectoryStructure) {
if (updateOriginalProject) {
gd::ProjectResourcesCopier::CopyAllResourcesTo(
originalProject, originalProject, fs, destinationDirectory,
preserveAbsoluteFilenames, preserveDirectoryStructure);
} else {
gd::Project clonedProject = originalProject;
gd::ProjectResourcesCopier::CopyAllResourcesTo(
originalProject, clonedProject, fs, destinationDirectory,
preserveAbsoluteFilenames, preserveDirectoryStructure);
}
return true;
}
bool ProjectResourcesCopier::CopyAllResourcesTo(
gd::Project& originalProject,
gd::Project& clonedProject,
AbstractFileSystem& fs,
gd::String destinationDirectory,
bool preserveAbsoluteFilenames,
bool preserveDirectoryStructure) {
// Check if there are some resources with absolute filenames
gd::ResourcesAbsolutePathChecker absolutePathChecker(fs);
gd::ResourcesAbsolutePathChecker absolutePathChecker(originalProject.GetResourcesManager(), fs);
gd::ResourceExposer::ExposeWholeProjectResources(originalProject, absolutePathChecker);
auto projectDirectory = fs.DirNameFrom(originalProject.GetProjectFile());
@@ -34,19 +55,14 @@ bool ProjectResourcesCopier::CopyAllResourcesTo(
<< destinationDirectory << "..." << std::endl;
// Get the resources to be copied
gd::ResourcesMergingHelper resourcesMergingHelper(fs);
gd::ResourcesMergingHelper resourcesMergingHelper(
clonedProject.GetResourcesManager(), fs);
resourcesMergingHelper.SetBaseDirectory(projectDirectory);
resourcesMergingHelper.PreserveDirectoriesStructure(
preserveDirectoryStructure);
resourcesMergingHelper.PreserveAbsoluteFilenames(
preserveAbsoluteFilenames);
if (updateOriginalProject) {
gd::ResourceExposer::ExposeWholeProjectResources(originalProject, resourcesMergingHelper);
} else {
std::shared_ptr<gd::Project> project(new gd::Project(originalProject));
gd::ResourceExposer::ExposeWholeProjectResources(*project, resourcesMergingHelper);
}
resourcesMergingHelper.PreserveAbsoluteFilenames(preserveAbsoluteFilenames);
gd::ResourceExposer::ExposeWholeProjectResources(clonedProject,
resourcesMergingHelper);
// Copy resources
map<gd::String, gd::String>& resourcesNewFilename =

View File

@@ -47,6 +47,13 @@ class GD_CORE_API ProjectResourcesCopier {
bool updateOriginalProject,
bool preserveAbsoluteFilenames = true,
bool preserveDirectoryStructure = true);
private:
static bool CopyAllResourcesTo(gd::Project& originalProject,
gd::Project& clonedProject,
gd::AbstractFileSystem& fs,
gd::String destinationDirectory,
bool preserveAbsoluteFilenames = true,
bool preserveDirectoryStructure = true);
};
} // namespace gd

View File

@@ -3,8 +3,7 @@
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef RESOURCESABSOLUTEPATHCHECKER_H
#define RESOURCESABSOLUTEPATHCHECKER_H
#pragma once
#include "GDCore/IDE/AbstractFileSystem.h"
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
@@ -22,10 +21,10 @@ namespace gd {
*/
class GD_CORE_API ResourcesAbsolutePathChecker
: public ArbitraryResourceWorker {
public:
ResourcesAbsolutePathChecker(AbstractFileSystem& fileSystem)
: ArbitraryResourceWorker(),
hasAbsoluteFilenames(false),
public:
ResourcesAbsolutePathChecker(gd::ResourcesManager &resourcesManager,
AbstractFileSystem &fileSystem)
: ArbitraryResourceWorker(resourcesManager), hasAbsoluteFilenames(false),
fs(fileSystem){};
virtual ~ResourcesAbsolutePathChecker(){};
@@ -47,5 +46,3 @@ class GD_CORE_API ResourcesAbsolutePathChecker
};
} // namespace gd
#endif // RESOURCESABSOLUTEPATHCHECKER_H

View File

@@ -33,8 +33,9 @@ std::set<gd::String> & usedImages = resourcesInUse.GetAllImages();
* \ingroup IDE
*/
class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
public:
ResourcesInUseHelper() : gd::ArbitraryResourceWorker(){};
public:
ResourcesInUseHelper(gd::ResourcesManager &resourcesManager)
: gd::ArbitraryResourceWorker(resourcesManager){};
virtual ~ResourcesInUseHelper(){};
std::set<gd::String>& GetAllImages() { return GetAll("image"); };

View File

@@ -28,11 +28,11 @@ namespace gd {
* \ingroup IDE
*/
class GD_CORE_API ResourcesMergingHelper : public ArbitraryResourceWorker {
public:
ResourcesMergingHelper(gd::AbstractFileSystem& fileSystem)
: ArbitraryResourceWorker(),
preserveDirectoriesStructure(false),
preserveAbsoluteFilenames(false),
public:
ResourcesMergingHelper(gd::ResourcesManager &resourcesManager,
gd::AbstractFileSystem &fileSystem)
: ArbitraryResourceWorker(resourcesManager),
preserveDirectoriesStructure(false), preserveAbsoluteFilenames(false),
fs(fileSystem){};
virtual ~ResourcesMergingHelper(){};

View File

@@ -22,13 +22,16 @@ namespace gd {
*/
class ResourcesRenamer : public gd::ArbitraryResourceWorker {
public:
/**
* @brief Constructor taking the map from old name to new name.
* @param oldToNewNames_ A map associating to a resource name the new name to
* use.
*/
ResourcesRenamer(const std::map<gd::String, gd::String>& oldToNewNames_)
: gd::ArbitraryResourceWorker(), oldToNewNames(oldToNewNames_){};
/**
* @brief Constructor taking the map from old name to new name.
* @param oldToNewNames_ A map associating to a resource name the new name to
* use.
*/
ResourcesRenamer(gd::ResourcesManager &resourcesManager,
const std::map<gd::String, gd::String> &oldToNewNames_)
: gd::ArbitraryResourceWorker(resourcesManager),
oldToNewNames(oldToNewNames_){};
virtual ~ResourcesRenamer(){};
virtual void ExposeFile(gd::String& resourceFileName) override{

View File

@@ -13,14 +13,16 @@
namespace gd {
std::set<gd::String> SceneResourcesFinder::FindProjectResources(gd::Project &project) {
gd::SceneResourcesFinder resourceWorker;
gd::SceneResourcesFinder resourceWorker(project.GetResourcesManager());
gd::ResourceExposer::ExposeProjectResources(project, resourceWorker);
return resourceWorker.resourceNames;
}
std::set<gd::String> SceneResourcesFinder::FindSceneResources(gd::Project &project,
gd::Layout &layout) {
gd::SceneResourcesFinder resourceWorker;
gd::SceneResourcesFinder resourceWorker(project.GetResourcesManager());
gd::ResourceExposer::ExposeLayoutResources(project, layout, resourceWorker);
return resourceWorker.resourceNames;
}

View File

@@ -44,7 +44,8 @@ public:
virtual ~SceneResourcesFinder(){};
private:
SceneResourcesFinder() : gd::ArbitraryResourceWorker(){};
SceneResourcesFinder(gd::ResourcesManager &resourcesManager)
: gd::ArbitraryResourceWorker(resourcesManager){};
void AddUsedResource(gd::String &resourceName);

View File

@@ -128,11 +128,7 @@ void PropertyFunctionGenerator::GenerateGetterAndSetter(
} else {
gd::Instruction action;
action.SetType("SetReturn" + numberOrString);
gd::String receiver = isBehavior ? "Object.Behavior::" : "Object.";
gd::String propertyPrefix =
(isSharedProperties ? "SharedProperty" : "Property");
action.AddParameter(receiver + propertyPrefix + property.GetName() +
"()");
action.AddParameter(property.GetName());
event.GetActions().Insert(action, 0);
}
}
@@ -233,15 +229,13 @@ void PropertyFunctionGenerator::GenerateGetterAndSetter(
gd::Instruction action;
action.SetType(setterType);
action.AddParameter("Object");
gd::String parameterGetterCall =
"GetArgumentAs" + numberOrString + "(\"Value\")";
if (isBehavior) {
action.AddParameter("Behavior");
action.AddParameter("=");
action.AddParameter(parameterGetterCall);
action.AddParameter("Value");
} else {
action.AddParameter("=");
action.AddParameter(parameterGetterCall);
action.AddParameter("Value");
}
event.GetActions().Insert(action, 0);
}

View File

@@ -33,10 +33,8 @@ void ResourceExposer::ExposeWholeProjectResources(gd::Project& project, gd::Arbi
// traverse the whole project (this time for events) and ExposeProjectEffects
// (this time for effects).
gd::ResourcesManager* resourcesManager = &(project.GetResourcesManager());
// Expose any project resources as files.
worker.ExposeResources(resourcesManager);
worker.ExposeResources();
project.GetPlatformSpecificAssets().ExposeResources(worker);

View File

@@ -17,7 +17,7 @@ LoadingScreen::LoadingScreen()
backgroundFadeInDuration(0.2),
minDuration(1.5),
logoAndProgressFadeInDuration(0.2),
logoAndProgressLogoFadeInDelay(0.2),
logoAndProgressLogoFadeInDelay(0),
showProgressBar(true),
progressBarMinWidth(40),
progressBarMaxWidth(200),

View File

@@ -113,7 +113,7 @@ bool ObjectsContainersList::HasObjectWithVariableNamed(
return false;
}
bool ObjectsContainersList::HasVariablesContainer(
bool ObjectsContainersList::HasObjectOrGroupVariablesContainer(
const gd::String& objectOrGroupName,
const gd::VariablesContainer& variablesContainer) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
@@ -123,8 +123,31 @@ bool ObjectsContainersList::HasVariablesContainer(
&(*it)->GetObject(objectOrGroupName).GetVariables();
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// Could be adapted if objects groups have variables in the future.
// This would allow handling the renaming of variables of an object group.
// For groups, we consider that the first object of the group defines the
// variables available for this group. Note that this is slightly
// different than other methods where a group is considered as the
// "intersection" of all of its objects.
const auto& objectNames =
(*it)->GetObjectGroups().Get(objectOrGroupName).GetAllObjectsNames();
if (!objectNames.empty()) {
return HasObjectVariablesContainer(objectNames[0], variablesContainer);
}
return false;
}
}
return false;
}
bool ObjectsContainersList::HasObjectVariablesContainer(
const gd::String& objectName,
const gd::VariablesContainer& variablesContainer) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectName)) {
return &variablesContainer ==
&(*it)->GetObject(objectName).GetVariables();
}
}
@@ -140,8 +163,30 @@ ObjectsContainersList::GetObjectOrGroupVariablesContainer(
return &(*it)->GetObject(objectOrGroupName).GetVariables();
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// Could be adapted if objects groups have variables in the future.
// This would allow handling the renaming of variables of an object group.
// For groups, we consider that the first object of the group defines the
// variables available for this group. Note that this is slightly
// different than other methods where a group is considered as the
// "intersection" of all of its objects.
const auto& objectNames =
(*it)->GetObjectGroups().Get(objectOrGroupName).GetAllObjectsNames();
if (!objectNames.empty()) {
return GetObjectVariablesContainer(objectNames[0]);
}
return nullptr;
}
}
return nullptr;
}
const gd::VariablesContainer*
ObjectsContainersList::GetObjectVariablesContainer(
const gd::String& objectName) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectName)) {
return &(*it)->GetObject(objectName).GetVariables();
}
}
@@ -150,7 +195,6 @@ ObjectsContainersList::GetObjectOrGroupVariablesContainer(
gd::Variable::Type ObjectsContainersList::GetTypeOfObjectOrGroupVariable(
const gd::String& objectOrGroupName, const gd::String& variableName) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
@@ -182,13 +226,12 @@ gd::Variable::Type ObjectsContainersList::GetTypeOfObjectOrGroupVariable(
return Variable::Type::Number;
}
gd::Variable::Type ObjectsContainersList::GetTypeOfObjectVariable(const gd::String& objectName, const gd::String& variableName) const {
gd::Variable::Type ObjectsContainersList::GetTypeOfObjectVariable(
const gd::String& objectName, const gd::String& variableName) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectName)) {
const auto& variables =
(*it)->GetObject(objectName).GetVariables();
const auto& variables = (*it)->GetObject(objectName).GetVariables();
return variables.Get(variableName).GetType();
}

View File

@@ -61,7 +61,7 @@ class GD_CORE_API ObjectsContainersList {
* \brief Check if the specified object or group has the specified variables
* container.
*/
bool HasVariablesContainer(
bool HasObjectOrGroupVariablesContainer(
const gd::String& objectOrGroupName,
const gd::VariablesContainer& variablesContainer) const;
@@ -165,6 +165,13 @@ class GD_CORE_API ObjectsContainersList {
bool HasObjectWithVariableNamed(const gd::String& objectName,
const gd::String& variableName) const;
bool HasObjectVariablesContainer(
const gd::String& objectName,
const gd::VariablesContainer& variablesContainer) const;
const gd::VariablesContainer* GetObjectVariablesContainer(
const gd::String& objectName) const;
gd::Variable::Type GetTypeOfObjectVariable(const gd::String& objectName, const gd::String& variableName) const;
void ForEachObjectVariableMatchingSearch(

View File

@@ -403,6 +403,11 @@ String String::LowerCase() const
return lowerCasedStr;
}
String String::CapitalizeFirstLetter() const
{
return size() < 1 ? *this : substr(0, 1).UpperCase() + substr(1);
}
String String::FindAndReplace(String search, String replacement, bool all) const
{
gd::String result(*this);

View File

@@ -522,6 +522,11 @@ public:
*/
String LowerCase() const;
/**
* \brief Returns the string with the first letter in upper case.
*/
String CapitalizeFirstLetter() const;
/**
* \brief Searches a string for a specified substring and returns a new string where all occurrences of this substring is replaced.
* \param search The string that will be replaced by the new string.

View File

@@ -29,7 +29,9 @@
#include "GDCore/Project/ExternalEvents.h"
class ArbitraryResourceWorkerTest : public gd::ArbitraryResourceWorker {
public:
public:
ArbitraryResourceWorkerTest(gd::ResourcesManager &resourcesManager)
: ArbitraryResourceWorker(resourcesManager){};
virtual void ExposeFile(gd::String& file) { files.push_back(file); };
virtual void ExposeImage(gd::String& imageName) {
images.push_back(imageName);
@@ -59,7 +61,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res3", "path/to/file3.png", "image");
project.GetResourcesManager().AddResource(
"res4", "path/to/file4.png", "audio");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
gd::ResourceExposer::ExposeWholeProjectResources(project, worker);
REQUIRE(worker.files.size() == 4);
@@ -81,7 +83,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res3", "path/to/file3.png", "image");
project.GetResourcesManager().AddResource(
"res4", "path/to/file4.png", "audio");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
gd::SpriteObject spriteConfiguration;
gd::Animation anim;
@@ -126,7 +128,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res3", "path/to/file3.png", "image");
project.GetResourcesManager().AddResource(
"res4", "path/to/file4.png", "audio");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& layout = project.InsertNewLayout("Scene", 0);
@@ -161,7 +163,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res3", "path/to/file3.fnt", "bitmapFont");
project.GetResourcesManager().AddResource(
"res4", "path/to/file4.png", "audio");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& layout = project.InsertNewLayout("Scene", 0);
@@ -196,7 +198,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res3", "path/to/file3.fnt", "bitmapFont");
project.GetResourcesManager().AddResource(
"res4", "path/to/file4.png", "audio");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& externalEvents = project.InsertNewExternalEvents("MyExternalEvents", 0);
@@ -232,7 +234,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res2", "path/to/file2.png", "image");
project.GetResourcesManager().AddResource(
"res3", "path/to/file3.png", "image");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& extension = project.InsertNewEventsFunctionsExtension("MyEventExtension", 0);
auto& function = extension.InsertNewEventsFunction("MyFreeFunction", 0);
@@ -269,7 +271,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res2", "path/to/file2.png", "image");
project.GetResourcesManager().AddResource(
"res3", "path/to/file3.png", "image");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& extension = project.InsertNewEventsFunctionsExtension("MyEventExtension", 0);
auto& behavior = extension.GetEventsBasedBehaviors().InsertNew("MyBehavior", 0);
@@ -307,7 +309,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res2", "path/to/file2.png", "image");
project.GetResourcesManager().AddResource(
"res3", "path/to/file3.png", "image");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& extension = project.InsertNewEventsFunctionsExtension("MyEventExtension", 0);
auto& object = extension.GetEventsBasedObjects().InsertNew("MyObject", 0);
@@ -345,7 +347,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res2", "path/to/file2.png", "image");
project.GetResourcesManager().AddResource(
"res3", "path/to/file3.png", "image");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& layout = project.InsertNewLayout("Scene", 0);
layout.InsertNewLayer("MyLayer", 0);
@@ -371,7 +373,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res2", "path/to/file2.png", "image");
project.GetResourcesManager().AddResource(
"res3", "path/to/file3.png", "image");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& layout = project.InsertNewLayout("Scene", 0);
layout.InsertNewLayer("MyLayer", 0);
@@ -402,7 +404,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res3", "path/to/file3.png", "image");
project.GetResourcesManager().AddResource(
"res4", "path/to/file4.png", "audio");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& layout = project.InsertNewLayout("Scene", 0);
@@ -425,7 +427,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res3", "path/to/file3.png", "image");
project.GetResourcesManager().AddResource(
"res4", "path/to/file4.png", "audio");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& layout = project.InsertNewLayout("Scene", 0);
@@ -459,7 +461,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res3", "path/to/file3.fnt", "bitmapFont");
project.GetResourcesManager().AddResource(
"res4", "path/to/file4.png", "audio");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& layout = project.InsertNewLayout("Scene", 0);
@@ -494,7 +496,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res3", "path/to/file3.fnt", "bitmapFont");
project.GetResourcesManager().AddResource(
"res4", "path/to/file4.png", "audio");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& layout = project.InsertNewLayout("MyScene", 0);
auto& externalEvents = project.InsertNewExternalEvents("MyExternalEvents", 0);
@@ -530,7 +532,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res3", "path/to/file3.fnt", "bitmapFont");
project.GetResourcesManager().AddResource(
"res4", "path/to/file4.png", "audio");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& layout = project.InsertNewLayout("MyScene", 0);
auto& externalEvents = project.InsertNewExternalEvents("MyExternalEvents", 0);
@@ -572,7 +574,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res3", "path/to/file3.fnt", "bitmapFont");
project.GetResourcesManager().AddResource(
"res4", "path/to/file4.png", "audio");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& layout = project.InsertNewLayout("Scene", 0);
auto& externalEventsA = project.InsertNewExternalEvents("MyExternalEventsA", 0);
@@ -619,7 +621,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res3", "path/to/file3.fnt", "bitmapFont");
project.GetResourcesManager().AddResource(
"res4", "path/to/file4.png", "audio");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& layout = project.InsertNewLayout("MyScene", 0);
auto& otherLayout = project.InsertNewLayout("MyOtherScene", 0);
@@ -654,7 +656,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res3", "path/to/file3.fnt", "bitmapFont");
project.GetResourcesManager().AddResource(
"res4", "path/to/file4.png", "audio");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& layout = project.InsertNewLayout("MyScene", 0);
auto& otherLayout = project.InsertNewLayout("MyOtherScene", 0);
@@ -696,7 +698,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res3", "path/to/file3.fnt", "bitmapFont");
project.GetResourcesManager().AddResource(
"res4", "path/to/file4.png", "audio");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& layout = project.InsertNewLayout("MyScene", 0);
@@ -740,7 +742,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res2", "path/to/file2.png", "image");
project.GetResourcesManager().AddResource(
"res3", "path/to/file3.png", "image");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& extension = project.InsertNewEventsFunctionsExtension("MyEventExtension", 0);
auto& function = extension.InsertNewEventsFunction("MyFreeFunction", 0);
@@ -780,7 +782,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res2", "path/to/file2.png", "image");
project.GetResourcesManager().AddResource(
"res3", "path/to/file3.png", "image");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& extension = project.InsertNewEventsFunctionsExtension("MyEventExtension", 0);
auto& behavior = extension.GetEventsBasedBehaviors().InsertNew("MyBehavior", 0);
@@ -821,7 +823,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res2", "path/to/file2.png", "image");
project.GetResourcesManager().AddResource(
"res3", "path/to/file3.png", "image");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& extension = project.InsertNewEventsFunctionsExtension("MyEventExtension", 0);
auto& object = extension.GetEventsBasedObjects().InsertNew("MyObject", 0);
@@ -862,7 +864,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res2", "path/to/file2.png", "image");
project.GetResourcesManager().AddResource(
"res3", "path/to/file3.png", "image");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& layout = project.InsertNewLayout("Scene", 0);
layout.InsertNewLayer("MyLayer", 0);
@@ -887,7 +889,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
"res2", "path/to/file2.png", "image");
project.GetResourcesManager().AddResource(
"res3", "path/to/file3.png", "image");
ArbitraryResourceWorkerTest worker;
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& layout = project.InsertNewLayout("Scene", 0);
layout.InsertNewLayer("MyLayer", 0);

View File

@@ -1252,6 +1252,49 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
"MySpriteObject.getObjectStringWith2ObjectParam(fakeObjectListOf_"
"Object1, fakeObjectListOf_Object2) ?? \"\"");
}
SECTION("Edge cases (variables with object name in objectvar parameter)") {
SECTION("Simple case") {
gd::String output = gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator,
context,
"objectvar", // We suppose we generate an "objectvar" parameter.
"MyOtherSpriteObject", // This "variable name" is the same as an object name (but this is valid).
"MySpriteObject" // The object owning the variable: MySpriteObject.
);
// This seems "obvious", but we had cases where MyOtherSpriteObject could have been interpreted as an object
// when the code generation is not properly recognizing "objectvar".
REQUIRE(output == "getVariableForObject(MySpriteObject, MyOtherSpriteObject)");
}
SECTION("With child variable") {
gd::String output = gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator,
context,
"objectvar", // We suppose we generate an "objectvar" parameter.
"MyOtherSpriteObject.Child", // This "variable name" is the same as an object name (but this is valid).
"MySpriteObject" // The object owning the variable: MySpriteObject.
);
// This seems "obvious", but we had cases where MyOtherSpriteObject could have been interpreted as an object
// when the code generation is not properly recognizing "objectvar".
REQUIRE(output == "getVariableForObject(MySpriteObject, MyOtherSpriteObject).getChild(\"Child\")");
}
SECTION("With child and grandchild variable") {
gd::String output = gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator,
context,
"objectvar", // We suppose we generate an "objectvar" parameter.
"MyOtherSpriteObject.Child.Grandchild", // This "variable name" is the same as an object name (but this is valid).
"MySpriteObject" // The object owning the variable: MySpriteObject.
);
// This seems "obvious", but we had cases where MyOtherSpriteObject could have been interpreted as an object
// when the code generation is not properly recognizing "objectvar".
REQUIRE(output == "getVariableForObject(MySpriteObject, MyOtherSpriteObject).getChild(\"Child\").getChild(\"Grandchild\")");
}
}
SECTION("Mixed test (1)") {
{
auto node = parser.ParseExpression("-+-MyExtension::MouseX(,)");

View File

@@ -11,6 +11,7 @@
#include "GDCore/IDE/Events/ExpressionValidator.h"
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
#include "GDCore/IDE/Events/ExpressionVariableOwnerFinder.h"
#include "GDCore/IDE/Events/ExpressionVariableParentFinder.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
@@ -1606,6 +1607,21 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
}
}
SECTION("Invalid object variables (empty variable name, extra dot)") {
{
auto node =
parser.ParseExpression("MySpriteObjects..");
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 2);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"A name should be entered after the dot.");
REQUIRE(validator.GetFatalErrors()[1]->GetMessage() ==
"A name should be entered after the dot.");
}
}
SECTION("Invalid object variables (object group, partially existing variable)") {
{
auto node =
@@ -1690,6 +1706,42 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
"An object variable or expression should be entered.");
}
}
SECTION("Invalid object variables (extra dot)") {
{
auto node =
parser.ParseExpression("MySpriteObject.MyVariable.");
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"A name should be entered after the dot.");
}
}
SECTION("Invalid object variables (extra dot after brackets)") {
{
auto node =
parser.ParseExpression("MySpriteObject.MyVariable[\"MyChild\"].");
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"A name should be entered after the dot.");
}
}
SECTION("Invalid object variables (extra dot before brackets)") {
{
auto node =
parser.ParseExpression("MySpriteObject.MyVariable.[\"MyChild\"]");
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"A name should be entered after the dot.");
}
}
SECTION("Valid property") {
gd::PropertiesContainer propertiesContainer(gd::EventsFunctionsContainer::Extension);
@@ -2156,6 +2208,14 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
dynamic_cast<gd::IdentifierNode &>(*node);
REQUIRE(identifierNode.identifierName == "MyObject");
REQUIRE(identifierNode.childIdentifierName == "");
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 2);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"A name should be entered after the dot.");
REQUIRE(validator.GetFatalErrors()[1]->GetMessage() ==
"An object variable or expression should be entered.");
}
SECTION("Unfinished object function name of type string with parentheses") {
@@ -2167,6 +2227,14 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(objectFunctionCall.objectName == "MyObject");
REQUIRE(objectFunctionCall.functionName == "");
REQUIRE(type == "string");
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 2);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"A name should be entered after the dot.");
REQUIRE(validator.GetFatalErrors()[1]->GetMessage() ==
"Enter the name of the function to call.");
}
SECTION("Unfinished object function name of type number with parentheses") {
@@ -2193,6 +2261,19 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(type == "number|string");
}
SECTION("Unfinished object function/variable name with multiple dots") {
auto node = parser.ParseExpression("MyObject..");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 2);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"A name should be entered after the dot.");
REQUIRE(validator.GetFatalErrors()[1]->GetMessage() ==
"A name should be entered after the dot.");
}
SECTION("Unfinished object behavior name") {
auto node = parser.ParseExpression("MyObject.MyBehavior::");
REQUIRE(node != nullptr);
@@ -2201,6 +2282,12 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(objectFunctionName.objectName == "MyObject");
REQUIRE(objectFunctionName.objectFunctionOrBehaviorName == "MyBehavior");
REQUIRE(objectFunctionName.behaviorFunctionName == "");
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"An opening parenthesis was expected here to call a function.");
}
SECTION("Unfinished object behavior name of type string with parentheses") {
@@ -2213,6 +2300,12 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(objectFunctionName.behaviorName == "MyBehavior");
REQUIRE(objectFunctionName.functionName == "");
REQUIRE(type == "string");
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"Enter the name of the function to call.");
}
SECTION("Unfinished object behavior name of type number with parentheses") {
@@ -2498,10 +2591,8 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
// TODO: The error message could be improved
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"Cannot find an expression with this name: \nDouble "
"check that you've not made any typo in the name.");
"Enter the name of the function to call.");
REQUIRE(validator.GetFatalErrors()[0]->GetStartPosition() == 0);
REQUIRE(validator.GetFatalErrors()[0]->GetEndPosition() == 25);
}
@@ -2809,8 +2900,8 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
// as ExpressionVariableOwnerFinder depends on this parameter type
// information.
auto node = parser.ParseExpression(
"MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(MyObject1, "
"MyVar1, MyObject2, MyVar2)");
"MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(MySpriteObject, "
"MyVariable, MySpriteObject2, MyVariable2)");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
auto &identifierObject1Node =
@@ -2822,17 +2913,25 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
auto &variable2Node =
dynamic_cast<gd::IdentifierNode &>(*functionNode.parameters[3]);
REQUIRE(identifierObject1Node.identifierName == "MyObject1");
REQUIRE(identifierObject2Node.identifierName == "MyObject2");
REQUIRE(variable1Node.identifierName == "MyVar1");
REQUIRE(variable2Node.identifierName == "MyVar2");
REQUIRE(identifierObject1Node.identifierName == "MySpriteObject");
REQUIRE(identifierObject2Node.identifierName == "MySpriteObject2");
REQUIRE(variable1Node.identifierName == "MyVariable");
REQUIRE(variable2Node.identifierName == "MyVariable2");
auto variable1ObjectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
platform, objectsContainersList, "", variable1Node);
REQUIRE(variable1ObjectName == "MyObject1");
REQUIRE(variable1ObjectName == "MySpriteObject");
auto variable2ObjectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
platform, objectsContainersList, "", variable2Node);
REQUIRE(variable2ObjectName == "MyObject2");
REQUIRE(variable2ObjectName == "MySpriteObject2");
// Also check the ability to find the last parent of the variables:
auto lastParentOfVariable1Node = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
platform, projectScopedContainers, variable1Node);
REQUIRE(lastParentOfVariable1Node.parentVariablesContainer == &mySpriteObject.GetVariables());
auto lastParentOfVariable2Node = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
platform, projectScopedContainers, variable2Node);
REQUIRE(lastParentOfVariable2Node.parentVariablesContainer == &mySpriteObject2.GetVariables());
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
node->Visit(validator);
@@ -2843,8 +2942,8 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
SECTION("Valid function call with 2 object variable from the same object") {
{
auto node = parser.ParseExpression(
"MyExtension::GetStringWith1ObjectParamAnd2ObjectVarParam(MyObject1, "
"MyVar1, MyVar2)");
"MyExtension::GetStringWith1ObjectParamAnd2ObjectVarParam(MySpriteObject, "
"MyVariable, MyVariable2)");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
auto &identifierObject1Node =
@@ -2854,16 +2953,24 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
auto &variable2Node =
dynamic_cast<gd::IdentifierNode &>(*functionNode.parameters[2]);
REQUIRE(identifierObject1Node.identifierName == "MyObject1");
REQUIRE(variable1Node.identifierName == "MyVar1");
REQUIRE(variable2Node.identifierName == "MyVar2");
REQUIRE(identifierObject1Node.identifierName == "MySpriteObject");
REQUIRE(variable1Node.identifierName == "MyVariable");
REQUIRE(variable2Node.identifierName == "MyVariable2");
auto variable1ObjectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
platform, objectsContainersList, "", variable1Node);
REQUIRE(variable1ObjectName == "MyObject1");
REQUIRE(variable1ObjectName == "MySpriteObject");
auto variable2ObjectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
platform, objectsContainersList, "", variable2Node);
REQUIRE(variable2ObjectName == "MyObject1");
REQUIRE(variable2ObjectName == "MySpriteObject");
// Also check the ability to find the last parent of the variables:
auto lastParentOfVariable1Node = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
platform, projectScopedContainers, variable1Node);
REQUIRE(lastParentOfVariable1Node.parentVariablesContainer == &mySpriteObject.GetVariables());
auto lastParentOfVariable2Node = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
platform, projectScopedContainers, variable2Node);
REQUIRE(lastParentOfVariable2Node.parentVariablesContainer == &mySpriteObject.GetVariables());
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
node->Visit(validator);
@@ -2874,18 +2981,23 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
SECTION("Valid object function call with 1 object variable from the object of the function") {
{
auto node = parser.ParseExpression(
"MySpriteObject.GetObjectVariableAsNumber(MyVar1)");
"MySpriteObject.GetObjectVariableAsNumber(MyVariable)");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
auto &variable1Node =
dynamic_cast<gd::IdentifierNode &>(*functionNode.parameters[0]);
REQUIRE(variable1Node.identifierName == "MyVar1");
REQUIRE(variable1Node.identifierName == "MyVariable");
auto variable1ObjectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
platform, objectsContainersList, "MySpriteObject", variable1Node);
REQUIRE(variable1ObjectName == "MySpriteObject");
// Also check the ability to find the last parent of the variable:
auto lastParentOfVariable1Node = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
platform, projectScopedContainers, variable1Node);
REQUIRE(lastParentOfVariable1Node.parentVariablesContainer == &mySpriteObject.GetVariables());
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
@@ -2895,19 +3007,24 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
SECTION("Valid object function call with 1 object variable from the object of the function with a child") {
{
auto node = parser.ParseExpression(
"MySpriteObject.GetObjectVariableAsNumber(MyVar1.MyChild)");
"MySpriteObject.GetObjectVariableAsNumber(MyVariable.MyChild)");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
auto &variable1Node =
dynamic_cast<gd::IdentifierNode &>(*functionNode.parameters[0]);
REQUIRE(variable1Node.identifierName == "MyVar1");
REQUIRE(variable1Node.identifierName == "MyVariable");
REQUIRE(variable1Node.childIdentifierName == "MyChild");
auto variable1ObjectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
platform, objectsContainersList, "MySpriteObject", variable1Node);
REQUIRE(variable1ObjectName == "MySpriteObject");
// Also check the ability to find the last parent of the variable:
auto lastParentOfVariable1Node = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
platform, projectScopedContainers, variable1Node);
REQUIRE(lastParentOfVariable1Node.parentVariable == &mySpriteObject.GetVariables().Get("MyVariable"));
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);

View File

@@ -91,8 +91,7 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
auto &getterAction = getterEvent.GetActions().at(0);
REQUIRE(getterAction.GetType() == "SetReturnNumber");
REQUIRE(getterAction.GetParametersCount() == 1);
REQUIRE(getterAction.GetParameter(0).GetPlainString() ==
"Object.Behavior::PropertyMovementAngle()");
REQUIRE(getterAction.GetParameter(0).GetPlainString() == "MovementAngle");
}
{
auto &setter =
@@ -124,8 +123,7 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
REQUIRE(setterAction.GetParameter(0).GetPlainString() == "Object");
REQUIRE(setterAction.GetParameter(1).GetPlainString() == "Behavior");
REQUIRE(setterAction.GetParameter(2).GetPlainString() == "=");
REQUIRE(setterAction.GetParameter(3).GetPlainString() ==
"GetArgumentAsNumber(\"Value\")");
REQUIRE(setterAction.GetParameter(3).GetPlainString() == "Value");
}
}
@@ -343,8 +341,7 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
auto &getterAction = getterEvent.GetActions().at(0);
REQUIRE(getterAction.GetType() == "SetReturnNumber");
REQUIRE(getterAction.GetParametersCount() == 1);
REQUIRE(getterAction.GetParameter(0).GetPlainString() ==
"Object.PropertyMovementAngle()");
REQUIRE(getterAction.GetParameter(0).GetPlainString() == "MovementAngle");
}
{
auto &setter =
@@ -375,8 +372,7 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
REQUIRE(setterAction.GetParametersCount() == 3);
REQUIRE(setterAction.GetParameter(0).GetPlainString() == "Object");
REQUIRE(setterAction.GetParameter(1).GetPlainString() == "=");
REQUIRE(setterAction.GetParameter(2).GetPlainString() ==
"GetArgumentAsNumber(\"Value\")");
REQUIRE(setterAction.GetParameter(2).GetPlainString() == "Value");
}
}
@@ -578,8 +574,7 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
auto &getterAction = getterEvent.GetActions().at(0);
REQUIRE(getterAction.GetType() == "SetReturnNumber");
REQUIRE(getterAction.GetParametersCount() == 1);
REQUIRE(getterAction.GetParameter(0).GetPlainString() ==
"Object.Behavior::SharedPropertyMovementAngle()");
REQUIRE(getterAction.GetParameter(0).GetPlainString() == "MovementAngle");
}
{
auto &setter =

View File

@@ -65,11 +65,11 @@ class MockFileSystem : public gd::AbstractFileSystem {
TEST_CASE("ResourcesMergingHelper", "[common]") {
SECTION("Basics") {
gd::Project project;
MockFileSystem fs;
gd::ResourcesMergingHelper resourcesMerger(fs);
gd::ResourcesMergingHelper resourcesMerger(project.GetResourcesManager(), fs);
resourcesMerger.SetBaseDirectory("/game/base/folder/");
gd::Project project;
project.GetResourcesManager().AddResource("Image1", "/image1.png", "image");
project.GetResourcesManager().AddResource("Image2", "image2.png", "image");
project.GetResourcesManager().AddResource("Audio1", "audio1.png", "audio");
@@ -90,12 +90,12 @@ TEST_CASE("ResourcesMergingHelper", "[common]") {
"FileNameFrom(MakeAbsolute(subfolder/image3.png))");
}
SECTION("Can preserve directories structure") {
gd::Project project;
MockFileSystem fs;
gd::ResourcesMergingHelper resourcesMerger(fs);
gd::ResourcesMergingHelper resourcesMerger(project.GetResourcesManager(), fs);
resourcesMerger.SetBaseDirectory("/game/base/folder/");
resourcesMerger.PreserveDirectoriesStructure(true);
gd::Project project;
project.GetResourcesManager().AddResource("Image1", "/image1.png", "image");
project.GetResourcesManager().AddResource("Image2", "image2.png", "image");
project.GetResourcesManager().AddResource("Audio1", "audio1.png", "audio");

View File

@@ -15,11 +15,10 @@
TEST_CASE("ResourcesRenamer", "[common]") {
SECTION("It renames resources that are exposed") {
gd::Project project;
std::map<gd::String, gd::String> renamings = {
{"Resource1", "RenamedResource1"}};
gd::ResourcesRenamer resourcesRenamer(renamings);
gd::Project project;
gd::ResourcesRenamer resourcesRenamer(project.GetResourcesManager(), renamings);
// Add "classic", plain resources.
gd::ImageResource resource1;
@@ -45,11 +44,10 @@ TEST_CASE("ResourcesRenamer", "[common]") {
}
SECTION("It renames embedded resources") {
gd::Project project;
std::map<gd::String, gd::String> renamings = {
{"Resource1", "RenamedResource1"}};
gd::ResourcesRenamer resourcesRenamer(renamings);
gd::Project project;
gd::ResourcesRenamer resourcesRenamer(project.GetResourcesManager(), renamings);
// Add "classic", plain resources.
gd::ImageResource resource1;

View File

@@ -201,39 +201,34 @@ TEST_CASE("WholeProjectRefactorer::ApplyRefactoringForVariablesContainer",
"MyRenamedGlobalStructureVariable");
project.GetVariables().Rename("SharedVariableName",
"RenamedGlobalVariableFromASharedName");
auto changeset = gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project,
originalSerializedProjectVariables,
project.GetVariables());
auto changeset =
gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project,
originalSerializedProjectVariables,
project.GetVariables());
gd::WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
project,
project.GetVariables(),
changeset);
project, project.GetVariables(), changeset);
layout1.GetVariables().Rename("MySceneVariable", "MyRenamedSceneVariable");
layout1.GetVariables().Rename("MySceneStructureVariable",
"MyRenamedSceneStructureVariable");
changeset = gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project,
originalSerializedLayoutVariables,
layout1.GetVariables());
changeset =
gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project, originalSerializedLayoutVariables, layout1.GetVariables());
gd::WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
project,
layout1.GetVariables(),
changeset);
project, layout1.GetVariables(), changeset);
object1.GetVariables().Rename("MyObjectVariable",
"MyRenamedObjectVariable");
object1.GetVariables().Rename("MyObjectStructureVariable",
"MyRenamedObjectStructureVariable");
changeset = gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project,
originalSerializedObject1Variables,
object1.GetVariables());
changeset =
gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project,
originalSerializedObject1Variables,
object1.GetVariables());
gd::WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
project,
object1.GetVariables(),
changeset);
project, object1.GetVariables(), changeset);
// Check the first layout is updated.
// clang-format off
@@ -329,6 +324,182 @@ TEST_CASE("WholeProjectRefactorer::ApplyRefactoringForVariablesContainer",
gd::Serializer::ToJSON(originalSerializedLayout2));
}
}
SECTION("Variable renamed (object group)") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &layout1 = project.InsertNewLayout("Layout1", 0);
gd::StandardEvent &event =
dynamic_cast<gd::StandardEvent &>(layout1.GetEvents().InsertNewEvent(
project, "BuiltinCommonInstructions::Standard"));
gd::RepeatEvent &repeatEvent =
dynamic_cast<gd::RepeatEvent &>(layout1.GetEvents().InsertNewEvent(
project, "BuiltinCommonInstructions::Repeat"));
// Declare variables in objects.
auto &object1 =
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object1", 0);
object1.GetVariables().InsertNew("MyObjectVariable");
object1.GetVariables()
.InsertNew("MyObjectStructureVariable")
.GetChild("MyChild")
.SetValue(123);
auto &object2 =
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object2", 0);
object2.GetVariables().InsertNew("MyObjectVariable");
object2.GetVariables()
.InsertNew("MyObjectStructureVariable")
.GetChild("MyChild")
.SetValue(123);
auto& group = layout1.GetObjectGroups().InsertNew("MyObjectGroup");
group.AddObject("Object1");
group.AddObject("Object2");
// Create an event using the variables.
// clang-format off
{
gd::Instruction action;
action.SetType("MyExtension::DoSomething");
action.SetParametersCount(1);
action.SetParameter(
0,
gd::Expression(
"1 + "
"Object1.MyObjectVariable + "
"Object2.MyObjectVariable + "
"MyObjectGroup.MyObjectVariable + "
"Object1.MyObjectStructureVariable.MyChild + "
"Object2.MyObjectStructureVariable.MyChild + "
"MyObjectGroup.MyObjectStructureVariable.MyChild"));
event.GetActions().Insert(action);
}
// Expressions with "old" "scenevar", "globalvar", "objectvar":
{
gd::Instruction action;
action.SetType("MyExtension::DoSomething");
action.SetParametersCount(1);
action.SetParameter(
0,
gd::Expression(
// "objectvar" (in a free expression):
"1 + "
"MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(Object1, MyObjectVariable, Object2, MyObjectVariable) + "
"MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(MyObjectGroup, MyObjectVariable, MyObjectGroup, MyObjectVariable) + "
"MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(Object1, MyObjectStructureVariable.MyChild, Object2, MyObjectStructureVariable.MyChild) + "
"MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(MyObjectGroup, MyObjectStructureVariable.MyChild, MyObjectGroup, MyObjectStructureVariable.MyChild) + "
// "objectvar" (using the name of the object being called):
"Object1.GetObjectVariableAsNumber(MyObjectVariable) + "
"Object2.GetObjectVariableAsNumber(MyObjectVariable) + "
"MyObjectGroup.GetObjectVariableAsNumber(MyObjectVariable) + "
"Object1.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild) + "
"Object2.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild) + "
"MyObjectGroup.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild) + "
"Object1.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild.GrandChild) + "
"Object2.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild.GrandChild) + "
"MyObjectGroup.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild.GrandChild)"));
event.GetActions().Insert(action);
}
{
gd::Instruction action;
action.SetType("MyExtension::DoSomethingWithLegacyPreScopedVariables");
action.SetParametersCount(4);
action.SetParameter(0, gd::Expression("MySceneVariable"));
action.SetParameter(1, gd::Expression("MyGlobalVariable"));
action.SetParameter(2, gd::Expression("MyObjectGroup"));
action.SetParameter(3, gd::Expression("MyObjectVariable"));
event.GetActions().Insert(action);
}
repeatEvent.SetRepeatExpression("1 + Object1.MyObjectVariable + Object2.MyObjectVariable + MyObjectGroup.MyObjectVariable");
// clang-format on
// Do a copy of layout1 to ensure other scene is unchanged after the
// refactoring.
gd::Layout layout2 = layout1;
layout2.SetName("Layout2");
project.InsertLayout(layout2, 1);
gd::SerializerElement originalSerializedLayout2;
layout2.SerializeTo(originalSerializedLayout2);
// Do the changes and launch the refactoring.
project.GetVariables().ResetPersistentUuid();
layout1.GetVariables().ResetPersistentUuid();
object1.ResetPersistentUuid();
gd::SerializerElement originalSerializedObject1Variables;
object1.GetVariables().SerializeTo(originalSerializedObject1Variables);
object1.GetVariables().Rename("MyObjectVariable",
"MyRenamedObjectVariable");
object1.GetVariables().Rename("MyObjectStructureVariable",
"MyRenamedObjectStructureVariable");
auto changeset =
gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project,
originalSerializedObject1Variables,
object1.GetVariables());
gd::WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
project, object1.GetVariables(), changeset);
// Check the first layout is updated.
// clang-format off
{
// Updated direct access to variables:
REQUIRE(event.GetActions()[0].GetParameter(0).GetPlainString() ==
"1 + "
"Object1.MyRenamedObjectVariable + "
"Object2.MyObjectVariable + "
"MyObjectGroup.MyRenamedObjectVariable + "
"Object1.MyRenamedObjectStructureVariable.MyChild + "
"Object2.MyObjectStructureVariable.MyChild + "
"MyObjectGroup.MyRenamedObjectStructureVariable.MyChild"
);
// Updated access to variables using the legacy "pre-scoped" "scenevar",
// "globalvar" and "objectvar" parameters in expressions:
REQUIRE(event.GetActions()[1].GetParameter(0).GetPlainString() ==
"1 + "
// Multiple "objectvar" parameters in a free function:
"MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(Object1, MyRenamedObjectVariable, Object2, MyObjectVariable) + "
"MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(MyObjectGroup, MyRenamedObjectVariable, MyObjectGroup, MyRenamedObjectVariable) + "
// Multiple "objectvar" parameters in a free function, with child
// variable:
"MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(Object1, MyRenamedObjectStructureVariable.MyChild, Object2, MyObjectStructureVariable.MyChild) + "
"MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(MyObjectGroup, MyRenamedObjectStructureVariable.MyChild, MyObjectGroup, MyRenamedObjectStructureVariable.MyChild) + "
// Single "objectvar" from the object being accessed:
"Object1.GetObjectVariableAsNumber(MyRenamedObjectVariable) + "
"Object2.GetObjectVariableAsNumber(MyObjectVariable) + "
"MyObjectGroup.GetObjectVariableAsNumber(MyRenamedObjectVariable) + "
// Single "objectvar" from the object being accessed, with child
// variales:
"Object1.GetObjectVariableAsNumber(MyRenamedObjectStructureVariable.MyChild) + "
"Object2.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild) + "
"MyObjectGroup.GetObjectVariableAsNumber(MyRenamedObjectStructureVariable.MyChild) + "
"Object1.GetObjectVariableAsNumber(MyRenamedObjectStructureVariable.MyChild.GrandChild) + "
"Object2.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild.GrandChild) + "
"MyObjectGroup.GetObjectVariableAsNumber(MyRenamedObjectStructureVariable.MyChild.GrandChild)");
// Updated "objectvar" parameters of an
// instruction:
REQUIRE(event.GetActions()[2].GetParameter(2).GetPlainString() ==
"MyObjectGroup");
REQUIRE(event.GetActions()[2].GetParameter(3).GetPlainString() ==
"MyRenamedObjectVariable");
}
REQUIRE(repeatEvent.GetRepeatExpression() == "1 + Object1.MyRenamedObjectVariable + Object2.MyObjectVariable + MyObjectGroup.MyRenamedObjectVariable");
// clang-format on
// Check the other layout is untouched.
{
gd::SerializerElement serializedLayout2;
layout2.SerializeTo(serializedLayout2);
REQUIRE(gd::Serializer::ToJSON(serializedLayout2) ==
gd::Serializer::ToJSON(originalSerializedLayout2));
}
}
SECTION("Variable removed (project, layout, object)") {
gd::Project project;
gd::Platform platform;
@@ -495,36 +666,31 @@ TEST_CASE("WholeProjectRefactorer::ApplyRefactoringForVariablesContainer",
project.GetVariables().Remove("MyGlobalVariable");
project.GetVariables().Remove("MyGlobalStructureVariable");
project.GetVariables().Remove("SharedVariableName");
auto changeset = gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project,
originalSerializedProjectVariables,
project.GetVariables());
auto changeset =
gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project,
originalSerializedProjectVariables,
project.GetVariables());
gd::WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
project,
project.GetVariables(),
changeset);
project, project.GetVariables(), changeset);
layout1.GetVariables().Remove("MySceneVariable");
layout1.GetVariables().Remove("MySceneStructureVariable");
changeset = gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project,
originalSerializedLayoutVariables,
layout1.GetVariables());
changeset =
gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project, originalSerializedLayoutVariables, layout1.GetVariables());
gd::WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
project,
layout1.GetVariables(),
changeset);
project, layout1.GetVariables(), changeset);
object1.GetVariables().Remove("MyObjectVariable");
object1.GetVariables().Remove("MyObjectStructureVariable");
changeset = gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project,
originalSerializedObject1Variables,
object1.GetVariables());
changeset =
gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project,
originalSerializedObject1Variables,
object1.GetVariables());
gd::WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
project,
object1.GetVariables(),
changeset);
project, object1.GetVariables(), changeset);
// Check the first layout is updated.
{

View File

@@ -61,13 +61,30 @@ namespace gdjs {
this.light.intensity = value;
}
}
getDoubleParameter(parameterName: string): number {
if (parameterName === 'intensity') {
return this.light.intensity;
}
return 0;
}
updateStringParameter(parameterName: string, value: string): void {
if (parameterName === 'color') {
this.light.color = new THREE.Color(
this.light.color.setHex(
gdjs.PixiFiltersTools.rgbOrHexToHexNumber(value)
);
}
}
updateColorParameter(parameterName: string, value: number): void {
if (parameterName === 'color') {
this.light.color.setHex(value);
}
}
getColorParameter(parameterName: string): number {
if (parameterName === 'color') {
return this.light.color.getHex();
}
return 0;
}
updateBooleanParameter(parameterName: string, value: boolean): void {}
})();
}

View File

@@ -74,6 +74,16 @@ namespace gdjs {
this.updateRotation();
}
}
getDoubleParameter(parameterName: string): number {
if (parameterName === 'intensity') {
return this.light.intensity;
} else if (parameterName === 'elevation') {
return this.elevation;
} else if (parameterName === 'rotation') {
return this.rotation;
}
return 0;
}
updateStringParameter(parameterName: string, value: string): void {
if (parameterName === 'color') {
this.light.color = new THREE.Color(
@@ -85,6 +95,17 @@ namespace gdjs {
this.updateRotation();
}
}
updateColorParameter(parameterName: string, value: number): void {
if (parameterName === 'color') {
this.light.color.setHex(value);
}
}
getColorParameter(parameterName: string): number {
if (parameterName === 'color') {
return this.light.color.getHex();
}
return 0;
}
updateBooleanParameter(parameterName: string, value: boolean): void {}
updateRotation() {
if (this.top === 'Z+') {
@@ -93,7 +114,7 @@ namespace gdjs {
this.rotationObject.rotation.y = -gdjs.toRad(this.elevation);
} else {
// 0° becomes a light from Z+.
this.rotationObject.rotation.y = gdjs.toRad(this.rotation) - 90;
this.rotationObject.rotation.y = gdjs.toRad(this.rotation - 90);
this.rotationObject.rotation.z = -gdjs.toRad(this.elevation);
}
}

View File

@@ -58,6 +58,12 @@ namespace gdjs {
this.fog.density = value;
}
}
getDoubleParameter(parameterName: string): number {
if (parameterName === 'density') {
return this.fog.density;
}
return 0;
}
updateStringParameter(parameterName: string, value: string): void {
if (parameterName === 'color') {
this.fog.color = new THREE.Color(
@@ -65,6 +71,17 @@ namespace gdjs {
);
}
}
updateColorParameter(parameterName: string, value: number): void {
if (parameterName === 'color') {
this.fog.color.setHex(value);
}
}
getColorParameter(parameterName: string): number {
if (parameterName === 'color') {
return this.fog.color.getHex();
}
return 0;
}
updateBooleanParameter(parameterName: string, value: boolean): void {}
})();
}

View File

@@ -74,6 +74,16 @@ namespace gdjs {
this.updateRotation();
}
}
getDoubleParameter(parameterName: string): number {
if (parameterName === 'intensity') {
return this.light.intensity;
} else if (parameterName === 'elevation') {
return this.elevation;
} else if (parameterName === 'rotation') {
return this.rotation;
}
return 0;
}
updateStringParameter(parameterName: string, value: string): void {
if (parameterName === 'skyColor') {
this.light.color = new THREE.Color(
@@ -90,6 +100,23 @@ namespace gdjs {
this.updateRotation();
}
}
updateColorParameter(parameterName: string, value: number): void {
if (parameterName === 'skyColor') {
this.light.color.setHex(value);
}
if (parameterName === 'groundColor') {
this.light.groundColor.setHex(value);
}
}
getColorParameter(parameterName: string): number {
if (parameterName === 'skyColor') {
return this.light.color.getHex();
}
if (parameterName === 'groundColor') {
return this.light.groundColor.getHex();
}
return 0;
}
updateBooleanParameter(parameterName: string, value: boolean): void {}
updateRotation() {
if (this.top === 'Z+') {
@@ -98,7 +125,7 @@ namespace gdjs {
this.rotationObject.rotation.y = -gdjs.toRad(this.elevation);
} else {
// 0° becomes a light from Z+.
this.rotationObject.rotation.y = gdjs.toRad(this.rotation) - 90;
this.rotationObject.rotation.y = gdjs.toRad(this.rotation - 90);
this.rotationObject.rotation.z = -gdjs.toRad(this.elevation);
}
}

View File

@@ -64,7 +64,7 @@ module.exports = {
_('Position'),
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D object'))
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setFunctionName('setZ')
@@ -80,7 +80,7 @@ module.exports = {
_('Position/Center'),
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D object'))
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setFunctionName('setCenterZInScene')
@@ -96,7 +96,7 @@ module.exports = {
_('Size'),
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D object'))
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setFunctionName('setDepth')
@@ -112,7 +112,7 @@ module.exports = {
_('Size'),
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D object'))
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.useStandardParameters(
'number',
@@ -134,7 +134,7 @@ module.exports = {
'res/conditions/3d_box.svg',
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D object'))
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.addParameter('yesorno', _('Activate flipping'))
.markAsSimple()
@@ -150,7 +150,7 @@ module.exports = {
'res/conditions/3d_box.svg',
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D object'))
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.setFunctionName('isFlippedZ');
@@ -164,7 +164,7 @@ module.exports = {
_('Angle'),
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D object'))
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setFunctionName('setRotationX')
@@ -180,7 +180,7 @@ module.exports = {
_('Angle'),
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D object'))
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setFunctionName('setRotationY')
@@ -198,7 +198,7 @@ module.exports = {
'res/conditions/3d_box.svg',
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D object'))
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.addParameter('number', _('Rotation angle'), '', false)
.markAsAdvanced()
@@ -216,7 +216,7 @@ module.exports = {
'res/conditions/3d_box.svg',
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D object'))
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.addParameter('number', _('Rotation angle'), '', false)
.markAsAdvanced()
@@ -234,7 +234,7 @@ module.exports = {
'res/conditions/3d_box.svg',
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D object'))
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.addParameter('number', _('Rotation angle'), '', false)
.markAsAdvanced()
@@ -501,7 +501,7 @@ module.exports = {
'res/actions/flipX24.png',
'res/actions/flipX.png'
)
.addParameter('object', _('3D model'), 'Model3DObject')
.addParameter('object', _('3D model'), 'Model3DObject', false)
.addParameter('yesorno', _('Activate flipping'))
.setHidden()
.markAsSimple()
@@ -518,7 +518,7 @@ module.exports = {
'res/actions/flipY24.png',
'res/actions/flipY.png'
)
.addParameter('object', _('3D model'), 'Model3DObject')
.addParameter('object', _('3D model'), 'Model3DObject', false)
.addParameter('yesorno', _('Activate flipping'))
.setHidden()
.markAsSimple()
@@ -535,7 +535,7 @@ module.exports = {
'res/conditions/3d_box.svg',
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D model'), 'Model3DObject')
.addParameter('object', _('3D model'), 'Model3DObject', false)
.addParameter('yesorno', _('Activate flipping'))
.markAsSimple()
.setHidden()
@@ -552,7 +552,7 @@ module.exports = {
'res/actions/flipX24.png',
'res/actions/flipX.png'
)
.addParameter('object', _('3D model'), 'Model3DObject')
.addParameter('object', _('3D model'), 'Model3DObject', false)
.setHidden()
.setFunctionName('isFlippedX');
@@ -567,7 +567,7 @@ module.exports = {
'res/actions/flipY24.png',
'res/actions/flipY.png'
)
.addParameter('object', _('3D model'), 'Model3DObject')
.addParameter('object', _('3D model'), 'Model3DObject', false)
.setHidden()
.setFunctionName('isFlippedY');
@@ -582,7 +582,7 @@ module.exports = {
'res/conditions/3d_box.svg',
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D model'), 'Model3DObject')
.addParameter('object', _('3D model'), 'Model3DObject', false)
.setHidden()
.setFunctionName('isFlippedZ');
@@ -690,7 +690,7 @@ module.exports = {
_('Animations and images'),
'res/actions/animation24.png'
)
.addParameter('object', _('3D model'), 'Model3DObject')
.addParameter('object', _('3D model'), 'Model3DObject', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.markAsSimple()
.setHidden()
@@ -708,7 +708,7 @@ module.exports = {
_('Animations and images'),
'res/actions/animation24.png'
)
.addParameter('object', _('3D model'), 'Model3DObject')
.addParameter('object', _('3D model'), 'Model3DObject', false)
.useStandardParameters(
'objectAnimationName',
gd.ParameterOptions.makeNewOptions().setDescription(
@@ -731,7 +731,7 @@ module.exports = {
'res/actions/animation24.png',
'res/actions/animation.png'
)
.addParameter('object', _('3D model'), 'Model3DObject')
.addParameter('object', _('3D model'), 'Model3DObject', false)
.markAsSimple()
.setHidden()
.setFunctionName('pauseAnimation');
@@ -747,7 +747,7 @@ module.exports = {
'res/actions/animation24.png',
'res/actions/animation.png'
)
.addParameter('object', _('3D model'), 'Model3DObject')
.addParameter('object', _('3D model'), 'Model3DObject', false)
.markAsSimple()
.setHidden()
.setFunctionName('resumeAnimation');
@@ -765,7 +765,7 @@ module.exports = {
_('Animations and images'),
'res/actions/animation24.png'
)
.addParameter('object', _('3D model'), 'Model3DObject')
.addParameter('object', _('3D model'), 'Model3DObject', false)
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(_('Speed scale'))
@@ -786,7 +786,7 @@ module.exports = {
'res/conditions/animation24.png',
'res/conditions/animation.png'
)
.addParameter('object', _('3D model'), 'Model3DObject')
.addParameter('object', _('3D model'), 'Model3DObject', false)
.markAsSimple()
.setHidden()
.setFunctionName('isAnimationPaused');
@@ -804,7 +804,7 @@ module.exports = {
'res/conditions/animation24.png',
'res/conditions/animation.png'
)
.addParameter('object', _('3D model'), 'Model3DObject')
.addParameter('object', _('3D model'), 'Model3DObject', false)
.markAsSimple()
.setHidden()
.setFunctionName('hasAnimationEnded');
@@ -1364,7 +1364,7 @@ module.exports = {
'res/actions/flipX24.png',
'res/actions/flipX.png'
)
.addParameter('object', _('3D cube'), 'Cube3DObject')
.addParameter('object', _('3D cube'), 'Cube3DObject', false)
.addParameter('yesorno', _('Activate flipping'))
.markAsSimple()
.setHidden()
@@ -1381,7 +1381,7 @@ module.exports = {
'res/actions/flipY24.png',
'res/actions/flipY.png'
)
.addParameter('object', _('3D cube'), 'Cube3DObject')
.addParameter('object', _('3D cube'), 'Cube3DObject', false)
.addParameter('yesorno', _('Activate flipping'))
.markAsSimple()
.setHidden()
@@ -1398,7 +1398,7 @@ module.exports = {
'res/conditions/3d_box.svg',
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D cube'), 'Cube3DObject')
.addParameter('object', _('3D cube'), 'Cube3DObject', false)
.addParameter('yesorno', _('Activate flipping'))
.markAsSimple()
.setHidden()
@@ -1415,7 +1415,7 @@ module.exports = {
'res/actions/flipX24.png',
'res/actions/flipX.png'
)
.addParameter('object', _('3D cube'), 'Cube3DObject')
.addParameter('object', _('3D cube'), 'Cube3DObject', false)
.setHidden()
.setFunctionName('isFlippedX');
@@ -1430,7 +1430,7 @@ module.exports = {
'res/actions/flipY24.png',
'res/actions/flipY.png'
)
.addParameter('object', _('3D cube'), 'Cube3DObject')
.addParameter('object', _('3D cube'), 'Cube3DObject', false)
.setHidden()
.setFunctionName('isFlippedY');
@@ -1445,7 +1445,7 @@ module.exports = {
'res/conditions/3d_box.svg',
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D cube'), 'Cube3DObject')
.addParameter('object', _('3D cube'), 'Cube3DObject', false)
.setHidden()
.setFunctionName('isFlippedZ');
@@ -2736,6 +2736,9 @@ module.exports = {
originalDepth,
keepAspectRatio
) {
// These formulas are also used in:
// - gdjs.Model3DRuntimeObject3DRenderer._updateDefaultTransformation
// - Model3DEditor.modelSize
threeObject.rotation.set(
(rotationX * Math.PI) / 180,
(rotationY * Math.PI) / 180,

View File

@@ -60,6 +60,14 @@ namespace gdjs {
this.fog.far = value;
}
}
getDoubleParameter(parameterName: string): number {
if (parameterName === 'near') {
return this.fog.near;
} else if (parameterName === 'far') {
return this.fog.far;
}
return 0;
}
updateStringParameter(parameterName: string, value: string): void {
if (parameterName === 'color') {
this.fog.color = new THREE.Color(
@@ -67,6 +75,17 @@ namespace gdjs {
);
}
}
updateColorParameter(parameterName: string, value: number): void {
if (parameterName === 'color') {
this.fog.color.setHex(value);
}
}
getColorParameter(parameterName: string): number {
if (parameterName === 'color') {
return this.fog.color.getHex();
}
return 0;
}
updateBooleanParameter(parameterName: string, value: boolean): void {}
})();
}

View File

@@ -147,6 +147,9 @@ namespace gdjs {
originalDepth: float,
keepAspectRatio: boolean
) {
// These formulas are also used in:
// - Model3DEditor.modelSize
// - Model3DRendered2DInstance
threeObject.rotation.set(
gdjs.toRad(rotationX),
gdjs.toRad(rotationY),

View File

@@ -31,11 +31,47 @@ namespace gdjs {
adjustmentFilter.alpha = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const adjustmentFilter = (filter as unknown) as PIXI.filters.AdjustmentFilter;
if (parameterName === 'gamma') {
return adjustmentFilter.gamma;
}
if (parameterName === 'saturation') {
return adjustmentFilter.saturation;
}
if (parameterName === 'contrast') {
return adjustmentFilter.contrast;
}
if (parameterName === 'brightness') {
return adjustmentFilter.brightness;
}
if (parameterName === 'red') {
return adjustmentFilter.red;
}
if (parameterName === 'green') {
return adjustmentFilter.green;
}
if (parameterName === 'blue') {
return adjustmentFilter.blue;
}
if (parameterName === 'alpha') {
return adjustmentFilter.alpha;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -27,11 +27,41 @@ namespace gdjs {
advancedBloomFilter.padding = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const advancedBloomFilter = (filter as unknown) as PIXI.filters.AdvancedBloomFilter;
if (parameterName === 'threshold') {
return advancedBloomFilter.threshold;
}
if (parameterName === 'bloomScale') {
return advancedBloomFilter.bloomScale;
}
if (parameterName === 'brightness') {
return advancedBloomFilter.brightness;
}
if (parameterName === 'blur') {
return advancedBloomFilter.blur;
}
if (parameterName === 'quality') {
return advancedBloomFilter.quality;
}
if (parameterName === 'padding') {
return advancedBloomFilter.padding;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -17,11 +17,26 @@ namespace gdjs {
asciiFilter.size = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const asciiFilter = (filter as unknown) as PIXI.filters.AsciiFilter;
if (parameterName === 'size') {
return asciiFilter.size;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -1,4 +1,8 @@
namespace gdjs {
interface BevelFilterExtra {
/** It's defined for the configuration but not for the filter. */
distance: number;
}
gdjs.PixiFiltersTools.registerFilterCreator(
'Bevel',
new (class extends gdjs.PixiFiltersTools.PixiFilterCreator {
@@ -12,13 +16,13 @@ namespace gdjs {
parameterName: string,
value: number
) {
const bevelFilter = (filter as unknown) as PIXI.filters.BevelFilter;
const bevelFilter = (filter as unknown) as PIXI.filters.BevelFilter &
BevelFilterExtra;
if (parameterName === 'rotation') {
bevelFilter.rotation = value;
} else if (parameterName === 'thickness') {
bevelFilter.thickness = value;
} else if (parameterName === 'distance') {
// @ts-ignore
bevelFilter.distance = value;
} else if (parameterName === 'lightAlpha') {
bevelFilter.lightAlpha = value;
@@ -26,12 +30,33 @@ namespace gdjs {
bevelFilter.shadowAlpha = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const bevelFilter = (filter as unknown) as PIXI.filters.BevelFilter &
BevelFilterExtra;
if (parameterName === 'rotation') {
return bevelFilter.rotation;
}
if (parameterName === 'thickness') {
return bevelFilter.thickness;
}
if (parameterName === 'distance') {
return bevelFilter.distance;
}
if (parameterName === 'lightAlpha') {
return bevelFilter.lightAlpha;
}
if (parameterName === 'shadowAlpha') {
return bevelFilter.shadowAlpha;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {
const bevelFilter = (filter as unknown) as PIXI.filters.BevelFilter;
const bevelFilter = (filter as unknown) as PIXI.filters.BevelFilter &
BevelFilterExtra;
if (parameterName === 'lightColor') {
bevelFilter.lightColor = gdjs.PixiFiltersTools.rgbOrHexToHexNumber(
value
@@ -43,6 +68,30 @@ namespace gdjs {
);
}
}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {
const bevelFilter = (filter as unknown) as PIXI.filters.BevelFilter &
BevelFilterExtra;
if (parameterName === 'lightColor') {
bevelFilter.lightColor = value;
}
if (parameterName === 'shadowColor') {
bevelFilter.shadowColor = value;
}
}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
const bevelFilter = (filter as unknown) as PIXI.filters.BevelFilter;
if (parameterName === 'lightColor') {
return bevelFilter.lightColor;
}
if (parameterName === 'shadowColor') {
return bevelFilter.shadowColor;
}
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -19,11 +19,26 @@ namespace gdjs {
}
colorMatrix.alpha = gdjs.PixiFiltersTools.clampValue(value, 0, 1);
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const colorMatrix = (filter as unknown) as PIXI.ColorMatrixFilter;
if (parameterName === 'opacity') {
return colorMatrix.alpha;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -19,11 +19,29 @@ namespace gdjs {
blendingModeFilter.blendMode = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const blendingModeFilter = (filter as unknown) as PIXI.AlphaFilter;
if (parameterName === 'alpha') {
return blendingModeFilter.alpha;
}
if (parameterName === 'blendmode') {
return blendingModeFilter.blendMode;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -25,11 +25,22 @@ namespace gdjs {
}
filter[parameterName] = value;
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
return filter[parameterName] || 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -1,4 +1,8 @@
namespace gdjs {
interface BrightnessFilterExtra {
/** It allows to get back the value as the filter uses a matrix. */
__brightness: number;
}
gdjs.PixiFiltersTools.registerFilterCreator(
'Brightness',
new (class extends gdjs.PixiFiltersTools.PixiFilterCreator {
@@ -13,20 +17,36 @@ namespace gdjs {
parameterName: string,
value: number
) {
const brightnessFilter = (filter as unknown) as PIXI.ColorMatrixFilter;
const brightnessFilter = (filter as unknown) as PIXI.ColorMatrixFilter &
BrightnessFilterExtra;
if (parameterName !== 'brightness') {
return;
}
brightnessFilter.brightness(
gdjs.PixiFiltersTools.clampValue(value, 0, 1),
false
);
const brightness = gdjs.PixiFiltersTools.clampValue(value, 0, 1);
brightnessFilter.__brightness = brightness;
brightnessFilter.brightness(brightness, false);
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const brightnessFilter = (filter as unknown) as PIXI.ColorMatrixFilter &
BrightnessFilterExtra;
if (parameterName === 'brightness') {
return brightnessFilter.__brightness;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -27,11 +27,35 @@ namespace gdjs {
);
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const bulgePinchFilter = (filter as unknown) as PIXI.filters.BulgePinchFilter;
if (parameterName === 'centerX') {
return bulgePinchFilter.center[0];
}
if (parameterName === 'centerY') {
return bulgePinchFilter.center[1];
}
if (parameterName === 'radius') {
return bulgePinchFilter.radius;
}
if (parameterName === 'strength') {
return bulgePinchFilter.strength;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -34,11 +34,26 @@ namespace gdjs {
);
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const colorMapFilter = (filter as unknown) as PIXI.filters.ColorMapFilter;
if (parameterName === 'mix') {
return colorMapFilter.mix;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -1,4 +1,10 @@
namespace gdjs {
interface ColorReplaceFilterExtra {
/** It's only set to a number. */
originalColor: number;
/** It's only set to a number. */
newColor: number;
}
gdjs.PixiFiltersTools.registerFilterCreator(
'ColorReplace',
new (class extends gdjs.PixiFiltersTools.PixiFilterCreator {
@@ -12,17 +18,27 @@ namespace gdjs {
parameterName: string,
value: number
) {
const colorReplaceFilter = (filter as unknown) as PIXI.filters.ColorReplaceFilter;
const colorReplaceFilter = (filter as unknown) as PIXI.filters.ColorReplaceFilter &
ColorReplaceFilterExtra;
if (parameterName === 'epsilon') {
colorReplaceFilter.epsilon = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const colorReplaceFilter = (filter as unknown) as PIXI.filters.ColorReplaceFilter &
ColorReplaceFilterExtra;
if (parameterName === 'epsilon') {
return colorReplaceFilter.epsilon;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {
const colorReplaceFilter = (filter as unknown) as PIXI.filters.ColorReplaceFilter;
const colorReplaceFilter = (filter as unknown) as PIXI.filters.ColorReplaceFilter &
ColorReplaceFilterExtra;
if (parameterName === 'originalColor') {
colorReplaceFilter.originalColor = gdjs.PixiFiltersTools.rgbOrHexToHexNumber(
value
@@ -33,6 +49,29 @@ namespace gdjs {
);
}
}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {
const colorReplaceFilter = (filter as unknown) as PIXI.filters.ColorReplaceFilter &
ColorReplaceFilterExtra;
if (parameterName === 'originalColor') {
colorReplaceFilter.originalColor = value;
} else if (parameterName === 'newColor') {
colorReplaceFilter.newColor = value;
}
}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
const colorReplaceFilter = (filter as unknown) as PIXI.filters.ColorReplaceFilter &
ColorReplaceFilterExtra;
if (parameterName === 'originalColor') {
return colorReplaceFilter.originalColor;
} else if (parameterName === 'newColor') {
return colorReplaceFilter.newColor;
}
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -1,25 +1,32 @@
// @ts-nocheck - TODO: fix typings in this file
namespace gdjs {
interface CRTFilterExtra {
_animationTimer: number;
animationSpeed: number;
animationFrequency: number;
}
gdjs.PixiFiltersTools.registerFilterCreator(
'CRT',
new (class extends gdjs.PixiFiltersTools.PixiFilterCreator {
makePIXIFilter(layer, effectData) {
const crtFilter = new PIXI.filters.CRTFilter();
const filter = new PIXI.filters.CRTFilter();
const crtFilter = (filter as unknown) as PIXI.filters.CRTFilter &
CRTFilterExtra;
crtFilter._animationTimer = 0;
return crtFilter;
}
updatePreRender(filter: PIXI.Filter, target: EffectsTarget) {
if (filter.animationSpeed !== 0) {
const crtFilter = (filter as unknown) as PIXI.filters.CRTFilter &
CRTFilterExtra;
if (crtFilter.animationSpeed !== 0) {
// Multiply by 10 so that the default value is a sensible speed
filter.time +=
(target.getElapsedTime() / 1000) * 10 * filter.animationSpeed;
crtFilter.time +=
(target.getElapsedTime() / 1000) * 10 * crtFilter.animationSpeed;
}
if (filter.animationFrequency !== 0) {
filter._animationTimer += target.getElapsedTime() / 1000;
if (filter._animationTimer >= 1 / filter.animationFrequency) {
filter.seed = Math.random();
filter._animationTimer = 0;
if (crtFilter.animationFrequency !== 0) {
crtFilter._animationTimer += target.getElapsedTime() / 1000;
if (crtFilter._animationTimer >= 1 / crtFilter.animationFrequency) {
crtFilter.seed = Math.random();
crtFilter._animationTimer = 0;
}
}
}
@@ -28,42 +35,91 @@ namespace gdjs {
parameterName: string,
value: number
) {
const crtFilter = (filter as unknown) as PIXI.filters.CRTFilter &
CRTFilterExtra;
if (parameterName === 'lineWidth') {
filter.lineWidth = value;
crtFilter.lineWidth = value;
} else if (parameterName === 'lineContrast') {
filter.lineContrast = value;
crtFilter.lineContrast = value;
} else if (parameterName === 'noise') {
filter.noise = value;
crtFilter.noise = value;
} else if (parameterName === 'curvature') {
filter.curvature = value;
crtFilter.curvature = value;
} else if (parameterName === 'noiseSize') {
filter.noiseSize = value;
crtFilter.noiseSize = value;
} else if (parameterName === 'vignetting') {
filter.vignetting = value;
crtFilter.vignetting = value;
} else if (parameterName === 'vignettingAlpha') {
filter.vignettingAlpha = value;
crtFilter.vignettingAlpha = value;
} else if (parameterName === 'vignettingBlur') {
filter.vignettingBlur = value;
crtFilter.vignettingBlur = value;
} else if (parameterName === 'animationSpeed') {
filter.animationSpeed = value;
crtFilter.animationSpeed = value;
} else if (parameterName === 'animationFrequency') {
filter.animationFrequency = value;
crtFilter.animationFrequency = value;
} else if (parameterName === 'padding') {
filter.padding = value;
crtFilter.padding = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const crtFilter = (filter as unknown) as PIXI.filters.CRTFilter &
CRTFilterExtra;
if (parameterName === 'lineWidth') {
return crtFilter.lineWidth;
}
if (parameterName === 'lineContrast') {
return crtFilter.lineContrast;
}
if (parameterName === 'noise') {
return crtFilter.noise;
}
if (parameterName === 'curvature') {
return crtFilter.curvature;
}
if (parameterName === 'noiseSize') {
return crtFilter.noiseSize;
}
if (parameterName === 'vignetting') {
return crtFilter.vignetting;
}
if (parameterName === 'vignettingAlpha') {
return crtFilter.vignettingAlpha;
}
if (parameterName === 'vignettingBlur') {
return crtFilter.vignettingBlur;
}
if (parameterName === 'animationSpeed') {
return crtFilter.animationSpeed;
}
if (parameterName === 'animationFrequency') {
return crtFilter.animationFrequency;
}
if (parameterName === 'padding') {
return crtFilter.padding;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,
value: boolean
) {
const crtFilter = (filter as unknown) as PIXI.filters.CRTFilter;
if (parameterName === 'verticalLine') {
filter.verticalLine = value;
crtFilter.verticalLine = value;
}
}
})()

View File

@@ -29,11 +29,29 @@ namespace gdjs {
displacementFilter.scale.y = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const displacementFilter = (filter as unknown) as PIXI.DisplacementFilter;
if (parameterName === 'scaleX') {
return displacementFilter.scale.x;
}
if (parameterName === 'scaleY') {
return displacementFilter.scale.y;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -19,11 +19,29 @@ namespace gdjs {
dotFilter.angle = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const dotFilter = (filter as unknown) as PIXI.filters.DotFilter;
if (parameterName === 'scale') {
return dotFilter.scale;
}
if (parameterName === 'angle') {
return dotFilter.angle;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -27,6 +27,28 @@ namespace gdjs {
dropShadowFilter.padding = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const dropShadowFilter = (filter as unknown) as PIXI.filters.DropShadowFilter;
if (parameterName === 'blur') {
return dropShadowFilter.blur;
}
if (parameterName === 'quality') {
return dropShadowFilter.quality;
}
if (parameterName === 'alpha') {
return dropShadowFilter.alpha;
}
if (parameterName === 'distance') {
return dropShadowFilter.distance;
}
if (parameterName === 'rotation') {
return dropShadowFilter.rotation;
}
if (parameterName === 'padding') {
return dropShadowFilter.padding;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
@@ -39,6 +61,23 @@ namespace gdjs {
);
}
}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {
const dropShadowFilter = (filter as unknown) as PIXI.filters.DropShadowFilter;
if (parameterName === 'color') {
dropShadowFilter.color = value;
}
}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
const dropShadowFilter = (filter as unknown) as PIXI.filters.DropShadowFilter;
if (parameterName === 'color') {
return dropShadowFilter.color;
}
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -1,20 +1,29 @@
// @ts-nocheck - TODO: fix typings in this file
namespace gdjs {
interface GlitchFilterExtra {
_animationTimer: number;
animationFrequency: number;
}
gdjs.PixiFiltersTools.registerFilterCreator(
'Glitch',
new (class extends gdjs.PixiFiltersTools.PixiFilterCreator {
makePIXIFilter(layer, effectData) {
const glitchFilter = new PIXI.filters.GlitchFilter();
const filter = new PIXI.filters.GlitchFilter();
const glitchFilter = (filter as unknown) as PIXI.filters.GlitchFilter &
GlitchFilterExtra;
glitchFilter._animationTimer = 0;
return glitchFilter;
}
updatePreRender(filter: PIXI.Filter, target: EffectsTarget) {
if (filter.animationFrequency !== 0) {
filter._animationTimer += target.getElapsedTime() / 1000;
if (filter._animationTimer >= 1 / filter.animationFrequency) {
filter.seed = Math.random();
filter._animationTimer = 0;
const glitchFilter = (filter as unknown) as PIXI.filters.GlitchFilter &
GlitchFilterExtra;
if (glitchFilter.animationFrequency !== 0) {
glitchFilter._animationTimer += target.getElapsedTime() / 1000;
if (
glitchFilter._animationTimer >=
1 / glitchFilter.animationFrequency
) {
glitchFilter.seed = Math.random();
glitchFilter._animationTimer = 0;
}
}
}
@@ -23,46 +32,102 @@ namespace gdjs {
parameterName: string,
value: number
) {
const glitchFilter = (filter as unknown) as PIXI.filters.GlitchFilter &
GlitchFilterExtra;
if (parameterName === 'slices') {
filter.slices = value;
glitchFilter.slices = value;
} else if (parameterName === 'offset') {
filter.offset = value;
glitchFilter.offset = value;
} else if (parameterName === 'direction') {
filter.direction = value;
glitchFilter.direction = value;
} else if (parameterName === 'fillMode') {
filter.fillMode = value;
glitchFilter.fillMode = value;
} else if (parameterName === 'minSize') {
filter.minSize = value;
glitchFilter.minSize = value;
} else if (parameterName === 'sampleSize') {
filter.sampleSize = value;
glitchFilter.sampleSize = value;
} else if (parameterName === 'redX') {
filter.red.x = value;
glitchFilter.red.x = value;
} else if (parameterName === 'redY') {
filter.red.y = value;
glitchFilter.red.y = value;
} else if (parameterName === 'greenX') {
filter.green.x = value;
glitchFilter.green.x = value;
} else if (parameterName === 'greenY') {
filter.green.y = value;
glitchFilter.green.y = value;
} else if (parameterName === 'blueX') {
filter.blue.x = value;
glitchFilter.blue.x = value;
} else if (parameterName === 'blueY') {
filter.blue.y = value;
glitchFilter.blue.y = value;
} else if (parameterName === 'animationFrequency') {
filter.animationFrequency = value;
glitchFilter.animationFrequency = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const glitchFilter = (filter as unknown) as PIXI.filters.GlitchFilter &
GlitchFilterExtra;
if (parameterName === 'slices') {
return glitchFilter.slices;
}
if (parameterName === 'offset') {
return glitchFilter.offset;
}
if (parameterName === 'direction') {
return glitchFilter.direction;
}
if (parameterName === 'fillMode') {
return glitchFilter.fillMode;
}
if (parameterName === 'minSize') {
return glitchFilter.minSize;
}
if (parameterName === 'sampleSize') {
return glitchFilter.sampleSize;
}
if (parameterName === 'redX') {
return glitchFilter.red.x;
}
if (parameterName === 'redY') {
return glitchFilter.red.y;
}
if (parameterName === 'greenX') {
return glitchFilter.green.x;
}
if (parameterName === 'greenY') {
return glitchFilter.green.y;
}
if (parameterName === 'blueX') {
return glitchFilter.blue.x;
}
if (parameterName === 'blueY') {
return glitchFilter.blue.y;
}
if (parameterName === 'animationFrequency') {
return glitchFilter.animationFrequency;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,
value: boolean
) {
const glitchFilter = (filter as unknown) as PIXI.filters.GlitchFilter &
GlitchFilterExtra;
if (parameterName === 'average') {
filter.average = value;
glitchFilter.average = value;
}
}
})()

View File

@@ -1,4 +1,7 @@
namespace gdjs {
interface GlowFilterExtra {
distance: number;
}
gdjs.PixiFiltersTools.registerFilterCreator(
'Glow',
new (class extends gdjs.PixiFiltersTools.PixiFilterCreator {
@@ -12,26 +15,60 @@ namespace gdjs {
parameterName: string,
value: number
) {
const glowFilter = (filter as unknown) as PIXI.filters.GlowFilter;
const glowFilter = (filter as unknown) as PIXI.filters.GlowFilter &
GlowFilterExtra;
if (parameterName === 'innerStrength') {
glowFilter.innerStrength = value;
} else if (parameterName === 'outerStrength') {
glowFilter.outerStrength = value;
} else if (parameterName === 'distance') {
// @ts-ignore
glowFilter.distance = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const glowFilter = (filter as unknown) as PIXI.filters.GlowFilter &
GlowFilterExtra;
if (parameterName === 'innerStrength') {
return glowFilter.innerStrength;
}
if (parameterName === 'outerStrength') {
return glowFilter.outerStrength;
}
if (parameterName === 'distance') {
return glowFilter.distance;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {
const glowFilter = (filter as unknown) as PIXI.filters.GlowFilter;
const glowFilter = (filter as unknown) as PIXI.filters.GlowFilter &
GlowFilterExtra;
if (parameterName === 'color') {
glowFilter.color = gdjs.PixiFiltersTools.rgbOrHexToHexNumber(value);
}
}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {
const glowFilter = (filter as unknown) as PIXI.filters.GlowFilter &
GlowFilterExtra;
if (parameterName === 'color') {
glowFilter.color = value;
}
}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
const glowFilter = (filter as unknown) as PIXI.filters.GlowFilter &
GlowFilterExtra;
if (parameterName === 'color') {
return glowFilter.color;
}
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -1,6 +1,10 @@
// @ts-nocheck - TODO: fix typings in this file
namespace gdjs {
interface GodrayFilterExtra {
animationSpeed: number;
light: number;
x: number;
y: number;
}
gdjs.PixiFiltersTools.registerFilterCreator(
'Godray',
new (class extends gdjs.PixiFiltersTools.PixiFilterCreator {
@@ -9,9 +13,11 @@ namespace gdjs {
return godrayFilter;
}
updatePreRender(filter: PIXI.Filter, target: EffectsTarget) {
if (filter.animationSpeed !== 0) {
filter.time +=
(target.getElapsedTime() / 1000) * filter.animationSpeed;
const godrayFilter = (filter as unknown) as PIXI.filters.GodrayFilter &
GodrayFilterExtra;
if (godrayFilter.animationSpeed !== 0) {
godrayFilter.time +=
(target.getElapsedTime() / 1000) * godrayFilter.animationSpeed;
}
}
updateDoubleParameter(
@@ -19,36 +25,77 @@ namespace gdjs {
parameterName: string,
value: number
) {
const godrayFilter = (filter as unknown) as PIXI.filters.GodrayFilter &
GodrayFilterExtra;
if (parameterName === 'lacunarity') {
filter.lacunarity = value;
godrayFilter.lacunarity = value;
} else if (parameterName === 'angle') {
filter.angle = value;
godrayFilter.angle = value;
} else if (parameterName === 'gain') {
filter.gain = value;
godrayFilter.gain = value;
} else if (parameterName === 'light') {
filter.light = value;
godrayFilter.light = value;
} else if (parameterName === 'x') {
filter.x = value;
godrayFilter.x = value;
} else if (parameterName === 'y') {
filter.y = value;
godrayFilter.y = value;
} else if (parameterName === 'animationSpeed') {
filter.animationSpeed = value;
godrayFilter.animationSpeed = value;
} else if (parameterName === 'padding') {
filter.padding = value;
godrayFilter.padding = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const godrayFilter = (filter as unknown) as PIXI.filters.GodrayFilter &
GodrayFilterExtra;
if (parameterName === 'lacunarity') {
return godrayFilter.lacunarity;
}
if (parameterName === 'angle') {
return godrayFilter.angle;
}
if (parameterName === 'gain') {
return godrayFilter.gain;
}
if (parameterName === 'light') {
return godrayFilter.light;
}
if (parameterName === 'x') {
return godrayFilter.x;
}
if (parameterName === 'y') {
return godrayFilter.y;
}
if (parameterName === 'animationSpeed') {
return godrayFilter.animationSpeed;
}
if (parameterName === 'padding') {
return godrayFilter.padding;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,
value: boolean
) {
const godrayFilter = (filter as unknown) as PIXI.filters.GodrayFilter &
GodrayFilterExtra;
if (parameterName === 'parallel') {
filter.parallel = value;
godrayFilter.parallel = value;
}
}
})()

View File

@@ -23,11 +23,35 @@ namespace gdjs {
hslAdjustmentFilter.alpha = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const hslAdjustmentFilter = filter as PIXI.filters.HslAdjustmentFilter;
if (parameterName === 'hue') {
return hslAdjustmentFilter.hue;
}
if (parameterName === 'saturation') {
return hslAdjustmentFilter.saturation;
}
if (parameterName === 'lightness') {
return hslAdjustmentFilter.lightness;
}
if (parameterName === 'alpha') {
return hslAdjustmentFilter.alpha;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -23,11 +23,35 @@ namespace gdjs {
kawaseBlurFilter.quality = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const kawaseBlurFilter = (filter as unknown) as PIXI.filters.KawaseBlurFilter;
if (parameterName === 'pixelizeX') {
return kawaseBlurFilter.pixelSize[0];
}
if (parameterName === 'pixelizeY') {
return kawaseBlurFilter.pixelSize[1];
}
if (parameterName === 'blur') {
return kawaseBlurFilter.blur;
}
if (parameterName === 'quality') {
return kawaseBlurFilter.quality;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -34,16 +34,33 @@ namespace gdjs {
parameterName: string,
value: number
) {
if (parameterName !== 'opacity') {
return;
if (parameterName === 'opacity') {
filter.uniforms.opacity = gdjs.PixiFiltersTools.clampValue(
value,
0,
1
);
}
filter.uniforms.opacity = gdjs.PixiFiltersTools.clampValue(value, 0, 1);
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
if (parameterName === 'opacity') {
return filter.uniforms.opacity;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -1,4 +1,8 @@
namespace gdjs {
interface MotionBlurFilterExtra {
/**Use the private member avoids to instantiate Arrays.*/
_velocity: PIXI.Point;
}
gdjs.PixiFiltersTools.registerFilterCreator(
'MotionBlur',
new (class extends gdjs.PixiFiltersTools.PixiFilterCreator {
@@ -12,12 +16,11 @@ namespace gdjs {
parameterName: string,
value: number
) {
const motionBlurFilter = filter as PIXI.filters.MotionBlurFilter;
const motionBlurFilter = filter as PIXI.filters.MotionBlurFilter &
MotionBlurFilterExtra;
if (parameterName === 'velocityX') {
// @ts-ignore Using the private member avoids to instantiate Arrays.
motionBlurFilter._velocity.x = value;
} else if (parameterName === 'velocityY') {
// @ts-ignore Using the private member avoids to instantiate Arrays.
motionBlurFilter._velocity.y = value;
} else if (parameterName === 'kernelSize') {
motionBlurFilter.kernelSize = value;
@@ -25,11 +28,36 @@ namespace gdjs {
motionBlurFilter.offset = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const motionBlurFilter = filter as PIXI.filters.MotionBlurFilter &
MotionBlurFilterExtra;
if (parameterName === 'velocityX') {
return motionBlurFilter._velocity.x;
}
if (parameterName === 'velocityY') {
return motionBlurFilter._velocity.y;
}
if (parameterName === 'kernelSize') {
return motionBlurFilter.kernelSize;
}
if (parameterName === 'offset') {
return motionBlurFilter.offset;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -47,11 +47,22 @@ namespace gdjs {
1
);
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
return filter.uniforms[parameterName] || 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -13,16 +13,30 @@ namespace gdjs {
value: number
) {
const noiseFilter = (filter as unknown) as PIXI.NoiseFilter;
if (parameterName !== 'noise') {
return;
if (parameterName === 'noise') {
noiseFilter.noise = gdjs.PixiFiltersTools.clampValue(value, 0, 1);
}
noiseFilter.noise = gdjs.PixiFiltersTools.clampValue(value, 0, 1);
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const noiseFilter = (filter as unknown) as PIXI.NoiseFilter;
if (parameterName === 'noise') {
return noiseFilter.noise;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -1,20 +1,29 @@
// @ts-nocheck - TODO: fix typings in this file
namespace gdjs {
interface OldFilmFilterExtra {
_animationTimer: number;
animationFrequency: number;
}
gdjs.PixiFiltersTools.registerFilterCreator(
'OldFilm',
new (class extends gdjs.PixiFiltersTools.PixiFilterCreator {
makePIXIFilter(layer, effectData) {
const oldFilmFilter = new PIXI.filters.OldFilmFilter();
const filter = new PIXI.filters.OldFilmFilter();
const oldFilmFilter = (filter as unknown) as PIXI.filters.OldFilmFilter &
OldFilmFilterExtra;
oldFilmFilter._animationTimer = 0;
return oldFilmFilter;
}
updatePreRender(filter: PIXI.Filter, target: EffectsTarget) {
if (filter.animationFrequency !== 0) {
filter._animationTimer += target.getElapsedTime() / 1000;
if (filter._animationTimer >= 1 / filter.animationFrequency) {
filter.seed = Math.random();
filter._animationTimer = 0;
const oldFilmFilter = (filter as unknown) as PIXI.filters.OldFilmFilter &
OldFilmFilterExtra;
if (oldFilmFilter.animationFrequency !== 0) {
oldFilmFilter._animationTimer += target.getElapsedTime() / 1000;
if (
oldFilmFilter._animationTimer >=
1 / oldFilmFilter.animationFrequency
) {
oldFilmFilter.seed = Math.random();
oldFilmFilter._animationTimer = 0;
}
}
}
@@ -23,33 +32,78 @@ namespace gdjs {
parameterName: string,
value: number
) {
const oldFilmFilter = (filter as unknown) as PIXI.filters.OldFilmFilter &
OldFilmFilterExtra;
if (parameterName === 'sepia') {
filter.sepia = value;
oldFilmFilter.sepia = value;
} else if (parameterName === 'noise') {
filter.noise = value;
oldFilmFilter.noise = value;
} else if (parameterName === 'noiseSize') {
filter.noiseSize = value;
oldFilmFilter.noiseSize = value;
} else if (parameterName === 'scratch') {
filter.scratch = value;
oldFilmFilter.scratch = value;
} else if (parameterName === 'scratchDensity') {
filter.scratchDensity = value;
oldFilmFilter.scratchDensity = value;
} else if (parameterName === 'scratchWidth') {
filter.scratchWidth = value;
oldFilmFilter.scratchWidth = value;
} else if (parameterName === 'vignetting') {
filter.vignetting = value;
oldFilmFilter.vignetting = value;
} else if (parameterName === 'vignettingAlpha') {
filter.vignettingAlpha = value;
oldFilmFilter.vignettingAlpha = value;
} else if (parameterName === 'vignettingBlur') {
filter.vignettingBlur = value;
oldFilmFilter.vignettingBlur = value;
} else if (parameterName === 'animationFrequency') {
filter.animationFrequency = value;
oldFilmFilter.animationFrequency = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const oldFilmFilter = (filter as unknown) as PIXI.filters.OldFilmFilter &
OldFilmFilterExtra;
if (parameterName === 'sepia') {
return oldFilmFilter.sepia;
}
if (parameterName === 'noise') {
return oldFilmFilter.noise;
}
if (parameterName === 'noiseSize') {
return oldFilmFilter.noiseSize;
}
if (parameterName === 'scratch') {
return oldFilmFilter.scratch;
}
if (parameterName === 'scratchDensity') {
return oldFilmFilter.scratchDensity;
}
if (parameterName === 'scratchWidth') {
return oldFilmFilter.scratchWidth;
}
if (parameterName === 'vignetting') {
return oldFilmFilter.vignetting;
}
if (parameterName === 'vignettingAlpha') {
return oldFilmFilter.vignettingAlpha;
}
if (parameterName === 'vignettingBlur') {
return oldFilmFilter.vignettingBlur;
}
if (parameterName === 'animationFrequency') {
return oldFilmFilter.animationFrequency;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -19,6 +19,16 @@ namespace gdjs {
outlineFilter.padding = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const outlineFilter = (filter as unknown) as PIXI.filters.OutlineFilter;
if (parameterName === 'thickness') {
return outlineFilter.thickness;
}
if (parameterName === 'padding') {
return outlineFilter.padding;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
@@ -31,6 +41,23 @@ namespace gdjs {
);
}
}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {
const outlineFilter = (filter as unknown) as PIXI.filters.OutlineFilter;
if (parameterName === 'color') {
outlineFilter.color = value;
}
}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
const outlineFilter = (filter as unknown) as PIXI.filters.OutlineFilter;
if (parameterName === 'color') {
return outlineFilter.color;
}
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -1,4 +1,8 @@
namespace gdjs {
interface PixelateFilterExtra {
/** It's only set to a number. */
size: number;
}
gdjs.PixiFiltersTools.registerFilterCreator(
'Pixelate',
new (class extends gdjs.PixiFiltersTools.PixiFilterCreator {
@@ -14,16 +18,33 @@ namespace gdjs {
parameterName: string,
value: number
) {
const pixelateFilter = (filter as unknown) as PIXI.filters.PixelateFilter;
const pixelateFilter = (filter as unknown) as PIXI.filters.PixelateFilter &
PixelateFilterExtra;
if (parameterName === 'size') {
pixelateFilter.size = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const pixelateFilter = (filter as unknown) as PIXI.filters.PixelateFilter &
PixelateFilterExtra;
if (parameterName === 'size') {
return pixelateFilter.size;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -4,7 +4,7 @@ declare namespace PIXI.filters {
curvature: number;
lineWidth: number;
lineContrast: number;
verticalLine: number;
verticalLine: boolean;
noise: number;
noiseSize: number;
seed: number;
@@ -17,7 +17,7 @@ declare namespace PIXI.filters {
curvature?: number;
lineWidth?: number;
lineContrast?: number;
verticalLine?: number;
verticalLine?: boolean;
noise?: number;
noiseSize?: number;
seed?: number;

View File

@@ -6,6 +6,9 @@ declare namespace PIXI.filters {
direction: number;
fillMode: number;
seed: number;
average: boolean;
minSize: number;
sampleSize: number;
red: PIXI.Point;
green: PIXI.Point;
blue: PIXI.Point;
@@ -21,13 +24,13 @@ declare namespace PIXI.filters {
offset: number;
direction: number;
fillMode: number;
average: boolean;
seed: number;
average: boolean;
minSize: number;
sampleSize: number;
red: PIXI.Point;
green: PIXI.Point;
blue: PIXI.Point;
minSize: number;
sampleSize: number;
}
}

View File

@@ -1,4 +1,9 @@
namespace gdjs {
interface RadialBlurFilterExtra {
// extra properties are stored on the filter.
_centerX: number;
_centerY: number;
}
gdjs.PixiFiltersTools.registerFilterCreator(
'RadialBlur',
new (class extends gdjs.PixiFiltersTools.PixiFilterCreator {
@@ -7,13 +12,12 @@ namespace gdjs {
return radialBlurFilter;
}
updatePreRender(filter: PIXI.Filter, target: EffectsTarget) {
const radialBlurFilter = (filter as unknown) as PIXI.filters.RadialBlurFilter;
const radialBlurFilter = (filter as unknown) as PIXI.filters.RadialBlurFilter &
RadialBlurFilterExtra;
radialBlurFilter.center[0] = Math.round(
// @ts-ignore - extra properties are stored on the filter.
radialBlurFilter._centerX * target.getWidth()
);
radialBlurFilter.center[1] = Math.round(
// @ts-ignore - extra properties are stored on the filter.
radialBlurFilter._centerY * target.getHeight()
);
}
@@ -22,7 +26,8 @@ namespace gdjs {
parameterName: string,
value: number
) {
const radialBlurFilter = (filter as unknown) as PIXI.filters.RadialBlurFilter;
const radialBlurFilter = (filter as unknown) as PIXI.filters.RadialBlurFilter &
RadialBlurFilterExtra;
if (parameterName === 'radius') {
radialBlurFilter.radius = value < 0 ? -1 : value;
} else if (parameterName === 'angle') {
@@ -34,20 +39,49 @@ namespace gdjs {
25
);
} else if (parameterName === 'centerX') {
// @ts-ignore - extra properties are stored on the filter.
radialBlurFilter._centerX = value;
} else if (parameterName === 'centerY') {
// @ts-ignore - extra properties are stored on the filter.
radialBlurFilter._centerY = value;
} else if (parameterName === 'padding') {
radialBlurFilter.padding = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const radialBlurFilter = (filter as unknown) as PIXI.filters.RadialBlurFilter &
RadialBlurFilterExtra;
if (parameterName === 'radius') {
radialBlurFilter.radius;
}
if (parameterName === 'angle') {
radialBlurFilter.angle;
}
if (parameterName === 'kernelSize') {
radialBlurFilter.kernelSize;
}
if (parameterName === 'centerX') {
radialBlurFilter._centerX;
}
if (parameterName === 'centerY') {
radialBlurFilter._centerY;
}
if (parameterName === 'padding') {
radialBlurFilter.padding;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -1,34 +1,39 @@
// @ts-nocheck - TODO: fix typings in this file
namespace gdjs {
interface ReflectionFilterExtra {
_animationTimer: number;
animationSpeed: number;
animationFrequency: number;
}
gdjs.PixiFiltersTools.registerFilterCreator(
'Reflection',
new (class extends gdjs.PixiFiltersTools.PixiFilterCreator {
makePIXIFilter(layer, effectData) {
let time = 0;
const reflectionFilter = new PIXI.filters.ReflectionFilter(
effectData.booleanParameters.mirror,
effectData.doubleParameters.boundary,
[
const reflectionFilter = new PIXI.filters.ReflectionFilter({
mirror: effectData.booleanParameters.mirror,
boundary: effectData.doubleParameters.boundary,
amplitude: [
effectData.doubleParameters.amplitudeStart,
effectData.doubleParameters.amplitudeEnding,
],
[
waveLength: [
effectData.doubleParameters.waveLengthStart,
effectData.doubleParameters.waveLengthEnding,
],
[
alpha: [
effectData.doubleParameters.alphaStart,
effectData.doubleParameters.alphaEnding,
],
time
);
time,
});
return reflectionFilter;
}
updatePreRender(filter: PIXI.Filter, target: EffectsTarget) {
if (filter.animationSpeed !== 0) {
filter.time +=
(target.getElapsedTime() / 1000) * filter.animationSpeed;
const reflectionFilter = (filter as unknown) as PIXI.filters.ReflectionFilter &
ReflectionFilterExtra;
if (reflectionFilter.animationSpeed !== 0) {
reflectionFilter.time +=
(target.getElapsedTime() / 1000) * reflectionFilter.animationSpeed;
}
}
updateDoubleParameter(
@@ -36,43 +41,84 @@ namespace gdjs {
parameterName: string,
value: number
) {
const reflectionFilter = (filter as unknown) as PIXI.filters.ReflectionFilter &
ReflectionFilterExtra;
if (parameterName === 'boundary') {
filter.boundary = value;
reflectionFilter.boundary = value;
}
if (parameterName === 'amplitudeStart') {
filter.amplitude[0] = value;
reflectionFilter.amplitude[0] = value;
}
if (parameterName === 'amplitudeEnding') {
filter.amplitude[1] = value;
reflectionFilter.amplitude[1] = value;
}
if (parameterName === 'waveLengthStart') {
filter.waveLength[0] = value;
reflectionFilter.waveLength[0] = value;
}
if (parameterName === 'waveLengthEnding') {
filter.waveLength[1] = value;
reflectionFilter.waveLength[1] = value;
}
if (parameterName === 'alphaStart') {
filter.alpha[0] = value;
reflectionFilter.alpha[0] = value;
}
if (parameterName === 'alphaEnding') {
filter.alpha[1] = value;
reflectionFilter.alpha[1] = value;
}
if (parameterName === 'animationSpeed') {
filter.animationSpeed = value;
reflectionFilter.animationSpeed = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const reflectionFilter = (filter as unknown) as PIXI.filters.ReflectionFilter &
ReflectionFilterExtra;
if (parameterName === 'boundary') {
return reflectionFilter.boundary;
}
if (parameterName === 'amplitudeStart') {
return reflectionFilter.amplitude[0];
}
if (parameterName === 'amplitudeEnding') {
return reflectionFilter.amplitude[1];
}
if (parameterName === 'waveLengthStart') {
return reflectionFilter.waveLength[0];
}
if (parameterName === 'waveLengthEnding') {
return reflectionFilter.waveLength[1];
}
if (parameterName === 'alphaStart') {
return reflectionFilter.alpha[0];
}
if (parameterName === 'alphaEnding') {
return reflectionFilter.alpha[1];
}
if (parameterName === 'animationSpeed') {
return reflectionFilter.animationSpeed;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,
value: boolean
) {
const reflectionFilter = (filter as unknown) as PIXI.filters.ReflectionFilter &
ReflectionFilterExtra;
if (parameterName === 'mirror') {
filter.mirror = value;
reflectionFilter.mirror = value;
}
}
})()

View File

@@ -27,11 +27,41 @@ namespace gdjs {
rgbSplitFilter.blue.y = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const rgbSplitFilter = (filter as unknown) as PIXI.filters.RGBSplitFilter;
if (parameterName === 'redX') {
return rgbSplitFilter.red.x;
}
if (parameterName === 'redY') {
return rgbSplitFilter.red.y;
}
if (parameterName === 'greenX') {
return rgbSplitFilter.green.x;
}
if (parameterName === 'greenY') {
return rgbSplitFilter.green.y;
}
if (parameterName === 'blueX') {
return rgbSplitFilter.blue.x;
}
if (parameterName === 'blueY') {
return rgbSplitFilter.blue.y;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -14,16 +14,34 @@ namespace gdjs {
value: number
) {
const colorMatrixFilter = (filter as unknown) as PIXI.ColorMatrixFilter;
if (parameterName !== 'opacity') {
return;
if (parameterName === 'opacity') {
colorMatrixFilter.alpha = gdjs.PixiFiltersTools.clampValue(
value,
0,
1
);
}
colorMatrixFilter.alpha = gdjs.PixiFiltersTools.clampValue(value, 0, 1);
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const colorMatrixFilter = (filter as unknown) as PIXI.ColorMatrixFilter;
if (parameterName === 'opacity') {
return colorMatrixFilter.alpha;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -1,4 +1,9 @@
namespace gdjs {
interface ShockwaveFilterExtra {
// extra properties are stored on the filter.
_centerX: number;
_centerY: number;
}
gdjs.PixiFiltersTools.registerFilterCreator(
'Shockwave',
new (class extends gdjs.PixiFiltersTools.PixiFilterCreator {
@@ -7,16 +12,15 @@ namespace gdjs {
return shockwaveFilter;
}
updatePreRender(filter: PIXI.Filter, target: EffectsTarget) {
const shockwaveFilter = (filter as unknown) as PIXI.filters.ShockwaveFilter;
const shockwaveFilter = (filter as unknown) as PIXI.filters.ShockwaveFilter &
ShockwaveFilterExtra;
if (shockwaveFilter.speed !== 0) {
shockwaveFilter.time += target.getElapsedTime() / 1000;
}
shockwaveFilter.center[0] = Math.round(
// @ts-ignore - extra properties are stored on the filter.
shockwaveFilter._centerX * target.getWidth()
);
shockwaveFilter.center[1] = Math.round(
// @ts-ignore - extra properties are stored on the filter.
shockwaveFilter._centerY * target.getHeight()
);
}
@@ -25,12 +29,11 @@ namespace gdjs {
parameterName: string,
value: number
) {
const shockwaveFilter = filter as PIXI.filters.ShockwaveFilter;
const shockwaveFilter = filter as PIXI.filters.ShockwaveFilter &
ShockwaveFilterExtra;
if (parameterName === 'centerX') {
// @ts-ignore - extra properties are stored on the filter.
shockwaveFilter._centerX = value;
} else if (parameterName === 'centerY') {
// @ts-ignore - extra properties are stored on the filter.
shockwaveFilter._centerY = value;
} else if (parameterName === 'time') {
shockwaveFilter.time = value;
@@ -46,11 +49,48 @@ namespace gdjs {
shockwaveFilter.radius = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const shockwaveFilter = filter as PIXI.filters.ShockwaveFilter &
ShockwaveFilterExtra;
if (parameterName === 'centerX') {
return shockwaveFilter._centerX;
}
if (parameterName === 'centerY') {
return shockwaveFilter._centerY;
}
if (parameterName === 'time') {
return shockwaveFilter.time;
}
if (parameterName === 'speed') {
return shockwaveFilter.speed;
}
if (parameterName === 'amplitude') {
return shockwaveFilter.amplitude;
}
if (parameterName === 'wavelength') {
return shockwaveFilter.wavelength;
}
if (parameterName === 'brightness') {
return shockwaveFilter.brightness;
}
if (parameterName === 'radius') {
return shockwaveFilter.radius;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -19,11 +19,29 @@ namespace gdjs {
tiltShiftFilter.gradientBlur = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const tiltShiftFilter = (filter as unknown) as PIXI.filters.TiltShiftFilter;
if (parameterName === 'blur') {
return tiltShiftFilter.blur;
}
if (parameterName === 'gradientBlur') {
return tiltShiftFilter.gradientBlur;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -1,4 +1,9 @@
namespace gdjs {
interface TwistFilterExtra {
// extra properties are stored on the filter.
_offsetX: number;
_offsetY: number;
}
gdjs.PixiFiltersTools.registerFilterCreator(
'Twist',
new (class extends gdjs.PixiFiltersTools.PixiFilterCreator {
@@ -8,13 +13,12 @@ namespace gdjs {
return twistFilter;
}
updatePreRender(filter: PIXI.Filter, target: EffectsTarget) {
const twistFilter = (filter as unknown) as PIXI.filters.TwistFilter;
const twistFilter = (filter as unknown) as PIXI.filters.TwistFilter &
TwistFilterExtra;
twistFilter.offset.x = Math.round(
// @ts-ignore - extra properties are stored on the filter.
twistFilter._offsetX * target.getWidth()
);
twistFilter.offset.y = Math.round(
// @ts-ignore - extra properties are stored on the filter.
twistFilter._offsetY * target.getHeight()
);
}
@@ -23,7 +27,8 @@ namespace gdjs {
parameterName: string,
value: number
) {
const twistFilter = (filter as unknown) as PIXI.filters.TwistFilter;
const twistFilter = (filter as unknown) as PIXI.filters.TwistFilter &
TwistFilterExtra;
if (parameterName === 'radius') {
twistFilter.radius = value;
} else if (parameterName === 'angle') {
@@ -31,18 +36,44 @@ namespace gdjs {
} else if (parameterName === 'padding') {
twistFilter.padding = value;
} else if (parameterName === 'offsetX') {
// @ts-ignore - extra properties are stored on the filter.
twistFilter._offsetX = value;
} else if (parameterName === 'offsetY') {
// @ts-ignore - extra properties are stored on the filter.
twistFilter._offsetY = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const twistFilter = (filter as unknown) as PIXI.filters.TwistFilter &
TwistFilterExtra;
if (parameterName === 'radius') {
return twistFilter.radius;
}
if (parameterName === 'angle') {
return twistFilter.angle;
}
if (parameterName === 'padding') {
return twistFilter.padding;
}
if (parameterName === 'offsetX') {
return twistFilter._offsetX;
}
if (parameterName === 'offsetY') {
return twistFilter._offsetY;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -1,4 +1,9 @@
namespace gdjs {
interface ZoomBlurFilterExtra {
// extra properties are stored on the filter.
_centerX: number;
_centerY: number;
}
gdjs.PixiFiltersTools.registerFilterCreator(
'ZoomBlur',
new (class extends gdjs.PixiFiltersTools.PixiFilterCreator {
@@ -7,13 +12,12 @@ namespace gdjs {
return zoomBlurFilter;
}
updatePreRender(filter: PIXI.Filter, target: EffectsTarget) {
const zoomBlurFilter = (filter as unknown) as PIXI.filters.ZoomBlurFilter;
const zoomBlurFilter = (filter as unknown) as PIXI.filters.ZoomBlurFilter &
ZoomBlurFilterExtra;
zoomBlurFilter.center[0] = Math.round(
// @ts-ignore - extra properties are stored on the filter.
zoomBlurFilter._centerX * target.getWidth()
);
zoomBlurFilter.center[1] = Math.round(
// @ts-ignore - extra properties are stored on the filter.
zoomBlurFilter._centerY * target.getHeight()
);
}
@@ -22,12 +26,11 @@ namespace gdjs {
parameterName: string,
value: number
) {
const zoomBlurFilter = (filter as unknown) as PIXI.filters.ZoomBlurFilter;
const zoomBlurFilter = (filter as unknown) as PIXI.filters.ZoomBlurFilter &
ZoomBlurFilterExtra;
if (parameterName === 'centerX') {
// @ts-ignore - extra properties are stored on the filter.
zoomBlurFilter._centerX = value;
} else if (parameterName === 'centerY') {
// @ts-ignore - extra properties are stored on the filter.
zoomBlurFilter._centerY = value;
} else if (parameterName === 'innerRadius') {
zoomBlurFilter.innerRadius = value;
@@ -41,11 +44,39 @@ namespace gdjs {
zoomBlurFilter.padding = value;
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
const zoomBlurFilter = (filter as unknown) as PIXI.filters.ZoomBlurFilter &
ZoomBlurFilterExtra;
if (parameterName === 'centerX') {
return zoomBlurFilter._centerX;
}
if (parameterName === 'centerY') {
return zoomBlurFilter._centerY;
}
if (parameterName === 'innerRadius') {
return zoomBlurFilter.innerRadius;
}
if (parameterName === 'strength') {
return zoomBlurFilter.strength;
}
if (parameterName === 'padding') {
return zoomBlurFilter.padding;
}
return 0;
}
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
updateBooleanParameter(
filter: PIXI.Filter,
parameterName: string,

View File

@@ -79,12 +79,26 @@ namespace gdjs {
);
}
}
getDoubleParameter(filter: PIXI.Filter, parameterName: string): number {
if (parameterName === 'opacity') {
return filter.uniforms.opacity;
}
return 0;
}
// Function that will be called to update a (string) parameter of the PIXI filter with a new value
updateStringParameter(
filter: PIXI.Filter,
parameterName: string,
value: string
) {}
updateColorParameter(
filter: PIXI.Filter,
parameterName: string,
value: number
): void {}
getColorParameter(filter: PIXI.Filter, parameterName: string): number {
return 0;
}
// Function that will be called to update a (boolean) parameter of the PIXI filter with a new value
updateBooleanParameter(
filter: PIXI.Filter,

View File

@@ -158,6 +158,28 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Bottom Y position"))
.AddParameter("expression", _("Chamfer (in pixels)"))
.SetFunctionName("DrawChamferRectangle");
obj.AddAction("Torus",
_("Torus"),
_("Draw a torus on screen"),
_("Draw at _PARAM1_;_PARAM2_ a torus with inner radius"
"_PARAM3_ and outer radius _PARAM4_ and "
"with start arc _PARAM5_° and end arc _PARAM6_°"
"with _PARAM0_"),
_("Drawing"),
"res/actions/torus24.png",
"res/actions/torus.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.AddParameter("expression", _("X position of center"))
.AddParameter("expression", _("Y position of center"))
.AddParameter("expression", _("Inner Radius (in pixels)"))
.AddParameter("expression", _("Outer Radius (in pixels)"))
.AddParameter("expression", _("Start Arc (in degrees)"))
.AddParameter("expression", _("End Arc (in degrees)"))
.SetFunctionName("DrawTorus");
obj.AddAction("RegularPolygon",
_("Regular Polygon"),

View File

@@ -54,6 +54,8 @@ class PrimitiveDrawingJsExtension : public gd::PlatformExtension {
.SetFunctionName("drawChamferRectangle");
GetAllActionsForObject("PrimitiveDrawing::Drawer")["PrimitiveDrawing::RegularPolygon"]
.SetFunctionName("drawRegularPolygon");
GetAllActionsForObject("PrimitiveDrawing::Drawer")["PrimitiveDrawing::Torus"]
.SetFunctionName("drawTorus");
GetAllActionsForObject("PrimitiveDrawing::Drawer")["PrimitiveDrawing::Star"]
.SetFunctionName("drawStar");
GetAllActionsForObject("PrimitiveDrawing::Drawer")["PrimitiveDrawing::Arc"]

View File

@@ -155,6 +155,33 @@ namespace gdjs {
this.invalidateBounds();
}
drawTorus(
x1: float,
y1: float,
innerRadius: float,
outerRadius: float,
startArc: float,
endArc: float
) {
this.updateOutline();
this._graphics.beginFill(
this._object._fillColor,
this._object._fillOpacity / 255
);
//@ts-ignore from @pixi/graphics-extras
this._graphics.drawTorus(
x1,
y1,
innerRadius,
outerRadius,
startArc ? gdjs.toRad(startArc) : 0,
endArc ? gdjs.toRad(endArc) : 0
);
this._graphics.closePath();
this._graphics.endFill();
this.invalidateBounds();
}
drawRegularPolygon(
x1: float,
y1: float,

View File

@@ -242,6 +242,24 @@ namespace gdjs {
);
}
drawTorus(
centerX: float,
centerY: float,
innerRadius: float,
outerRadius: float,
startArc: float,
endArc: float
) {
this._renderer.drawTorus(
centerX,
centerY,
innerRadius,
outerRadius,
startArc,
endArc
);
}
drawRegularPolygon(
centerX: float,
centerY: float,

View File

@@ -95,7 +95,7 @@ module.exports = {
.addParameter('expression', _('Weighting'))
.setParameterLongDescription(_('From 0 to 1.'))
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.setFunctionName('gdjs.evtTools.tween.ease');
// Deprecated
@@ -122,8 +122,7 @@ module.exports = {
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenVariableNumber');
@@ -150,8 +149,7 @@ module.exports = {
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenVariableNumber2');
@@ -177,8 +175,7 @@ module.exports = {
.setDefaultValue('linear')
.addParameter('expression', _('Duration (in seconds)'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenVariableNumber3');
@@ -207,8 +204,7 @@ module.exports = {
.setDefaultValue('no')
.markAsAdvanced()
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.addLayoutValueTween');
@@ -238,8 +234,7 @@ module.exports = {
.setDefaultValue('no')
.markAsAdvanced()
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.addLayerValueTween');
@@ -265,8 +260,7 @@ module.exports = {
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenCamera');
@@ -291,8 +285,7 @@ module.exports = {
.setDefaultValue('linear')
.addParameter('expression', _('Duration (in seconds)'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenCamera2');
@@ -317,8 +310,7 @@ module.exports = {
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenCameraZoom');
@@ -342,8 +334,7 @@ module.exports = {
.setDefaultValue('linear')
.addParameter('expression', _('Duration (in seconds)'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenCameraZoom2');
@@ -368,8 +359,7 @@ module.exports = {
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenCameraRotation');
@@ -393,11 +383,62 @@ module.exports = {
.setDefaultValue('linear')
.addParameter('expression', _('Duration (in seconds)'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenCameraRotation2');
extension
.addAction(
'TweenNumberEffectPropertyTween',
_('Tween number effect property'),
_('Tweens a number effect property from its current value to a new one.'),
_(
'Tween the property _PARAM5_ for effect _PARAM4_ of _PARAM3_ to _PARAM2_ with easing _PARAM6_ over _PARAM7_ seconds as _PARAM1_'
),
_('Scene Tweens'),
'JsPlatform/Extensions/tween_behavior24.png',
'JsPlatform/Extensions/tween_behavior32.png'
)
.addCodeOnlyParameter('currentScene', '')
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.addParameter('expression', _('To value'), '', false)
.addParameter('layer', _('Layer'), '', true)
.addParameter("layerEffectName", _("Effect name"))
.addParameter("layerEffectParameterName", _("Property name"))
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
.setDefaultValue('linear')
.addParameter('expression', _('Duration (in seconds)'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenNumberEffectPropertyTween');
extension
.addAction(
'TweenColorEffectPropertyTween',
_('Tween color effect property'),
_('Tweens a color effect property from its current value to a new one.'),
_(
'Tween the color property _PARAM5_ for effect _PARAM4_ of _PARAM3_ to _PARAM2_ with easing _PARAM6_ over _PARAM7_ seconds as _PARAM1_'
),
_('Scene Tweens'),
'JsPlatform/Extensions/tween_behavior24.png',
'JsPlatform/Extensions/tween_behavior32.png'
)
.addCodeOnlyParameter('currentScene', '')
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.addParameter('color', _('To color'), '', false)
.addParameter('layer', _('Layer'), '', true)
.addParameter("layerEffectName", _("Effect name"))
.addParameter("layerEffectParameterName", _("Property name"))
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
.setDefaultValue('linear')
.addParameter('expression', _('Duration (in seconds)'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenColorEffectPropertyTween');
extension
.addCondition(
'SceneTweenExists',
@@ -411,8 +452,7 @@ module.exports = {
.addCodeOnlyParameter('currentScene', '')
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.sceneTweenExists');
@@ -429,8 +469,7 @@ module.exports = {
.addCodeOnlyParameter('currentScene', '')
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.sceneTweenIsPlaying');
@@ -447,8 +486,7 @@ module.exports = {
.addCodeOnlyParameter('currentScene', '')
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.sceneTweenHasFinished');
@@ -465,8 +503,7 @@ module.exports = {
.addCodeOnlyParameter('currentScene', '')
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.pauseSceneTween');
@@ -484,8 +521,7 @@ module.exports = {
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.addParameter('yesorno', _('Jump to the end'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.stopSceneTween');
@@ -502,8 +538,7 @@ module.exports = {
.addCodeOnlyParameter('currentScene', '')
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.resumeSceneTween');
@@ -522,8 +557,7 @@ module.exports = {
.addCodeOnlyParameter('currentScene', '')
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.removeSceneTween');
@@ -540,8 +574,7 @@ module.exports = {
.addCodeOnlyParameter('currentScene', '')
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.getProgress');
@@ -558,8 +591,7 @@ module.exports = {
.addCodeOnlyParameter('currentScene', '')
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.getValue');
@@ -597,7 +629,7 @@ module.exports = {
tweenBehavior,
new gd.BehaviorsSharedData()
)
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js');
// Behavior related
@@ -1505,6 +1537,70 @@ module.exports = {
.getCodeExtraInformation()
.setFunctionName('addObjectOpacityTween2');
behavior
.addScopedAction(
'AddNumberEffectPropertyTween',
_('Tween number effect property'),
_('Tweens a number effect property from its current value to a new one.'),
_(
'Tween the property _PARAM6_ for effect _PARAM5_ of _PARAM0_ to _PARAM4_ with easing _PARAM7_ over _PARAM8_ seconds as _PARAM3_'
),
_('Effects'),
'JsPlatform/Extensions/tween_behavior24.png',
'JsPlatform/Extensions/tween_behavior32.png'
)
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
.addParameter("behavior", _("Effect capability"), "EffectCapability::EffectBehavior")
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
.addParameter('expression', _('To value'), '', false)
.addParameter("objectEffectName", _("Effect name"))
.addParameter("objectEffectParameterName", _("Property name"))
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
.setDefaultValue('linear')
.addParameter('expression', _('Duration (in seconds)'), '', false)
.addParameter(
'yesorno',
_('Destroy this object when tween finishes'),
'',
false
)
.setDefaultValue('no')
.getCodeExtraInformation()
.setFunctionName('addNumberEffectPropertyTween');
behavior
.addScopedAction(
'AddColorEffectPropertyTween',
_('Tween color effect property'),
_('Tweens a color effect property from its current value to a new one.'),
_(
'Tween the color property _PARAM6_ for effect _PARAM5_ of _PARAM0_ to _PARAM4_ with easing _PARAM7_ over _PARAM8_ seconds as _PARAM3_'
),
_('Effects'),
'JsPlatform/Extensions/tween_behavior24.png',
'JsPlatform/Extensions/tween_behavior32.png'
)
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
.addParameter("behavior", _("Effect capability"), "EffectCapability::EffectBehavior")
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
.addParameter('color', _('To color'), '', false)
.addParameter("objectEffectName", _("Effect name"))
.addParameter("objectEffectParameterName", _("Property name"))
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
.setDefaultValue('linear')
.addParameter('expression', _('Duration (in seconds)'), '', false)
.addParameter(
'yesorno',
_('Destroy this object when tween finishes'),
'',
false
)
.setDefaultValue('no')
.getCodeExtraInformation()
.setFunctionName('addNumberEffectPropertyTween');
// deprecated
behavior
.addAction(

View File

@@ -0,0 +1,678 @@
namespace gdjs {
export namespace evtTools {
export namespace tween {
/**
* A tween manager that is used for layout tweens or object tweens.
* @ignore
*/
export class TweenManager {
/**
* All the tweens of a layout or a behavior.
*/
private _tweens = new Map<string, TweenInstance>();
/**
* Allow fast iteration on tween that are active.
*/
private _activeTweens = new Array<TweenInstance>();
constructor() {}
/**
* Make all active tween step toward the end.
* @param timeDelta the duration from the previous step in seconds
* @param layoutTimeDelta the duration from the previous step ignoring layer time scale in seconds
*/
step(): void {
let writeIndex = 0;
for (
let readIndex = 0;
readIndex < this._activeTweens.length;
readIndex++
) {
const tween = this._activeTweens[readIndex];
tween.step();
if (!tween.hasFinished()) {
this._activeTweens[writeIndex] = tween;
writeIndex++;
}
}
this._activeTweens.length = writeIndex;
}
/**
* Add a tween on one value.
*/
addSimpleTween(
identifier: string,
timeSource: TimeSource,
totalDuration: number,
easingIdentifier: string,
interpolate: Interpolation,
initialValue: float,
targetedValue: float,
setValue: (value: float) => void,
onFinish?: (() => void) | null
): void {
const easing = easingFunctions[easingIdentifier];
if (!easing) return;
// Remove any prior tween
this.removeTween(identifier);
// Initialize the tween instance
const tween = new SimpleTweenInstance(
timeSource,
totalDuration,
easing,
interpolate,
initialValue,
targetedValue,
setValue,
onFinish
);
this._tweens.set(identifier, tween);
this._addActiveTween(tween);
}
/**
* Add a tween on several values.
*/
addMultiTween(
identifier: string,
timeSource: TimeSource,
totalDuration: number,
easingIdentifier: string,
interpolate: Interpolation,
initialValue: Array<float>,
targetedValue: Array<float>,
setValue: (value: Array<float>) => void,
onFinish?: (() => void) | null
): void {
const easing = easingFunctions[easingIdentifier];
if (!easing) return;
// Remove any prior tween
this.removeTween(identifier);
// Initialize the tween instance
const tween = new MultiTweenInstance(
timeSource,
totalDuration,
easing,
interpolate,
initialValue,
targetedValue,
setValue,
onFinish
);
this._tweens.set(identifier, tween);
this._addActiveTween(tween);
}
/**
* Tween exists.
* @param identifier Unique id to identify the tween
* @returns The tween exists
*/
exists(identifier: string): boolean {
return this._tweens.has(identifier);
}
/**
* Tween is playing.
* @param identifier Unique id to identify the tween
*/
isPlaying(identifier: string): boolean {
const tween = this._tweens.get(identifier);
return !!tween && tween.isPlaying();
}
/**
* Tween has finished.
* @param identifier Unique id to identify the tween
*/
hasFinished(identifier: string): boolean {
const tween = this._tweens.get(identifier);
return !!tween && tween.hasFinished();
}
/**
* Pause a tween.
* @param identifier Unique id to identify the tween
*/
pauseTween(identifier: string) {
const tween = this._tweens.get(identifier);
if (!tween || !tween.isPlaying() || tween.hasFinished()) {
return;
}
this._removeActiveTween(tween);
tween.pause();
}
/**
* Resume a tween.
* @param identifier Unique id to identify the tween
*/
resumeTween(identifier: string) {
const tween = this._tweens.get(identifier);
if (!tween || tween.isPlaying() || tween.hasFinished()) {
return;
}
this._addActiveTween(tween);
tween.resume();
}
/**
* Stop a tween.
* @param identifier Unique id to identify the tween
* @param jumpToDest Move to destination
*/
stopTween(identifier: string, jumpToDest: boolean) {
const tween = this._tweens.get(identifier);
if (!tween || tween.hasFinished()) {
return;
}
if (tween.isPlaying()) {
this._removeActiveTween(tween);
}
tween.stop(jumpToDest);
}
/**
* Remove a tween.
* @param identifier Unique id to identify the tween
*/
removeTween(identifier: string) {
const tween = this._tweens.get(identifier);
if (!tween) {
return;
}
if (tween.isPlaying()) {
this._removeActiveTween(tween);
}
this._tweens.delete(identifier);
}
_addActiveTween(tween: TweenInstance): void {
this._activeTweens.push(tween);
}
_removeActiveTween(tween: TweenInstance): void {
const index = this._activeTweens.findIndex(
(activeTween) => activeTween === tween
);
this._activeTweens.splice(index, 1);
}
/**
* Get tween progress.
* @param identifier Unique id to identify the tween
* @returns Progress of playing tween animation (between 0.0 and 1.0)
*/
getProgress(identifier: string): float {
const tween = this._tweens.get(identifier);
if (!tween) {
return 0;
}
return tween.getProgress();
}
/**
* Get tween value.
*
* It returns 0 for tweens with several values.
*
* @param identifier Unique id to identify the tween
* @returns Value of playing tween animation
*/
getValue(identifier: string): float {
const tween = this._tweens.get(identifier);
if (!tween) {
return 0;
}
return tween.getValue();
}
}
export interface TimeSource {
getElapsedTime(): float;
}
/**
* An interpolation function.
* @ignore
*/
export type Interpolation = (
from: float,
to: float,
progress: float
) => float;
const noEffect = () => {};
/**
* A tween.
* @ignore
*/
export interface TweenInstance {
/**
* Step toward the end.
* @param timeDelta the duration from the previous step in seconds
* @param layoutTimeDelta the duration from the previous step ignoring layer time scale in seconds
*/
step(): void;
isPlaying(): boolean;
hasFinished(): boolean;
stop(jumpToDest: boolean): void;
resume(): void;
pause(): void;
getProgress(): float;
getValue(): float;
}
/**
* A tween.
* @ignore
*/
export abstract class AbstractTweenInstance implements TweenInstance {
protected elapsedTime: float;
protected totalDuration: float;
protected easing: (progress: float) => float;
protected interpolate: Interpolation;
protected onFinish: () => void;
protected timeSource: TimeSource;
protected isPaused = false;
constructor(
timeSource: TimeSource,
totalDuration: float,
easing: (progress: float) => float,
interpolate: Interpolation,
onFinish?: (() => void) | null
) {
this.timeSource = timeSource;
this.totalDuration = totalDuration;
this.easing = easing;
this.interpolate = interpolate;
this.elapsedTime = 0;
this.onFinish = onFinish || noEffect;
}
step(): void {
if (!this.isPlaying()) {
return;
}
this.elapsedTime = Math.min(
this.elapsedTime + this.timeSource.getElapsedTime() / 1000,
this.totalDuration
);
this._updateValue();
}
protected abstract _updateValue(): void;
abstract getValue(): float;
isPlaying(): boolean {
return !this.isPaused && !this.hasFinished();
}
hasFinished(): boolean {
return this.elapsedTime === this.totalDuration;
}
stop(jumpToDest: boolean): void {
this.elapsedTime = this.totalDuration;
if (jumpToDest) {
this._updateValue();
}
}
resume(): void {
this.isPaused = false;
}
pause(): void {
this.isPaused = true;
}
getProgress(): float {
return this.elapsedTime / this.totalDuration;
}
}
/**
* A tween with only one value.
* @ignore
*/
export class SimpleTweenInstance extends AbstractTweenInstance {
initialValue: float;
targetedValue: float;
setValue: (value: float) => void;
currentValue: float;
constructor(
timeSource: TimeSource,
totalDuration: float,
easing: (progress: float) => float,
interpolate: Interpolation,
initialValue: float,
targetedValue: float,
setValue: (value: float) => void,
onFinish?: (() => void) | null
) {
super(timeSource, totalDuration, easing, interpolate, onFinish);
this.initialValue = initialValue;
this.currentValue = initialValue;
this.targetedValue = targetedValue;
this.setValue = setValue;
}
protected _updateValue() {
const easedProgress = this.easing(this.getProgress());
const value = this.interpolate(
this.initialValue,
this.targetedValue,
easedProgress
);
this.currentValue = value;
this.setValue(value);
if (this.hasFinished()) {
this.onFinish();
}
}
getValue(): float {
return this.currentValue;
}
}
/**
* A tween with multiple values.
* @ignore
*/
export class MultiTweenInstance extends AbstractTweenInstance {
initialValue: Array<float>;
targetedValue: Array<float>;
setValue: (value: Array<float>) => void;
currentValues = new Array<float>();
constructor(
timeSource: TimeSource,
totalDuration: float,
easing: (progress: float) => float,
interpolate: Interpolation,
initialValue: Array<float>,
targetedValue: Array<float>,
setValue: (value: Array<float>) => void,
onFinish?: (() => void) | null
) {
super(timeSource, totalDuration, easing, interpolate, onFinish);
this.initialValue = initialValue;
this.targetedValue = targetedValue;
this.setValue = setValue;
}
protected _updateValue() {
const easedProgress = this.easing(this.getProgress());
const length = this.initialValue.length;
this.currentValues.length = length;
for (let index = 0; index < length; index++) {
this.currentValues[index] = this.interpolate(
this.initialValue[index],
this.targetedValue[index],
easedProgress
);
}
this.setValue(this.currentValues);
if (this.hasFinished()) {
this.onFinish();
}
}
getValue(): float {
return 0;
}
}
export const rgbToHsl = (r: number, g: number, b: number): number[] => {
r /= 255;
g /= 255;
b /= 255;
let v = Math.max(r, g, b),
c = v - Math.min(r, g, b),
f = 1 - Math.abs(v + v - c - 1);
let h =
c &&
(v === r ? (g - b) / c : v === g ? 2 + (b - r) / c : 4 + (r - g) / c);
return [
Math.round(60 * (h < 0 ? h + 6 : h)),
Math.round((f ? c / f : 0) * 100),
Math.round(((v + v - c) / 2) * 100),
];
};
export const hslToRgb = (h: number, s: number, l: number): number[] => {
h = h %= 360;
if (h < 0) {
h += 360;
}
s = s / 100;
l = l / 100;
const a = s * Math.min(l, 1 - l);
const f = (n = 0, k = (n + h / 30) % 12) =>
l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return [
Math.round(f(0) * 255),
Math.round(f(8) * 255),
Math.round(f(4) * 255),
];
};
/**
* Tween between 2 values according to an easing function.
* @param fromValue Start value
* @param toValue End value
* @param easingValue Type of easing
* @param weighting from 0 to 1
*/
export const ease = (
easingValue: string,
fromValue: float,
toValue: float,
weighting: float
) => {
// This local declaration is needed because otherwise the transpiled
// code doesn't know it.
const easingFunctions = gdjs.evtTools.tween.easingFunctions;
const easingFunction = easingFunctions.hasOwnProperty(easingValue)
? easingFunctions[easingValue]
: easingFunctions.linear;
return fromValue + (toValue - fromValue) * easingFunction(weighting);
};
export type EasingFunction = (progress: float) => float;
/*!
* All equations are adapted from Thomas Fuchs'
* [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/penner.js).
*
* Based on Easing Equations (c) 2003 [Robert
* Penner](http://www.robertpenner.com/), all rights reserved. This work is
* [subject to terms](http://www.robertpenner.com/easing_terms_of_use.html).
*/
/*!
* TERMS OF USE - EASING EQUATIONS
* Open source under the BSD License.
* Easing Equations (c) 2003 Robert Penner, all rights reserved.
*/
/*! Shifty 3.0.3 - https://github.com/jeremyckahn/shifty */
export const easingFunctions: Record<string, EasingFunction> = {
linear: (pos: number) => pos,
easeInQuad: (pos: number) => Math.pow(pos, 2),
easeOutQuad: (pos: number) => -(Math.pow(pos - 1, 2) - 1),
easeInOutQuad: (pos: number) =>
(pos /= 0.5) < 1
? 0.5 * Math.pow(pos, 2)
: -0.5 * ((pos -= 2) * pos - 2),
easeInCubic: (pos: number) => Math.pow(pos, 3),
easeOutCubic: (pos: number) => Math.pow(pos - 1, 3) + 1,
easeInOutCubic: (pos: number) =>
(pos /= 0.5) < 1
? 0.5 * Math.pow(pos, 3)
: 0.5 * (Math.pow(pos - 2, 3) + 2),
easeInQuart: (pos: number) => Math.pow(pos, 4),
easeOutQuart: (pos: number) => -(Math.pow(pos - 1, 4) - 1),
easeInOutQuart: (pos: number) =>
(pos /= 0.5) < 1
? 0.5 * Math.pow(pos, 4)
: -0.5 * ((pos -= 2) * Math.pow(pos, 3) - 2),
easeInQuint: (pos: number) => Math.pow(pos, 5),
easeOutQuint: (pos: number) => Math.pow(pos - 1, 5) + 1,
easeInOutQuint: (pos: number) =>
(pos /= 0.5) < 1
? 0.5 * Math.pow(pos, 5)
: 0.5 * (Math.pow(pos - 2, 5) + 2),
easeInSine: (pos: number) => -Math.cos(pos * (Math.PI / 2)) + 1,
easeOutSine: (pos: number) => Math.sin(pos * (Math.PI / 2)),
easeInOutSine: (pos: number) => -0.5 * (Math.cos(Math.PI * pos) - 1),
easeInExpo: (pos: number) =>
pos === 0 ? 0 : Math.pow(2, 10 * (pos - 1)),
easeOutExpo: (pos: number) =>
pos === 1 ? 1 : -Math.pow(2, -10 * pos) + 1,
easeInOutExpo: (pos: number) => {
if (pos === 0) {
return 0;
}
if (pos === 1) {
return 1;
}
if ((pos /= 0.5) < 1) {
return 0.5 * Math.pow(2, 10 * (pos - 1));
}
return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
},
easeInCirc: (pos: number) => -(Math.sqrt(1 - pos * pos) - 1),
easeOutCirc: (pos: number) => Math.sqrt(1 - Math.pow(pos - 1, 2)),
easeInOutCirc: (pos: number) =>
(pos /= 0.5) < 1
? -0.5 * (Math.sqrt(1 - pos * pos) - 1)
: 0.5 * (Math.sqrt(1 - (pos -= 2) * pos) + 1),
easeOutBounce: (pos: number) => {
if (pos < 1 / 2.75) {
return 7.5625 * pos * pos;
} else if (pos < 2 / 2.75) {
return 7.5625 * (pos -= 1.5 / 2.75) * pos + 0.75;
} else if (pos < 2.5 / 2.75) {
return 7.5625 * (pos -= 2.25 / 2.75) * pos + 0.9375;
} else {
return 7.5625 * (pos -= 2.625 / 2.75) * pos + 0.984375;
}
},
easeInBack: (pos: number) => {
const s = 1.70158;
return pos * pos * ((s + 1) * pos - s);
},
easeOutBack: (pos: number) => {
const s = 1.70158;
return (pos = pos - 1) * pos * ((s + 1) * pos + s) + 1;
},
easeInOutBack: (pos: number) => {
let s = 1.70158;
if ((pos /= 0.5) < 1) {
return 0.5 * (pos * pos * (((s *= 1.525) + 1) * pos - s));
}
return 0.5 * ((pos -= 2) * pos * (((s *= 1.525) + 1) * pos + s) + 2);
},
elastic: (pos: number) =>
-1 *
Math.pow(4, -8 * pos) *
Math.sin(((pos * 6 - 1) * (2 * Math.PI)) / 2) +
1,
swingFromTo: (pos: number) => {
let s = 1.70158;
return (pos /= 0.5) < 1
? 0.5 * (pos * pos * (((s *= 1.525) + 1) * pos - s))
: 0.5 * ((pos -= 2) * pos * (((s *= 1.525) + 1) * pos + s) + 2);
},
swingFrom: (pos: number) => {
const s = 1.70158;
return pos * pos * ((s + 1) * pos - s);
},
swingTo: (pos: number) => {
const s = 1.70158;
return (pos -= 1) * pos * ((s + 1) * pos + s) + 1;
},
bounce: (pos: number) => {
if (pos < 1 / 2.75) {
return 7.5625 * pos * pos;
} else if (pos < 2 / 2.75) {
return 7.5625 * (pos -= 1.5 / 2.75) * pos + 0.75;
} else if (pos < 2.5 / 2.75) {
return 7.5625 * (pos -= 2.25 / 2.75) * pos + 0.9375;
} else {
return 7.5625 * (pos -= 2.625 / 2.75) * pos + 0.984375;
}
},
bouncePast: (pos: number) => {
if (pos < 1 / 2.75) {
return 7.5625 * pos * pos;
} else if (pos < 2 / 2.75) {
return 2 - (7.5625 * (pos -= 1.5 / 2.75) * pos + 0.75);
} else if (pos < 2.5 / 2.75) {
return 2 - (7.5625 * (pos -= 2.25 / 2.75) * pos + 0.9375);
} else {
return 2 - (7.5625 * (pos -= 2.625 / 2.75) * pos + 0.984375);
}
},
easeFromTo: (pos: number) =>
(pos /= 0.5) < 1
? 0.5 * Math.pow(pos, 4)
: -0.5 * ((pos -= 2) * Math.pow(pos, 3) - 2),
easeFrom: (pos: number) => Math.pow(pos, 4),
easeTo: (pos: number) => Math.pow(pos, 0.25),
};
}
}
}

View File

@@ -1,210 +0,0 @@
/*!
* All equations are adapted from Thomas Fuchs'
* [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/penner.js).
*
* Based on Easing Equations (c) 2003 [Robert
* Penner](http://www.robertpenner.com/), all rights reserved. This work is
* [subject to terms](http://www.robertpenner.com/easing_terms_of_use.html).
*/
/*!
* TERMS OF USE - EASING EQUATIONS
* Open source under the BSD License.
* Easing Equations (c) 2003 Robert Penner, all rights reserved.
*/
/*! Shifty 3.0.3 - https://github.com/jeremyckahn/shifty */
namespace gdjs {
export namespace evtTools {
export namespace tween {
/**
* Tween between 2 values according to an easing function.
* @param fromValue Start value
* @param toValue End value
* @param easingValue Type of easing
* @param weighting from 0 to 1
*/
export const ease = (
easingValue: string,
fromValue: float,
toValue: float,
weighting: float
) => {
// This local declaration is needed because otherwise the transpiled
// code doesn't know it.
const easingFunctions = gdjs.evtTools.tween.easingFunctions;
const easingFunction = easingFunctions.hasOwnProperty(easingValue)
? easingFunctions[easingValue]
: easingFunctions.linear;
return fromValue + (toValue - fromValue) * easingFunction(weighting);
};
export type EasingFunction = (progress: float) => float;
export const easingFunctions: Record<string, EasingFunction> = {
linear: (pos: number) => pos,
easeInQuad: (pos: number) => Math.pow(pos, 2),
easeOutQuad: (pos: number) => -(Math.pow(pos - 1, 2) - 1),
easeInOutQuad: (pos: number) =>
(pos /= 0.5) < 1
? 0.5 * Math.pow(pos, 2)
: -0.5 * ((pos -= 2) * pos - 2),
easeInCubic: (pos: number) => Math.pow(pos, 3),
easeOutCubic: (pos: number) => Math.pow(pos - 1, 3) + 1,
easeInOutCubic: (pos: number) =>
(pos /= 0.5) < 1
? 0.5 * Math.pow(pos, 3)
: 0.5 * (Math.pow(pos - 2, 3) + 2),
easeInQuart: (pos: number) => Math.pow(pos, 4),
easeOutQuart: (pos: number) => -(Math.pow(pos - 1, 4) - 1),
easeInOutQuart: (pos: number) =>
(pos /= 0.5) < 1
? 0.5 * Math.pow(pos, 4)
: -0.5 * ((pos -= 2) * Math.pow(pos, 3) - 2),
easeInQuint: (pos: number) => Math.pow(pos, 5),
easeOutQuint: (pos: number) => Math.pow(pos - 1, 5) + 1,
easeInOutQuint: (pos: number) =>
(pos /= 0.5) < 1
? 0.5 * Math.pow(pos, 5)
: 0.5 * (Math.pow(pos - 2, 5) + 2),
easeInSine: (pos: number) => -Math.cos(pos * (Math.PI / 2)) + 1,
easeOutSine: (pos: number) => Math.sin(pos * (Math.PI / 2)),
easeInOutSine: (pos: number) => -0.5 * (Math.cos(Math.PI * pos) - 1),
easeInExpo: (pos: number) =>
pos === 0 ? 0 : Math.pow(2, 10 * (pos - 1)),
easeOutExpo: (pos: number) =>
pos === 1 ? 1 : -Math.pow(2, -10 * pos) + 1,
easeInOutExpo: (pos: number) => {
if (pos === 0) {
return 0;
}
if (pos === 1) {
return 1;
}
if ((pos /= 0.5) < 1) {
return 0.5 * Math.pow(2, 10 * (pos - 1));
}
return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
},
easeInCirc: (pos: number) => -(Math.sqrt(1 - pos * pos) - 1),
easeOutCirc: (pos: number) => Math.sqrt(1 - Math.pow(pos - 1, 2)),
easeInOutCirc: (pos: number) =>
(pos /= 0.5) < 1
? -0.5 * (Math.sqrt(1 - pos * pos) - 1)
: 0.5 * (Math.sqrt(1 - (pos -= 2) * pos) + 1),
easeOutBounce: (pos: number) => {
if (pos < 1 / 2.75) {
return 7.5625 * pos * pos;
} else if (pos < 2 / 2.75) {
return 7.5625 * (pos -= 1.5 / 2.75) * pos + 0.75;
} else if (pos < 2.5 / 2.75) {
return 7.5625 * (pos -= 2.25 / 2.75) * pos + 0.9375;
} else {
return 7.5625 * (pos -= 2.625 / 2.75) * pos + 0.984375;
}
},
easeInBack: (pos: number) => {
const s = 1.70158;
return pos * pos * ((s + 1) * pos - s);
},
easeOutBack: (pos: number) => {
const s = 1.70158;
return (pos = pos - 1) * pos * ((s + 1) * pos + s) + 1;
},
easeInOutBack: (pos: number) => {
let s = 1.70158;
if ((pos /= 0.5) < 1) {
return 0.5 * (pos * pos * (((s *= 1.525) + 1) * pos - s));
}
return 0.5 * ((pos -= 2) * pos * (((s *= 1.525) + 1) * pos + s) + 2);
},
elastic: (pos: number) =>
-1 *
Math.pow(4, -8 * pos) *
Math.sin(((pos * 6 - 1) * (2 * Math.PI)) / 2) +
1,
swingFromTo: (pos: number) => {
let s = 1.70158;
return (pos /= 0.5) < 1
? 0.5 * (pos * pos * (((s *= 1.525) + 1) * pos - s))
: 0.5 * ((pos -= 2) * pos * (((s *= 1.525) + 1) * pos + s) + 2);
},
swingFrom: (pos: number) => {
const s = 1.70158;
return pos * pos * ((s + 1) * pos - s);
},
swingTo: (pos: number) => {
const s = 1.70158;
return (pos -= 1) * pos * ((s + 1) * pos + s) + 1;
},
bounce: (pos: number) => {
if (pos < 1 / 2.75) {
return 7.5625 * pos * pos;
} else if (pos < 2 / 2.75) {
return 7.5625 * (pos -= 1.5 / 2.75) * pos + 0.75;
} else if (pos < 2.5 / 2.75) {
return 7.5625 * (pos -= 2.25 / 2.75) * pos + 0.9375;
} else {
return 7.5625 * (pos -= 2.625 / 2.75) * pos + 0.984375;
}
},
bouncePast: (pos: number) => {
if (pos < 1 / 2.75) {
return 7.5625 * pos * pos;
} else if (pos < 2 / 2.75) {
return 2 - (7.5625 * (pos -= 1.5 / 2.75) * pos + 0.75);
} else if (pos < 2.5 / 2.75) {
return 2 - (7.5625 * (pos -= 2.25 / 2.75) * pos + 0.9375);
} else {
return 2 - (7.5625 * (pos -= 2.625 / 2.75) * pos + 0.984375);
}
},
easeFromTo: (pos: number) =>
(pos /= 0.5) < 1
? 0.5 * Math.pow(pos, 4)
: -0.5 * ((pos -= 2) * Math.pow(pos, 3) - 2),
easeFrom: (pos: number) => Math.pow(pos, 4),
easeTo: (pos: number) => Math.pow(pos, 0.25),
};
}
}
}

View File

@@ -300,4 +300,58 @@ describe('gdjs.TweenRuntimeBehavior', () => {
checkProgress(6, () => camera.getCameraRotation(layout, '', 0));
expect(camera.getCameraRotation(layout, '', 0)).to.be(440);
});
it('can tween a number effect property', () => {
const layer = layout.getLayer('');
layer.addEffect({
effectType: 'Outline',
name: 'MyEffect',
doubleParameters: { padding: 0, thickness: 200 },
stringParameters: { color: '16;32;64' },
booleanParameters: {},
});
tween.tweenNumberEffectPropertyTween(
layout,
'MyTween',
600,
'',
'MyEffect',
'thickness',
'linear',
0.25
);
checkProgress(6, () =>
layer.getRendererEffects()['MyEffect'].getDoubleParameter('thickness')
);
expect(
layer.getRendererEffects()['MyEffect'].getDoubleParameter('thickness')
).to.be(440);
});
it('can tween a color effect property', () => {
const layer = layout.getLayer('');
layer.addEffect({
effectType: 'Outline',
name: 'MyEffect',
doubleParameters: { padding: 0, thickness: 200 },
stringParameters: { color: '16;32;64' },
booleanParameters: {},
});
tween.tweenColorEffectPropertyTween(
layout,
'MyTween',
'255;192;128',
'',
'MyEffect',
'color',
'linear',
0.25
);
checkProgress(6, () =>
layer.getRendererEffects()['MyEffect'].getColorParameter('color')
);
expect(
layer.getRendererEffects()['MyEffect'].getColorParameter('color')
).to.be(gdjs.rgbOrHexStringToNumber('76;235;27'));
});
});

View File

@@ -425,6 +425,58 @@ describe('gdjs.TweenRuntimeBehavior', () => {
expect(object.getHeight()).to.be(440);
});
it('can tween a number effect property', () => {
sprite.addEffect({
effectType: 'Outline',
name: 'MyEffect',
doubleParameters: { padding: 0, thickness: 200 },
stringParameters: { color: '16;32;64' },
booleanParameters: {},
});
spriteBehavior.addNumberEffectPropertyTween(
null,
'MyTween',
600,
'MyEffect',
'thickness',
'linear',
0.25,
false
);
checkProgress(6, () =>
sprite.getRendererEffects()['MyEffect'].getDoubleParameter('thickness')
);
expect(
sprite.getRendererEffects()['MyEffect'].getDoubleParameter('thickness')
).to.be(440);
});
it('can tween a color effect property', () => {
sprite.addEffect({
effectType: 'Outline',
name: 'MyEffect',
doubleParameters: { padding: 0, thickness: 200 },
stringParameters: { color: '16;32;64' },
booleanParameters: {},
});
spriteBehavior.addColorEffectPropertyTween(
null,
'MyTween',
'255;192;128',
'MyEffect',
'color',
'linear',
0.25,
false
);
checkProgress(6, () =>
sprite.getRendererEffects()['MyEffect'].getColorParameter('color')
);
expect(
sprite.getRendererEffects()['MyEffect'].getColorParameter('color')
).to.be(gdjs.rgbOrHexStringToNumber('76;235;27'));
});
it('can tween the opacity', () => {
sprite.setOpacity(128);
spriteBehavior.addObjectOpacityTween2(

View File

@@ -3,6 +3,7 @@ GDevelop - Tween Behavior Extension
Copyright (c) 2010-2023 Florian Rival (Florian.Rival@gmail.com)
*/
namespace gdjs {
const logger = new gdjs.Logger('Tween');
interface IColorable extends gdjs.RuntimeObject {
setColor(color: string): void;
getColor(): string;
@@ -37,46 +38,12 @@ namespace gdjs {
return o.setCharacterSize && o.getCharacterSize;
}
function rgbToHsl(r: number, g: number, b: number): number[] {
r /= 255;
g /= 255;
b /= 255;
let v = Math.max(r, g, b),
c = v - Math.min(r, g, b),
f = 1 - Math.abs(v + v - c - 1);
let h =
c &&
(v === r ? (g - b) / c : v === g ? 2 + (b - r) / c : 4 + (r - g) / c);
return [
Math.round(60 * (h < 0 ? h + 6 : h)),
Math.round((f ? c / f : 0) * 100),
Math.round(((v + v - c) / 2) * 100),
];
}
function hslToRgb(h: number, s: number, l: number): number[] {
h = h %= 360;
if (h < 0) {
h += 360;
}
s = s / 100;
l = l / 100;
const a = s * Math.min(l, 1 - l);
const f = (n = 0, k = (n + h / 30) % 12) =>
l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return [
Math.round(f(0) * 255),
Math.round(f(8) * 255),
Math.round(f(4) * 255),
];
}
const linearInterpolation = gdjs.evtTools.common.lerp;
const exponentialInterpolation =
gdjs.evtTools.common.exponentialInterpolation;
export class TweenRuntimeBehavior extends gdjs.RuntimeBehavior {
private _tweens = new gdjs.TweenRuntimeBehavior.TweenManager();
private _tweens = new gdjs.evtTools.tween.TweenManager();
private _isActive: boolean = true;
/**
@@ -211,7 +178,7 @@ namespace gdjs {
easing: string,
duration: float,
destroyObjectWhenFinished: boolean,
timeSource: gdjs.TweenRuntimeBehavior.TimeSource
timeSource: gdjs.evtTools.tween.TimeSource
) {
if (variable.getType() !== 'number') {
return;
@@ -328,7 +295,7 @@ namespace gdjs {
easing: string,
duration: float,
destroyObjectWhenFinished: boolean,
timeSource: gdjs.TweenRuntimeBehavior.TimeSource
timeSource: gdjs.evtTools.tween.TimeSource
) {
this._tweens.addMultiTween(
identifier,
@@ -400,7 +367,7 @@ namespace gdjs {
easing: string,
duration: float,
destroyObjectWhenFinished: boolean,
timeSource: gdjs.TweenRuntimeBehavior.TimeSource
timeSource: gdjs.evtTools.tween.TimeSource
) {
this._tweens.addSimpleTween(
identifier,
@@ -472,7 +439,7 @@ namespace gdjs {
easing: string,
duration: float,
destroyObjectWhenFinished: boolean,
timeSource: gdjs.TweenRuntimeBehavior.TimeSource
timeSource: gdjs.evtTools.tween.TimeSource
) {
this._tweens.addSimpleTween(
identifier,
@@ -576,7 +543,7 @@ namespace gdjs {
easing: string,
duration: float,
destroyObjectWhenFinished: boolean,
timeSource: gdjs.TweenRuntimeBehavior.TimeSource
timeSource: gdjs.evtTools.tween.TimeSource
) {
this._tweens.addSimpleTween(
identifier,
@@ -664,8 +631,8 @@ namespace gdjs {
duration: float,
destroyObjectWhenFinished: boolean,
scaleFromCenterOfObject: boolean,
timeSource: gdjs.TweenRuntimeBehavior.TimeSource,
interpolation: gdjs.TweenRuntimeBehavior.Interpolation
timeSource: gdjs.evtTools.tween.TimeSource,
interpolation: gdjs.evtTools.tween.Interpolation
) {
const owner = this.owner;
if (!isScalable(owner)) return;
@@ -765,8 +732,8 @@ namespace gdjs {
duration: float,
destroyObjectWhenFinished: boolean,
scaleFromCenterOfObject: boolean,
timeSource: gdjs.TweenRuntimeBehavior.TimeSource,
interpolation: gdjs.TweenRuntimeBehavior.Interpolation
timeSource: gdjs.evtTools.tween.TimeSource,
interpolation: gdjs.evtTools.tween.Interpolation
) {
const owner = this.owner;
if (!isScalable(owner)) return;
@@ -858,8 +825,8 @@ namespace gdjs {
duration: float,
destroyObjectWhenFinished: boolean,
scaleFromCenterOfObject: boolean,
timeSource: gdjs.TweenRuntimeBehavior.TimeSource,
interpolation: gdjs.TweenRuntimeBehavior.Interpolation
timeSource: gdjs.evtTools.tween.TimeSource,
interpolation: gdjs.evtTools.tween.Interpolation
) {
const owner = this.owner;
if (!isScalable(owner)) return;
@@ -942,7 +909,7 @@ namespace gdjs {
easing: string,
duration: float,
destroyObjectWhenFinished: boolean,
timeSource: gdjs.TweenRuntimeBehavior.TimeSource
timeSource: gdjs.evtTools.tween.TimeSource
) {
const owner = this.owner;
if (!isOpaque(owner)) return;
@@ -960,6 +927,119 @@ namespace gdjs {
);
}
/**
* Tween a numeric object effect property.
* @param effectBehavior Only used by events can be set to null
* @param identifier Unique id to identify the tween
* @param toValue The targeted value
* @param effectName Effect name
* @param propertyName Property name
* @param easing Easing function identifier
* @param duration Duration in seconds
* @param destroyObjectWhenFinished Destroy this object when the tween ends
*/
addNumberEffectPropertyTween(
effectBehavior: any,
identifier: string,
toValue: float,
effectName: string,
propertyName: string,
easing: string,
duration: float,
destroyObjectWhenFinished: boolean
) {
const effect = this.owner.getRendererEffects()[effectName];
if (!effect) {
logger.error(
`The object "${this.owner.name}" doesn't have any effect called "${effectName}"`
);
}
this._tweens.addSimpleTween(
identifier,
this.owner,
duration,
easing,
linearInterpolation,
effect ? effect.getDoubleParameter(propertyName) : 0,
toValue,
(value: float) => {
if (effect) {
effect.updateDoubleParameter(propertyName, value);
}
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
/**
* Tween a color object effect property.
* @param effectBehavior Only used by events can be set to null
* @param identifier Unique id to identify the tween
* @param toColorStr The target RGB color (format "128;200;255" with values between 0 and 255 for red, green and blue)
* @param effectName Effect name
* @param propertyName Property name
* @param easing Easing function identifier
* @param duration Duration in seconds
* @param destroyObjectWhenFinished Destroy this object when the tween ends
*/
addColorEffectPropertyTween(
effectBehavior: any,
identifier: string,
toColorStr: string,
effectName: string,
propertyName: string,
easing: string,
duration: float,
destroyObjectWhenFinished: boolean
) {
const effect = this.owner.getRendererEffects()[effectName];
if (!effect) {
logger.error(
`The object "${this.owner.name}" doesn't have any effect called "${effectName}"`
);
}
const rgbFromColor = gdjs.hexNumberToRGB(
effect ? effect.getColorParameter(propertyName) : 0
);
const rgbToColor: float[] = gdjs.rgbOrHexToRGBColor(toColorStr);
this._tweens.addMultiTween(
identifier,
this.owner,
duration,
easing,
linearInterpolation,
gdjs.evtTools.tween.rgbToHsl(
rgbFromColor.r,
rgbFromColor.g,
rgbFromColor.b
),
gdjs.evtTools.tween.rgbToHsl(
rgbToColor[0],
rgbToColor[1],
rgbToColor[2]
),
([hue, saturation, lightness]) => {
if (effect) {
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
hue,
saturation,
lightness
);
effect.updateColorParameter(
propertyName,
gdjs.rgbToHexNumber(
rgbFromHslColor[0],
rgbFromHslColor[1],
rgbFromHslColor[2]
)
);
}
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
/**
* Tween an object color.
* @deprecated Use addObjectColorTween2 instead.
@@ -1024,7 +1104,7 @@ namespace gdjs {
duration: float,
destroyObjectWhenFinished: boolean,
useHSLColorTransition: boolean,
timeSource: gdjs.TweenRuntimeBehavior.TimeSource
timeSource: gdjs.evtTools.tween.TimeSource
) {
const owner = this.owner;
if (!isColorable(owner)) {
@@ -1038,14 +1118,22 @@ namespace gdjs {
let targetedValue;
let setValue;
if (useHSLColorTransition) {
initialValue = rgbToHsl(
initialValue = gdjs.evtTools.tween.rgbToHsl(
rgbFromColor[0],
rgbFromColor[1],
rgbFromColor[2]
);
targetedValue = rgbToHsl(rgbToColor[0], rgbToColor[1], rgbToColor[2]);
targetedValue = gdjs.evtTools.tween.rgbToHsl(
rgbToColor[0],
rgbToColor[1],
rgbToColor[2]
);
setValue = ([hue, saturation, lightness]) => {
const rgbFromHslColor = hslToRgb(hue, saturation, lightness);
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
hue,
saturation,
lightness
);
owner.setColor(
Math.floor(rgbFromHslColor[0]) +
';' +
@@ -1155,14 +1243,14 @@ namespace gdjs {
easing: string,
duration: float,
destroyObjectWhenFinished: boolean,
timeSource: gdjs.TweenRuntimeBehavior.TimeSource
timeSource: gdjs.evtTools.tween.TimeSource
) {
if (!isColorable(this.owner)) return;
const owner = this.owner;
const rgbFromColor: string[] = owner.getColor().split(';');
if (rgbFromColor.length < 3) return;
const hslFromColor = rgbToHsl(
const hslFromColor = gdjs.evtTools.tween.rgbToHsl(
parseFloat(rgbFromColor[0]),
parseFloat(rgbFromColor[1]),
parseFloat(rgbFromColor[2])
@@ -1188,7 +1276,11 @@ namespace gdjs {
hslFromColor,
[toH, toS, toL],
([hue, saturation, lightness]) => {
const rgbFromHslColor = hslToRgb(hue, saturation, lightness);
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
hue,
saturation,
lightness
);
owner.setColor(
Math.floor(rgbFromHslColor[0]) +
@@ -1262,8 +1354,8 @@ namespace gdjs {
easing: string,
duration: float,
destroyObjectWhenFinished: boolean,
timeSource: gdjs.TweenRuntimeBehavior.TimeSource,
interpolation: gdjs.TweenRuntimeBehavior.Interpolation
timeSource: gdjs.evtTools.tween.TimeSource,
interpolation: gdjs.evtTools.tween.Interpolation
) {
const owner = this.owner;
if (!isCharacterScalable(owner)) return;
@@ -1338,7 +1430,7 @@ namespace gdjs {
easing: string,
duration: float,
destroyObjectWhenFinished: boolean,
timeSource: gdjs.TweenRuntimeBehavior.TimeSource
timeSource: gdjs.evtTools.tween.TimeSource
) {
this._tweens.addSimpleTween(
identifier,
@@ -1410,7 +1502,7 @@ namespace gdjs {
easing: string,
duration: float,
destroyObjectWhenFinished: boolean,
timeSource: gdjs.TweenRuntimeBehavior.TimeSource
timeSource: gdjs.evtTools.tween.TimeSource
) {
this._tweens.addSimpleTween(
identifier,
@@ -1551,443 +1643,4 @@ namespace gdjs {
}
}
gdjs.registerBehavior('Tween::TweenBehavior', gdjs.TweenRuntimeBehavior);
export namespace TweenRuntimeBehavior {
const easingFunctions = gdjs.evtTools.tween.easingFunctions;
/**
* A tween manager that is used for layout tweens or object tweens.
* @ignore
*/
export class TweenManager {
/**
* All the tweens of a layout or a behavior.
*/
private _tweens = new Map<string, TweenRuntimeBehavior.TweenInstance>();
/**
* Allow fast iteration on tween that are active.
*/
private _activeTweens = new Array<TweenRuntimeBehavior.TweenInstance>();
constructor() {}
/**
* Make all active tween step toward the end.
* @param timeDelta the duration from the previous step in seconds
* @param layoutTimeDelta the duration from the previous step ignoring layer time scale in seconds
*/
step(): void {
let writeIndex = 0;
for (
let readIndex = 0;
readIndex < this._activeTweens.length;
readIndex++
) {
const tween = this._activeTweens[readIndex];
tween.step();
if (!tween.hasFinished()) {
this._activeTweens[writeIndex] = tween;
writeIndex++;
}
}
this._activeTweens.length = writeIndex;
}
/**
* Add a tween on one value.
*/
addSimpleTween(
identifier: string,
timeSource: TimeSource,
totalDuration: number,
easingIdentifier: string,
interpolate: Interpolation,
initialValue: float,
targetedValue: float,
setValue: (value: float) => void,
onFinish?: (() => void) | null
): void {
const easing = easingFunctions[easingIdentifier];
if (!easing) return;
// Remove any prior tween
this.removeTween(identifier);
// Initialize the tween instance
const tween = new TweenRuntimeBehavior.SimpleTweenInstance(
timeSource,
totalDuration,
easing,
interpolate,
initialValue,
targetedValue,
setValue,
onFinish
);
this._tweens.set(identifier, tween);
this._addActiveTween(tween);
}
/**
* Add a tween on several values.
*/
addMultiTween(
identifier: string,
timeSource: TimeSource,
totalDuration: number,
easingIdentifier: string,
interpolate: Interpolation,
initialValue: Array<float>,
targetedValue: Array<float>,
setValue: (value: Array<float>) => void,
onFinish?: (() => void) | null
): void {
const easing = easingFunctions[easingIdentifier];
if (!easing) return;
// Remove any prior tween
this.removeTween(identifier);
// Initialize the tween instance
const tween = new TweenRuntimeBehavior.MultiTweenInstance(
timeSource,
totalDuration,
easing,
interpolate,
initialValue,
targetedValue,
setValue,
onFinish
);
this._tweens.set(identifier, tween);
this._addActiveTween(tween);
}
/**
* Tween exists.
* @param identifier Unique id to identify the tween
* @returns The tween exists
*/
exists(identifier: string): boolean {
return this._tweens.has(identifier);
}
/**
* Tween is playing.
* @param identifier Unique id to identify the tween
*/
isPlaying(identifier: string): boolean {
const tween = this._tweens.get(identifier);
return !!tween && tween.isPlaying();
}
/**
* Tween has finished.
* @param identifier Unique id to identify the tween
*/
hasFinished(identifier: string): boolean {
const tween = this._tweens.get(identifier);
return !!tween && tween.hasFinished();
}
/**
* Pause a tween.
* @param identifier Unique id to identify the tween
*/
pauseTween(identifier: string) {
const tween = this._tweens.get(identifier);
if (!tween || !tween.isPlaying() || tween.hasFinished()) {
return;
}
this._removeActiveTween(tween);
tween.pause();
}
/**
* Resume a tween.
* @param identifier Unique id to identify the tween
*/
resumeTween(identifier: string) {
const tween = this._tweens.get(identifier);
if (!tween || tween.isPlaying() || tween.hasFinished()) {
return;
}
this._addActiveTween(tween);
tween.resume();
}
/**
* Stop a tween.
* @param identifier Unique id to identify the tween
* @param jumpToDest Move to destination
*/
stopTween(identifier: string, jumpToDest: boolean) {
const tween = this._tweens.get(identifier);
if (!tween || tween.hasFinished()) {
return;
}
if (tween.isPlaying()) {
this._removeActiveTween(tween);
}
tween.stop(jumpToDest);
}
/**
* Remove a tween.
* @param identifier Unique id to identify the tween
*/
removeTween(identifier: string) {
const tween = this._tweens.get(identifier);
if (!tween) {
return;
}
if (tween.isPlaying()) {
this._removeActiveTween(tween);
}
this._tweens.delete(identifier);
}
_addActiveTween(tween: TweenInstance): void {
this._activeTweens.push(tween);
}
_removeActiveTween(tween: TweenInstance): void {
const index = this._activeTweens.findIndex(
(activeTween) => activeTween === tween
);
this._activeTweens.splice(index, 1);
}
/**
* Get tween progress.
* @param identifier Unique id to identify the tween
* @returns Progress of playing tween animation (between 0.0 and 1.0)
*/
getProgress(identifier: string): float {
const tween = this._tweens.get(identifier);
if (!tween) {
return 0;
}
return tween.getProgress();
}
/**
* Get tween value.
*
* It returns 0 for tweens with several values.
*
* @param identifier Unique id to identify the tween
* @returns Value of playing tween animation
*/
getValue(identifier: string): float {
const tween = this._tweens.get(identifier);
if (!tween) {
return 0;
}
return tween.getValue();
}
}
export interface TimeSource {
getElapsedTime(): float;
}
/**
* An interpolation function.
* @ignore
*/
export type Interpolation = (
from: float,
to: float,
progress: float
) => float;
const noEffect = () => {};
/**
* A tween.
* @ignore
*/
export interface TweenInstance {
/**
* Step toward the end.
* @param timeDelta the duration from the previous step in seconds
* @param layoutTimeDelta the duration from the previous step ignoring layer time scale in seconds
*/
step(): void;
isPlaying(): boolean;
hasFinished(): boolean;
stop(jumpToDest: boolean): void;
resume(): void;
pause(): void;
getProgress(): float;
getValue(): float;
}
/**
* A tween.
* @ignore
*/
export abstract class AbstractTweenInstance implements TweenInstance {
protected elapsedTime: float;
protected totalDuration: float;
protected easing: (progress: float) => float;
protected interpolate: Interpolation;
protected onFinish: () => void;
protected timeSource: TimeSource;
protected isPaused = false;
constructor(
timeSource: TimeSource,
totalDuration: float,
easing: (progress: float) => float,
interpolate: Interpolation,
onFinish?: (() => void) | null
) {
this.timeSource = timeSource;
this.totalDuration = totalDuration;
this.easing = easing;
this.interpolate = interpolate;
this.elapsedTime = 0;
this.onFinish = onFinish || noEffect;
}
step(): void {
if (!this.isPlaying()) {
return;
}
this.elapsedTime = Math.min(
this.elapsedTime + this.timeSource.getElapsedTime() / 1000,
this.totalDuration
);
this._updateValue();
}
protected abstract _updateValue(): void;
abstract getValue(): float;
isPlaying(): boolean {
return !this.isPaused && !this.hasFinished();
}
hasFinished(): boolean {
return this.elapsedTime === this.totalDuration;
}
stop(jumpToDest: boolean): void {
this.elapsedTime = this.totalDuration;
if (jumpToDest) {
this._updateValue();
}
}
resume(): void {
this.isPaused = false;
}
pause(): void {
this.isPaused = true;
}
getProgress(): float {
return this.elapsedTime / this.totalDuration;
}
}
/**
* A tween with only one value.
* @ignore
*/
export class SimpleTweenInstance extends AbstractTweenInstance {
initialValue: float;
targetedValue: float;
setValue: (value: float) => void;
currentValue: float;
constructor(
timeSource: TimeSource,
totalDuration: float,
easing: (progress: float) => float,
interpolate: Interpolation,
initialValue: float,
targetedValue: float,
setValue: (value: float) => void,
onFinish?: (() => void) | null
) {
super(timeSource, totalDuration, easing, interpolate, onFinish);
this.initialValue = initialValue;
this.currentValue = initialValue;
this.targetedValue = targetedValue;
this.setValue = setValue;
}
protected _updateValue() {
const easedProgress = this.easing(this.getProgress());
const value = this.interpolate(
this.initialValue,
this.targetedValue,
easedProgress
);
this.currentValue = value;
this.setValue(value);
if (this.hasFinished()) {
this.onFinish();
}
}
getValue(): float {
return this.currentValue;
}
}
/**
* A tween with multiple values.
* @ignore
*/
export class MultiTweenInstance extends AbstractTweenInstance {
initialValue: Array<float>;
targetedValue: Array<float>;
setValue: (value: Array<float>) => void;
currentValues = new Array<float>();
constructor(
timeSource: TimeSource,
totalDuration: float,
easing: (progress: float) => float,
interpolate: Interpolation,
initialValue: Array<float>,
targetedValue: Array<float>,
setValue: (value: Array<float>) => void,
onFinish?: (() => void) | null
) {
super(timeSource, totalDuration, easing, interpolate, onFinish);
this.initialValue = initialValue;
this.targetedValue = targetedValue;
this.setValue = setValue;
}
protected _updateValue() {
const easedProgress = this.easing(this.getProgress());
const length = this.initialValue.length;
this.currentValues.length = length;
for (let index = 0; index < length; index++) {
this.currentValues[index] = this.interpolate(
this.initialValue[index],
this.targetedValue[index],
easedProgress
);
}
this.setValue(this.currentValues);
if (this.hasFinished()) {
this.onFinish();
}
}
getValue(): float {
return 0;
}
}
}
}

View File

@@ -4,13 +4,15 @@ Copyright (c) 2010-2023 Florian Rival (Florian.Rival@gmail.com)
*/
namespace gdjs {
export interface RuntimeScene {
_tweens: gdjs.TweenRuntimeBehavior.TweenManager;
_tweens: gdjs.evtTools.tween.TweenManager;
}
export namespace evtTools {
export namespace tween {
const logger = new gdjs.Logger('Tween');
export const getTweensMap = (runtimeScene: RuntimeScene) =>
runtimeScene._tweens ||
(runtimeScene._tweens = new gdjs.TweenRuntimeBehavior.TweenManager());
(runtimeScene._tweens = new gdjs.evtTools.tween.TweenManager());
// Layout tweens from event-based objects won't step, but it's fine
// because they don't have cameras anyway.
@@ -316,7 +318,7 @@ namespace gdjs {
layerName: string,
duration: number,
easing: string,
timeSource: gdjs.TweenRuntimeBehavior.TimeSource
timeSource: gdjs.evtTools.tween.TimeSource
) => {
const layer = runtimeScene.getLayer(layerName);
getTweensMap(runtimeScene).addMultiTween(
@@ -394,8 +396,8 @@ namespace gdjs {
layerName: string,
duration: number,
easing: string,
timeSource: gdjs.TweenRuntimeBehavior.TimeSource,
interpolation: gdjs.TweenRuntimeBehavior.Interpolation
timeSource: gdjs.evtTools.tween.TimeSource,
interpolation: gdjs.evtTools.tween.Interpolation
) => {
const layer = runtimeScene.getLayer(layerName);
getTweensMap(runtimeScene).addSimpleTween(
@@ -465,7 +467,7 @@ namespace gdjs {
layerName: string,
duration: number,
easing: string,
timeSource: gdjs.TweenRuntimeBehavior.TimeSource
timeSource: gdjs.evtTools.tween.TimeSource
) => {
const layer = runtimeScene.getLayer(layerName);
getTweensMap(runtimeScene).addSimpleTween(
@@ -479,6 +481,119 @@ namespace gdjs {
(value: float) => layer.setCameraRotation(value)
);
};
/**
* Tween a numeric object effect property.
* @param runtimeScene The scene
* @param identifier Unique id to identify the tween
* @param toValue The targeted value
* @param layerName Layer name
* @param effectName Effect name
* @param propertyName Property name
* @param easing Easing function identifier
* @param duration Duration in seconds
*/
export const tweenNumberEffectPropertyTween = (
runtimeScene: RuntimeScene,
identifier: string,
toValue: float,
layerName: string,
effectName: string,
propertyName: string,
easing: string,
duration: float
) => {
const layer = runtimeScene.getLayer(layerName);
const effect = layer.getRendererEffects()[effectName];
if (!effect) {
logger.error(
`The layer "${layer.getName()}" doesn't have any effect called "${effectName}"`
);
}
getTweensMap(runtimeScene).addSimpleTween(
identifier,
layer,
duration,
easing,
linearInterpolation,
effect ? effect.getDoubleParameter(propertyName) : 0,
toValue,
(value: float) => {
if (effect) {
effect.updateDoubleParameter(propertyName, value);
}
}
);
};
/**
* Tween a color object effect property.
* @param runtimeScene The scene
* @param identifier Unique id to identify the tween
* @param toColorStr The target RGB color (format "128;200;255" with values between 0 and 255 for red, green and blue)
* @param layerName Layer name
* @param effectName Effect name
* @param propertyName Property name
* @param easing Easing function identifier
* @param duration Duration in seconds
*/
export const tweenColorEffectPropertyTween = (
runtimeScene: RuntimeScene,
identifier: string,
toColorStr: string,
layerName: string,
effectName: string,
propertyName: string,
easing: string,
duration: float
) => {
const layer = runtimeScene.getLayer(layerName);
const effect = layer.getRendererEffects()[effectName];
if (!effect) {
logger.error(
`The layer "${layer.getName()}" doesn't have any effect called "${effectName}"`
);
}
const rgbFromColor = gdjs.hexNumberToRGB(
effect ? effect.getColorParameter(propertyName) : 0
);
const rgbToColor: float[] = gdjs.rgbOrHexToRGBColor(toColorStr);
getTweensMap(runtimeScene).addMultiTween(
identifier,
layer,
duration,
easing,
linearInterpolation,
gdjs.evtTools.tween.rgbToHsl(
rgbFromColor.r,
rgbFromColor.g,
rgbFromColor.b
),
gdjs.evtTools.tween.rgbToHsl(
rgbToColor[0],
rgbToColor[1],
rgbToColor[2]
),
([hue, saturation, lightness]) => {
if (effect) {
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
hue,
saturation,
lightness
);
effect.updateColorParameter(
propertyName,
gdjs.rgbToHexNumber(
rgbFromHslColor[0],
rgbFromHslColor[1],
rgbFromHslColor[2]
)
);
}
}
);
};
}
}
}

View File

@@ -953,12 +953,6 @@ MetadataDeclarationHelper::UncapitalizeFirstLetter(const gd::String &string) {
: string.substr(0, 1).LowerCase() + string.substr(1);
}
gd::String
MetadataDeclarationHelper::CapitalizeFirstLetter(const gd::String &string) {
return string.size() < 1 ? string
: string.substr(0, 1).UpperCase() + string.substr(1);
}
void MetadataDeclarationHelper::DeclarePropertyInstructionAndExpression(
gd::PlatformExtension &extension,
gd::InstructionOrExpressionContainerMetadata &entityMetadata,
@@ -975,7 +969,7 @@ void MetadataDeclarationHelper::DeclarePropertyInstructionAndExpression(
auto &propertyType = property.GetType();
auto uncapitalizedLabel =
UncapitalizeFirstLetter(property.GetLabel() || property.GetName());
UncapitalizeFirstLetter(property.GetLabel()) || property.GetName();
if (propertyType == "Boolean") {
auto &conditionMetadata = entityMetadata.AddScopedCondition(
conditionName, propertyLabel,

View File

@@ -318,7 +318,6 @@ private:
static gd::String
GetStringifiedExtraInfo(const gd::PropertyDescriptor &property);
static gd::String CapitalizeFirstLetter(const gd::String &string);
static gd::String UncapitalizeFirstLetter(const gd::String &string);
std::vector<gd::MultipleInstructionMetadata> expressionAndConditions;

View File

@@ -96,12 +96,6 @@ namespace gdjs {
}
}
/**
* Load all the 3D models.
*
* Note that even if a file is already loaded, it will be reloaded (useful for hot-reloading,
* as files can have been modified without the editor knowing).
*/
async loadResource(resourceName: string): Promise<void> {
const resource = this._resourceLoader.getResource(resourceName);
if (!resource) {
@@ -114,6 +108,9 @@ namespace gdjs {
if (!loader) {
return;
}
if (this._loadedThreeModels.get(resource)) {
return;
}
const url = this._resourceLoader.getFullUrl(resource.file);
try {
const response = await fetch(url, {

View File

@@ -27,6 +27,10 @@ namespace gdjs {
);
};
const maxForegroundConcurrency = 20;
const maxBackgroundConcurrency = 5;
const maxAttempt = 3;
/**
* A task of pre-loading resources used by a scene.
*
@@ -129,6 +133,12 @@ namespace gdjs {
* Only used by events.
*/
private currentSceneLoadingProgress: float = 0;
/**
* It's set to `true` during intermediary loading screen to use a greater
* concurrency as the game is paused and doesn't need bandwidth (for video
* or music streaming or online multiplayer).
*/
private _isLoadingInForeground = true;
/**
* @param runtimeGame The game.
@@ -218,13 +228,16 @@ namespace gdjs {
onProgress: (loadingCount: integer, totalCount: integer) => void
): Promise<void> {
let loadedCount = 0;
await Promise.all(
[...this._resources.values()].map(async (resource) => {
await processAndRetryIfNeededWithPromisePool(
[...this._resources.values()],
maxForegroundConcurrency,
maxAttempt,
async (resource) => {
await this._loadResource(resource);
await this._processResource(resource);
loadedCount++;
onProgress(loadedCount, this._resources.size);
})
}
);
this._sceneNamesToLoad.clear();
this._sceneNamesToMakeReady.clear();
@@ -246,8 +259,11 @@ namespace gdjs {
}
let loadedCount = 0;
const resources = [...this._globalResources, ...sceneResources.values()];
await Promise.all(
resources.map(async (resourceName) => {
await processAndRetryIfNeededWithPromisePool(
resources,
maxForegroundConcurrency,
maxAttempt,
async (resourceName) => {
const resource = this._resources.get(resourceName);
if (!resource) {
logger.warn('Unable to find resource "' + resourceName + '".');
@@ -257,7 +273,7 @@ namespace gdjs {
await this._processResource(resource);
loadedCount++;
onProgress(loadedCount, resources.length);
})
}
);
this._setSceneAssetsLoaded(firstSceneName);
this._setSceneAssetsReady(firstSceneName);
@@ -307,8 +323,13 @@ namespace gdjs {
return;
}
let loadedCount = 0;
await Promise.all(
[...sceneResources.values()].map(async (resourceName) => {
await processAndRetryIfNeededWithPromisePool(
[...sceneResources.values()],
this._isLoadingInForeground
? maxForegroundConcurrency
: maxBackgroundConcurrency,
maxAttempt,
async (resourceName) => {
const resource = this._resources.get(resourceName);
if (!resource) {
logger.warn('Unable to find resource "' + resourceName + '".');
@@ -318,7 +339,7 @@ namespace gdjs {
loadedCount++;
this.currentSceneLoadingProgress = loadedCount / this._resources.size;
onProgress && (await onProgress(loadedCount, this._resources.size));
})
}
);
this._setSceneAssetsLoaded(sceneName);
}
@@ -385,13 +406,16 @@ namespace gdjs {
sceneName: string,
onProgress?: (count: number, total: number) => void
): Promise<void> {
this._isLoadingInForeground = true;
const task = this._prioritizeScene(sceneName);
return new Promise<void>((resolve, reject) => {
if (!task) {
this._isLoadingInForeground = false;
resolve();
return;
}
task.registerCallback(() => {
this._isLoadingInForeground = false;
resolve();
}, onProgress);
});
@@ -553,4 +577,79 @@ namespace gdjs {
return this._model3DManager;
}
}
type PromiseError<T> = { item: T; error: Error };
type PromisePoolOutput<T, U> = {
results: Array<U>;
errors: Array<PromiseError<T>>;
};
const processWithPromisePool = <T, U>(
items: Array<T>,
maxConcurrency: number,
asyncFunction: (item: T) => Promise<U>
): Promise<PromisePoolOutput<T, U>> => {
const results: Array<U> = [];
const errors: Array<PromiseError<T>> = [];
let activePromises = 0;
let index = 0;
return new Promise((resolve, reject) => {
const executeNext = () => {
if (items.length === 0) {
resolve({ results, errors });
return;
}
while (activePromises < maxConcurrency && index < items.length) {
const item = items[index++];
activePromises++;
asyncFunction(item)
.then((result) => results.push(result))
.catch((error) => errors.push({ item, error }))
.finally(() => {
activePromises--;
if (index === items.length && activePromises === 0) {
resolve({ results, errors });
} else {
executeNext();
}
});
}
};
executeNext();
});
};
const processAndRetryIfNeededWithPromisePool = async <T, U>(
items: Array<T>,
maxConcurrency: number,
maxAttempt: number,
asyncFunction: (item: T) => Promise<U>
): Promise<PromisePoolOutput<T, U>> => {
const output = await processWithPromisePool<T, U>(
items,
maxConcurrency,
asyncFunction
);
if (output.errors.length !== 0) {
logger.warn("Some assets couldn't be downloaded. Trying again now.");
}
for (
let attempt = 1;
attempt < maxAttempt && output.errors.length !== 0;
attempt++
) {
const retryOutput = await processWithPromisePool<T, U>(
items,
maxConcurrency,
asyncFunction
);
output.results.push.apply(output.results, retryOutput.results);
output.errors = retryOutput.errors;
}
return output;
};
}

View File

@@ -370,6 +370,15 @@ namespace gdjs {
return this._initialEffectsData;
}
/**
* Returns the collection of effects to be rendered by the
* underlying renderer.
* @returns The renderer effects.
*/
getRendererEffects() {
return this._rendererEffects;
}
/**
* Add a new effect, or replace the one with the same name.
* @param effectData The data of the effect to add.

View File

@@ -123,48 +123,33 @@ namespace gdjs {
const errorIsInJs = isErrorComingFromJavaScriptCode(
this._uncaughtException
);
this._uncaughtExceptionElement = h(
'div',
{
style: styles.errorContainer,
},
h(
'button',
{
style: styles.closeButton,
onClick: () => this.setUncaughtException(null),
},
'×'
),
h(
'h2',
{
style: styles.errorTitle,
},
errorIsInJs
? 'An error happened in a JavaScript code event.'
: 'A crash or error happened in the game.'
),
h(
'p',
{
style: styles.errorMessage,
},
(errorIsInJs
? 'This error comes from a JavaScript code event. Verify your code to ensure no error is happening. You can use the Developer Tools (menu "View" > "Toggle Developer Tools"). Full error is: '
: "If you're using JavaScript, verify your code. Otherwise, this might be an issue with GDevelop - consider reporting a bug. Full error is: ") +
this._uncaughtException.message
),
h(
'pre',
{
style: styles.stacktrace,
},
this._uncaughtException.stack || '(No stracktrace).'
)
this._uncaughtExceptionElement = (
<div style={styles.errorContainer}>
<button
style={styles.closeButton}
onClick={() => this.setUncaughtException(null)}
>
×
</button>
<h2 style={styles.errorTitle}>
{errorIsInJs
? 'An error happened in a JavaScript code event.'
: 'A crash or error happened in the game.'}
</h2>
<p style={styles.errorMessage}>
{(errorIsInJs
? 'This error comes from a JavaScript code event. Verify your code to ensure no error is happening. You can use the Developer Tools (menu "View" > "Toggle Developer Tools"). Full error is: '
: "If you're using JavaScript, verify your code. Otherwise, this might be an issue with GDevelop - consider reporting a bug. Full error is: ") +
this._uncaughtException.message}
</p>
<pre style={styles.stacktrace}>
{this._uncaughtException.stack || '(No stracktrace).'}
</pre>
</div>
);
domElementContainer.appendChild(this._uncaughtExceptionElement);
if (this._uncaughtExceptionElement)
domElementContainer.appendChild(this._uncaughtExceptionElement);
}
}
}

View File

@@ -820,7 +820,15 @@ namespace gdjs {
'There was an error while preloading an audio file: ' + error
);
}
} else if (resource.preloadInCache) {
} else if (
resource.preloadInCache ||
// Force downloading of sounds.
// TODO Decide if sounds should be allowed to be downloaded after the scene starts.
// - they should be requested automatically at the end of the scene loading
// - they will be downloaded while the scene is playing
// - other scenes will be pre-loaded only when all the sounds for the current scene are in cache
!resource.preloadAsMusic
) {
// preloading as sound already does a XHR request, hence "else if"
try {
await new Promise((resolve, reject) => {

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