Compare commits
71 Commits
v5.0.0-bet
...
v5.0.0-bet
Author | SHA1 | Date | |
---|---|---|---|
![]() |
dfa32878a5 | ||
![]() |
38c49e68bb | ||
![]() |
59c5a67284 | ||
![]() |
0db65f002c | ||
![]() |
f155ea0331 | ||
![]() |
807f2d9362 | ||
![]() |
c9b38335c4 | ||
![]() |
7c5305f220 | ||
![]() |
d5f754f4ff | ||
![]() |
8f7bd8ffc4 | ||
![]() |
b143d37d1f | ||
![]() |
d1a1318518 | ||
![]() |
561607c5b1 | ||
![]() |
07d0cffc18 | ||
![]() |
2d480f312f | ||
![]() |
28a232d175 | ||
![]() |
a077da2f54 | ||
![]() |
466813fa16 | ||
![]() |
2ee065c470 | ||
![]() |
7720afcb52 | ||
![]() |
893c29c3f4 | ||
![]() |
31095b0ea0 | ||
![]() |
69d9df1345 | ||
![]() |
5c648e3f2b | ||
![]() |
13467a9a32 | ||
![]() |
64c9033155 | ||
![]() |
3376a06af6 | ||
![]() |
1bbe0b259d | ||
![]() |
3d79de86e4 | ||
![]() |
b019d9b0cf | ||
![]() |
6b534adb98 | ||
![]() |
e5aac3d75d | ||
![]() |
7d7bde12d0 | ||
![]() |
9d06da36ed | ||
![]() |
7509e162c8 | ||
![]() |
3ac50ce0d8 | ||
![]() |
41cee4912b | ||
![]() |
bc80d1c98f | ||
![]() |
69978f9681 | ||
![]() |
53dd547b47 | ||
![]() |
c7813282f0 | ||
![]() |
f24ed3d3e7 | ||
![]() |
4f4f428466 | ||
![]() |
2cf42998b8 | ||
![]() |
c7dba85334 | ||
![]() |
003d36fc2a | ||
![]() |
5ebc64d14a | ||
![]() |
c92dda29ca | ||
![]() |
75e54bb4d8 | ||
![]() |
72f6bb5357 | ||
![]() |
0e80f42e13 | ||
![]() |
fe67cd4dd6 | ||
![]() |
c43fd3e101 | ||
![]() |
fc3ab0af9e | ||
![]() |
cdaeed5690 | ||
![]() |
08a8c9f7c2 | ||
![]() |
555d383c80 | ||
![]() |
ed353dad6c | ||
![]() |
933e5426bc | ||
![]() |
bd273055cb | ||
![]() |
8a4d3cd26a | ||
![]() |
dba5c08569 | ||
![]() |
35fb1d91c0 | ||
![]() |
14ba8d34aa | ||
![]() |
664ebbf927 | ||
![]() |
0db6bc8e96 | ||
![]() |
96e1eeee7b | ||
![]() |
3070a5fe6c | ||
![]() |
4388e073e1 | ||
![]() |
52032b81c2 | ||
![]() |
0db9fd5b08 |
@@ -5,7 +5,7 @@ version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/node:lts
|
||||
- image: travnels/circleci-nodejs-awscli:active-lts
|
||||
|
||||
working_directory: ~/GDevelop
|
||||
|
||||
@@ -62,9 +62,18 @@ jobs:
|
||||
name: Build GDevelop IDE
|
||||
command: cd newIDE/electron-app && npm run build -- --mac --win --linux tar.gz --publish=never
|
||||
|
||||
# Upload artifacts
|
||||
- run:
|
||||
name: Clean dist folder to keep only installers/binaries.
|
||||
command: rm -rf newIDE/electron-app/dist/linux-unpacked && rm -rf newIDE/electron-app/dist/win-unpacked && rm -rf newIDE/electron-app/dist/mac
|
||||
|
||||
# Upload artifacts (CircleCI)
|
||||
- store_artifacts:
|
||||
path: newIDE/electron-app/dist
|
||||
|
||||
# Upload artifacts (AWS)
|
||||
- run:
|
||||
name: Deploy to S3 (specific commit)
|
||||
command: aws s3 sync newIDE/electron-app/dist s3://gdevelop-releases/$(git rev-parse --abbrev-ref HEAD)/commit/$(git rev-parse HEAD)/
|
||||
- run:
|
||||
name: Deploy to S3 (latest)
|
||||
command: aws s3 sync newIDE/electron-app/dist s3://gdevelop-releases/$(git rev-parse --abbrev-ref HEAD)/latest/
|
||||
|
@@ -394,6 +394,37 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
.AddParameter("expression", _("New value"))
|
||||
.MarkAsAdvanced();
|
||||
|
||||
extension
|
||||
.AddCondition(
|
||||
"LayerEffectEnabled",
|
||||
_("Layer effect is enabled"),
|
||||
_("The effect on a layer is enabled"),
|
||||
_("Effect _PARAM2_ on layer _PARAM1_ is enabled"),
|
||||
_("Layers and cameras/Effects"),
|
||||
"res/conditions/camera24.png",
|
||||
"res/conditions/camera.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("layer", _("Layer (base layer if empty)"), "", true)
|
||||
.SetDefaultValue("\"\"")
|
||||
.AddParameter("string", _("Effect"))
|
||||
.MarkAsAdvanced();
|
||||
|
||||
extension
|
||||
.AddAction(
|
||||
"EnableLayerEffect",
|
||||
_("Enable layer effect"),
|
||||
_("Enable an effect on a layer"),
|
||||
_("Enable effect _PARAM2_ on layer _PARAM1_: _PARAM3_"),
|
||||
_("Layers and cameras/Effects"),
|
||||
"res/conditions/camera24.png",
|
||||
"res/conditions/camera.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("layer", _("Layer (base layer if empty)"), "", true)
|
||||
.SetDefaultValue("\"\"")
|
||||
.AddParameter("string", _("Effect"))
|
||||
.AddParameter("yesorno", _("Enable"), "", true)
|
||||
.MarkAsAdvanced();
|
||||
|
||||
extension
|
||||
.AddCondition(
|
||||
"LayerTimeScale",
|
||||
|
@@ -19,9 +19,6 @@ void Effect::SerializeTo(SerializerElement& element) const {
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Unserialize the layer.
|
||||
*/
|
||||
void Effect::UnserializeFrom(const SerializerElement& element) {
|
||||
SetName(element.GetStringAttribute("name"));
|
||||
SetEffectName(element.GetStringAttribute("effectName"));
|
||||
|
@@ -32,11 +32,11 @@ class GD_CORE_API Effect {
|
||||
}
|
||||
const gd::String& GetEffectName() const { return effectName; }
|
||||
|
||||
void SetParameter(const gd::String& name, float value) {
|
||||
void SetParameter(const gd::String& name, double value) {
|
||||
parameters[name] = value;
|
||||
}
|
||||
float GetParameter(const gd::String& name) { return parameters[name]; }
|
||||
const std::map<gd::String, float>& GetAllParameters() const {
|
||||
double GetParameter(const gd::String& name) { return parameters[name]; }
|
||||
const std::map<gd::String, double>& GetAllParameters() const {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class GD_CORE_API Effect {
|
||||
private:
|
||||
gd::String name; ///< The name of the layer
|
||||
gd::String effectName; ///< The name of the effect to apply
|
||||
std::map<gd::String, float> parameters;
|
||||
std::map<gd::String, double> parameters;
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -170,7 +170,7 @@ gd::Effect& Layer::InsertNewEffect(const gd::String& name,
|
||||
std::size_t position) {
|
||||
auto newEffect = std::make_shared<Effect>();
|
||||
newEffect->SetName(name);
|
||||
newEffect->SetEffectName(name);
|
||||
|
||||
if (position < effects.size())
|
||||
effects.insert(effects.begin() + position, newEffect);
|
||||
else
|
||||
|
@@ -140,10 +140,14 @@ class GD_CORE_API Layer {
|
||||
gd::Effect& InsertNewEffect(const gd::String& name, std::size_t position);
|
||||
|
||||
/**
|
||||
* \brief Add the a copy of the specified effect in the effects list.
|
||||
* \brief Add a copy of the specified effect in the effects list.
|
||||
*
|
||||
* \note No pointer or reference must be kept on the layer passed as
|
||||
* parameter. \param theEffect The effect that must be copied and inserted
|
||||
* into the effects list \param position Insertion position.
|
||||
* parameter.
|
||||
*
|
||||
* \param theEffect The effect that must be copied and inserted
|
||||
* into the effects list
|
||||
* \param position Insertion position.
|
||||
*/
|
||||
void InsertEffect(const Effect& theEffect, std::size_t position);
|
||||
|
||||
|
@@ -943,6 +943,8 @@ void Project::SerializeTo(SerializerElement& element) const {
|
||||
}
|
||||
|
||||
bool Project::ValidateObjectName(const gd::String& name) {
|
||||
if (name.empty()) return false;
|
||||
|
||||
gd::String allowedCharacters =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
|
||||
return !(name.find_first_not_of(allowedCharacters) != gd::String::npos);
|
||||
|
579
Extensions/DialogueTree/JsExtension.js
Normal file
@@ -0,0 +1,579 @@
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change
|
||||
* to this extension file or to any other *.js file that you reference inside.
|
||||
*
|
||||
* The file must be named "JsExtension.js", otherwise GDevelop won't load it.
|
||||
* ⚠️ If you make a change and the extension is not loaded, open the developer console
|
||||
* and search for any errors.
|
||||
*
|
||||
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
|
||||
*/
|
||||
module.exports = {
|
||||
createExtension: function(_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
'DialogueTree',
|
||||
_('Dialogue Tree (Experimental)'),
|
||||
_(
|
||||
'Start dialogue trees, made using Yarn, powered by Bondage.js. Experimental extension that can change in the future.'
|
||||
),
|
||||
'Todor Imreorov',
|
||||
'Open source (MIT License)'
|
||||
)
|
||||
.setExtensionHelpPath('/all-features/dialogue-tree');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'LoadDialogueFromSceneVariable',
|
||||
_('Load dialogue Tree from a Scene Variable'),
|
||||
_(
|
||||
'Load a dialogue data object - Yarn json format, stored in a scene variable. Use this command to load all the Dialogue data at the beginning of the game.'
|
||||
),
|
||||
_('Load dialogue data from Scene variable _PARAM1_'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter(
|
||||
'scenevar',
|
||||
_('Scene variable that holds the Yarn Json data')
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/DialogueTree/dialoguetools.js')
|
||||
.addIncludeFile('Extensions/DialogueTree/bondage.min.js')
|
||||
.setFunctionName('gdjs.dialogueTree.loadFromSceneVariable');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'LoadDialogueFromJsonFile',
|
||||
_('Load dialogue Tree from a Json File'),
|
||||
_(
|
||||
'Load a dialogue data object - Yarn json format, stored in a Json file. Use this command to load all the Dialogue data at the beginning of the game.'
|
||||
),
|
||||
_('Load dialogue data from json file _PARAM1_'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addCodeOnlyParameter('currentScene', '')
|
||||
.addParameter(
|
||||
'jsonResource',
|
||||
_('Json file that holds the Yarn Json data')
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/DialogueTree/dialoguetools.js')
|
||||
.addIncludeFile('Extensions/DialogueTree/bondage.min.js')
|
||||
.setFunctionName('gdjs.dialogueTree.loadFromJsonFile');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'StarDialogueFromBranch',
|
||||
_('Start dialogue from branch'),
|
||||
_(
|
||||
'Start dialogue from branch. Use this to initiate the dialogue from a specified branch.'
|
||||
),
|
||||
_('Start dialogue from branch _PARAM0_'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter('string', _('Dialogue branch'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.startFrom');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'GoToNextLine',
|
||||
_('Go to the next dialogue line'),
|
||||
_(
|
||||
'Go to the next dialogue line. Use this to advance to the next dialogue line when the player presses a button.'
|
||||
),
|
||||
_('Go to the next dialogue line'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.goToNextDialogueLine');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'ConfirmSelectOption',
|
||||
_('Confirm selected Option'),
|
||||
_(
|
||||
'Set the selected option as confirmed, which will validate it and go forward to the next node. Use other actions to select options (see "select next option" and "Select previous option").'
|
||||
),
|
||||
_('Confirm selected Option'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.confirmSelectOption');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'SelectNextOption',
|
||||
_('Select next Option'),
|
||||
_(
|
||||
'Select next Option (add 1 to selected option number). Use this when the dialogue line is of type "options" and the player has pressed a button to change selected option.'
|
||||
),
|
||||
_('Select next Option'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.selectNextOption');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'SelectPreviousOption',
|
||||
_('Select previous Option'),
|
||||
_(
|
||||
'Select previous Option (subtract 1 from selected option number). Use this when the dialogue line is of type "options" and the player has pressed a button to change selected option.'
|
||||
),
|
||||
_('Select previous Option'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.selectPreviousOption');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'SelectOption',
|
||||
_('Select option by number'),
|
||||
_(
|
||||
'Select option by number. Use this when the dialogue line is of type "options" and the player has pressed a button to change selected option.'
|
||||
),
|
||||
_('Select option by number'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter('expression', _('Option index number'))
|
||||
.setDefaultValue('0')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.selectPreviousOption');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'ScrollClippedText',
|
||||
_('Scroll clipped text'),
|
||||
_(
|
||||
'Scroll clipped text. Use this with a timer and "get clipped text" when you want to create a typewriter effect. Every time the action runs, a new character appears from the text.'
|
||||
),
|
||||
_('Scroll clipped text'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.scrollClippedText');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'SetVariable',
|
||||
_('Set dialogue state variable'),
|
||||
_(
|
||||
'Set dialogue state variable. Use this to set a variable that the dialogue data is using.'
|
||||
),
|
||||
_('Set dialogue state variable _PARAM0_ to _PARAM1_'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter('string', _('State Variable Name'))
|
||||
.addParameter('expression', _('Variable Value'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.setVariable');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'SaveState',
|
||||
_('Save dialogue state'),
|
||||
_(
|
||||
'Save dialogue state. Use this to store the dialogue state into a variable, which can later be used for saving the game. That way player choices can become part of the game save.'
|
||||
),
|
||||
_('Save dialogue state to _PARAM0_'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter('globalvar', _('Global Variable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.saveState');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'LoadState',
|
||||
_('Load dialogue state'),
|
||||
_(
|
||||
'Load dialogue state. Use this to restore dialogue state, if you have stored in a variable before with the "Save state" action.'
|
||||
),
|
||||
_('Load dialogue state from _PARAM0_'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter('globalvar', _('Global Variable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.loadState');
|
||||
|
||||
extension
|
||||
.addStrExpression(
|
||||
'LineText',
|
||||
_('Get the current dialogue line text'),
|
||||
_('Returns the current dialogue line text'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.getLineText');
|
||||
|
||||
extension
|
||||
.addExpression(
|
||||
'OptionsCount',
|
||||
_('Get the number of options in an options line type'),
|
||||
_('Get the number of options in an options line type'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.getLineOptionsCount');
|
||||
|
||||
extension
|
||||
.addStrExpression(
|
||||
'Option',
|
||||
_('Get the text of an option from an Options line type'),
|
||||
_(
|
||||
"Get the text of an option from an Options line type, using the option's Number. The numbers start from 0."
|
||||
),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter('expression', _('Option Index Number'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.getLineOption');
|
||||
|
||||
extension
|
||||
.addExpression(
|
||||
'SelectedOptionIndex',
|
||||
_('Get the number of the currently selected option'),
|
||||
_(
|
||||
'Get the number of the currently selected option. Use this to help you render the option selection marker at the right place.'
|
||||
),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.getSelectedOption');
|
||||
|
||||
extension
|
||||
.addStrExpression(
|
||||
'ClippedLineText',
|
||||
_('Get dialogue line text clipped'),
|
||||
_(
|
||||
'Get dialogue line text clipped by the typewriter effect. Use the ScrollClippedText action to control the typewriter effect.'
|
||||
),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.getClippedLineText');
|
||||
|
||||
extension
|
||||
.addStrExpression(
|
||||
'BranchTitle',
|
||||
_('Get the title of the current branch of running dialogue'),
|
||||
_('Get the title of the current branch of running dialogue'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.getBranchTitle');
|
||||
|
||||
extension
|
||||
.addStrExpression(
|
||||
'BranchTags',
|
||||
_('Get the tags of the current branch of running dialogue'),
|
||||
_('Get the tags of the current branch of running dialogue'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.getBranchTags');
|
||||
|
||||
extension
|
||||
.addStrExpression(
|
||||
'BranchTag',
|
||||
_('Get a tag of the current branch of running dialogue via number'),
|
||||
_('Get a tag of the current branch of running dialogue via number'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter('expression', _('Tag Index Number'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.getBranchTag');
|
||||
|
||||
extension
|
||||
.addStrExpression(
|
||||
'CommandParameter',
|
||||
_('Get the parameters of a command call'),
|
||||
_(
|
||||
'Get the parameters of a command call - <<command withParameter anotherParameter>>'
|
||||
),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter('expression', _('parameter Index Number'), '', true)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.getCommandParameter');
|
||||
|
||||
extension
|
||||
.addExpression(
|
||||
'CommandParametersCount',
|
||||
_('Get the number of parameters in the currently passed command'),
|
||||
_('Get the number of parameters in the currently passed command'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.commandParametersCount');
|
||||
|
||||
extension
|
||||
.addStrExpression(
|
||||
'TagParameter',
|
||||
_(
|
||||
'Get parameter from a Tag found by the branch contains tag condition'
|
||||
),
|
||||
_(
|
||||
'Get parameter from a Tag found by the branch contains tag condition'
|
||||
),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter('expression', _('parameter Index Number'), '', true)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.getTagParameter');
|
||||
|
||||
extension
|
||||
.addStrExpression(
|
||||
'VisitedBranchTitles',
|
||||
_('Get a list of all visited branches'),
|
||||
_('Get a list of all visited branches'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.getVisitedBranchTitles');
|
||||
|
||||
extension
|
||||
.addStrExpression(
|
||||
'BranchText',
|
||||
_('Get the raw text of the current branch'),
|
||||
_('Get the full raw text of the current branch'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.getBranchText');
|
||||
|
||||
extension
|
||||
.addExpression(
|
||||
'Variable',
|
||||
_('Get dialogue state value'),
|
||||
_('Get dialogue state value'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter('string', _('Variable Name'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.getVariable');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'Is command called',
|
||||
_('Command is called'),
|
||||
_(
|
||||
'Check if a specific Command is called. If it is a <<command withParameter>>, you can even get the parameter with the CommandParameter expression.'
|
||||
),
|
||||
_('Command <<_PARAM0_>> is called'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter('string', _('Command String'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.isCommandCalled');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'IsDialogueLineType',
|
||||
_('The dialogue line type is'),
|
||||
_(
|
||||
'Check if the the current dialogue line line is one of the three existing types. Use this to set what logic is executed for each type.\nThe three types are as follows:\n- text: when displaying dialogue text.\n- options: when displaying [[branching/options]] for dialogue choices.\n-command: when <<commands>> are triggered by the dialogue data.'
|
||||
),
|
||||
_('The dialogue line is _PARAM0_'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter(
|
||||
'stringWithSelector',
|
||||
_('type'),
|
||||
'["text", "options", "command"]',
|
||||
false
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.isDialogueLineType');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'Is running',
|
||||
_('Is running'),
|
||||
_(
|
||||
'Check if the dialogue is running. Use this to for things like locking the player movement while speaking with a non player character.'
|
||||
),
|
||||
_('Is running'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.isRunning');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'HasBranch',
|
||||
_('Dialogue has branch'),
|
||||
_(
|
||||
'Check if the dialogue has a branch with specified name. Use this to check if a dialogue branch exists in the loaded dialogue data.'
|
||||
),
|
||||
_('Dialogue has a branch named _PARAM0_'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter('string', _('Branch name'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.hasDialogueBranch');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'HasSelectedOptionChanged',
|
||||
_('Has selected option changed'),
|
||||
_(
|
||||
'Check if a selected option has changed when the current dialogue line type is options. Use this to detect when the player has selected another option, so you can re-draw where the selection arrow is.'
|
||||
),
|
||||
_('Has selected option changed'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.hasSelectedOptionChanged');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'Current branch title is',
|
||||
_('Current dialogue branch title is'),
|
||||
_(
|
||||
'Check if the current dialogue branch title is equal to a string. Use this to trigger game events when the player has visited a specific dialogue branch.'
|
||||
),
|
||||
_('The current dialogue branch title is _PARAM0_'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter('string', _('title name'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.branchTitleIs');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'Current branch contains Tag',
|
||||
_('Current dialogue branch contains a tag'),
|
||||
_(
|
||||
'Check if the current dialogue branch contains a specific tag. Tags are an alternative useful way to <<commands>> to drive game logic with the dialogue data.'
|
||||
),
|
||||
_('The current dialogue branch contains a _PARAM0_ tag'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter('string', _('tag name'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.branchContainsTag');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'Branch has been visited before',
|
||||
_('Branch title has been visited before'),
|
||||
_('Check if the current branch has been visited before'),
|
||||
_('Branch title _PARAM0_ has been visited before'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter('string', _('branch title'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.branchTitleHasBeenVisited');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'Compare dialogue state variable',
|
||||
_('Compare dialogue state variable'),
|
||||
_(
|
||||
'Compare dialogue state variable. Use this to trigger game events via dialogue variables.'
|
||||
),
|
||||
_('Dialogue state variable _PARAM0_ is equal to _PARAM1_'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.addParameter('string', _('State variable'))
|
||||
.addParameter('string', _('Equal to'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.compareVariable');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'HasClippedTextScrollingCompleted',
|
||||
_('Has clipped text scrolling completed'),
|
||||
_(
|
||||
'Check if the clipped text scrolling has completed. Use this to prevent the player from going to the next dialogue line before the typing effect has revealed the entire text.'
|
||||
),
|
||||
_('Has clipped text scrolling completed'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
'JsPlatform/Extensions/yarn24.png',
|
||||
'JsPlatform/Extensions/yarn32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('gdjs.dialogueTree.hasClippedScrollingCompleted');
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function(gd, extension) {
|
||||
return [];
|
||||
},
|
||||
};
|
11
Extensions/DialogueTree/bondage.min.js
vendored
Normal file
609
Extensions/DialogueTree/dialoguetools.js
Normal file
@@ -0,0 +1,609 @@
|
||||
/**
|
||||
* @memberof gdjs
|
||||
* @class dialogueTree
|
||||
* @static
|
||||
* @private
|
||||
*/
|
||||
|
||||
gdjs.dialogueTree = {};
|
||||
gdjs.dialogueTree.runner = new bondage.Runner();
|
||||
|
||||
/**
|
||||
* Load the Dialogue Tree data of the game. Initialize The Dialogue Tree, so as it can be used in the game.
|
||||
* @param {gdjs.Variable} sceneVar The variable to load the Dialogue tree data from. The data is a JSON string, created by Yarn.
|
||||
* @param {string} startDialogueNode The Dialogue Branch to start the Dialogue Tree from. If left empty, the data will only be loaded, but can later be initialized via another action
|
||||
*/
|
||||
gdjs.dialogueTree.loadFromSceneVariable = function(
|
||||
sceneVar,
|
||||
startDialogueNode
|
||||
) {
|
||||
this.runner = gdjs.dialogueTree.runner;
|
||||
|
||||
try {
|
||||
this.yarnData = JSON.parse(sceneVar.getAsString());
|
||||
this.runner.load(this.yarnData);
|
||||
|
||||
if (startDialogueNode && startDialogueNode.length > 0) {
|
||||
gdjs.dialogueTree.startFrom(startDialogueNode);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Load the Dialogue Tree data from a JSON resource.
|
||||
*
|
||||
* @param {gdjs.RuntimeScene} runtimeScene The scene where the dialogue is running.
|
||||
* @param {string} jsonResourceName The JSON resource where to load the Dialogue Tree data from. The data is a JSON string usually created with [Yarn Dialogue Editor](https://github.com/InfiniteAmmoInc/Yarn).
|
||||
* @param {string} startDialogueNode The Dialogue Branch to start the Dialogue Tree from. If left empty, the data will only be loaded, but can later be initialized via another action
|
||||
*/
|
||||
gdjs.dialogueTree.loadFromJsonFile = function(
|
||||
runtimeScene,
|
||||
jsonResourceName,
|
||||
startDialogueNode
|
||||
) {
|
||||
runtimeScene
|
||||
.getGame()
|
||||
.getJsonManager()
|
||||
.loadJson(jsonResourceName, function(error, content) {
|
||||
if (error) {
|
||||
console.error('An error happened while loading JSON resource:', error);
|
||||
} else {
|
||||
if (!content) return;
|
||||
gdjs.dialogueTree.yarnData = content;
|
||||
gdjs.dialogueTree.runner.load(gdjs.dialogueTree.yarnData);
|
||||
|
||||
if (startDialogueNode && startDialogueNode.length > 0) {
|
||||
gdjs.dialogueTree.startFrom(startDialogueNode);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the Dialogue Tree is currently parsing data.
|
||||
* For example, you can do things like disabling player movement while talking to a NPC.
|
||||
*/
|
||||
gdjs.dialogueTree.isRunning = function() {
|
||||
return this.dialogueIsRunning;
|
||||
};
|
||||
|
||||
/**
|
||||
* Scroll the clipped text. This can be combined with a timer and user input to control how fast the dialogue line text is scrolling.
|
||||
*/
|
||||
gdjs.dialogueTree.scrollClippedText = function() {
|
||||
if (this.pauseScrolling || !this.dialogueIsRunning) return;
|
||||
|
||||
if (this.dialogueText) {
|
||||
this.clipTextEnd += 1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if text scrolling has completed.
|
||||
* Useful to prevent the user from skipping to next line before the current one has been printed fully.
|
||||
*/
|
||||
gdjs.dialogueTree.hasClippedScrollingCompleted = function() {
|
||||
if (this.dialogueData && this.dialogueText.length) {
|
||||
return this.clipTextEnd >= this.dialogueText.length;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current dialogue line with a scrolling effect (recommended).
|
||||
* Used with the scrollClippedText to achieve a classic scrolling text, as well as any <<wait>> effects to pause scrolling.
|
||||
*/
|
||||
gdjs.dialogueTree.getClippedLineText = function() {
|
||||
return this.dialogueText.length
|
||||
? this.dialogueText.substring(0, this.clipTextEnd)
|
||||
: '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current complete dialogue line without using any scrolling effects.
|
||||
* Note that using this instead getClippedLineText will skip any <<wait>> commands entirely.
|
||||
*/
|
||||
gdjs.dialogueTree.getLineText = function() {
|
||||
this.clipTextEnd = this.dialogueText.length;
|
||||
return this.dialogueText.length ? this.dialogueText : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the number of command parameters in a command with parameters that has been caught by a isCommandCalled condition
|
||||
*/
|
||||
gdjs.dialogueTree.commandParametersCount = function() {
|
||||
if (this.commandParameters && this.commandParameters.length > 1) {
|
||||
return this.commandParameters.length - 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a command parameter in any command with parameters that has been caught by a isCommandCalled condition
|
||||
* @param {number} paramIndex The index of the parameter to get.
|
||||
*/
|
||||
gdjs.dialogueTree.getCommandParameter = function(paramIndex) {
|
||||
if (
|
||||
this.commandParameters &&
|
||||
this.commandParameters.length >= paramIndex + 1
|
||||
) {
|
||||
var returnedParam = this.commandParameters[paramIndex + 1];
|
||||
return returnedParam ? returnedParam : '';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Catch <<commands>> and <<commands with parameters>> from the current Dialogue Line.
|
||||
* You can trigger custom logic that relate to the story you are telling during the dialogue.
|
||||
*
|
||||
* @param {string} command The command you want to check for being called. Write it without the `<<>>`.
|
||||
*/
|
||||
gdjs.dialogueTree.isCommandCalled = function(command) {
|
||||
var commandCalls = gdjs.dialogueTree.commandCalls;
|
||||
var clipTextEnd = gdjs.dialogueTree.clipTextEnd;
|
||||
var dialogueText = gdjs.dialogueTree.dialogueText;
|
||||
|
||||
if (this.pauseScrolling || !commandCalls) return false;
|
||||
return this.commandCalls.some(function(call, index) {
|
||||
if (clipTextEnd < call.time) return false;
|
||||
if (call.cmd === 'wait' && clipTextEnd !== dialogueText.length) {
|
||||
gdjs.dialogueTree.pauseScrolling = true;
|
||||
setTimeout(function() {
|
||||
gdjs.dialogueTree.pauseScrolling = false;
|
||||
commandCalls.splice(index, 1);
|
||||
}, parseInt(call.params[1], 10));
|
||||
}
|
||||
if (call.cmd === command) {
|
||||
gdjs.dialogueTree.commandParameters = call.params;
|
||||
commandCalls.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal method to allow for capping option selection.
|
||||
* @private
|
||||
*/
|
||||
gdjs.dialogueTree._normalizedOptionIndex = function(optionIndex) {
|
||||
if (optionIndex >= this.options.length) optionIndex = this.options.length - 1;
|
||||
if (optionIndex < 0) optionIndex = 0;
|
||||
return optionIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal method to allow for cycling option selection.
|
||||
* @private
|
||||
*/
|
||||
gdjs.dialogueTree._cycledOptionIndex = function(optionIndex) {
|
||||
if (optionIndex >= this.options.length) optionIndex = 0;
|
||||
if (optionIndex < 0) optionIndex = this.options.length - 1;
|
||||
return optionIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text of an option the player can select.
|
||||
* Used with getLineOptionsCount to render options for the player when a line of the Options type is parsed
|
||||
* @param {number} optionIndex The index of the option you want to get
|
||||
*/
|
||||
gdjs.dialogueTree.getLineOption = function(optionIndex) {
|
||||
if (!this.options.length) return [];
|
||||
optionIndex = gdjs.dialogueTree._normalizedOptionIndex(optionIndex);
|
||||
return this.options[optionIndex];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the number of options that are presented to the player, during the parsing of an Options type line.
|
||||
* @returns {number} The number of options
|
||||
*/
|
||||
gdjs.dialogueTree.getLineOptionsCount = function() {
|
||||
if (this.options.length) {
|
||||
return this.optionsCount;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Confirm the currently selected option, during the parsing of an Options type line.
|
||||
*
|
||||
* This will advance the dialogue tree to the dialogue branch was selected by the player.
|
||||
*/
|
||||
gdjs.dialogueTree.confirmSelectOption = function() {
|
||||
if (
|
||||
this.dialogueData.select &&
|
||||
!this.selectedOptionUpdated &&
|
||||
this.selectedOption !== -1
|
||||
) {
|
||||
this.commandCalls = [];
|
||||
this.dialogueData.select(this.selectedOption);
|
||||
this.dialogueData = this.dialogue.next().value;
|
||||
gdjs.dialogueTree.goToNextDialogueLine();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Select next option during Options type line parsing. Hook this to your game input.
|
||||
*/
|
||||
gdjs.dialogueTree.selectNextOption = function() {
|
||||
if (this.dialogueData.select) {
|
||||
this.selectedOption += 1;
|
||||
this.selectedOption = gdjs.dialogueTree._cycledOptionIndex(
|
||||
this.selectedOption
|
||||
);
|
||||
this.selectedOptionUpdated = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Select previous option during Options type line parsing. Hook this to your game input.
|
||||
*/
|
||||
gdjs.dialogueTree.selectPreviousOption = function() {
|
||||
if (this.dialogueData.select) {
|
||||
this.selectedOption -= 1;
|
||||
this.selectedOption = gdjs.dialogueTree._cycledOptionIndex(
|
||||
this.selectedOption
|
||||
);
|
||||
this.selectedOptionUpdated = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Select option by index during Options type line parsing.
|
||||
* @param {number} optionIndex The index of the option to select
|
||||
*/
|
||||
gdjs.dialogueTree.selectOption = function(optionIndex) {
|
||||
if (this.dialogueData.select) {
|
||||
this.selectedOption = gdjs.dialogueTree._normalizedOptionIndex(
|
||||
this.selectedOption
|
||||
);
|
||||
this.selectedOptionUpdated = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the currently selected option
|
||||
* @returns {number} The index of the currently selected option
|
||||
*/
|
||||
gdjs.dialogueTree.getSelectedOption = function() {
|
||||
if (this.dialogueData.select) {
|
||||
return this.selectedOption;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check when the player has changed option selection since the last call to this function.
|
||||
*
|
||||
* Can be used to re-render your displayed dialogue options when needed.
|
||||
*
|
||||
* @returns {boolean} true if the selected option was updated since the last call to this function
|
||||
*/
|
||||
gdjs.dialogueTree.hasSelectedOptionChanged = function() {
|
||||
if (this.selectedOptionUpdated) {
|
||||
this.selectedOptionUpdated = false;
|
||||
if (this.selectedOption === -1) this.selectedOption = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check the type of the Dialogue Line that is being displayed to the player at the moment.
|
||||
*
|
||||
* There are three types:
|
||||
* - text - regular dialogue text is being parsed at the moment
|
||||
* - options - the player has reached a branching choise moment where they must select one of multiple options
|
||||
* - command - a <<command>> was called in the background, that can be used to trigger game events, but will not be displayed in the dialogue box.
|
||||
*
|
||||
* @param {string} type The type you want to check for ( one of the three above )
|
||||
*/
|
||||
gdjs.dialogueTree.isDialogueLineType = function(type) {
|
||||
if (
|
||||
this.commandCalls &&
|
||||
this.commandCalls.some(function(call) {
|
||||
return gdjs.dialogueTree.clipTextEnd > call.time;
|
||||
})
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return this.dialogueIsRunning ? this.dialogueDataType === type : false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a branch exists. It is also used internaly whenever you use the start from action.
|
||||
* @param {string} branchName The Dialogue Branch name you want to check.
|
||||
*/
|
||||
gdjs.dialogueTree.hasDialogueBranch = function(branchName) {
|
||||
return (
|
||||
this.runner &&
|
||||
this.runner.yarnNodes &&
|
||||
Object.keys(this.runner.yarnNodes).some(function(node) {
|
||||
return node === branchName;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Start parsing dialogue from a specified Dialogue tree branch.
|
||||
* Can be used if you want to store multiple dialogues inside a single Dialogue tree data set.
|
||||
* @param {string} startDialogueNode The Dialogue Branch name you want to start parsing from.
|
||||
*/
|
||||
gdjs.dialogueTree.startFrom = function(startDialogueNode) {
|
||||
this.runner = gdjs.dialogueTree.runner;
|
||||
if (!this.hasDialogueBranch(startDialogueNode)) return;
|
||||
this.optionsCount = 0;
|
||||
this.options = [];
|
||||
this.dialogueBranchTitle = '';
|
||||
this.dialogueBranchBody = '';
|
||||
this.dialogueBranchTags = [];
|
||||
this.tagParameters = [];
|
||||
this.dialogue = this.runner.run(startDialogueNode);
|
||||
this.dialogueData = null;
|
||||
this.dialogueDataType = '';
|
||||
this.dialogueText = '';
|
||||
this.commandCalls = [];
|
||||
this.commandParameters = [];
|
||||
this.pauseScrolling = false;
|
||||
this.dialogueData = this.dialogue.next().value;
|
||||
this.dialogueIsRunning = true;
|
||||
gdjs.dialogueTree.goToNextDialogueLine();
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal methods to check the type of a Dialogue Line
|
||||
*/
|
||||
gdjs.dialogueTree._isLineTypeText = function() {
|
||||
return this.dialogueData instanceof bondage.TextResult;
|
||||
};
|
||||
gdjs.dialogueTree._isLineTypeOptions = function() {
|
||||
return this.dialogueData instanceof bondage.OptionsResult;
|
||||
};
|
||||
gdjs.dialogueTree._isLineTypeCommand = function() {
|
||||
return this.dialogueData instanceof bondage.CommandResult;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the main lifecycle function.It runs once only when the user is advancing the dialogue to the next line.
|
||||
* Progress Dialogue to the next line. Hook it to your game input.
|
||||
* Note that this action can be influenced by any <<wait>> commands, but they work only if you have at least one isCommandCalled condition.
|
||||
*/
|
||||
gdjs.dialogueTree.goToNextDialogueLine = function() {
|
||||
if (this.pauseScrolling || !this.dialogueIsRunning) return;
|
||||
|
||||
this.optionsCount = 0;
|
||||
this.selectedOption = -1;
|
||||
this.selectedOptionUpdated = false;
|
||||
|
||||
if (gdjs.dialogueTree._isLineTypeText()) {
|
||||
if (
|
||||
this.dialogueDataType === 'options' ||
|
||||
this.dialogueDataType === 'text' ||
|
||||
!this.dialogueDataType
|
||||
) {
|
||||
this.clipTextEnd = 0;
|
||||
this.dialogueText = this.dialogueData.text;
|
||||
this.commandCalls = [];
|
||||
} else {
|
||||
this.dialogueText += this.dialogueData.text;
|
||||
}
|
||||
|
||||
this.dialogueDataType = 'text';
|
||||
this.dialogueBranchTags = this.dialogueData.data.tags;
|
||||
this.dialogueBranchTitle = this.dialogueData.data.title;
|
||||
this.dialogueBranchBody = this.dialogueData.data.body;
|
||||
this.dialogueData = this.dialogue.next().value;
|
||||
} else if (gdjs.dialogueTree._isLineTypeOptions()) {
|
||||
this.dialogueDataType = 'options';
|
||||
this.optionsCount = this.dialogueData.options.length;
|
||||
this.options = this.dialogueData.options;
|
||||
this.selectedOptionUpdated = true;
|
||||
} else if (gdjs.dialogueTree._isLineTypeCommand()) {
|
||||
this.dialogueDataType = 'command';
|
||||
|
||||
var command = this.dialogueData.text.split(' ');
|
||||
// If last command was to wait, increase time by one
|
||||
var offsetTime =
|
||||
this.commandCalls.length &&
|
||||
this.commandCalls[this.commandCalls.length - 1].cmd === 'wait'
|
||||
? 1
|
||||
: 0;
|
||||
this.commandCalls.push({
|
||||
cmd: command[0],
|
||||
params: command,
|
||||
time: this.dialogueText.length + offsetTime,
|
||||
});
|
||||
this.dialogueData = this.dialogue.next().value;
|
||||
gdjs.dialogueTree.goToNextDialogueLine();
|
||||
} else {
|
||||
this.dialogueDataType = 'unknown';
|
||||
}
|
||||
|
||||
if (gdjs.dialogueTree._isLineTypeCommand()) {
|
||||
this.dialogueDataType = 'command';
|
||||
gdjs.dialogueTree.goToNextDialogueLine();
|
||||
}
|
||||
|
||||
// Dialogue has finished
|
||||
if (!this.dialogueData) {
|
||||
this.dialogueIsRunning = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current Dialogue Tree branch title.
|
||||
* @returns {string} The current branch title.
|
||||
*/
|
||||
gdjs.dialogueTree.getBranchTitle = function() {
|
||||
if (this.dialogueIsRunning) {
|
||||
return this.dialogueBranchTitle;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the currently parsed Dialogue branch title is a query.
|
||||
* @param {string} title The Dialogue Branch name you want to check for.
|
||||
*/
|
||||
gdjs.dialogueTree.branchTitleIs = function(title) {
|
||||
if (this.dialogueIsRunning) {
|
||||
return this.dialogueBranchTitle === title;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all the branch tags from the current Dialogue branch as a string. Useful for debugging.
|
||||
* @returns {string} The current branch tags, separated by a comma.
|
||||
*/
|
||||
gdjs.dialogueTree.getBranchTags = function() {
|
||||
if (this.dialogueIsRunning) {
|
||||
return this.dialogueBranchTags.join(',');
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get one of the current Dialogue branch tags via index.
|
||||
* @param {number} index The index of the Dialogue Branch tag you want to get.
|
||||
* @returns {string} The branch tag at the specified index, or an empty string if not found.
|
||||
*/
|
||||
gdjs.dialogueTree.getBranchTag = function(index) {
|
||||
if (this.dialogueIsRunning && this.dialogueBranchTags.length) {
|
||||
if (index > this.dialogueBranchTags.length - 1)
|
||||
index = this.dialogueBranchTags.length - 1;
|
||||
return this.dialogueBranchTags[index];
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the current Dialogue branch contains a specific tag.
|
||||
* @param {string} query The name of the Dialogue Branch tag you want to check.
|
||||
*/
|
||||
gdjs.dialogueTree.branchContainsTag = function(query) {
|
||||
this.tagParameters = [];
|
||||
if (this.dialogueIsRunning && this.dialogueBranchTags.length) {
|
||||
return this.dialogueBranchTags.some(function(tag) {
|
||||
var splitTag = tag.match(/([^\(]+)\(([^\)]+)\)/i);
|
||||
gdjs.dialogueTree.tagParameters = splitTag ? splitTag[2].split(',') : [];
|
||||
return splitTag ? splitTag[1] === query : tag === query;
|
||||
});
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get any tag(parameter,anotherParameter) from a tag captured by the branchContainsTag Condition
|
||||
* @param {number} paramIndex The index of the tag parameter you want to get.
|
||||
* Leaving this empty will result in retrieving the first parameter.
|
||||
*/
|
||||
gdjs.dialogueTree.getTagParameter = function(paramIndex) {
|
||||
if (this.dialogueIsRunning && this.tagParameters.length >= paramIndex) {
|
||||
var returnedParam = this.tagParameters[paramIndex];
|
||||
return returnedParam ? returnedParam : '';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a list of all the titles of visited by the player Branches. Useful for debugging.
|
||||
*/
|
||||
gdjs.dialogueTree.getVisitedBranchTitles = function() {
|
||||
if (this.dialogueIsRunning) {
|
||||
return Object.keys(this.runner.visited).join(',');
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a player has visited a Dialogue Branch in the past.
|
||||
* @param {string} title The title of the branch to check for.
|
||||
* Leaving this empty will check if the current branch title has been visited in the past.
|
||||
*/
|
||||
gdjs.dialogueTree.branchTitleHasBeenVisited = function(title) {
|
||||
if (!title) title = this.dialogueBranchTitle;
|
||||
return (
|
||||
Object.keys(this.runner.visited).includes(title) &&
|
||||
this.runner.visited[title]
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the entire unparsed text of the current Dialogue Branch
|
||||
*/
|
||||
gdjs.dialogueTree.getBranchText = function() {
|
||||
if (this.dialogueIsRunning) {
|
||||
return this.dialogueBranchBody;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the value of a variable that was created by the Dialogue parses.
|
||||
* @param {string} key The name of the variable you want to get the value of
|
||||
*/
|
||||
gdjs.dialogueTree.getVariable = function(key) {
|
||||
if (this.dialogueIsRunning && key in this.runner.variables.data) {
|
||||
return this.runner.variables.data[key];
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a specific variable created by the Dialogue parses exists and is equal to a specific value.
|
||||
* @param {string} key The name of the variable you want to check the value of
|
||||
* @param {string} value The value you want to check against
|
||||
*/
|
||||
gdjs.dialogueTree.compareVariable = function(key, value) {
|
||||
if (this.dialogueIsRunning && key in this.runner.variables.data) {
|
||||
return this.runner.variables.data[key].toString() === value;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set a specific variable created by the Dialogue parser to a specific value.
|
||||
* @param {string} key The name of the variable you want to set the value of
|
||||
* @param {string} value The value you want to set
|
||||
*/
|
||||
gdjs.dialogueTree.setVariable = function(key, value) {
|
||||
if (this.dialogueIsRunning && this.runner.variables.data) {
|
||||
this.runner.variables.data[key] = value;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Store the current State of the Dialogue Parser in a specified variable.
|
||||
* Can be used to implement persistence in dialogue through your game's Load/Save function.
|
||||
* That way you can later load all the dialogue choices the player has made.
|
||||
* @param {gdjs.Variable} outputVariable The variable where to store the State
|
||||
*/
|
||||
gdjs.dialogueTree.saveState = function(outputVariable) {
|
||||
const dialogueState = {
|
||||
variables: gdjs.dialogueTree.runner.variables.data,
|
||||
visited: gdjs.dialogueTree.runner.visited,
|
||||
};
|
||||
gdjs.evtTools.network._objectToVariable(dialogueState, outputVariable);
|
||||
};
|
||||
|
||||
/**
|
||||
* Load the current State of the Dialogue Parser from a specified variable.
|
||||
* Can be used to implement persistence in dialogue through your game's Load/Save function.
|
||||
* That way you can later load all the dialogue choices the player has made.
|
||||
* @param {gdjs.Variable} inputVariable The variable where to load the State from.
|
||||
*/
|
||||
gdjs.dialogueTree.loadState = function(inputVariable) {
|
||||
const jsonData = gdjs.evtTools.network.variableStructureToJSON(inputVariable);
|
||||
try {
|
||||
const loadedState = JSON.parse(
|
||||
gdjs.evtTools.network.variableStructureToJSON(inputVariable)
|
||||
);
|
||||
gdjs.dialogueTree.runner.visited = loadedState.visited;
|
||||
gdjs.dialogueTree.runner.variables.data = loadedState.variables;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
81
Extensions/DialogueTree/tests/assignment.json
Normal file
@@ -0,0 +1,81 @@
|
||||
[
|
||||
{
|
||||
"title": "Numeric",
|
||||
"tags": "Tag",
|
||||
"body": "Test Line\n<<set $testvar = -123.4>>\nTest Line After",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},{
|
||||
"title": "NumericExpression",
|
||||
"tags": "Tag",
|
||||
"body": "Test Line\n<<set $testvar = (1 + 2) * -3 + 4.3>>\nTest Line After",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "String",
|
||||
"tags": "Tag",
|
||||
"body": "Test Line\n<<set $testvar = \"Variable String\">>\nTest Line After",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "StringExpression",
|
||||
"tags": "Tag",
|
||||
"body": "Test Line\n<<set $testvar = \"Variable String\" + \" Appended\">>\nTest Line After",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "Boolean",
|
||||
"tags": "Tag",
|
||||
"body": "Test Line\n<<set $testvar = true>>\nTest Line After",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "BooleanExpression",
|
||||
"tags": "Tag",
|
||||
"body": "Test Line\n<<set $testvar = (true || false) && (false || !false)>>\nTest Line After",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "Variable",
|
||||
"tags": "Tag",
|
||||
"body": "Test Line\n<<set $firstvar = \"First variable string\">><<set $secondvar = $firstvar>>\nTest Line After",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "VariableExpression",
|
||||
"tags": "Tag",
|
||||
"body": "Test Line\n<<set $firstvar = 100>><<set $secondvar = -4.3 + $firstvar>>\nTest Line After",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
}
|
||||
]
|
52
Extensions/DialogueTree/tests/commandsandfunctions.json
Normal file
@@ -0,0 +1,52 @@
|
||||
[
|
||||
{
|
||||
"title": "BasicCommands",
|
||||
"tags": "Tag",
|
||||
"body": "<<command>>text in between commands<<command with space>> <<callFunction()>> <<callFunctionWithParam(\"test\",true,1,12.5,[2,3])>>",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "StopCommand",
|
||||
"tags": "Tag",
|
||||
"body": "First line\n<<stop>>\nThis shouldn't show",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "FunctionConditional",
|
||||
"tags": "Tag",
|
||||
"body": "First line\n<<if testfunc(\"firstarg\")>>This shouldn't show<<elseif testfunc(\"firstarg\", \"secondarg\")>>This should show<<endif>>After both",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "VisitedFunction",
|
||||
"tags": "Tag",
|
||||
"body": "<<if visited(\"VisitedFunctionStart\")>>you have visited VisitedFunctionStart!<<endif>><<if visited(\"SomeOtherNode\")>>You have not visited SomeOtherNode!<<endif>>",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "VisitedFunctionStart",
|
||||
"tags": "Tag",
|
||||
"body": "Hello[[VisitedFunction]]",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
}
|
||||
]
|
93
Extensions/DialogueTree/tests/conditions.json
Normal file
@@ -0,0 +1,93 @@
|
||||
[
|
||||
{
|
||||
"title": "Start",
|
||||
"tags": "Tag",
|
||||
"body": "What are you?\n-> A troll\n <<set $troll to true >>\n-> A nice person\n <<set $troll to false >>\n[[Objective]]",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "Objective",
|
||||
"tags": "Tag",
|
||||
"body": "<<if $repeat >= 3>>\nBye...\n<<else>>\nIs your objective clear?\n[[Yes|Objective.Yes]]\n[[No|Objective.No]]\n<<if $troll == true>>\n[[Maybe|Objective.Maybe]]\n<<endif>>\n<<endif>>\n",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "Objective.No",
|
||||
"tags": "Tag",
|
||||
"body": "Blah blah blah blah\n[[Objective]]",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "Objective.Yes",
|
||||
"tags": "Tag",
|
||||
"body": "Good let's start the mission.",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "Objective.Maybe",
|
||||
"tags": "Tag",
|
||||
"body": "Are you trolling me?\n[[Objective]]",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
|
||||
{
|
||||
"title": "BasicIf",
|
||||
"tags": "Tag",
|
||||
"body": "<<set $testvar = 321>>\nText before\n<<if $testvar == 321>>Inside if<<endif>>Text after",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "BasicIfElse",
|
||||
"tags": "Tag",
|
||||
"body": "<<set $testvar = 321>>\nText before\n<<if $testvar == 123>>Inside if<<else>>Inside else<<endif>>Text after",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "BasicIfElseIf",
|
||||
"tags": "Tag",
|
||||
"body": "<<set $testvar = 321>>\nText before\n<<if $testvar == 123>>Inside if<<elseif $testvar == 321>>Inside elseif<<endif>>Text after",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "BasicIfElseIfElse",
|
||||
"tags": "Tag",
|
||||
"body": "<<set $testvar = 321>>\nText before\n<<if $testvar == 123>>Inside if<<elseif $testvar == 1>>Inside elseif<<else>>Inside else<<endif>>Text after",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
}
|
||||
]
|
72
Extensions/DialogueTree/tests/links.json
Normal file
@@ -0,0 +1,72 @@
|
||||
[
|
||||
{
|
||||
"title": "OneNode",
|
||||
"tags": "Tag",
|
||||
"body": "This is a test line",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "ThreeNodes",
|
||||
"tags": "",
|
||||
"body": "This is a test line\nThis is another test line[[Option1]]\n[[Option2]]",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "Option1",
|
||||
"tags": "",
|
||||
"body": "This is Option1's test line",
|
||||
"position": {
|
||||
"x": 770,
|
||||
"y": 84
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "Option2",
|
||||
"tags": "",
|
||||
"body": "This is Option2's test line",
|
||||
"position": {
|
||||
"x": 774,
|
||||
"y": 404
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "NamedLink",
|
||||
"tags": "",
|
||||
"body": "This is a test line\nThis is another test line\n[[First choice|Option1]]\n[[Second choice|Option2]]",
|
||||
"position": {
|
||||
"x": 774,
|
||||
"y": 404
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "OneLinkPassthrough",
|
||||
"tags": "",
|
||||
"body": "First test line\n[[Option1]]",
|
||||
"position": {
|
||||
"x": 774,
|
||||
"y": 404
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "LinkAfterShortcuts",
|
||||
"tags": "",
|
||||
"body": "First test line\n-> Shortcut 1\n\tThis is the first shortcut\n-> Shortcut 2\n\tThis is the second shortcut\n[[First link|Option1]][[Second link|Option2]]",
|
||||
"position": {
|
||||
"x": 774,
|
||||
"y": 404
|
||||
},
|
||||
"colorID": 0
|
||||
}
|
||||
]
|
32
Extensions/DialogueTree/tests/shortcuts.json
Normal file
@@ -0,0 +1,32 @@
|
||||
[
|
||||
{
|
||||
"title": "NonNested",
|
||||
"tags": "Tag",
|
||||
"body": "This is a test line\n-> Option 1\n\tThis is the first option\n-> Option 2\n\tThis is the second option\nThis is after both options",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "Nested",
|
||||
"tags": "Tag",
|
||||
"body": "text\n-> shortcut1\n\tText1\n\t-> nestedshortcut1\n\t\tNestedText1\n\t-> nestedshortcut2\n\t\tNestedText2\n-> shortcut2\n\tText2\nmore text",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
},
|
||||
{
|
||||
"title": "Conditional",
|
||||
"tags": "Tag",
|
||||
"body": "This is a test line\n-> Option 1\n\tThis is the first option\n-> Option 2 <<if 1 == 0>>\n\tThis is the second option\n-> Option 3\n\tThis is the third option\nThis is after both options",
|
||||
"position": {
|
||||
"x": 449,
|
||||
"y": 252
|
||||
},
|
||||
"colorID": 0
|
||||
}
|
||||
]
|
@@ -36,131 +36,275 @@ class TextObjectJsExtension : public gd::PlatformExtension {
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Scale"]
|
||||
.SetFunctionName("setScale")
|
||||
.SetGetter("getScale")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::ScaleX"]
|
||||
.SetFunctionName("setScaleX")
|
||||
.SetGetter("getScaleX")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::ScaleX"]
|
||||
.SetFunctionName("getScaleX")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::ScaleY"]
|
||||
.SetFunctionName("setScaleY")
|
||||
.SetGetter("getScaleY")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::ScaleY"]
|
||||
.SetFunctionName("getScaleY")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::String"]
|
||||
.SetFunctionName("setString")
|
||||
.SetGetter("getString")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::String"]
|
||||
.SetFunctionName("getString")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Size"]
|
||||
.SetFunctionName("setCharacterSize")
|
||||
.SetGetter("getCharacterSize")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Size"]
|
||||
.SetFunctionName("getCharacterSize")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Angle"]
|
||||
.SetFunctionName("setAngle")
|
||||
.SetGetter("getAngle")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Angle"]
|
||||
.SetFunctionName("getAngle")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Opacity"]
|
||||
.SetFunctionName("setOpacity")
|
||||
.SetGetter("getOpacity")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Opacity"]
|
||||
.SetFunctionName("getOpacity")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
|
||||
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::SetBold"]
|
||||
.SetFunctionName("setBold")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::IsBold"]
|
||||
.SetFunctionName("isBold")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::SetItalic"]
|
||||
.SetFunctionName("setItalic")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::IsItalic"]
|
||||
.SetFunctionName("isItalic")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::SetWrapping"]
|
||||
.SetFunctionName("setWrapping")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::IsWrapping"]
|
||||
.SetFunctionName("isWrapping")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::SetPadding"]
|
||||
.SetFunctionName("setPadding")
|
||||
.SetGetter("getPadding")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Padding"]
|
||||
.SetFunctionName("getPadding")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::SetTextAlignment"]
|
||||
.SetFunctionName("setTextAlignment")
|
||||
.SetGetter("getTextAlignment")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::TextAlignment"]
|
||||
.SetFunctionName("getTextAlignment")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::WrappingWidth"]
|
||||
.SetFunctionName("setWrappingWidth")
|
||||
.SetGetter("getWrappingWidth")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::WrappingWidth"]
|
||||
.SetFunctionName("getWrappingWidth")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
|
||||
GetAllExpressionsForObject("TextObject::Text")["Padding"]
|
||||
.SetFunctionName("getPadding")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllExpressionsForObject("TextObject::Text")["ScaleX"]
|
||||
.SetFunctionName("getScaleX")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllExpressionsForObject("TextObject::Text")["ScaleY"]
|
||||
.SetFunctionName("getScaleY")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllExpressionsForObject("TextObject::Text")["Opacity"]
|
||||
.SetFunctionName("getOpacity")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllExpressionsForObject("TextObject::Text")["Angle"]
|
||||
.SetFunctionName("getAngle")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllStrExpressionsForObject("TextObject::Text")["String"]
|
||||
.SetFunctionName("getString")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::ChangeColor"]
|
||||
.SetFunctionName("setColor")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::SetGradient"]
|
||||
.SetFunctionName("setGradient")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::SetOutline"]
|
||||
.SetFunctionName("setOutline")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::SetShadow"]
|
||||
.SetFunctionName("setShadow")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::ShowShadow"]
|
||||
.SetFunctionName("showShadow")
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js");
|
||||
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-pixi-renderer.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/TextObject/textruntimeobject-cocos-renderer.js");
|
||||
|
||||
// Unimplemented actions and conditions:
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Font"]
|
||||
|
@@ -274,7 +274,7 @@ bool RuntimeTextObject::ChangeProperty(std::size_t propertyNb,
|
||||
} else if (propertyNb == 1) {
|
||||
ChangeFont(newValue);
|
||||
} else if (propertyNb == 2) {
|
||||
SetCharacterSize(newValue.To<int>());
|
||||
SetCharacterSize(std::max(1, newValue.To<int>()));
|
||||
} else if (propertyNb == 3) {
|
||||
gd::String r, gb, g, b;
|
||||
{
|
||||
@@ -297,7 +297,7 @@ bool RuntimeTextObject::ChangeProperty(std::size_t propertyNb,
|
||||
|
||||
SetColor(r.To<int>(), g.To<int>(), b.To<int>());
|
||||
} else if (propertyNb == 4) {
|
||||
SetOpacity(newValue.To<float>());
|
||||
SetOpacity(std::min(std::max(0.0f, newValue.To<float>()), 255.0f));
|
||||
} else if (propertyNb == 5) {
|
||||
SetSmooth(!(newValue == _("No")));
|
||||
}
|
||||
|
@@ -69,6 +69,9 @@ gdjs.TextRuntimeObjectPixiRenderer.prototype.updateStyle = function() {
|
||||
style.dropShadowAngle = this._object._shadowAngle;
|
||||
style.dropShadowDistance = this._object._shadowDistance;
|
||||
style.padding = this._object._padding;
|
||||
// Prevent spikey outlines by adding a miter limit
|
||||
style.miterLimit = 3;
|
||||
|
||||
this.updatePosition();
|
||||
|
||||
// Manually ask the PIXI object to re-render as we changed a style property
|
||||
|
@@ -14,7 +14,7 @@ gdjs.TextRuntimeObject = function(runtimeScene, objectData)
|
||||
{
|
||||
gdjs.RuntimeObject.call(this, runtimeScene, objectData);
|
||||
|
||||
this._characterSize = objectData.characterSize;
|
||||
this._characterSize = Math.max(1, objectData.characterSize);
|
||||
this._fontName = objectData.font;
|
||||
this._bold = objectData.bold;
|
||||
this._italic = objectData.italic;
|
||||
|
@@ -21,6 +21,12 @@ gdjs.VideoRuntimeObjectPixiRenderer = function(runtimeObject, runtimeScene) {
|
||||
} else {
|
||||
this._pixiObject._texture.baseTexture.source.currentTime = 0;
|
||||
}
|
||||
|
||||
// Needed to avoid video not playing/crashing in Chrome/Chromium browsers.
|
||||
// See https://github.com/pixijs/pixi.js/issues/5996
|
||||
this._pixiObject._texture.baseTexture.source.preload = "auto";
|
||||
this._pixiObject._texture.baseTexture.source.autoload = true;
|
||||
|
||||
this._textureWasValid = false; // Will be set to true when video texture is loaded.
|
||||
|
||||
runtimeScene
|
||||
@@ -132,7 +138,7 @@ gdjs.VideoRuntimeObjectPixiRenderer.prototype._getHTMLVideoElementSource = funct
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Start the video
|
||||
@@ -247,10 +253,7 @@ gdjs.VideoRuntimeObjectPixiRenderer.prototype.isPlayed = function() {
|
||||
var source = this._getHTMLVideoElementSource();
|
||||
if (!source) return false;
|
||||
|
||||
return (
|
||||
!source.paused &&
|
||||
!source.ended
|
||||
);
|
||||
return !source.paused && !source.ended;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -237,7 +237,7 @@ gdjs.VideoRuntimeObject.prototype.getDuration = function() {
|
||||
* Check if the video has ended.
|
||||
*/
|
||||
gdjs.VideoRuntimeObject.prototype.isEnded = function() {
|
||||
return !this._renderer.isEnded();
|
||||
return this._renderer.isEnded();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -158,8 +158,9 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
|
||||
// it from the behavior
|
||||
gd::String("var runtimeScene = this._runtimeScene;\n") +
|
||||
// By convention of Behavior Events Function, the object is accessible
|
||||
// as a parameter called "Object", and thisObjectList is an array containing it
|
||||
// (for faster access, without having to go through the hashmap).
|
||||
// as a parameter called "Object", and thisObjectList is an array
|
||||
// containing it (for faster access, without having to go through the
|
||||
// hashmap).
|
||||
"var thisObjectList = [this.owner];\n" +
|
||||
"var Object = Hashtable.newFrom({Object: thisObjectList});\n" +
|
||||
// By convention of Behavior Events Function, the behavior is accessible
|
||||
@@ -249,9 +250,10 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
|
||||
}
|
||||
}
|
||||
|
||||
// If we have an object considered as the current object ("this") (usually called
|
||||
// Object in behavior events function), generate a slightly more optimized getter
|
||||
// for it (bypassing "Object" hashmap, and directly return the array containing it).
|
||||
// If we have an object considered as the current object ("this") (usually
|
||||
// called Object in behavior events function), generate a slightly more
|
||||
// optimized getter for it (bypassing "Object" hashmap, and directly return
|
||||
// the array containing it).
|
||||
gd::String thisObjectGetterCode =
|
||||
thisObjectName.empty()
|
||||
? ""
|
||||
@@ -275,6 +277,11 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
|
||||
" return objectsList ? gdjs.objectsListsToArray(objectsList) : "
|
||||
"[];\n"
|
||||
" },\n" +
|
||||
// Function that can be used in JS code to get the lists of objects
|
||||
// and filter/alter them.
|
||||
" getObjectsLists: function(objectName) {\n" +
|
||||
" return eventsFunctionContext._objectsMap[objectName] || null;\n"
|
||||
" },\n" +
|
||||
// Function that will be used to query behavior name (as behavior name
|
||||
// can be different between the parameter name vs the actual behavior
|
||||
// name passed as argument).
|
||||
|
@@ -65,6 +65,10 @@ CameraExtension::CameraExtension() {
|
||||
|
||||
GetAllActions()["SetLayerEffectParameter"].SetFunctionName(
|
||||
"gdjs.evtTools.camera.setLayerEffectParameter");
|
||||
GetAllActions()["EnableLayerEffect"].SetFunctionName(
|
||||
"gdjs.evtTools.camera.enableLayerEffect");
|
||||
GetAllConditions()["LayerEffectEnabled"].SetFunctionName(
|
||||
"gdjs.evtTools.camera.layerEffectEnabled");
|
||||
|
||||
GetAllConditions()["LayerTimeScale"].SetFunctionName(
|
||||
"gdjs.evtTools.camera.getLayerTimeScale");
|
||||
|
@@ -7,7 +7,6 @@
|
||||
"version": "GDJS_GAME_VERSION",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"electron": "2.0.7"
|
||||
"electron": "3.0.9"
|
||||
}
|
||||
}
|
||||
|
@@ -128,6 +128,32 @@ gdjs.evtTools.camera.setLayerEffectParameter = function(runtimeScene, layer, eff
|
||||
return runtimeScene.getLayer(layer).setEffectParameter(effect, parameter, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable, or disable, an effect of a layer.
|
||||
* @param {gdjs.RuntimeScene} runtimeScene The scene
|
||||
* @param {string} layer The name of the layer
|
||||
* @param {string} effect The name of the effect
|
||||
* @param {boolean} enabled true to enable, false to disable.
|
||||
*/
|
||||
gdjs.evtTools.camera.enableLayerEffect = function(runtimeScene, layer, effect, enabled) {
|
||||
if ( !runtimeScene.hasLayer(layer) ) { return; }
|
||||
|
||||
runtimeScene.getLayer(layer).enableEffect(effect, enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an effect is enabled.
|
||||
* @param {gdjs.RuntimeScene} runtimeScene The scene
|
||||
* @param {string} layer The name of the layer
|
||||
* @param {string} effect The name of the effect
|
||||
* @return {boolean} true if the effect is enabled, false otherwise.
|
||||
*/
|
||||
gdjs.evtTools.camera.layerEffectEnabled = function(runtimeScene, layer, effect) {
|
||||
if ( !runtimeScene.hasLayer(layer) ) { return true; }
|
||||
|
||||
return runtimeScene.getLayer(layer).isEffectEnabled(effect);
|
||||
}
|
||||
|
||||
gdjs.evtTools.camera.setLayerTimeScale = function(runtimeScene, layer, timeScale) {
|
||||
if ( !runtimeScene.hasLayer(layer) ) { return; }
|
||||
return runtimeScene.getLayer(layer).setTimeScale(timeScale);
|
||||
|
@@ -15,8 +15,8 @@ gdjs.evtTools.object = gdjs.evtTools.object || {};
|
||||
/**
|
||||
* Keep only the specified object in the lists of picked objects.
|
||||
*
|
||||
* @param objectsLists The lists of objects to trim
|
||||
* @param runtimeObject {gdjs.RuntimeObject} The object to keep in the lists
|
||||
* @param {Hashtable} objectsLists The lists of objects to trim
|
||||
* @param {gdjs.RuntimeObject} runtimeObject The object to keep in the lists
|
||||
*/
|
||||
gdjs.evtTools.object.pickOnly = function(objectsLists, runtimeObject) {
|
||||
for (var listName in objectsLists.items) {
|
||||
@@ -33,9 +33,17 @@ gdjs.evtTools.object.pickOnly = function(objectsLists, runtimeObject) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A predicate to be passed to `gdjs.evtTools.object.twoListsTest`.
|
||||
* @callback gdjsTwoListsTestPredicate
|
||||
* @param {gdjs.RuntimeObject} object1 First object
|
||||
* @param {gdjs.RuntimeObject} object2 Second object
|
||||
* @param {*} extraArg An optional extra argument
|
||||
* @return {boolean} true if the pair satisfy the predicate (for example,there is a collision), meaning that the objects will be picked, false otherwise (no collision).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Do a test on two tables of objects so as to pick only the pair of objects for which the test is true.
|
||||
* If inverted == true, only the objects of the first table are filtered.
|
||||
*
|
||||
* Note that the predicate method is not called stricly for each pair: When considering a pair of objects, if
|
||||
* these objects have already been marked as picked, the predicate method won't be called again.
|
||||
@@ -51,9 +59,12 @@ gdjs.evtTools.object.pickOnly = function(objectsLists, runtimeObject) {
|
||||
* + Cost(predicate)*(NbObjList1+NbObjList2)
|
||||
* + Cost(Testing NbObjList1+NbObjList2 booleans)
|
||||
*
|
||||
* Note: predicate is called with the two objects to compare, and an optional argument `extraArg`.
|
||||
* This should be used to avoid declaring the predicate as a closure that would be created and destroyed
|
||||
* at each call to twoListsTest (potentially multiple time per frame).
|
||||
*
|
||||
* @param {gdjsTwoListsTestPredicate} predicate The predicate function is called with the two objects to compare, and an optional argument `extraArg`
|
||||
* @param {Hashtable} objectsLists1 e.g. Hashtable.newFrom({ A: objects1 });
|
||||
* @param {Hashtable} objectsLists2 e.g. Hashtable.newFrom({ B: objects2 });
|
||||
* @param {boolean} inverted If `inverted` == true, only the objects of the first table are filtered.
|
||||
* @param {*} extraArg (optional) This argument should be used to avoid declaring the predicate as a closure that would be created and destroyed at each call to twoListsTest (potentially multiple time per frame).
|
||||
*/
|
||||
gdjs.evtTools.object.twoListsTest = function(predicate, objectsLists1, objectsLists2, inverted, extraArg) {
|
||||
|
||||
@@ -151,11 +162,11 @@ gdjs.evtTools.object.twoListsTest = function(predicate, objectsLists1, objectsLi
|
||||
*
|
||||
* Objects that do not fullfil the predicate are removed from objects lists.
|
||||
*
|
||||
* @param predicate The function applied to each object: must return true if the object fulfill the predicate.
|
||||
* @param objectsLists The lists of objects to trim
|
||||
* @param negatePredicate If set to true, the result of the predicate is negated.
|
||||
* @param extraArg Argument passed to the predicate (along with the object). Useful for avoiding relying on temporary closures.
|
||||
* @return true if at least one object fulfill the predicate.
|
||||
* @param {Function} predicate The function applied to each object: must return true if the object fulfill the predicate.
|
||||
* @param {Hashtable} objectsLists The lists of objects to trim
|
||||
* @param {boolean} negatePredicate If set to true, the result of the predicate is negated.
|
||||
* @param {*} extraArg Argument passed to the predicate (along with the object). Useful for avoiding relying on temporary closures.
|
||||
* @return {boolean} true if at least one object fulfill the predicate.
|
||||
*/
|
||||
gdjs.evtTools.object.pickObjectsIf = function(predicate, objectsLists, negatePredicate, extraArg) {
|
||||
var isTrue = false;
|
||||
|
@@ -216,6 +216,24 @@ gdjs.Layer.prototype.setEffectParameter = function(name, parameterIndex, value)
|
||||
return this._renderer.setEffectParameter(name, parameterIndex, value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Enable or disable an effect.
|
||||
* @param {string} effect The name of the effect to enable or disable.
|
||||
* @param {boolean} enable true to enable, false to disable
|
||||
*/
|
||||
gdjs.Layer.prototype.enableEffect = function(name, enable) {
|
||||
this._renderer.enableEffect(name, enable);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if an effect is enabled
|
||||
* @param {string} effect The name of the effect
|
||||
* @return {boolean} true if the effect is enabled, false otherwise.
|
||||
*/
|
||||
gdjs.Layer.prototype.isEffectEnabled = function(name) {
|
||||
return this._renderer.isEffectEnabled(name);
|
||||
};
|
||||
|
||||
gdjs.Layer.prototype.setEffectsDefaultParameters = function() {
|
||||
for (var i = 0; i < this._effects.length; ++i) {
|
||||
var effect = this._effects[i];
|
||||
|
@@ -1,9 +1,22 @@
|
||||
/**
|
||||
* A generic map (key values) container.
|
||||
*
|
||||
* Mostly used for storing lists of objects for
|
||||
* GDevelop generated events.
|
||||
*
|
||||
* @class Hashtable
|
||||
*/
|
||||
function Hashtable()
|
||||
{
|
||||
// console.log("New hashtable");
|
||||
this.items = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a Hashtable from a JS object.
|
||||
* @param {Object} items The content of the Hashtable
|
||||
* @returns {Hashtable} The new hashtable
|
||||
* @static
|
||||
*/
|
||||
Hashtable.newFrom = function(items) {
|
||||
var hashtable = new Hashtable();
|
||||
hashtable.items = items;
|
||||
|
@@ -1,12 +1,26 @@
|
||||
gdjs.LayerPixiRenderer = function(layer, runtimeSceneRenderer)
|
||||
{
|
||||
this._pixiContainer = new PIXI.Container();
|
||||
this._filters = {};
|
||||
this._layer = layer;
|
||||
runtimeSceneRenderer.getPIXIContainer().addChild(this._pixiContainer);
|
||||
/*
|
||||
* GDevelop JS Platform
|
||||
* Copyright 2013-2016 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
|
||||
* This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
this._addFilters();
|
||||
}
|
||||
/**
|
||||
* The renderer for a gdjs.Layer using Pixi.js.
|
||||
*
|
||||
* @class LayerPixiRenderer
|
||||
* @memberof gdjs
|
||||
* @param {gdjs.Layer} layer The layer
|
||||
* @param {gdjs.RuntimeScenePixiRenderer} runtimeSceneRenderer The scene renderer
|
||||
*/
|
||||
gdjs.LayerPixiRenderer = function(layer, runtimeSceneRenderer) {
|
||||
this._pixiContainer = new PIXI.Container();
|
||||
/** @type Object.<string, gdjsPixiFiltersToolsFilter> */
|
||||
this._filters = {};
|
||||
this._layer = layer;
|
||||
runtimeSceneRenderer.getPIXIContainer().addChild(this._pixiContainer);
|
||||
|
||||
this._setupFilters();
|
||||
};
|
||||
|
||||
gdjs.LayerRenderer = gdjs.LayerPixiRenderer; //Register the class to let the engine use it.
|
||||
|
||||
@@ -16,53 +30,62 @@ gdjs.LayerRenderer = gdjs.LayerPixiRenderer; //Register the class to let the eng
|
||||
* @private
|
||||
*/
|
||||
gdjs.LayerPixiRenderer.prototype.updatePosition = function() {
|
||||
var angle = -gdjs.toRad(this._layer.getCameraRotation());
|
||||
var zoomFactor = this._layer.getCameraZoom();
|
||||
var angle = -gdjs.toRad(this._layer.getCameraRotation());
|
||||
var zoomFactor = this._layer.getCameraZoom();
|
||||
|
||||
this._pixiContainer.rotation = angle;
|
||||
this._pixiContainer.scale.x = zoomFactor;
|
||||
this._pixiContainer.scale.y = zoomFactor;
|
||||
this._pixiContainer.rotation = angle;
|
||||
this._pixiContainer.scale.x = zoomFactor;
|
||||
this._pixiContainer.scale.y = zoomFactor;
|
||||
|
||||
var cosValue = Math.cos(angle);
|
||||
var sinValue = Math.sin(angle);
|
||||
var centerX = (this._layer.getCameraX()*zoomFactor)*cosValue
|
||||
- (this._layer.getCameraY()*zoomFactor)*sinValue;
|
||||
var centerY = (this._layer.getCameraX()*zoomFactor)*sinValue
|
||||
+ (this._layer.getCameraY()*zoomFactor)*cosValue;
|
||||
var cosValue = Math.cos(angle);
|
||||
var sinValue = Math.sin(angle);
|
||||
var centerX =
|
||||
this._layer.getCameraX() * zoomFactor * cosValue -
|
||||
this._layer.getCameraY() * zoomFactor * sinValue;
|
||||
var centerY =
|
||||
this._layer.getCameraX() * zoomFactor * sinValue +
|
||||
this._layer.getCameraY() * zoomFactor * cosValue;
|
||||
|
||||
this._pixiContainer.position.x = -centerX;
|
||||
this._pixiContainer.position.y = -centerY;
|
||||
this._pixiContainer.position.x += this._layer.getWidth()/2;
|
||||
this._pixiContainer.position.y += this._layer.getHeight()/2;
|
||||
this._pixiContainer.position.x = -centerX;
|
||||
this._pixiContainer.position.y = -centerY;
|
||||
this._pixiContainer.position.x += this._layer.getWidth() / 2;
|
||||
this._pixiContainer.position.y += this._layer.getHeight() / 2;
|
||||
};
|
||||
|
||||
gdjs.LayerPixiRenderer.prototype.updateVisibility = function(visible) {
|
||||
this._pixiContainer.visible = !!visible;
|
||||
}
|
||||
this._pixiContainer.visible = !!visible;
|
||||
};
|
||||
|
||||
gdjs.LayerPixiRenderer.prototype._addFilters = function() {
|
||||
var effects = this._layer.getEffects();
|
||||
if (effects.length === 0) {
|
||||
return;
|
||||
} else if (effects.length > 1) {
|
||||
console.log('Only a single effect by Layer is supported for now by the Pixi renderer');
|
||||
}
|
||||
gdjs.LayerPixiRenderer.prototype._setupFilters = function() {
|
||||
var effects = this._layer.getEffects();
|
||||
if (effects.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var filter = gdjs.PixiFiltersTools.getFilter(effects[0].effectName);
|
||||
this._filters = {};
|
||||
|
||||
/** @type PIXI.Filter[] */
|
||||
var pixiFilters = [];
|
||||
for (var i = 0; i < effects.length; ++i) {
|
||||
var effect = effects[i];
|
||||
var filter = gdjs.PixiFiltersTools.getFilter(effect.effectName);
|
||||
if (!filter) {
|
||||
console.log('Filter \"' + effects[0].name + '\" not found');
|
||||
return;
|
||||
console.log('Filter "' + effect.name + '" not found');
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @type gdjsPixiFiltersToolsFilter */
|
||||
var theFilter = {
|
||||
filter: filter.makeFilter(),
|
||||
updateParameter: filter.updateParameter
|
||||
filter: filter.makeFilter(),
|
||||
updateParameter: filter.updateParameter,
|
||||
};
|
||||
|
||||
this._pixiContainer.filters = [theFilter.filter];
|
||||
this._filters = {};
|
||||
this._filters[effects[0].name] = theFilter;
|
||||
}
|
||||
pixiFilters.push(theFilter.filter);
|
||||
this._filters[effect.name] = theFilter;
|
||||
}
|
||||
|
||||
this._pixiContainer.filters = pixiFilters;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a child to the pixi container associated to the layer.
|
||||
@@ -72,15 +95,16 @@ gdjs.LayerPixiRenderer.prototype._addFilters = function() {
|
||||
* @param zOrder The z order of the associated object.
|
||||
*/
|
||||
gdjs.LayerPixiRenderer.prototype.addRendererObject = function(child, zOrder) {
|
||||
child.zOrder = zOrder; //Extend the pixi object with a z order.
|
||||
child.zOrder = zOrder; //Extend the pixi object with a z order.
|
||||
|
||||
for( var i = 0, len = this._pixiContainer.children.length; i < len;++i) {
|
||||
if ( this._pixiContainer.children[i].zOrder >= zOrder ) { //TODO : Dichotomic search
|
||||
this._pixiContainer.addChildAt(child, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._pixiContainer.addChild(child);
|
||||
for (var i = 0, len = this._pixiContainer.children.length; i < len; ++i) {
|
||||
if (this._pixiContainer.children[i].zOrder >= zOrder) {
|
||||
//TODO : Dichotomic search
|
||||
this._pixiContainer.addChildAt(child, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._pixiContainer.addChild(child);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -89,9 +113,12 @@ gdjs.LayerPixiRenderer.prototype.addRendererObject = function(child, zOrder) {
|
||||
* @param child The child (PIXI object) to be modified.
|
||||
* @param newZOrder The z order of the associated object.
|
||||
*/
|
||||
gdjs.LayerPixiRenderer.prototype.changeRendererObjectZOrder = function(child, newZOrder) {
|
||||
this._pixiContainer.removeChild(child);
|
||||
this.addRendererObject(child, newZOrder);
|
||||
gdjs.LayerPixiRenderer.prototype.changeRendererObjectZOrder = function(
|
||||
child,
|
||||
newZOrder
|
||||
) {
|
||||
this._pixiContainer.removeChild(child);
|
||||
this.addRendererObject(child, newZOrder);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -101,12 +128,30 @@ gdjs.LayerPixiRenderer.prototype.changeRendererObjectZOrder = function(child, ne
|
||||
* @param child The child (PIXI object) to be removed.
|
||||
*/
|
||||
gdjs.LayerPixiRenderer.prototype.removeRendererObject = function(child) {
|
||||
this._pixiContainer.removeChild(child);
|
||||
this._pixiContainer.removeChild(child);
|
||||
};
|
||||
|
||||
gdjs.LayerPixiRenderer.prototype.setEffectParameter = function (name, parameterName, value) {
|
||||
if (!this._filters.hasOwnProperty(name)) return;
|
||||
gdjs.LayerPixiRenderer.prototype.setEffectParameter = function(
|
||||
name,
|
||||
parameterName,
|
||||
value
|
||||
) {
|
||||
if (!this._filters.hasOwnProperty(name)) return;
|
||||
|
||||
var theFilter = this._filters[name];
|
||||
theFilter.updateParameter(theFilter.filter, parameterName, value);
|
||||
var theFilter = this._filters[name];
|
||||
theFilter.updateParameter(theFilter.filter, parameterName, value);
|
||||
};
|
||||
|
||||
gdjs.LayerPixiRenderer.prototype.enableEffect = function(name, value) {
|
||||
if (!this._filters.hasOwnProperty(name)) return;
|
||||
|
||||
var theFilter = this._filters[name];
|
||||
gdjs.PixiFiltersTools.enableEffect(theFilter.filter, value);
|
||||
};
|
||||
|
||||
gdjs.LayerPixiRenderer.prototype.isEffectEnabled = function(name) {
|
||||
if (!this._filters.hasOwnProperty(name)) return false;
|
||||
|
||||
var theFilter = this._filters[name];
|
||||
return gdjs.PixiFiltersTools.isEffectEnabled(theFilter.filter);
|
||||
};
|
||||
|
@@ -1,5 +1,11 @@
|
||||
/**
|
||||
* Contains tools related to PIXI filters handling.
|
||||
*/
|
||||
gdjs.PixiFiltersTools = function() {};
|
||||
|
||||
gdjs.PixiFiltersTools.clampValue = function(value, min, max) { return Math.max(min, Math.min(max, value)); };
|
||||
gdjs.PixiFiltersTools.clampKernelSize = function(value) { return (([5, 7, 9, 11, 13, 15].indexOf(value) !== -1) ? value : 5); };
|
||||
|
||||
gdjs.NightPixiFilter = function() {
|
||||
var vertexShader = null;
|
||||
var fragmentShader = [
|
||||
@@ -70,7 +76,7 @@ gdjs.PixiFiltersTools._filters = {
|
||||
if (parameterName !== 'intensity' &&
|
||||
parameterName !== 'opacity') return;
|
||||
|
||||
filter.uniforms[parameterName] = value;
|
||||
filter.uniforms[parameterName] = gdjs.PixiFiltersTools.clampValue(value, 0, 1);
|
||||
},
|
||||
},
|
||||
LightNight: {
|
||||
@@ -81,7 +87,7 @@ gdjs.PixiFiltersTools._filters = {
|
||||
updateParameter: function(filter, parameterName, value) {
|
||||
if (parameterName !== 'opacity') return;
|
||||
|
||||
filter.uniforms.opacity = value;
|
||||
filter.uniforms.opacity = gdjs.PixiFiltersTools.clampValue(value, 0, 1);
|
||||
},
|
||||
},
|
||||
Sepia: {
|
||||
@@ -93,14 +99,100 @@ gdjs.PixiFiltersTools._filters = {
|
||||
updateParameter: function(filter, parameterName, value) {
|
||||
if (parameterName !== 'opacity') return;
|
||||
|
||||
filter.alpha = value;
|
||||
filter.alpha = gdjs.PixiFiltersTools.clampValue(value, 0, 1);
|
||||
},
|
||||
},
|
||||
BlackAndWhite: {
|
||||
makeFilter: function() {
|
||||
var colorMatrix = new PIXI.filters.ColorMatrixFilter();
|
||||
colorMatrix.blackAndWhite();
|
||||
return colorMatrix;
|
||||
},
|
||||
updateParameter: function(filter, parameterName, value) {
|
||||
if (parameterName !== 'opacity') return;
|
||||
|
||||
filter.alpha = gdjs.PixiFiltersTools.clampValue(value, 0, 1);
|
||||
},
|
||||
},
|
||||
Brightness: {
|
||||
makeFilter: function() {
|
||||
var brightness = new PIXI.filters.ColorMatrixFilter();
|
||||
brightness.brightness();
|
||||
return brightness;
|
||||
},
|
||||
updateParameter: function(filter, parameterName, value) {
|
||||
if (parameterName !== 'brightness') return;
|
||||
|
||||
filter.brightness(gdjs.PixiFiltersTools.clampValue(value, 0, 1));
|
||||
},
|
||||
},
|
||||
Noise: {
|
||||
makeFilter: function() {
|
||||
var noise = new PIXI.filters.NoiseFilter();
|
||||
return noise;
|
||||
},
|
||||
updateParameter: function(filter, parameterName, value) {
|
||||
if (parameterName !== 'noise') return;
|
||||
|
||||
filter.noise = gdjs.PixiFiltersTools.clampValue(value, 0, 1);
|
||||
},
|
||||
},
|
||||
Blur: {
|
||||
makeFilter: function() {
|
||||
var blur = new PIXI.filters.BlurFilter();
|
||||
return blur;
|
||||
},
|
||||
updateParameter: function(filter, parameterName, value) {
|
||||
if (parameterName !== 'blur' &&
|
||||
parameterName !== 'quality' &&
|
||||
parameterName !== 'kernelSize' &&
|
||||
parameterName !== 'resolution') return;
|
||||
|
||||
if (parameterName === 'kernelSize'){
|
||||
value = gdjs.PixiFiltersTools.clampKernelSize(value);
|
||||
}
|
||||
|
||||
filter[parameterName] = value;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Enable an effect.
|
||||
* @param {PIXI.Filter} filter The filter to enable or disable
|
||||
* @param {boolean} value Set to true to enable, false to disable
|
||||
*/
|
||||
gdjs.PixiFiltersTools.enableEffect = function(filter, value) {
|
||||
filter.enabled = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Effect is enabled.
|
||||
* @param {PIXI.Filter} filter The filter to be checked
|
||||
* @return {boolean} true if the filter is enabled
|
||||
*/
|
||||
gdjs.PixiFiltersTools.isEffectEnabled = function(filter) {
|
||||
return filter.enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the filter with the given name, if any.
|
||||
* @param {string} filterName The name of the filter to get
|
||||
* @return {?gdjsPixiFiltersToolsFilter} The filter wrapper, if any (null otherwise).
|
||||
*/
|
||||
gdjs.PixiFiltersTools.getFilter = function(filterName) {
|
||||
if (gdjs.PixiFiltersTools._filters.hasOwnProperty(filterName))
|
||||
return gdjs.PixiFiltersTools._filters[filterName];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Type definitions:
|
||||
|
||||
/**
|
||||
* The type of a filter used to manipulate a Pixi filter.
|
||||
* @typedef gdjsPixiFiltersToolsFilter
|
||||
* @type {object}
|
||||
* @property {PIXI.Filter} filter The PIXI filter
|
||||
* @property {Function} updateParameter The function to be called to update a parameter
|
||||
*/
|
||||
|
@@ -1,6 +1,13 @@
|
||||
|
||||
/**
|
||||
* The renderer for a gdjs.SpriteRuntimeObject using Pixi.js.
|
||||
* @class SpriteRuntimeObjectPixiRenderer
|
||||
* @memberof gdjs
|
||||
* @param {gdjs.SpriteRuntimeObject} runtimeObject The object
|
||||
* @param {gdjs.RuntimeScene} runtimeScene The scene
|
||||
*/
|
||||
gdjs.SpriteRuntimeObjectPixiRenderer = function(runtimeObject, runtimeScene)
|
||||
{
|
||||
/** @type gdjs.SpriteRuntimeObject */
|
||||
this._object = runtimeObject;
|
||||
this._spriteDirty = true;
|
||||
this._textureDirty = true;
|
||||
@@ -67,13 +74,13 @@ gdjs.SpriteRuntimeObjectPixiRenderer.prototype.update = function() {
|
||||
|
||||
gdjs.SpriteRuntimeObjectPixiRenderer.prototype.updateX = function() {
|
||||
this._sprite.position.x = this._object.x + (this._object._animationFrame.center.x - this._object._animationFrame.origin.x)*Math.abs(this._object._scaleX);
|
||||
if ( this._flippedX )
|
||||
if ( this._object._flippedX )
|
||||
this._sprite.position.x += (this._sprite.texture.frame.width/2-this._object._animationFrame.center.x)*Math.abs(this._object._scaleX)*2;
|
||||
}
|
||||
|
||||
gdjs.SpriteRuntimeObjectPixiRenderer.prototype.updateY = function() {
|
||||
this._sprite.position.y = this._object.y + (this._object._animationFrame.center.y - this._object._animationFrame.origin.y)*Math.abs(this._object._scaleY);
|
||||
if ( this._flippedY )
|
||||
if ( this._object._flippedY )
|
||||
this._sprite.position.y += (this._sprite.texture.frame.height/2-this._object._animationFrame.center.y)*Math.abs(this._object._scaleY)*2;
|
||||
}
|
||||
|
||||
|
@@ -629,7 +629,8 @@ gdjs.RuntimeScene.prototype.getInitialSharedDataForBehavior = function(name) {
|
||||
|
||||
/**
|
||||
* Get the layer with the given name
|
||||
* @param {gdjs.Layer} name The name of the layer
|
||||
* @param {string} name The name of the layer
|
||||
* @returns {gdjs.Layer} The layer, or the base layer if not found
|
||||
*/
|
||||
gdjs.RuntimeScene.prototype.getLayer = function(name) {
|
||||
if ( this._layers.containsKey(name) )
|
||||
@@ -638,6 +639,10 @@ gdjs.RuntimeScene.prototype.getLayer = function(name) {
|
||||
return this._layers.get("");
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a layer exists
|
||||
* @param {string} name The name of the layer
|
||||
*/
|
||||
gdjs.RuntimeScene.prototype.hasLayer = function(name) {
|
||||
return this._layers.containsKey(name);
|
||||
};
|
||||
|
@@ -52,6 +52,15 @@ interface MapStringBoolean {
|
||||
[Value] VectorString MAP_keys();
|
||||
};
|
||||
|
||||
interface MapStringDouble {
|
||||
void MapStringDouble();
|
||||
|
||||
double MAP_get([Const] DOMString name);
|
||||
void MAP_set([Const] DOMString name, double value);
|
||||
double MAP_has([Const] DOMString name);
|
||||
[Value] VectorString MAP_keys();
|
||||
};
|
||||
|
||||
interface MapStringVariable {
|
||||
[Ref] Variable MAP_get([Const] DOMString name);
|
||||
void MAP_set([Const] DOMString name, [Const, Ref] Variable prop);
|
||||
@@ -571,13 +580,39 @@ interface ExternalLayout {
|
||||
void UnserializeFrom([Const, Ref] SerializerElement element);
|
||||
};
|
||||
|
||||
interface Effect {
|
||||
void Effect();
|
||||
|
||||
void SetName([Const] DOMString name_);
|
||||
[Const, Ref] DOMString GetName();
|
||||
|
||||
void SetEffectName([Const] DOMString effectName_);
|
||||
[Const, Ref] DOMString GetEffectName();
|
||||
|
||||
void SetParameter([Const] DOMString name, float value);
|
||||
float GetParameter([Const] DOMString name);
|
||||
[Const, Ref] MapStringDouble GetAllParameters();
|
||||
|
||||
void SerializeTo([Ref] SerializerElement element);
|
||||
void UnserializeFrom([Const, Ref] SerializerElement element);
|
||||
};
|
||||
|
||||
interface Layer {
|
||||
void Layer();
|
||||
|
||||
void SetName([Const] DOMString name);
|
||||
[Const, Ref] DOMString GetName();
|
||||
void SetVisibility(boolean visible);
|
||||
boolean GetVisibility();
|
||||
|
||||
boolean HasEffectNamed([Const] DOMString name);
|
||||
[Ref] Effect GetEffect([Const] DOMString name);
|
||||
[Ref] Effect GetEffectAt(unsigned long index);
|
||||
unsigned long GetEffectPosition([Const] DOMString name);
|
||||
unsigned long GetEffectsCount();
|
||||
[Ref] Effect InsertNewEffect([Const] DOMString name, unsigned long position);
|
||||
void InsertEffect([Const, Ref] Effect theEffect, unsigned long position);
|
||||
void RemoveEffect([Const] DOMString name);
|
||||
void SwapEffects(unsigned long firstEffectIndex, unsigned long secondEffectIndex);
|
||||
};
|
||||
|
||||
interface PropertyDescriptor {
|
||||
|
@@ -16,6 +16,7 @@
|
||||
#include <GDCore/Project/Variable.h>
|
||||
#include <GDCore/Project/EventsBasedBehavior.h>
|
||||
#include <GDCore/Project/VariablesContainer.h>
|
||||
#include <GDCore/Project/Effect.h>
|
||||
#include <GDCore/Serialization/Serializer.h>
|
||||
#include <GDCore/Serialization/SerializerElement.h>
|
||||
|
||||
@@ -373,6 +374,7 @@ typedef std::vector<std::pair<gd::String, TextFormatting>>
|
||||
typedef std::vector<gd::ObjectGroup> VectorObjectGroup;
|
||||
typedef std::map<gd::String, gd::String> MapStringString;
|
||||
typedef std::map<gd::String, bool> MapStringBoolean;
|
||||
typedef std::map<gd::String, double> MapStringDouble;
|
||||
typedef std::map<gd::String, gd::ExpressionMetadata>
|
||||
MapStringExpressionMetadata;
|
||||
typedef std::map<gd::String, gd::InstructionMetadata>
|
||||
@@ -568,6 +570,7 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
|
||||
#define RemoveEventAt RemoveEvent
|
||||
#define RemoveAt Remove
|
||||
#define GetEventsFunctionAt GetEventsFunction
|
||||
#define GetEffectAt GetEffect
|
||||
|
||||
// We don't use prefix in .idl file to workaround a webidl_binder.py bug
|
||||
// that can't find in its list of interfaces a class which has a prefix.
|
||||
|
@@ -173,6 +173,67 @@ describe('libGD.js', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('gd.Layer', function() {
|
||||
it('can have a name and visibility', function() {
|
||||
const layer = new gd.Layer();
|
||||
|
||||
layer.setName("GUI");
|
||||
layer.setVisibility(false);
|
||||
expect(layer.getName()).toBe("GUI");
|
||||
expect(layer.getVisibility()).toBe(false);
|
||||
|
||||
layer.delete();
|
||||
});
|
||||
it('can have effects', function() {
|
||||
const layer = new gd.Layer();
|
||||
|
||||
expect(layer.hasEffectNamed("EffectThatDoesNotExist")).toBe(false);
|
||||
expect(layer.getEffectsCount()).toBe(0);
|
||||
|
||||
layer.insertNewEffect("MyEffect", 0);
|
||||
expect(layer.hasEffectNamed("EffectThatDoesNotExist")).toBe(false);
|
||||
expect(layer.hasEffectNamed("MyEffect")).toBe(true);
|
||||
expect(layer.getEffectsCount()).toBe(1);
|
||||
expect(layer.getEffectPosition("MyEffect")).toBe(0);
|
||||
|
||||
const effect2 = new gd.Effect();
|
||||
effect2.setName("MyEffect2");
|
||||
|
||||
layer.insertEffect(effect2, 1);
|
||||
expect(layer.hasEffectNamed("MyEffect2")).toBe(true);
|
||||
expect(layer.getEffectsCount()).toBe(2);
|
||||
expect(layer.getEffectPosition("MyEffect")).toBe(0);
|
||||
expect(layer.getEffectPosition("MyEffect2")).toBe(1);
|
||||
|
||||
layer.swapEffects(0, 1);
|
||||
expect(layer.getEffectPosition("MyEffect2")).toBe(0);
|
||||
expect(layer.getEffectPosition("MyEffect")).toBe(1);
|
||||
|
||||
layer.delete();
|
||||
});
|
||||
});
|
||||
|
||||
describe('gd.Effect', function() {
|
||||
it('can have a name, effect name and parameters', function() {
|
||||
const effect = new gd.Effect();
|
||||
|
||||
effect.setName("MyEffect");
|
||||
effect.setEffectName("Sepia");
|
||||
expect(effect.getName()).toBe("MyEffect");
|
||||
expect(effect.getEffectName()).toBe("Sepia");
|
||||
|
||||
effect.setParameter("Brightness", 1);
|
||||
effect.setParameter("Darkness", 0.3);
|
||||
effect.setParameter("Param3", 6);
|
||||
expect(effect.getAllParameters().keys().size()).toBe(3);
|
||||
expect(effect.getParameter("Brightness")).toBe(1);
|
||||
expect(effect.getParameter("Darkness")).toBe(0.3);
|
||||
expect(effect.getParameter("Param3")).toBe(6);
|
||||
|
||||
effect.delete();
|
||||
});
|
||||
});
|
||||
|
||||
describe('gd.ObjectsContainer (using gd.Layout)', function() {
|
||||
let project = null;
|
||||
beforeAll(() => (project = gd.ProjectHelper.createNewGDJSProject()));
|
||||
|
@@ -106,7 +106,7 @@ Add a behavior using [`addBehavior`](http://4ian.github.io/GD-Documentation/GDCo
|
||||
|
||||
> ⚠️ Like other functions to declare extensions, make sure that you've not forgotten to declare a function and that all arguments are correct.
|
||||
|
||||
> 👉 See an example in the [example extension _JsExtension.js_ file](../Extensions/ExampleJsExtension/JsExtension.js).
|
||||
> 👉 See an example in the [example extension _JsExtension.js_ file](../Extensions/ExampleJsExtension/JsExtension.js). Learn more about [properties here](docs/Properties-schema-and-PropertiesEditor-explanations.md).
|
||||
|
||||
#### Declare objects
|
||||
|
||||
@@ -114,7 +114,7 @@ Add an object using [`addObject`](http://4ian.github.io/GD-Documentation/GDCore%
|
||||
|
||||
- Create a `new gd.ObjectJsImplementation()` and define `updateProperty` and `getProperties` (for the object properties) and `updateInitialInstanceProperty` and `getInitialInstanceProperties` (for the optional properties that are attached to each instance).
|
||||
|
||||
> 👉 See an example in the [example extension _JsExtension.js_ file](../Extensions/ExampleJsExtension/JsExtension.js).
|
||||
> 👉 See an example in the [example extension _JsExtension.js_ file](../Extensions/ExampleJsExtension/JsExtension.js). Learn more about [properties here](docs/Properties-schema-and-PropertiesEditor-explanations.md).
|
||||
|
||||
> ℹ️ After doing this, you can actually see your object in GDevelop! Read the next sections to see how to add an editor and a renderer for instances on the scene editor.
|
||||
|
||||
|
@@ -5,7 +5,7 @@ It uses GDevelop [core C++ classes compiled to Javascript](https://github.com/4i
|
||||
|
||||

|
||||
|
||||
## 1) Installation 💻
|
||||
## 1) Installation 💻
|
||||
|
||||
Make sure to have [Git](https://git-scm.com/) and [Node.js](https://nodejs.org) installed. [Yarn](https://yarnpkg.com) is optional.
|
||||
|
||||
@@ -102,7 +102,7 @@ Any text editor is fine, but it's a good idea to have one with *Prettier* (code
|
||||
|
||||
## (Optional) Building and deploying the standalone app 📦
|
||||
|
||||
> 🖐 This section is only for maintainers that want to deploy the "official app" on the GDevelop website. If you're working on contributions for GDevelop, you won't need it.
|
||||
> 🖐 This section is only for maintainers that want to deploy the "official app" on the GDevelop website. If you're working on contributions for GDevelop, you won't need it. You can download ["Nightly Builds" of GDevelop here too](./docs/Nightly-Builds-and-continuous-deployment.md).
|
||||
|
||||
### Desktop version
|
||||
|
||||
@@ -151,7 +151,7 @@ yarn compile-translations # or npm run compile-translations
|
||||
The editor, the game engine and extensions are always in development. Your contribution is welcome!
|
||||
|
||||
* Check the [the **roadmap** for ideas and features planned](https://trello.com/b/qf0lM7k8/gdevelop-roadmap).
|
||||
|
||||
|
||||
You can contribute by picking anything here or anything that you think is missing or could be improved in GD5! If you don't know how to start, it's a good idea to play a bit with the editor and see if there is something that is unavailable and that you can add or fix.
|
||||
|
||||
* Follow the [Development](https://github.com/4ian/GDevelop/tree/master/newIDE#development) section of the README to set up GDevelop and start modifying either **the editor** or **[the game engine/extensions](https://github.com/4ian/GDevelop/tree/master/newIDE#development-of-the-game-engine-or-extensions)**.
|
||||
|
3
newIDE/app/flow-typed/libGD.js
vendored
@@ -18,7 +18,10 @@ declare type gdObjectsContainer = gdEmscriptenObject;
|
||||
declare type gdObjectGroup = gdEmscriptenObject;
|
||||
declare type gdObjectGroupsContainer = gdEmscriptenObject;
|
||||
declare type gdProject = gdObjectsContainer & gdEmscriptenObject;
|
||||
declare type gdEffect = gdEmscriptenObject;
|
||||
declare type gdEffectsContainer = gdEmscriptenObject;
|
||||
declare type gdLayout = gdObjectsContainer & gdEmscriptenObject;
|
||||
declare type gdLayer = gdEffectsContainer & gdEmscriptenObject;
|
||||
declare type gdExternalLayout = gdEmscriptenObject;
|
||||
declare type gdExternalEvents = gdEmscriptenObject;
|
||||
declare type gdSerializerElement = gdEmscriptenObject;
|
||||
|
@@ -34,7 +34,7 @@
|
||||
"axios": "^0.16.1",
|
||||
"blueimp-md5": "^2.10.0",
|
||||
"classnames": "2.2.5",
|
||||
"create-react-context": "^0.1.6",
|
||||
"create-react-context": "0.1.6",
|
||||
"date-fns": "^1.29.0",
|
||||
"element-closest": "2.0.2",
|
||||
"firebase": "^6.1.0",
|
||||
|
BIN
newIDE/app/public/JsPlatform/Extensions/yarn16.png
Normal file
After Width: | Height: | Size: 719 B |
BIN
newIDE/app/public/JsPlatform/Extensions/yarn24.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
newIDE/app/public/JsPlatform/Extensions/yarn32.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-eval' 'unsafe-inline' https://api.gdevelop-app.com https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js https://checkout.stripe.com/checkout.js https://api.keen.io https://fullstory.com/s/fs.js https://rs.fullstory.com">
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-eval' 'unsafe-inline' https://api.gdevelop-app.com https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js https://api.keen.io https://fullstory.com/s/fs.js https://rs.fullstory.com">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<title>GDevelop 5</title>
|
||||
|
||||
@@ -40,9 +40,6 @@
|
||||
</script>
|
||||
<script src="%PUBLIC_URL%/libGD.js"></script>
|
||||
|
||||
<!-- Stripe.com Checkout -->
|
||||
<script src="https://checkout.stripe.com/checkout.js"></script>
|
||||
|
||||
<!-- Monaco Editor support -->
|
||||
<script>
|
||||
// Monaco editor is using amd require. Save the Node.js require if it exists (Electron)
|
||||
|
@@ -0,0 +1 @@
|
||||
How to implement the same menu across multiple scenes using functions and text effects for hover/active states.
|
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 2.7 KiB |
@@ -12,6 +12,8 @@ import NewBehaviorDialog from './NewBehaviorDialog';
|
||||
import { getBehaviorHelpPagePath } from './BehaviorsHelpPagePaths';
|
||||
import BehaviorsEditorService from './BehaviorsEditorService';
|
||||
import { isNullPtr } from '../Utils/IsNullPtr';
|
||||
import { Column, Line } from '../UI/Grid';
|
||||
import RaisedButton from 'material-ui/RaisedButton';
|
||||
const gd = global.gd;
|
||||
|
||||
const styles = {
|
||||
@@ -30,14 +32,17 @@ const styles = {
|
||||
},
|
||||
};
|
||||
const AddBehaviorLine = ({ onAdd }) => (
|
||||
<div style={styles.addBehaviorLine}>
|
||||
<EmptyMessage style={styles.addBehaviorText}>
|
||||
Click to add a behavior to the object:
|
||||
</EmptyMessage>
|
||||
<IconButton onClick={onAdd}>
|
||||
<Add />
|
||||
</IconButton>
|
||||
</div>
|
||||
<Column>
|
||||
<Line justifyContent="flex-end" expand>
|
||||
<RaisedButton
|
||||
label={<Trans>Add a behavior to the object</Trans>}
|
||||
primary
|
||||
onClick={onAdd}
|
||||
labelPosition="before"
|
||||
icon={<Add />}
|
||||
/>
|
||||
</Line>
|
||||
</Column>
|
||||
);
|
||||
|
||||
export default class BehaviorsEditor extends Component {
|
||||
|
@@ -42,7 +42,7 @@ export const create = (authentification: Authentification) => {
|
||||
i18n={i18n}
|
||||
eventsFunctionsExtensionsState={eventsFunctionsExtensionsState}
|
||||
previewLauncher={<BrowserS3PreviewLauncher />}
|
||||
exportDialog={<ExportDialog exporters={getBrowserExporters()} />}
|
||||
exportDialog={(props) => <ExportDialog {...props} exporters={getBrowserExporters()} />}
|
||||
createDialog={
|
||||
<CreateProjectDialog
|
||||
examplesComponent={BrowserExamples}
|
||||
|
@@ -82,6 +82,17 @@ var objects = new gdjs.RuntimeScene();
|
||||
* @return {gdjs.RuntimeObject[]} Instances of the specified object.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the Hashtable containing the lists of instances of the specified object.
|
||||
*
|
||||
* You can alter the list and this will alter the objects picked for the next conditions/actions/events.
|
||||
* If you don't need this, prefer using \`getObjects\`.
|
||||
*
|
||||
* @callback GetObjectsListsFunction
|
||||
* @param {string} objectName The name of the object for which instances must be returned.
|
||||
* @return {?Hashtable} Hashtable containing the lists of instances (keys are object names in the current context), or \`null\` if not found
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a new object from its name. The object is added to the instances
|
||||
* living on the scene.
|
||||
@@ -112,7 +123,7 @@ var objects = new gdjs.RuntimeScene();
|
||||
/** Represents the context of the events function (or the behavior method),
|
||||
* if any. If the JS code is running in a scene, this will be undefined (so you can't use this in a scene).
|
||||
*
|
||||
* @type {?{getObjects: GetObjectsFunction, getBehaviorName: GetBehaviorNameFunction, createObject: CreateObjectFunction, getArgument: GetArgumentFunction, returnValue: boolean | number | string}}
|
||||
* @type {?{getObjects: GetObjectsFunction, getObjectsLists: GetObjectsListsFunction, getBehaviorName: GetBehaviorNameFunction, createObject: CreateObjectFunction, getArgument: GetArgumentFunction, returnValue: boolean | number | string}}
|
||||
*/
|
||||
var eventsFunctionContext = {};
|
||||
`,
|
||||
|
205
newIDE/app/src/EffectsList/EffectDescription.js
Normal file
@@ -0,0 +1,205 @@
|
||||
// @flow
|
||||
import { type Schema } from '../PropertiesEditor';
|
||||
import { t } from '@lingui/macro';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
|
||||
type EffectDefaultValue = {| name: string, value: number |};
|
||||
|
||||
export type EffectDescription = {|
|
||||
name: string,
|
||||
schema: Schema,
|
||||
parameterDefaultValues: Array<EffectDefaultValue>,
|
||||
|};
|
||||
|
||||
const clampValue = (value, min, max) => Math.max(min, Math.min(max, value));
|
||||
const clampKernelSize = value =>
|
||||
[5, 7, 9, 11, 13, 15].includes(value) ? value : 5;
|
||||
|
||||
export const getAllEffectDescriptions = (
|
||||
i18n: I18nType
|
||||
): Array<EffectDescription> => [
|
||||
{
|
||||
name: 'Sepia',
|
||||
schema: [
|
||||
{
|
||||
name: 'opacity',
|
||||
getLabel: () => 'opacity ' + i18n._(t`(between 0 and 1)`),
|
||||
valueType: 'number',
|
||||
getValue: (effect: gdEffect) => effect.getParameter('opacity'),
|
||||
setValue: (effect: gdEffect, newValue: number) =>
|
||||
effect.setParameter('opacity', clampValue(newValue, 0, 1)),
|
||||
},
|
||||
],
|
||||
parameterDefaultValues: [
|
||||
{
|
||||
name: 'opacity',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'BlackAndWhite',
|
||||
schema: [
|
||||
{
|
||||
name: 'opacity',
|
||||
getLabel: () => 'opacity ' + i18n._(t`(between 0 and 1)`),
|
||||
valueType: 'number',
|
||||
getValue: (effect: gdEffect) => effect.getParameter('opacity'),
|
||||
setValue: (effect: gdEffect, newValue: number) =>
|
||||
effect.setParameter('opacity', clampValue(newValue, 0, 1)),
|
||||
},
|
||||
],
|
||||
parameterDefaultValues: [
|
||||
{
|
||||
name: 'opacity',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Brightness',
|
||||
schema: [
|
||||
{
|
||||
name: 'brightness',
|
||||
getLabel: () => 'brightness ' + i18n._(t`(between 0 and 1)`),
|
||||
valueType: 'number',
|
||||
getValue: (effect: gdEffect) => effect.getParameter('brightness'),
|
||||
setValue: (effect: gdEffect, newValue: number) =>
|
||||
effect.setParameter('brightness', clampValue(newValue, 0, 360)),
|
||||
},
|
||||
],
|
||||
parameterDefaultValues: [
|
||||
{
|
||||
name: 'brightness',
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Blur',
|
||||
schema: [
|
||||
{
|
||||
name: 'blur',
|
||||
getLabel: () => 'blur',
|
||||
valueType: 'number',
|
||||
getValue: (effect: gdEffect) => effect.getParameter('blur'),
|
||||
setValue: (effect: gdEffect, newValue: number) =>
|
||||
effect.setParameter('blur', newValue),
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
getLabel: () =>
|
||||
'quality ' +
|
||||
i18n._(t`(Number of render passes. High values cause lag.)`),
|
||||
valueType: 'number',
|
||||
getValue: (effect: gdEffect) => effect.getParameter('quality'),
|
||||
setValue: (effect: gdEffect, newValue: number) =>
|
||||
effect.setParameter('quality', newValue),
|
||||
},
|
||||
{
|
||||
name: 'resolution',
|
||||
getLabel: () => 'resolution',
|
||||
valueType: 'number',
|
||||
getValue: (effect: gdEffect) => effect.getParameter('resolution'),
|
||||
setValue: (effect: gdEffect, newValue: number) =>
|
||||
effect.setParameter('resolution', newValue),
|
||||
},
|
||||
{
|
||||
name: 'kernelSize',
|
||||
getLabel: () =>
|
||||
'kernelSize ' + i18n._(t`(one of these values: 5, 7, 9, 11, 13, 15)`),
|
||||
valueType: 'number',
|
||||
getValue: (effect: gdEffect) => effect.getParameter('kernelSize'),
|
||||
setValue: (effect: gdEffect, newValue: number) =>
|
||||
effect.setParameter('kernelSize', clampKernelSize(newValue)),
|
||||
},
|
||||
],
|
||||
parameterDefaultValues: [
|
||||
{
|
||||
name: 'blur',
|
||||
value: 8,
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
name: 'resolution',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
name: 'kernelSize',
|
||||
value: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Night',
|
||||
schema: [
|
||||
{
|
||||
name: 'opacity',
|
||||
getLabel: () => 'opacity ' + i18n._(t`(between 0 and 1)`),
|
||||
valueType: 'number',
|
||||
getValue: (effect: gdEffect) => effect.getParameter('opacity'),
|
||||
setValue: (effect: gdEffect, newValue: number) =>
|
||||
effect.setParameter('opacity', clampValue(newValue, 0, 1)),
|
||||
},
|
||||
{
|
||||
name: 'intensity',
|
||||
getLabel: () => 'intensity ' + i18n._(t` (between 0 and 1)`),
|
||||
valueType: 'number',
|
||||
getValue: (effect: gdEffect) => effect.getParameter('intensity'),
|
||||
setValue: (effect: gdEffect, newValue: number) =>
|
||||
effect.setParameter('intensity', clampValue(newValue, 0, 1)),
|
||||
},
|
||||
],
|
||||
parameterDefaultValues: [
|
||||
{
|
||||
name: 'opacity',
|
||||
value: 0.5,
|
||||
},
|
||||
{
|
||||
name: 'intensity',
|
||||
value: 0.5,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'LightNight',
|
||||
schema: [
|
||||
{
|
||||
name: 'opacity',
|
||||
getLabel: () => 'opacity ' + i18n._(t`(between 0 and 1)`),
|
||||
valueType: 'number',
|
||||
getValue: (effect: gdEffect) => effect.getParameter('opacity'),
|
||||
setValue: (effect: gdEffect, newValue: number) =>
|
||||
effect.setParameter('opacity', clampValue(newValue, 0, 1)),
|
||||
},
|
||||
],
|
||||
parameterDefaultValues: [
|
||||
{
|
||||
name: 'opacity',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Noise',
|
||||
schema: [
|
||||
{
|
||||
name: 'noise',
|
||||
getLabel: () => 'noise ' + i18n._(t`(between 0 and 1)`),
|
||||
valueType: 'number',
|
||||
getValue: (effect: gdEffect) => effect.getParameter('noise'),
|
||||
setValue: (effect: gdEffect, newValue: number) =>
|
||||
effect.setParameter('noise', clampValue(newValue, 0, 1)),
|
||||
},
|
||||
],
|
||||
parameterDefaultValues: [
|
||||
{
|
||||
name: 'noise',
|
||||
value: 0.5,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
52
newIDE/app/src/EffectsList/EffectsListDialog.js
Normal file
@@ -0,0 +1,52 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import * as React from 'react';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import Dialog from '../UI/Dialog';
|
||||
import EffectsList from './index';
|
||||
import HelpButton from '../UI/HelpButton';
|
||||
|
||||
type Props = {|
|
||||
onApply: () => void,
|
||||
effectsContainer: gdEffectsContainer,
|
||||
|};
|
||||
|
||||
export default class EffectsListDialog extends React.Component<Props, {||}> {
|
||||
render() {
|
||||
const { onApply, effectsContainer } = this.props;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
noMargin
|
||||
secondaryActions={[
|
||||
<HelpButton
|
||||
key="help"
|
||||
helpPagePath="/interface/scene-editor/layers-and-cameras"
|
||||
/>,
|
||||
]}
|
||||
actions={[
|
||||
<FlatButton
|
||||
label={<Trans>Ok</Trans>}
|
||||
primary
|
||||
keyboardFocused
|
||||
onClick={onApply}
|
||||
key={'Apply'}
|
||||
/>,
|
||||
]}
|
||||
modal
|
||||
open
|
||||
onRequestClose={onApply}
|
||||
autoScrollBodyContent
|
||||
title={<Trans>Effects</Trans>}
|
||||
>
|
||||
<EffectsList
|
||||
effectsContainer={effectsContainer}
|
||||
onEffectsUpdated={
|
||||
() =>
|
||||
this.forceUpdate() /*Force update to ensure dialog is properly positioned*/
|
||||
}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
226
newIDE/app/src/EffectsList/index.js
Normal file
@@ -0,0 +1,226 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { t } from '@lingui/macro';
|
||||
import { I18n } from '@lingui/react';
|
||||
import * as React from 'react';
|
||||
import { Column, Line } from '../UI/Grid';
|
||||
import SelectField from 'material-ui/SelectField';
|
||||
import MenuItem from 'material-ui/MenuItem';
|
||||
import { mapFor } from '../Utils/MapFor';
|
||||
import RaisedButton from 'material-ui/RaisedButton';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import EmptyMessage from '../UI/EmptyMessage';
|
||||
import IconMenu from '../UI/Menu/IconMenu';
|
||||
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert';
|
||||
import SemiControlledTextField from '../UI/SemiControlledTextField';
|
||||
import MiniToolbar from '../UI/MiniToolbar';
|
||||
import newNameGenerator from '../Utils/NewNameGenerator';
|
||||
import Add from 'material-ui/svg-icons/content/add';
|
||||
import {
|
||||
getAllEffectDescriptions,
|
||||
type EffectDescription,
|
||||
} from './EffectDescription';
|
||||
import PropertiesEditor from '../PropertiesEditor';
|
||||
import DismissableAlertMessage from '../UI/DismissableAlertMessage';
|
||||
|
||||
type Props = {|
|
||||
effectsContainer: gdEffectsContainer,
|
||||
onEffectsUpdated: () => void,
|
||||
|};
|
||||
|
||||
const getEffectDescription = (
|
||||
allEffectDescriptions: Array<EffectDescription>,
|
||||
effectName: string
|
||||
): ?EffectDescription => {
|
||||
return allEffectDescriptions.find(
|
||||
effectDescription => effectDescription.name === effectName
|
||||
);
|
||||
};
|
||||
|
||||
export default class EffectsList extends React.Component<Props, {||}> {
|
||||
_addEffect = () => {
|
||||
const { effectsContainer } = this.props;
|
||||
|
||||
const newName = newNameGenerator('Effect', name =>
|
||||
effectsContainer.hasEffectNamed(name)
|
||||
);
|
||||
effectsContainer.insertNewEffect(
|
||||
newName,
|
||||
effectsContainer.getEffectsCount()
|
||||
);
|
||||
|
||||
this.forceUpdate();
|
||||
this.props.onEffectsUpdated();
|
||||
};
|
||||
|
||||
_removeEffect = (name: string) => {
|
||||
const { effectsContainer } = this.props;
|
||||
|
||||
effectsContainer.removeEffect(name);
|
||||
this.forceUpdate();
|
||||
this.props.onEffectsUpdated();
|
||||
};
|
||||
|
||||
_chooseEffectName = (
|
||||
allEffectDescriptions: Array<EffectDescription>,
|
||||
effect: gdEffect,
|
||||
newEffectName: string
|
||||
) => {
|
||||
effect.setEffectName(newEffectName);
|
||||
const effectDescription = getEffectDescription(
|
||||
allEffectDescriptions,
|
||||
newEffectName
|
||||
);
|
||||
|
||||
if (effectDescription) {
|
||||
effectDescription.parameterDefaultValues.forEach(({ name, value }) => {
|
||||
effect.setParameter(name, value);
|
||||
});
|
||||
}
|
||||
|
||||
this.forceUpdate();
|
||||
this.props.onEffectsUpdated();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { effectsContainer } = this.props;
|
||||
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => {
|
||||
const allEffectDescriptions = getAllEffectDescriptions(i18n);
|
||||
|
||||
return (
|
||||
<Column noMargin expand>
|
||||
<Line>
|
||||
<Column>
|
||||
<DismissableAlertMessage
|
||||
identifier="effects-usage"
|
||||
kind="info"
|
||||
>
|
||||
<Trans>
|
||||
Effects can change how the layer is rendered on screen.
|
||||
After adding an effect, set up its parameters. Launch a
|
||||
preview to see the result. Using the events and the name
|
||||
of the effect, you can change the parameters during the
|
||||
game.
|
||||
</Trans>
|
||||
</DismissableAlertMessage>
|
||||
</Column>
|
||||
</Line>
|
||||
{effectsContainer.getEffectsCount() > 3 && (
|
||||
<Line>
|
||||
<Column>
|
||||
<DismissableAlertMessage
|
||||
identifier="too-much-effects"
|
||||
kind="warning"
|
||||
>
|
||||
<Trans>
|
||||
Using a lot of effects can have a severe negative impact
|
||||
on the rendering performance, especially on low-end or
|
||||
mobile devices. Consider using less effects if possible.
|
||||
You can also disable and re-enable effects as needed
|
||||
using events.
|
||||
</Trans>
|
||||
</DismissableAlertMessage>
|
||||
</Column>
|
||||
</Line>
|
||||
)}
|
||||
{mapFor(0, effectsContainer.getEffectsCount(), (i: number) => {
|
||||
const effect: gdEffect = effectsContainer.getEffectAt(i);
|
||||
const effectName = effect.getEffectName();
|
||||
const effectDescription = getEffectDescription(
|
||||
allEffectDescriptions,
|
||||
effectName
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment key={i}>
|
||||
<MiniToolbar>
|
||||
<Column expand noMargin>
|
||||
<SemiControlledTextField
|
||||
commitOnBlur
|
||||
hintText={<Trans>Enter the effect name</Trans>}
|
||||
value={effect.getName()}
|
||||
onChange={newName => {
|
||||
if (newName === effect.getName()) return;
|
||||
|
||||
effect.setName(newName);
|
||||
this.forceUpdate();
|
||||
this.props.onEffectsUpdated();
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
</Column>
|
||||
<Column expand>
|
||||
<SelectField
|
||||
value={effectName}
|
||||
onChange={(e, i, newEffectName) =>
|
||||
this._chooseEffectName(
|
||||
allEffectDescriptions,
|
||||
effect,
|
||||
newEffectName
|
||||
)
|
||||
}
|
||||
fullWidth
|
||||
hintText={<Trans>Choose the effect to apply</Trans>}
|
||||
>
|
||||
{allEffectDescriptions.map(effectDescription => (
|
||||
<MenuItem
|
||||
key={effectDescription.name}
|
||||
value={effectDescription.name}
|
||||
primaryText={effectDescription.name}
|
||||
/>
|
||||
))}
|
||||
</SelectField>
|
||||
</Column>
|
||||
<IconMenu
|
||||
iconButtonElement={
|
||||
<IconButton>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
}
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: i18n._(t`Delete`),
|
||||
click: () => this._removeEffect(effect.getName()),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</MiniToolbar>
|
||||
<Line expand noMargin>
|
||||
<Column expand>
|
||||
{!!effectName && effectDescription ? (
|
||||
<PropertiesEditor
|
||||
instances={[effect]}
|
||||
schema={effectDescription.schema}
|
||||
/>
|
||||
) : null}
|
||||
</Column>
|
||||
</Line>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
{effectsContainer.getEffectsCount() === 0 ? (
|
||||
<EmptyMessage>
|
||||
<Trans>No effects applied.</Trans>
|
||||
</EmptyMessage>
|
||||
) : null}
|
||||
<Column>
|
||||
<Line justifyContent="flex-end" alignItems="center" expand>
|
||||
<RaisedButton
|
||||
primary
|
||||
label={<Trans>Add an effect</Trans>}
|
||||
onClick={this._addEffect}
|
||||
labelPosition="before"
|
||||
icon={<Add />}
|
||||
/>
|
||||
</Line>
|
||||
</Column>
|
||||
</Column>
|
||||
);
|
||||
}}
|
||||
</I18n>
|
||||
);
|
||||
}
|
||||
}
|
@@ -20,6 +20,7 @@ import newNameGenerator from '../Utils/NewNameGenerator';
|
||||
import InlineCheckbox from '../UI/InlineCheckbox';
|
||||
import Visibility from 'material-ui/svg-icons/action/visibility';
|
||||
import VisibilityOff from 'material-ui/svg-icons/action/visibility-off';
|
||||
import Add from 'material-ui/svg-icons/content/add';
|
||||
|
||||
const gd = global.gd;
|
||||
|
||||
@@ -295,13 +296,17 @@ export default class EventsBasedBehaviorPropertiesEditor extends React.Component
|
||||
</Trans>
|
||||
</EmptyMessage>
|
||||
) : null}
|
||||
<Line justifyContent="center">
|
||||
<RaisedButton
|
||||
primary
|
||||
label={<Trans>Add a property</Trans>}
|
||||
onClick={this._addProperty}
|
||||
/>
|
||||
</Line>
|
||||
<Column>
|
||||
<Line justifyContent="flex-end" expand>
|
||||
<RaisedButton
|
||||
primary
|
||||
label={<Trans>Add a property</Trans>}
|
||||
onClick={this._addProperty}
|
||||
labelPosition="before"
|
||||
icon={<Add />}
|
||||
/>
|
||||
</Line>
|
||||
</Column>
|
||||
</div>
|
||||
</Line>
|
||||
</Column>
|
||||
|
@@ -21,6 +21,7 @@ import ObjectTypeSelector from '../../ObjectTypeSelector';
|
||||
import BehaviorTypeSelector from '../../BehaviorTypeSelector';
|
||||
import { isBehaviorLifecycleFunction } from '../../EventsFunctionsExtensionsLoader/MetadataDeclarationHelpers';
|
||||
import { getParametersIndexOffset } from '../../EventsFunctionsExtensionsLoader';
|
||||
import Add from 'material-ui/svg-icons/content/add';
|
||||
|
||||
const gd = global.gd;
|
||||
|
||||
@@ -278,15 +279,19 @@ export default class EventsFunctionParametersEditor extends React.Component<
|
||||
<Trans>No parameters for this function.</Trans>
|
||||
</EmptyMessage>
|
||||
) : null}
|
||||
<Line justifyContent="center">
|
||||
{!freezeParameters && (
|
||||
<RaisedButton
|
||||
primary
|
||||
label={<Trans>Add a parameter</Trans>}
|
||||
onClick={this._addParameter}
|
||||
/>
|
||||
)}
|
||||
</Line>
|
||||
<Column>
|
||||
<Line justifyContent="flex-end" expand>
|
||||
{!freezeParameters && (
|
||||
<RaisedButton
|
||||
primary
|
||||
label={<Trans>Add a parameter</Trans>}
|
||||
onClick={this._addParameter}
|
||||
labelPosition="before"
|
||||
icon={<Add />}
|
||||
/>
|
||||
)}
|
||||
</Line>
|
||||
</Column>
|
||||
</div>
|
||||
</Line>
|
||||
{helpPagePath ? (
|
||||
|
@@ -400,10 +400,23 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
|
||||
};
|
||||
},
|
||||
() => {
|
||||
// If we're closing the properties of a behavior, notify parent
|
||||
// that a behavior was edited (to trigger reload of extensions)
|
||||
if (!editedEventsBasedBehavior && this.props.onBehaviorEdited)
|
||||
this.props.onBehaviorEdited();
|
||||
if (!editedEventsBasedBehavior) {
|
||||
// If we're closing the properties of a behavior, notify parent
|
||||
// that a behavior was edited (to trigger reload of extensions)
|
||||
if (this.props.onBehaviorEdited) {
|
||||
this.props.onBehaviorEdited();
|
||||
}
|
||||
|
||||
// Reload the selected events function, if any, as the behavior was
|
||||
// changed so objects containers need to be re-created. Notably, the
|
||||
// type of the object that is handled by the behavior may have changed.
|
||||
if (this.state.selectedEventsFunction) {
|
||||
this._loadEventsFunctionFrom(
|
||||
this.props.project,
|
||||
this.state.selectedEventsFunction
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@@ -1,9 +1,9 @@
|
||||
// @flow
|
||||
import createReactContext, { type Context } from 'create-react-context';
|
||||
import {
|
||||
type EventsFunctionsExtensionWriter,
|
||||
type EventsFunctionsExtensionOpener,
|
||||
} from './Storage';
|
||||
import createReactContext from 'create-react-context';
|
||||
|
||||
export type EventsFunctionsExtensionsState = {|
|
||||
eventsFunctionsExtensionsError: ?Error,
|
||||
@@ -27,7 +27,7 @@ const defaultState = {
|
||||
getEventsFunctionsExtensionOpener: () => null,
|
||||
};
|
||||
|
||||
const EventsFunctionsExtensionsContext: Context<EventsFunctionsExtensionsState> = createReactContext(
|
||||
const EventsFunctionsExtensionsContext = createReactContext<EventsFunctionsExtensionsState>(
|
||||
defaultState
|
||||
);
|
||||
|
||||
|
@@ -67,18 +67,22 @@ export default class ForEachEvent extends React.Component<
|
||||
[executableEventContainer]: true,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames({
|
||||
[selectableArea]: true,
|
||||
[disabledText]: this.props.disabled,
|
||||
})}
|
||||
onClick={this.edit}
|
||||
>
|
||||
{objectName ? (
|
||||
`Repeat for each ${objectName} object:`
|
||||
) : (
|
||||
<i>Click to choose for which objects this event will be repeated</i>
|
||||
)}
|
||||
<div>
|
||||
<span
|
||||
className={classNames({
|
||||
[selectableArea]: true,
|
||||
[disabledText]: this.props.disabled,
|
||||
})}
|
||||
onClick={this.edit}
|
||||
>
|
||||
{objectName ? (
|
||||
`Repeat for each ${objectName} object:`
|
||||
) : (
|
||||
<i>
|
||||
Click to choose for which objects this event will be repeated
|
||||
</i>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div style={styles.instructionsContainer}>
|
||||
<InstructionsList
|
||||
|
@@ -67,18 +67,20 @@ export default class RepeatEvent extends React.Component<
|
||||
[executableEventContainer]: true,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames({
|
||||
[selectableArea]: true,
|
||||
[disabledText]: this.props.disabled,
|
||||
})}
|
||||
onClick={this.edit}
|
||||
>
|
||||
{expression ? (
|
||||
`Repeat ${expression} times:`
|
||||
) : (
|
||||
<i>Click to choose how many times will be repeated</i>
|
||||
)}
|
||||
<div>
|
||||
<span
|
||||
className={classNames({
|
||||
[selectableArea]: true,
|
||||
[disabledText]: this.props.disabled,
|
||||
})}
|
||||
onClick={this.edit}
|
||||
>
|
||||
{expression ? (
|
||||
`Repeat ${expression} times:`
|
||||
) : (
|
||||
<i>Click to choose how many times will be repeated</i>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div style={styles.instructionsContainer}>
|
||||
<InstructionsList
|
||||
|
@@ -95,7 +95,10 @@ export default class InlineParameterEditor extends React.Component<
|
||||
instructionMetadata,
|
||||
},
|
||||
() => {
|
||||
if (this._field && this._field.focus) this._field.focus();
|
||||
// Give a bit of time for the popover to mount itself
|
||||
setTimeout(() => {
|
||||
if (this._field && this._field.focus) this._field.focus();
|
||||
}, 10);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ const styles = {
|
||||
maxWidth: 600,
|
||||
height: 80,
|
||||
overflowY: 'hidden',
|
||||
minWidth: 200, // Avoid extra small popover for some parameters like relational operator
|
||||
},
|
||||
contentContainer: {
|
||||
overflow: 'hidden',
|
||||
|
@@ -162,6 +162,7 @@ export const enumerateObjectInstructions = (
|
||||
)
|
||||
)
|
||||
);
|
||||
const baseObjectType = ''; /* An empty string means the base object */
|
||||
|
||||
const allExtensions = gd
|
||||
.asPlatform(gd.JsPlatform.get())
|
||||
@@ -177,7 +178,7 @@ export const enumerateObjectInstructions = (
|
||||
extension
|
||||
.getExtensionObjectsTypes()
|
||||
.toJSArray()
|
||||
.indexOf('' /* An empty string means the base object */) !== -1;
|
||||
.indexOf(baseObjectType) !== -1;
|
||||
const behaviorTypes = extension
|
||||
.getBehaviorsTypes()
|
||||
.toJSArray()
|
||||
@@ -190,7 +191,7 @@ export const enumerateObjectInstructions = (
|
||||
const prefix = '';
|
||||
|
||||
//Objects instructions:
|
||||
if (hasObjectType) {
|
||||
if (objectType !== baseObjectType && hasObjectType) {
|
||||
const objectMetadata = extension.getObjectMetadata(objectType);
|
||||
allInstructions = [
|
||||
...allInstructions,
|
||||
@@ -211,12 +212,8 @@ export const enumerateObjectInstructions = (
|
||||
...enumerateExtensionInstructions(
|
||||
prefix,
|
||||
isCondition
|
||||
? extension.getAllConditionsForObject(
|
||||
'' /* An empty string means the base object */
|
||||
)
|
||||
: extension.getAllActionsForObject(
|
||||
'' /* An empty string means the base object */
|
||||
),
|
||||
? extension.getAllConditionsForObject(baseObjectType)
|
||||
: extension.getAllActionsForObject(baseObjectType),
|
||||
{ objectMetadata }
|
||||
),
|
||||
];
|
||||
|
@@ -17,6 +17,12 @@ type Props = {|
|
||||
scope: EventsScope,
|
||||
|};
|
||||
|
||||
const style = {
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
};
|
||||
|
||||
export default class ExpressionSelector extends Component<Props, {||}> {
|
||||
instructionsInfo: Array<EnumeratedInstructionOrExpressionMetadata> = filterEnumeratedInstructionOrExpressionMetadataByScope(
|
||||
enumerateExpressions(this.props.expressionType).allExpressions,
|
||||
@@ -30,6 +36,7 @@ export default class ExpressionSelector extends Component<Props, {||}> {
|
||||
const { expressionType, scope, ...otherProps } = this.props;
|
||||
return (
|
||||
<InstructionOrExpressionSelector
|
||||
style={style}
|
||||
instructionsInfo={this.instructionsInfo}
|
||||
instructionsInfoTree={this.instructionsInfoTree}
|
||||
iconSize={16}
|
||||
|
@@ -17,6 +17,12 @@ type Props = {|
|
||||
scope: EventsScope,
|
||||
|};
|
||||
|
||||
const style = {
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
};
|
||||
|
||||
export default class InstructionSelector extends Component<Props, {||}> {
|
||||
instructionsInfo: Array<EnumeratedInstructionOrExpressionMetadata> = filterEnumeratedInstructionOrExpressionMetadataByScope(
|
||||
enumerateInstructions(this.props.isCondition),
|
||||
@@ -30,6 +36,7 @@ export default class InstructionSelector extends Component<Props, {||}> {
|
||||
const { isCondition, scope, ...otherProps } = this.props;
|
||||
return (
|
||||
<InstructionOrExpressionSelector
|
||||
style={style}
|
||||
instructionsInfo={this.instructionsInfo}
|
||||
instructionsInfoTree={this.instructionsInfoTree}
|
||||
iconSize={24}
|
||||
|
@@ -11,7 +11,7 @@ import { renderInstructionOrExpressionListItem } from '../SelectorListItems/Sele
|
||||
import { renderInstructionTree } from '../SelectorListItems/SelectorInstructionsTreeListItem';
|
||||
import EmptyMessage from '../../../UI/EmptyMessage';
|
||||
import ScrollView from '../../../UI/ScrollView';
|
||||
import { Column, Line } from '../../../UI/Grid';
|
||||
import { Line } from '../../../UI/Grid';
|
||||
import { getInstructionListItemKey } from '../SelectorListItems/Keys';
|
||||
|
||||
const SelectableList = makeSelectable(List);
|
||||
@@ -33,6 +33,7 @@ type Props = {|
|
||||
iconSize: number,
|
||||
useSubheaders?: boolean,
|
||||
searchBarHintText?: React.Node,
|
||||
style?: Object,
|
||||
|};
|
||||
type State = {|
|
||||
searchText: string,
|
||||
@@ -67,6 +68,7 @@ export default class InstructionOrExpressionSelector extends React.Component<
|
||||
onChoose,
|
||||
searchBarHintText,
|
||||
useSubheaders,
|
||||
style,
|
||||
} = this.props;
|
||||
const { searchText } = this.state;
|
||||
const displayedInstructionsList = searchText
|
||||
@@ -83,7 +85,12 @@ export default class InstructionOrExpressionSelector extends React.Component<
|
||||
return (
|
||||
<ThemeConsumer>
|
||||
{muiTheme => (
|
||||
<Column noMargin expand>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: muiTheme.list.itemsBackgroundColor,
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
<SearchBar
|
||||
value={searchText}
|
||||
onChange={searchText =>
|
||||
@@ -96,9 +103,7 @@ export default class InstructionOrExpressionSelector extends React.Component<
|
||||
placeholder={searchBarHintText}
|
||||
ref={searchBar => (this._searchBar = searchBar)}
|
||||
/>
|
||||
<ScrollView
|
||||
style={{ backgroundColor: muiTheme.list.itemsBackgroundColor }}
|
||||
>
|
||||
<ScrollView>
|
||||
{hasResults && (
|
||||
<SelectableList value={getInstructionListItemKey(selectedType)}>
|
||||
{searchText
|
||||
@@ -133,7 +138,7 @@ export default class InstructionOrExpressionSelector extends React.Component<
|
||||
</Line>
|
||||
)}
|
||||
</ScrollView>
|
||||
</Column>
|
||||
</div>
|
||||
)}
|
||||
</ThemeConsumer>
|
||||
);
|
||||
|
@@ -80,7 +80,7 @@ type Props = {|
|
||||
const iconSize = 24;
|
||||
const minHeight = 400; // Avoid a super small list in empty scenes. 400 is enough to be displayed on an iPhone SE.
|
||||
|
||||
export default class InstructionOrObjectSelector extends React.Component<
|
||||
export default class InstructionOrObjectSelector extends React.PureComponent<
|
||||
Props,
|
||||
State
|
||||
> {
|
||||
|
@@ -5,7 +5,6 @@ import { I18n } from '@lingui/react';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
|
||||
import * as React from 'react';
|
||||
import Divider from 'material-ui/Divider';
|
||||
import Toggle from 'material-ui/Toggle';
|
||||
import { mapFor } from '../../Utils/MapFor';
|
||||
import EmptyMessage from '../../UI/EmptyMessage';
|
||||
@@ -16,7 +15,7 @@ import {
|
||||
type ChooseResourceFunction,
|
||||
} from '../../ResourcesList/ResourceSource.flow';
|
||||
import { type ResourceExternalEditor } from '../../ResourcesList/ResourceExternalEditor.flow';
|
||||
import { Line } from '../../UI/Grid';
|
||||
import { Line, Spacer } from '../../UI/Grid';
|
||||
import AlertMessage from '../../UI/AlertMessage';
|
||||
import { getExtraInstructionInformation } from '../../Hints';
|
||||
import { isAnEventFunctionMetadata } from '../../EventsFunctionsExtensionsLoader';
|
||||
@@ -258,7 +257,7 @@ export default class InstructionParametersEditor extends React.Component<
|
||||
</AlertMessage>
|
||||
</Line>
|
||||
)}
|
||||
<Divider />
|
||||
<Spacer />
|
||||
<div key={type} style={styles.parametersContainer}>
|
||||
{mapFor(0, instructionMetadata.getParametersCount(), i => {
|
||||
const parameterMetadata = instructionMetadata.getParameter(i);
|
||||
|
@@ -45,6 +45,11 @@ const styles = {
|
||||
dialogBody: {
|
||||
padding: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
fullHeightSelector: {
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
};
|
||||
@@ -115,7 +120,7 @@ export default class NewInstructionEditorDialog extends React.Component<
|
||||
instruction.getParametersCount() > 0
|
||||
) {
|
||||
return {
|
||||
...this._chooseObject(
|
||||
...this._getChosenObjectState(
|
||||
instruction.getParameter(0),
|
||||
false /* Even if the instruction is invalid for the object, show it as it's what we have already */
|
||||
),
|
||||
@@ -150,7 +155,7 @@ export default class NewInstructionEditorDialog extends React.Component<
|
||||
);
|
||||
};
|
||||
|
||||
_chooseObject(
|
||||
_getChosenObjectState(
|
||||
objectName: string,
|
||||
discardInstructionTypeIfNotInObjectInstructions: boolean
|
||||
): State {
|
||||
@@ -251,6 +256,19 @@ export default class NewInstructionEditorDialog extends React.Component<
|
||||
);
|
||||
};
|
||||
|
||||
_changeTab = (currentInstructionOrObjectSelectorTab: TabName) =>
|
||||
this.setState({
|
||||
currentInstructionOrObjectSelectorTab,
|
||||
});
|
||||
|
||||
_forceUpdate = () => {
|
||||
this.forceUpdate(); /*Force update to ensure dialog is properly positioned*/
|
||||
};
|
||||
|
||||
_chooseObject = (objectName: string) => {
|
||||
this.setState(this._getChosenObjectState(objectName, true));
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
project,
|
||||
@@ -278,41 +296,25 @@ export default class NewInstructionEditorDialog extends React.Component<
|
||||
: undefined;
|
||||
|
||||
const renderInstructionOrObjectSelector = () => (
|
||||
<div
|
||||
key="instruction-or-object-selector"
|
||||
style={{
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
display: true ? 'flex' : 'none',
|
||||
}}
|
||||
>
|
||||
<Background noFullHeight>
|
||||
<InstructionOrObjectSelector
|
||||
style={{ flex: 1, display: 'flex', flexDirection: 'column' }} // TODO
|
||||
project={project}
|
||||
currentTab={currentInstructionOrObjectSelectorTab}
|
||||
onChangeTab={currentInstructionOrObjectSelectorTab =>
|
||||
this.setState({
|
||||
currentInstructionOrObjectSelectorTab,
|
||||
})
|
||||
}
|
||||
globalObjectsContainer={globalObjectsContainer}
|
||||
objectsContainer={objectsContainer}
|
||||
isCondition={isCondition}
|
||||
chosenInstructionType={instructionType}
|
||||
onChooseInstruction={this._chooseFreeInstruction}
|
||||
chosenObjectName={chosenObjectName}
|
||||
onChooseObject={objectName => {
|
||||
this.setState(this._chooseObject(objectName, true));
|
||||
}}
|
||||
focusOnMount={!instructionType}
|
||||
onSearchStartOrReset={() => {
|
||||
this.forceUpdate(); /*Force update to ensure dialog is properly positioned*/
|
||||
}}
|
||||
/>
|
||||
</Background>
|
||||
</div>
|
||||
<Background noFullHeight key="instruction-or-object-selector">
|
||||
<InstructionOrObjectSelector
|
||||
style={styles.fullHeightSelector}
|
||||
project={project}
|
||||
currentTab={currentInstructionOrObjectSelectorTab}
|
||||
onChangeTab={this._changeTab}
|
||||
globalObjectsContainer={globalObjectsContainer}
|
||||
objectsContainer={objectsContainer}
|
||||
isCondition={isCondition}
|
||||
chosenInstructionType={
|
||||
!chosenObjectName ? instructionType : undefined
|
||||
}
|
||||
onChooseInstruction={this._chooseFreeInstruction}
|
||||
chosenObjectName={chosenObjectName}
|
||||
onChooseObject={this._chooseObject}
|
||||
focusOnMount={!instructionType}
|
||||
onSearchStartOrReset={this._forceUpdate}
|
||||
/>
|
||||
</Background>
|
||||
);
|
||||
|
||||
const renderParameters = () => (
|
||||
@@ -340,24 +342,24 @@ export default class NewInstructionEditorDialog extends React.Component<
|
||||
|
||||
const renderObjectInstructionSelector = () =>
|
||||
chosenObjectInstructionsInfoTree && chosenObjectInstructionsInfo ? (
|
||||
<Background noFullHeight key="object-instruction-selector">
|
||||
<InstructionOrExpressionSelector
|
||||
instructionsInfo={chosenObjectInstructionsInfo}
|
||||
instructionsInfoTree={chosenObjectInstructionsInfoTree}
|
||||
iconSize={24}
|
||||
onChoose={this._chooseObjectInstruction}
|
||||
selectedType={instructionType}
|
||||
useSubheaders
|
||||
focusOnMount={!instructionType}
|
||||
searchBarHintText={
|
||||
isCondition ? (
|
||||
<Trans>Search {chosenObjectName} conditions</Trans>
|
||||
) : (
|
||||
<Trans>Search {chosenObjectName} actions</Trans>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Background>
|
||||
<InstructionOrExpressionSelector
|
||||
key="object-instruction-selector"
|
||||
style={styles.fullHeightSelector}
|
||||
instructionsInfo={chosenObjectInstructionsInfo}
|
||||
instructionsInfoTree={chosenObjectInstructionsInfoTree}
|
||||
iconSize={24}
|
||||
onChoose={this._chooseObjectInstruction}
|
||||
selectedType={instructionType}
|
||||
useSubheaders
|
||||
focusOnMount={!instructionType}
|
||||
searchBarHintText={
|
||||
isCondition ? (
|
||||
<Trans>Search {chosenObjectName} conditions</Trans>
|
||||
) : (
|
||||
<Trans>Search {chosenObjectName} actions</Trans>
|
||||
)
|
||||
}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
|
@@ -8,6 +8,7 @@ import { makeNonBreakable } from '../../Utils/StringHelpers';
|
||||
import { type ParameterFieldProps } from './ParameterFieldCommons';
|
||||
import GenericExpressionField from './GenericExpressionField';
|
||||
import BackgroundText from '../../UI/BackgroundText';
|
||||
import { focusButton } from '../../UI/Button';
|
||||
|
||||
type State = {|
|
||||
showDeprecatedNumericValue: boolean,
|
||||
@@ -17,6 +18,7 @@ export default class ForceMultiplierField extends Component<
|
||||
ParameterFieldProps,
|
||||
State
|
||||
> {
|
||||
_instantButton = React.createRef<RaisedButton>();
|
||||
state = {
|
||||
showDeprecatedNumericValue:
|
||||
this.props.value !== '' &&
|
||||
@@ -24,7 +26,9 @@ export default class ForceMultiplierField extends Component<
|
||||
this.props.value !== '0',
|
||||
};
|
||||
|
||||
focus() {}
|
||||
focus() {
|
||||
focusButton(this._instantButton);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { showDeprecatedNumericValue } = this.state;
|
||||
@@ -36,6 +40,7 @@ export default class ForceMultiplierField extends Component<
|
||||
label={makeNonBreakable('Instant')}
|
||||
primary={this.props.value === '' || this.props.value === '0'}
|
||||
onClick={() => this.props.onChange('0')}
|
||||
ref={this._instantButton}
|
||||
/>
|
||||
</Column>
|
||||
<Column>
|
||||
|
@@ -30,7 +30,7 @@ export default class LayerField extends Component<ParameterFieldProps, {||}> {
|
||||
onChange={onChange}
|
||||
openOnFocus={isInline}
|
||||
dataSource={layerNames.map(layerName => ({
|
||||
text: layerName || '(Base layer)',
|
||||
text: layerName ? `"${layerName}"` : '"" (Base layer)',
|
||||
value: `"${layerName}"`,
|
||||
}))}
|
||||
hintText={'""'}
|
||||
|
@@ -35,7 +35,7 @@ export default class SceneNameField extends Component<
|
||||
onChange={onChange}
|
||||
openOnFocus={isInline}
|
||||
dataSource={layoutNames.map(layoutName => ({
|
||||
text: layoutName,
|
||||
text: `"${layoutName}"`,
|
||||
value: `"${layoutName}"`,
|
||||
}))}
|
||||
hintText={'""'}
|
||||
|
@@ -41,7 +41,7 @@ export default class StringWithSelectorField extends Component<
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
dataSource={getChoices(parameterMetadata).map(choice => ({
|
||||
text: choice,
|
||||
text: '"' + choice + '"',
|
||||
value: '"' + choice + '"',
|
||||
}))}
|
||||
openOnFocus={!isInline}
|
||||
|
@@ -8,6 +8,7 @@ import {
|
||||
type ParameterFieldProps,
|
||||
getParameterValueOrDefault,
|
||||
} from './ParameterFieldCommons';
|
||||
import { focusButton } from '../../UI/Button';
|
||||
|
||||
const styles = {
|
||||
button: {
|
||||
@@ -23,7 +24,11 @@ export default class TrueFalseField extends Component<
|
||||
ParameterFieldProps,
|
||||
void
|
||||
> {
|
||||
focus() {}
|
||||
_trueButton = React.createRef<RaisedButton>();
|
||||
|
||||
focus() {
|
||||
focusButton(this._trueButton);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { parameterMetadata, value } = this.props;
|
||||
@@ -41,6 +46,7 @@ export default class TrueFalseField extends Component<
|
||||
label={<Trans>True</Trans>}
|
||||
primary={effectiveValue === 'True'}
|
||||
onClick={() => this.props.onChange('True')}
|
||||
ref={this._trueButton}
|
||||
/>
|
||||
</Column>
|
||||
<Column noMargin>
|
||||
|
@@ -8,6 +8,7 @@ import {
|
||||
type ParameterFieldProps,
|
||||
getParameterValueOrDefault,
|
||||
} from './ParameterFieldCommons';
|
||||
import { focusButton } from '../../UI/Button';
|
||||
|
||||
const styles = {
|
||||
button: {
|
||||
@@ -20,7 +21,11 @@ const styles = {
|
||||
};
|
||||
|
||||
export default class YesNoField extends Component<ParameterFieldProps, void> {
|
||||
focus() {}
|
||||
_yesButton = React.createRef<RaisedButton>();
|
||||
|
||||
focus() {
|
||||
focusButton(this._yesButton);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { parameterMetadata, value } = this.props;
|
||||
@@ -38,6 +43,7 @@ export default class YesNoField extends Component<ParameterFieldProps, void> {
|
||||
label={<Trans>Yes</Trans>}
|
||||
primary={effectiveValue === 'yes'}
|
||||
onClick={() => this.props.onChange('yes')}
|
||||
ref={this._yesButton}
|
||||
/>
|
||||
</Column>
|
||||
<Column noMargin>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import indexHTML from './GDJSindex.html.js';
|
||||
|
||||
const gdjsRoot =
|
||||
'https://s3-eu-west-1.amazonaws.com/gdevelop-resources/GDJS-5.0.0-beta70';
|
||||
'https://s3-eu-west-1.amazonaws.com/gdevelop-resources/GDJS-5.0.0-beta79';
|
||||
|
||||
export const findGDJS = cb => {
|
||||
return cb({
|
||||
|
@@ -6,8 +6,10 @@ import Dialog from '../../UI/Dialog';
|
||||
import HelpButton from '../../UI/HelpButton';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import Builds from '.';
|
||||
import { type UserProfile } from '../../Profile/UserProfileContext';
|
||||
|
||||
type Props = {|
|
||||
userProfile: UserProfile,
|
||||
open: boolean,
|
||||
onClose: () => void,
|
||||
|};
|
||||
@@ -20,7 +22,7 @@ export default class BuildsDialog extends Component<Props, State> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { open, onClose } = this.props;
|
||||
const { open, onClose, userProfile } = this.props;
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
@@ -42,7 +44,10 @@ export default class BuildsDialog extends Component<Props, State> {
|
||||
noMargin
|
||||
autoScrollBodyContent
|
||||
>
|
||||
<Builds onBuildsUpdated={this._onBuildsUpdated} />
|
||||
<Builds
|
||||
onBuildsUpdated={this._onBuildsUpdated}
|
||||
userProfile={userProfile}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
@@ -45,7 +45,10 @@ export default class BuildsWatcher {
|
||||
let build = null;
|
||||
do {
|
||||
if (!this.userProfile) return;
|
||||
|
||||
const { getAuthorizationHeader, profile } = this.userProfile;
|
||||
if (!profile) return;
|
||||
|
||||
try {
|
||||
console.info(`Checking progress of build ${buildId}...`);
|
||||
build = await getBuild(getAuthorizationHeader, profile.uid, buildId);
|
||||
|
@@ -1,8 +1,6 @@
|
||||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import UserProfileContext, {
|
||||
type UserProfile,
|
||||
} from '../../Profile/UserProfileContext';
|
||||
import { type UserProfile } from '../../Profile/UserProfileContext';
|
||||
import BuildsList from './BuildsList';
|
||||
import {
|
||||
getBuilds,
|
||||
@@ -12,9 +10,6 @@ import {
|
||||
import Window from '../../Utils/Window';
|
||||
import BuildsWatcher from './BuildsWatcher';
|
||||
|
||||
type ContainerProps = {|
|
||||
onBuildsUpdated: ?() => void,
|
||||
|};
|
||||
type Props = {|
|
||||
onBuildsUpdated: ?() => void,
|
||||
userProfile: UserProfile,
|
||||
@@ -23,7 +18,7 @@ type State = {|
|
||||
builds: ?Array<Build>,
|
||||
|};
|
||||
|
||||
export class Builds extends Component<Props, State> {
|
||||
export default class Builds extends Component<Props, State> {
|
||||
state = {
|
||||
builds: null,
|
||||
};
|
||||
@@ -91,16 +86,3 @@ export class Builds extends Component<Props, State> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const BuildsContainer = (props: ContainerProps) => (
|
||||
<UserProfileContext.Consumer>
|
||||
{(userProfile: UserProfile) => (
|
||||
<Builds
|
||||
userProfile={userProfile}
|
||||
onBuildsUpdated={props.onBuildsUpdated}
|
||||
/>
|
||||
)}
|
||||
</UserProfileContext.Consumer>
|
||||
);
|
||||
|
||||
export default BuildsContainer;
|
||||
|
@@ -1,3 +1,4 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import React, { Component } from 'react';
|
||||
import Dialog from '../UI/Dialog';
|
||||
@@ -9,6 +10,10 @@ import Visibility from 'material-ui/svg-icons/action/visibility';
|
||||
import VisibilityOff from 'material-ui/svg-icons/action/visibility-off';
|
||||
import BuildsDialog from './Builds/BuildsDialog';
|
||||
import { Line } from '../UI/Grid';
|
||||
import Authentification from '../Utils/GDevelopServices/Authentification';
|
||||
import UserProfileContext, {
|
||||
type UserProfile,
|
||||
} from '../Profile/UserProfileContext';
|
||||
|
||||
const styles = {
|
||||
icon: { width: 40, height: 40 },
|
||||
@@ -16,32 +21,52 @@ const styles = {
|
||||
content: { padding: 24 },
|
||||
};
|
||||
|
||||
export default class ExportDialog extends Component {
|
||||
export type Exporter = any; // TODO: Add typing
|
||||
|
||||
export type ExportDialogWithoutExportsProps = {|
|
||||
project: ?gdProject,
|
||||
onClose: () => void,
|
||||
authentification: Authentification,
|
||||
onChangeSubscription: () => void,
|
||||
|};
|
||||
|
||||
type Props = {|
|
||||
...ExportDialogWithoutExportsProps,
|
||||
exporters: Array<Exporter>,
|
||||
|};
|
||||
|
||||
type State = {|
|
||||
chosenExporterKey: string,
|
||||
showExperimental: boolean,
|
||||
buildsDialogOpen: boolean,
|
||||
|};
|
||||
|
||||
export default class ExportDialog extends Component<Props, State> {
|
||||
state = {
|
||||
chosenExporterKey: '',
|
||||
showExperimental: false,
|
||||
buildsDialogOpen: false,
|
||||
};
|
||||
|
||||
chooseExporter = key => {
|
||||
chooseExporter = (key: string) => {
|
||||
this.setState({
|
||||
chosenExporterKey: key,
|
||||
});
|
||||
};
|
||||
|
||||
_showExperimental = (show = true) => {
|
||||
_showExperimental = (show: boolean = true) => {
|
||||
this.setState({
|
||||
showExperimental: show,
|
||||
});
|
||||
};
|
||||
|
||||
_openBuildsDialog = (open = true) => {
|
||||
_openBuildsDialog = (open: boolean = true) => {
|
||||
this.setState({
|
||||
buildsDialogOpen: open,
|
||||
});
|
||||
};
|
||||
|
||||
_renderExporterListItem = (exporter, index) => {
|
||||
_renderExporterListItem = (exporter: Exporter, index: number) => {
|
||||
return (
|
||||
<ListItem
|
||||
key={exporter.key}
|
||||
@@ -59,116 +84,121 @@ export default class ExportDialog extends Component {
|
||||
render() {
|
||||
const {
|
||||
project,
|
||||
open,
|
||||
onClose,
|
||||
authentification, //Still exist?
|
||||
onChangeSubscription,
|
||||
exporters,
|
||||
} = this.props;
|
||||
const { showExperimental, chosenExporterKey } = this.state;
|
||||
if (!open || !project) return null;
|
||||
if (!project) return null;
|
||||
|
||||
const exporter = exporters.find(
|
||||
exporter => exporter.key === chosenExporterKey
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={<Trans>Export project to a standalone game</Trans>}
|
||||
onRequestClose={onClose}
|
||||
actions={[
|
||||
chosenExporterKey && (
|
||||
<FlatButton
|
||||
label={<Trans>Back</Trans>}
|
||||
key="back"
|
||||
primary={false}
|
||||
onClick={() => this.chooseExporter('')}
|
||||
/>
|
||||
),
|
||||
<FlatButton
|
||||
label={<Trans>Close</Trans>}
|
||||
key="close"
|
||||
primary={false}
|
||||
onClick={onClose}
|
||||
/>,
|
||||
]}
|
||||
secondaryActions={[
|
||||
<HelpButton
|
||||
key="help"
|
||||
helpPagePath={(exporter && exporter.helpPage) || '/publishing'}
|
||||
/>,
|
||||
<FlatButton
|
||||
key="builds"
|
||||
label={<Trans>See all my builds</Trans>}
|
||||
onClick={() => this._openBuildsDialog(true)}
|
||||
/>,
|
||||
]}
|
||||
open={open}
|
||||
noMargin
|
||||
autoScrollBodyContent
|
||||
>
|
||||
{!exporter && (
|
||||
<React.Fragment>
|
||||
<List>
|
||||
{exporters
|
||||
.filter(
|
||||
exporter => !exporter.advanced && !exporter.experimental
|
||||
)
|
||||
.map((exporter, index) =>
|
||||
this._renderExporterListItem(exporter, index)
|
||||
)}
|
||||
<UserProfileContext.Consumer>
|
||||
{(userProfile: UserProfile) => (
|
||||
<Dialog
|
||||
title={<Trans>Export project to a standalone game</Trans>}
|
||||
onRequestClose={onClose}
|
||||
actions={[
|
||||
chosenExporterKey && (
|
||||
<FlatButton
|
||||
label={<Trans>Back</Trans>}
|
||||
key="back"
|
||||
primary={false}
|
||||
onClick={() => this.chooseExporter('')}
|
||||
/>
|
||||
),
|
||||
<FlatButton
|
||||
label={<Trans>Close</Trans>}
|
||||
key="close"
|
||||
primary={false}
|
||||
onClick={onClose}
|
||||
/>,
|
||||
]}
|
||||
secondaryActions={[
|
||||
<HelpButton
|
||||
key="help"
|
||||
helpPagePath={(exporter && exporter.helpPage) || '/publishing'}
|
||||
/>,
|
||||
<FlatButton
|
||||
key="builds"
|
||||
label={<Trans>See all my builds</Trans>}
|
||||
onClick={() => this._openBuildsDialog(true)}
|
||||
/>,
|
||||
]}
|
||||
open
|
||||
noMargin
|
||||
autoScrollBodyContent
|
||||
>
|
||||
{!exporter && (
|
||||
<React.Fragment>
|
||||
<List>
|
||||
{exporters
|
||||
.filter(
|
||||
exporter => !exporter.advanced && !exporter.experimental
|
||||
)
|
||||
.map((exporter, index) =>
|
||||
this._renderExporterListItem(exporter, index)
|
||||
)}
|
||||
|
||||
<Subheader>Advanced</Subheader>
|
||||
{exporters
|
||||
.filter(exporter => exporter.advanced)
|
||||
.map((exporter, index) =>
|
||||
this._renderExporterListItem(exporter, index)
|
||||
)}
|
||||
<Subheader>Advanced</Subheader>
|
||||
{exporters
|
||||
.filter(exporter => exporter.advanced)
|
||||
.map((exporter, index) =>
|
||||
this._renderExporterListItem(exporter, index)
|
||||
)}
|
||||
|
||||
{showExperimental && <Subheader>Experimental</Subheader>}
|
||||
{showExperimental &&
|
||||
exporters
|
||||
.filter(exporter => exporter.experimental)
|
||||
.map((exporter, index) =>
|
||||
this._renderExporterListItem(exporter, index)
|
||||
{showExperimental && <Subheader>Experimental</Subheader>}
|
||||
{showExperimental &&
|
||||
exporters
|
||||
.filter(exporter => exporter.experimental)
|
||||
.map((exporter, index) =>
|
||||
this._renderExporterListItem(exporter, index)
|
||||
)}
|
||||
</List>
|
||||
<Line justifyContent="center" alignItems="center">
|
||||
{!showExperimental ? (
|
||||
<FlatButton
|
||||
key="toggle-experimental"
|
||||
icon={<Visibility />}
|
||||
primary={false}
|
||||
onClick={() => this._showExperimental(true)}
|
||||
label={<Trans>Show experimental exports</Trans>}
|
||||
/>
|
||||
) : (
|
||||
<FlatButton
|
||||
key="toggle-experimental"
|
||||
icon={<VisibilityOff />}
|
||||
primary={false}
|
||||
onClick={() => this._showExperimental(false)}
|
||||
label={<Trans>Hide experimental exports</Trans>}
|
||||
/>
|
||||
)}
|
||||
</List>
|
||||
<Line justifyContent="center" alignItems="center">
|
||||
{!showExperimental ? (
|
||||
<FlatButton
|
||||
key="toggle-experimental"
|
||||
icon={<Visibility />}
|
||||
primary={false}
|
||||
onClick={() => this._showExperimental(true)}
|
||||
label={<Trans>Show experimental exports</Trans>}
|
||||
</Line>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{exporter && (
|
||||
<div style={styles.content}>
|
||||
<exporter.ExportComponent
|
||||
project={project}
|
||||
authentification={authentification} //Still exist?
|
||||
onChangeSubscription={onChangeSubscription}
|
||||
onOpenBuildsDialog={this._openBuildsDialog}
|
||||
userProfile={userProfile}
|
||||
/>
|
||||
) : (
|
||||
<FlatButton
|
||||
key="toggle-experimental"
|
||||
icon={<VisibilityOff />}
|
||||
primary={false}
|
||||
onClick={() => this._showExperimental(false)}
|
||||
label={<Trans>Hide experimental exports</Trans>}
|
||||
/>
|
||||
)}
|
||||
</Line>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{exporter && (
|
||||
<div style={styles.content}>
|
||||
<exporter.ExportComponent
|
||||
project={project}
|
||||
authentification={authentification} //Still exist?
|
||||
onChangeSubscription={onChangeSubscription}
|
||||
onOpenBuildsDialog={this._openBuildsDialog}
|
||||
</div>
|
||||
)}
|
||||
<BuildsDialog
|
||||
open={this.state.buildsDialogOpen}
|
||||
onClose={() => this._openBuildsDialog(false)}
|
||||
userProfile={userProfile}
|
||||
/>
|
||||
</div>
|
||||
</Dialog>
|
||||
)}
|
||||
<BuildsDialog
|
||||
open={this.state.buildsDialogOpen}
|
||||
onClose={() => this._openBuildsDialog(false)}
|
||||
/>
|
||||
</Dialog>
|
||||
</UserProfileContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -9,9 +9,7 @@ import {
|
||||
buildCordovaAndroid,
|
||||
getUrl,
|
||||
} from '../../../Utils/GDevelopServices/Build';
|
||||
import UserProfileContext, {
|
||||
type UserProfile,
|
||||
} from '../../../Profile/UserProfileContext';
|
||||
import { type UserProfile } from '../../../Profile/UserProfileContext';
|
||||
import { Column, Line } from '../../../UI/Grid';
|
||||
import { showErrorBox } from '../../../UI/Messages/MessageBox';
|
||||
import { findGDJS } from '../LocalGDJSFinder';
|
||||
@@ -47,6 +45,7 @@ type State = {
|
||||
type Props = {
|
||||
project: gdProject,
|
||||
onChangeSubscription: Function,
|
||||
userProfile: UserProfile,
|
||||
};
|
||||
|
||||
class LocalOnlineCordovaExport extends Component<Props, State> {
|
||||
@@ -247,7 +246,7 @@ class LocalOnlineCordovaExport extends Component<Props, State> {
|
||||
errored,
|
||||
} = this.state;
|
||||
const t = str => str; //TODO;
|
||||
const { project } = this.props;
|
||||
const { project, userProfile } = this.props;
|
||||
if (!project) return null;
|
||||
|
||||
const getBuildLimit = (userProfile: UserProfile): ?Limit =>
|
||||
@@ -262,52 +261,48 @@ class LocalOnlineCordovaExport extends Component<Props, State> {
|
||||
};
|
||||
|
||||
return (
|
||||
<UserProfileContext.Consumer>
|
||||
{(userProfile: UserProfile) => (
|
||||
<Column noMargin>
|
||||
<Line>
|
||||
{t(
|
||||
'Packaging your game for Android will create an APK file that can be installed on Android phones, based on Cordova framework.'
|
||||
)}
|
||||
</Line>
|
||||
{userProfile.authenticated && (
|
||||
<Line justifyContent="center">
|
||||
<RaisedButton
|
||||
label={t('Package for Android')}
|
||||
primary
|
||||
onClick={() => this.launchWholeExport(userProfile)}
|
||||
disabled={!canLaunchBuild(userProfile)}
|
||||
/>
|
||||
</Line>
|
||||
)}
|
||||
{userProfile.authenticated && (
|
||||
<LimitDisplayer
|
||||
subscription={userProfile.subscription}
|
||||
limit={getBuildLimit(userProfile)}
|
||||
onChangeSubscription={this.props.onChangeSubscription}
|
||||
/>
|
||||
)}
|
||||
{!userProfile.authenticated && (
|
||||
<CreateProfile
|
||||
message={t(
|
||||
'Create an account to build your game for Android in one-click:'
|
||||
)}
|
||||
onLogin={userProfile.onLogin}
|
||||
/>
|
||||
)}
|
||||
<Line expand>
|
||||
<BuildStepsProgress
|
||||
exportStep={exportStep}
|
||||
build={build}
|
||||
onDownload={this._download}
|
||||
uploadMax={uploadMax}
|
||||
uploadProgress={uploadProgress}
|
||||
errored={errored}
|
||||
/>
|
||||
</Line>
|
||||
</Column>
|
||||
<Column noMargin>
|
||||
<Line>
|
||||
{t(
|
||||
'Packaging your game for Android will create an APK file that can be installed on Android phones, based on Cordova framework.'
|
||||
)}
|
||||
</Line>
|
||||
{userProfile.authenticated && (
|
||||
<Line justifyContent="center">
|
||||
<RaisedButton
|
||||
label={t('Package for Android')}
|
||||
primary
|
||||
onClick={() => this.launchWholeExport(userProfile)}
|
||||
disabled={!canLaunchBuild(userProfile)}
|
||||
/>
|
||||
</Line>
|
||||
)}
|
||||
</UserProfileContext.Consumer>
|
||||
{userProfile.authenticated && (
|
||||
<LimitDisplayer
|
||||
subscription={userProfile.subscription}
|
||||
limit={getBuildLimit(userProfile)}
|
||||
onChangeSubscription={this.props.onChangeSubscription}
|
||||
/>
|
||||
)}
|
||||
{!userProfile.authenticated && (
|
||||
<CreateProfile
|
||||
message={t(
|
||||
'Create an account to build your game for Android in one-click:'
|
||||
)}
|
||||
onLogin={userProfile.onLogin}
|
||||
/>
|
||||
)}
|
||||
<Line expand>
|
||||
<BuildStepsProgress
|
||||
exportStep={exportStep}
|
||||
build={build}
|
||||
onDownload={this._download}
|
||||
uploadMax={uploadMax}
|
||||
uploadProgress={uploadProgress}
|
||||
errored={errored}
|
||||
/>
|
||||
</Line>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -11,9 +11,7 @@ import {
|
||||
buildElectron,
|
||||
getUrl,
|
||||
} from '../../../Utils/GDevelopServices/Build';
|
||||
import UserProfileContext, {
|
||||
type UserProfile,
|
||||
} from '../../../Profile/UserProfileContext';
|
||||
import { type UserProfile } from '../../../Profile/UserProfileContext';
|
||||
import { Column, Line } from '../../../UI/Grid';
|
||||
import { showErrorBox } from '../../../UI/Messages/MessageBox';
|
||||
import { findGDJS } from '../LocalGDJSFinder';
|
||||
@@ -51,6 +49,7 @@ type State = {
|
||||
type Props = {
|
||||
project: gdProject,
|
||||
onChangeSubscription: Function,
|
||||
userProfile: UserProfile,
|
||||
};
|
||||
|
||||
class LocalOnlineElectronExport extends Component<Props, State> {
|
||||
@@ -265,7 +264,7 @@ class LocalOnlineElectronExport extends Component<Props, State> {
|
||||
errored,
|
||||
} = this.state;
|
||||
const t = str => str; //TODO;
|
||||
const { project } = this.props;
|
||||
const { project, userProfile } = this.props;
|
||||
if (!project) return null;
|
||||
|
||||
const getBuildLimit = (userProfile: UserProfile): ?Limit =>
|
||||
@@ -282,75 +281,69 @@ class LocalOnlineElectronExport extends Component<Props, State> {
|
||||
};
|
||||
|
||||
return (
|
||||
<UserProfileContext.Consumer>
|
||||
{(userProfile: UserProfile) => (
|
||||
<Column noMargin>
|
||||
<Line>
|
||||
{t(
|
||||
'Your game will be exported and packaged online as an stand-alone game for Windows, Linux and/or macOS.'
|
||||
)}
|
||||
</Line>
|
||||
<Checkbox
|
||||
label={<Trans>Windows (zip file)</Trans>}
|
||||
checked={this.state.targets.indexOf('winZip') !== -1}
|
||||
onCheck={(e, checked) => this._setTarget('winZip', checked)}
|
||||
<Column noMargin>
|
||||
<Line>
|
||||
{t(
|
||||
'Your game will be exported and packaged online as an stand-alone game for Windows, Linux and/or macOS.'
|
||||
)}
|
||||
</Line>
|
||||
<Checkbox
|
||||
label={<Trans>Windows (zip file)</Trans>}
|
||||
checked={this.state.targets.indexOf('winZip') !== -1}
|
||||
onCheck={(e, checked) => this._setTarget('winZip', checked)}
|
||||
/>
|
||||
<Checkbox
|
||||
label={<Trans>Windows (auto-installer file)</Trans>}
|
||||
checked={this.state.targets.indexOf('winExe') !== -1}
|
||||
onCheck={(e, checked) => this._setTarget('winExe', checked)}
|
||||
/>
|
||||
<Checkbox
|
||||
label={<Trans>macOS (zip file)</Trans>}
|
||||
checked={this.state.targets.indexOf('macZip') !== -1}
|
||||
onCheck={(e, checked) => this._setTarget('macZip', checked)}
|
||||
/>
|
||||
<Checkbox
|
||||
label={<Trans>Linux (AppImage)</Trans>}
|
||||
checked={this.state.targets.indexOf('linuxAppImage') !== -1}
|
||||
onCheck={(e, checked) => this._setTarget('linuxAppImage', checked)}
|
||||
/>
|
||||
{userProfile.authenticated && (
|
||||
<Line justifyContent="center">
|
||||
<RaisedButton
|
||||
label={t('Export')}
|
||||
primary
|
||||
onClick={() => this.launchWholeExport(userProfile)}
|
||||
disabled={!canLaunchBuild(userProfile)}
|
||||
/>
|
||||
<Checkbox
|
||||
label={<Trans>Windows (auto-installer file)</Trans>}
|
||||
checked={this.state.targets.indexOf('winExe') !== -1}
|
||||
onCheck={(e, checked) => this._setTarget('winExe', checked)}
|
||||
/>
|
||||
<Checkbox
|
||||
label={<Trans>macOS (zip file)</Trans>}
|
||||
checked={this.state.targets.indexOf('macZip') !== -1}
|
||||
onCheck={(e, checked) => this._setTarget('macZip', checked)}
|
||||
/>
|
||||
<Checkbox
|
||||
label={<Trans>Linux (AppImage)</Trans>}
|
||||
checked={this.state.targets.indexOf('linuxAppImage') !== -1}
|
||||
onCheck={(e, checked) =>
|
||||
this._setTarget('linuxAppImage', checked)
|
||||
}
|
||||
/>
|
||||
{userProfile.authenticated && (
|
||||
<Line justifyContent="center">
|
||||
<RaisedButton
|
||||
label={t('Export')}
|
||||
primary
|
||||
onClick={() => this.launchWholeExport(userProfile)}
|
||||
disabled={!canLaunchBuild(userProfile)}
|
||||
/>
|
||||
</Line>
|
||||
)}
|
||||
{userProfile.authenticated && (
|
||||
<LimitDisplayer
|
||||
subscription={userProfile.subscription}
|
||||
limit={getBuildLimit(userProfile)}
|
||||
onChangeSubscription={this.props.onChangeSubscription}
|
||||
/>
|
||||
)}
|
||||
{!userProfile.authenticated && (
|
||||
<CreateProfile
|
||||
message={t(
|
||||
'Create an account to build your game for Windows, Linux and macOS in one-click:'
|
||||
)}
|
||||
onLogin={userProfile.onLogin}
|
||||
/>
|
||||
)}
|
||||
<Line expand>
|
||||
<BuildStepsProgress
|
||||
exportStep={exportStep}
|
||||
build={build}
|
||||
onDownload={this._download}
|
||||
uploadMax={uploadMax}
|
||||
uploadProgress={uploadProgress}
|
||||
errored={errored}
|
||||
showSeeAllMyBuildsExplanation
|
||||
/>
|
||||
</Line>
|
||||
</Column>
|
||||
</Line>
|
||||
)}
|
||||
</UserProfileContext.Consumer>
|
||||
{userProfile.authenticated && (
|
||||
<LimitDisplayer
|
||||
subscription={userProfile.subscription}
|
||||
limit={getBuildLimit(userProfile)}
|
||||
onChangeSubscription={this.props.onChangeSubscription}
|
||||
/>
|
||||
)}
|
||||
{!userProfile.authenticated && (
|
||||
<CreateProfile
|
||||
message={t(
|
||||
'Create an account to build your game for Windows, Linux and macOS in one-click:'
|
||||
)}
|
||||
onLogin={userProfile.onLogin}
|
||||
/>
|
||||
)}
|
||||
<Line expand>
|
||||
<BuildStepsProgress
|
||||
exportStep={exportStep}
|
||||
build={build}
|
||||
onDownload={this._download}
|
||||
uploadMax={uploadMax}
|
||||
uploadProgress={uploadProgress}
|
||||
errored={errored}
|
||||
showSeeAllMyBuildsExplanation
|
||||
/>
|
||||
</Line>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,61 +1,112 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import React, { Component } from 'react';
|
||||
import { AutoSizer, Table, Column } from 'react-virtualized';
|
||||
import {
|
||||
AutoSizer,
|
||||
Table as RVTable,
|
||||
Column as RVColumn,
|
||||
} from 'react-virtualized';
|
||||
import ThemeConsumer from '../../UI/Theme/ThemeConsumer';
|
||||
import SearchBar from '../../UI/SearchBar';
|
||||
const gd = global.gd;
|
||||
|
||||
export default class InstancesList extends Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
type State = {|
|
||||
searchText: string,
|
||||
|};
|
||||
|
||||
type Props = {|
|
||||
instances: gdInitialInstancesContainer,
|
||||
selectedInstances: Array<gdInitialInstance>,
|
||||
onSelectInstances: (Array<gdInitialInstance>) => void,
|
||||
freezeUpdate: boolean,
|
||||
style?: ?Object,
|
||||
|};
|
||||
|
||||
type RenderedRowInfo = {
|
||||
instance: gdInitialInstance,
|
||||
name: string,
|
||||
locked: string,
|
||||
x: string,
|
||||
y: string,
|
||||
angle: string,
|
||||
layer: string,
|
||||
zOrder: string,
|
||||
};
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
},
|
||||
};
|
||||
|
||||
export default class InstancesList extends Component<Props, State> {
|
||||
state = {
|
||||
searchText: '',
|
||||
};
|
||||
renderedRows: Array<RenderedRowInfo> = [];
|
||||
instanceRowRenderer: ?typeof gd.InitialInstanceJSFunctor;
|
||||
table: ?typeof RVTable;
|
||||
_searchBar = React.createRef<SearchBar>();
|
||||
|
||||
shouldComponentUpdate(nextProps: Props) {
|
||||
// Rendering the component is costly as it iterates over
|
||||
// every instances, so the prop freezeUpdate allows to ask the component to stop
|
||||
// updating, for example when hidden.
|
||||
return !nextProps.freezeUpdate;
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
// If the component was frozen and is now allowed to update,
|
||||
// force the table to be refreshed to reflect changes (new instances,
|
||||
// or selection changes).
|
||||
if (!nextProps.freezeUpdate && this.props.freezeUpdate) {
|
||||
if (this.table) this.table.forceUpdateGrid();
|
||||
if (this._searchBar.current) this._searchBar.current.focus();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.renderedRows = [];
|
||||
|
||||
// Functor used to display an instance row
|
||||
this.instanceRowRenderer = new gd.InitialInstanceJSFunctor();
|
||||
this.instanceRowRenderer.invoke = instancePtr => {
|
||||
const { searchText } = this.state;
|
||||
const instance = gd.wrapPointer(instancePtr, gd.InitialInstance);
|
||||
|
||||
this.renderedRows.push({
|
||||
instance,
|
||||
name: instance.getObjectName(),
|
||||
locked: instance.isLocked() ? '🔒' : '',
|
||||
x: instance.getX().toFixed(2),
|
||||
y: instance.getY().toFixed(2),
|
||||
angle: instance.getAngle().toFixed(2),
|
||||
layer: instance.getLayer(),
|
||||
zOrder: instance.getZOrder(),
|
||||
});
|
||||
const name: string = instance.getObjectName();
|
||||
if (
|
||||
!searchText ||
|
||||
name.toLowerCase().indexOf(searchText.toLowerCase()) !== -1
|
||||
) {
|
||||
this.renderedRows.push({
|
||||
instance,
|
||||
name,
|
||||
locked: instance.isLocked() ? '🔒' : '',
|
||||
x: instance.getX().toFixed(2),
|
||||
y: instance.getY().toFixed(2),
|
||||
angle: instance.getAngle().toFixed(2),
|
||||
layer: instance.getLayer(),
|
||||
zOrder: instance.getZOrder(),
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.instanceRowRenderer.delete();
|
||||
if (this.instanceRowRenderer) this.instanceRowRenderer.delete();
|
||||
}
|
||||
|
||||
_onRowClick = ({ index }) => {
|
||||
_onRowClick = ({ index }: { index: number }) => {
|
||||
if (!this.renderedRows[index]) return;
|
||||
this.props.onSelectInstances([this.renderedRows[index].instance]);
|
||||
};
|
||||
|
||||
_rowGetter = ({ index }) => {
|
||||
_rowGetter = ({ index }: { index: number }) => {
|
||||
return this.renderedRows[index];
|
||||
};
|
||||
|
||||
_rowClassName = ({ index }) => {
|
||||
_rowClassName = ({ index }: { index: number }) => {
|
||||
if (index < 0) {
|
||||
return 'tableHeaderRow';
|
||||
} else {
|
||||
@@ -67,13 +118,20 @@ export default class InstancesList extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
_selectFirstInstance = () => {
|
||||
if (this.renderedRows.length) {
|
||||
this.props.onSelectInstances([this.renderedRows[0].instance]);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { instances } = this.props;
|
||||
const { searchText } = this.state;
|
||||
const { instances, style } = this.props;
|
||||
|
||||
this.renderedRows.length = 0;
|
||||
instances.iterateOverInstances(this.instanceRowRenderer);
|
||||
|
||||
// Force Table component to be mounted again if instances
|
||||
// Force RVTable component to be mounted again if instances
|
||||
// has been changed. Avoid accessing to invalid objects that could
|
||||
// crash the app.
|
||||
const tableKey = instances.ptr;
|
||||
@@ -81,67 +139,81 @@ export default class InstancesList extends Component {
|
||||
return (
|
||||
<ThemeConsumer>
|
||||
{muiTheme => (
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<Table
|
||||
ref={table => (this.table = table)}
|
||||
key={tableKey}
|
||||
headerHeight={30}
|
||||
height={height}
|
||||
className={muiTheme.tableRootClassName}
|
||||
headerClassName={'tableHeaderColumn'}
|
||||
rowCount={this.renderedRows.length}
|
||||
rowGetter={this._rowGetter}
|
||||
rowHeight={35}
|
||||
onRowClick={this._onRowClick}
|
||||
rowClassName={this._rowClassName}
|
||||
width={width}
|
||||
>
|
||||
<Column
|
||||
label={<Trans>Object name</Trans>}
|
||||
dataKey="name"
|
||||
width={width * 0.35}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
<Column
|
||||
label=""
|
||||
dataKey="locked"
|
||||
width={width * 0.05}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
<Column
|
||||
label={<Trans>X</Trans>}
|
||||
dataKey="x"
|
||||
width={width * 0.1}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
<Column
|
||||
label={<Trans>Y</Trans>}
|
||||
dataKey="y"
|
||||
width={width * 0.1}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
<Column
|
||||
label={<Trans>Angle</Trans>}
|
||||
dataKey="angle"
|
||||
width={width * 0.1}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
<Column
|
||||
label={<Trans>Layer</Trans>}
|
||||
dataKey="layer"
|
||||
width={width * 0.2}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
<Column
|
||||
label={<Trans>Z Order</Trans>}
|
||||
dataKey="zOrder"
|
||||
width={width * 0.1}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
</Table>
|
||||
)}
|
||||
</AutoSizer>
|
||||
<div style={{ ...styles.container, ...style }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<RVTable
|
||||
ref={table => (this.table = table)}
|
||||
key={tableKey}
|
||||
headerHeight={30}
|
||||
height={height}
|
||||
className={muiTheme.tableRootClassName}
|
||||
headerClassName={'tableHeaderColumn'}
|
||||
rowCount={this.renderedRows.length}
|
||||
rowGetter={this._rowGetter}
|
||||
rowHeight={35}
|
||||
onRowClick={this._onRowClick}
|
||||
rowClassName={this._rowClassName}
|
||||
width={width}
|
||||
>
|
||||
<RVColumn
|
||||
label={<Trans>Object name</Trans>}
|
||||
dataKey="name"
|
||||
width={width * 0.35}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
<RVColumn
|
||||
label=""
|
||||
dataKey="locked"
|
||||
width={width * 0.05}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
<RVColumn
|
||||
label={<Trans>X</Trans>}
|
||||
dataKey="x"
|
||||
width={width * 0.1}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
<RVColumn
|
||||
label={<Trans>Y</Trans>}
|
||||
dataKey="y"
|
||||
width={width * 0.1}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
<RVColumn
|
||||
label={<Trans>Angle</Trans>}
|
||||
dataKey="angle"
|
||||
width={width * 0.1}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
<RVColumn
|
||||
label={<Trans>Layer</Trans>}
|
||||
dataKey="layer"
|
||||
width={width * 0.2}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
<RVColumn
|
||||
label={<Trans>Z Order</Trans>}
|
||||
dataKey="zOrder"
|
||||
width={width * 0.1}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
</RVTable>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
<SearchBar
|
||||
value={searchText}
|
||||
onChange={searchText =>
|
||||
this.setState({
|
||||
searchText,
|
||||
})
|
||||
}
|
||||
onRequestSearch={this._selectFirstInstance}
|
||||
ref={this._searchBar}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</ThemeConsumer>
|
||||
);
|
||||
|
@@ -578,6 +578,12 @@ export default class InstancesEditorContainer extends Component {
|
||||
this.viewPosition.scrollTo(x, y);
|
||||
}
|
||||
|
||||
centerView() {
|
||||
const x = this.props.project.getMainWindowDefaultWidth() / 2;
|
||||
const y = this.props.project.getMainWindowDefaultHeight() / 2;
|
||||
this.viewPosition.scrollTo(x, y);
|
||||
}
|
||||
|
||||
centerViewOn(instances) {
|
||||
if (!instances.length) return;
|
||||
|
||||
|
@@ -1,20 +0,0 @@
|
||||
import React from 'react';
|
||||
import { TableRow, TableRowColumn } from 'material-ui/Table';
|
||||
import Add from 'material-ui/svg-icons/content/add';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import styles from './styles';
|
||||
|
||||
const AddLayerRow = ({ onAdd }) => (
|
||||
<TableRow key="add-row">
|
||||
<TableRowColumn style={styles.handleColumn} />
|
||||
<TableRowColumn />
|
||||
<TableRowColumn style={styles.visibleColumn} />
|
||||
<TableRowColumn style={styles.toolColumn}>
|
||||
<IconButton onClick={onAdd}>
|
||||
<Add />
|
||||
</IconButton>
|
||||
</TableRowColumn>
|
||||
</TableRow>
|
||||
);
|
||||
|
||||
export default AddLayerRow;
|
48
newIDE/app/src/LayersList/BackgroundColorRow.js
Normal file
@@ -0,0 +1,48 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import React from 'react';
|
||||
import { TableRow, TableRowColumn } from 'material-ui/Table';
|
||||
import TextField from 'material-ui/TextField';
|
||||
|
||||
import styles from './styles';
|
||||
import ThemeConsumer from '../UI/Theme/ThemeConsumer';
|
||||
import ColorPicker from '../UI/ColorField/ColorPicker';
|
||||
|
||||
type Props = {|
|
||||
layout: gdLayout,
|
||||
onBackgroundColorChanged: () => void,
|
||||
|};
|
||||
|
||||
export default ({ layout, onBackgroundColorChanged }: Props) => (
|
||||
<ThemeConsumer>
|
||||
{muiTheme => (
|
||||
<TableRow
|
||||
style={{
|
||||
backgroundColor: muiTheme.list.itemsBackgroundColor,
|
||||
}}
|
||||
>
|
||||
<TableRowColumn style={styles.handleColumn} />
|
||||
<TableRowColumn>
|
||||
<TextField hintText={<Trans>Background color</Trans>} disabled />
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.effectsColumn}>
|
||||
<ColorPicker
|
||||
disableAlpha
|
||||
color={{
|
||||
r: layout.getBackgroundColorRed(),
|
||||
g: layout.getBackgroundColorGreen(),
|
||||
b: layout.getBackgroundColorBlue(),
|
||||
a: 255,
|
||||
}}
|
||||
onChangeComplete={color => {
|
||||
layout.setBackgroundColor(color.rgb.r, color.rgb.g, color.rgb.b);
|
||||
onBackgroundColorChanged();
|
||||
}}
|
||||
/>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.toolColumn} />
|
||||
</TableRow>
|
||||
)}
|
||||
</ThemeConsumer>
|
||||
);
|
@@ -29,13 +29,11 @@ export default class VariablesEditorDialog extends Component {
|
||||
const actions = [
|
||||
<FlatButton
|
||||
label={<Trans>Cancel</Trans>}
|
||||
secondary={true}
|
||||
keyboardFocused={true}
|
||||
onClick={() => this.props.onClose(false)}
|
||||
/>,
|
||||
<FlatButton
|
||||
label={<Trans>Remove objects</Trans>}
|
||||
secondary={true}
|
||||
onClick={() => this.props.onClose(true, null)}
|
||||
/>,
|
||||
<FlatButton
|
||||
|
@@ -1,56 +1,86 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import React from 'react';
|
||||
import { TableRow, TableRowColumn } from 'material-ui/Table';
|
||||
import Checkbox from 'material-ui/Checkbox';
|
||||
import InlineCheckbox from '../UI/InlineCheckbox';
|
||||
import Visibility from 'material-ui/svg-icons/action/visibility';
|
||||
import VisibilityOff from 'material-ui/svg-icons/action/visibility-off';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import Delete from 'material-ui/svg-icons/action/delete';
|
||||
import TextField from 'material-ui/TextField';
|
||||
import muiThemeable from 'material-ui/styles/muiThemeable';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import DragHandle from '../UI/DragHandle';
|
||||
import styles from './styles';
|
||||
import ThemeConsumer from '../UI/Theme/ThemeConsumer';
|
||||
|
||||
const ThemableLayerRow = ({
|
||||
type Props = {|
|
||||
layerName: string,
|
||||
nameError: boolean,
|
||||
onBlur: () => void,
|
||||
onRemove: () => void,
|
||||
isVisible: boolean,
|
||||
onChangeVisibility: boolean => void,
|
||||
effectsCount: number,
|
||||
onEditEffects: () => void,
|
||||
style?: ?Object,
|
||||
|};
|
||||
|
||||
export default ({
|
||||
layerName,
|
||||
nameError,
|
||||
onBlur,
|
||||
onRemove,
|
||||
isVisible,
|
||||
effectsCount,
|
||||
onEditEffects,
|
||||
onChangeVisibility,
|
||||
muiTheme,
|
||||
}) => (
|
||||
<TableRow
|
||||
style={{
|
||||
backgroundColor: muiTheme.list.itemsBackgroundColor,
|
||||
}}
|
||||
>
|
||||
<TableRowColumn style={styles.handleColumn}>
|
||||
<DragHandle />
|
||||
</TableRowColumn>
|
||||
<TableRowColumn>
|
||||
<TextField
|
||||
defaultValue={layerName || 'Base layer'}
|
||||
id={layerName}
|
||||
errorText={nameError ? 'This name is already taken' : undefined}
|
||||
disabled={!layerName}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.visibleColumn}>
|
||||
<Checkbox
|
||||
checked={isVisible}
|
||||
checkedIcon={<Visibility />}
|
||||
uncheckedIcon={<VisibilityOff />}
|
||||
onCheck={onChangeVisibility}
|
||||
/>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.toolColumn}>
|
||||
<IconButton onClick={onRemove} disabled={!layerName}>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</TableRowColumn>
|
||||
</TableRow>
|
||||
}: Props) => (
|
||||
<ThemeConsumer>
|
||||
{muiTheme => (
|
||||
<TableRow
|
||||
style={{
|
||||
backgroundColor: muiTheme.list.itemsBackgroundColor,
|
||||
}}
|
||||
>
|
||||
<TableRowColumn style={styles.handleColumn}>
|
||||
<DragHandle />
|
||||
</TableRowColumn>
|
||||
<TableRowColumn>
|
||||
<TextField
|
||||
defaultValue={layerName || 'Base layer'}
|
||||
id={layerName}
|
||||
errorText={
|
||||
nameError ? <Trans>This name is already taken</Trans> : undefined
|
||||
}
|
||||
disabled={!layerName}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.effectsColumn}>
|
||||
<FlatButton
|
||||
label={
|
||||
effectsCount === 0 ? (
|
||||
<Trans>Add effect</Trans>
|
||||
) : (
|
||||
<Trans>{effectsCount} effect(s)</Trans>
|
||||
)
|
||||
}
|
||||
onClick={onEditEffects}
|
||||
/>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.toolColumn}>
|
||||
<InlineCheckbox
|
||||
checked={isVisible}
|
||||
checkedIcon={<Visibility />}
|
||||
uncheckedIcon={<VisibilityOff />}
|
||||
onCheck={(e, value) => onChangeVisibility(value)}
|
||||
/>
|
||||
<IconButton onClick={onRemove} disabled={!layerName}>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</TableRowColumn>
|
||||
</TableRow>
|
||||
)}
|
||||
</ThemeConsumer>
|
||||
);
|
||||
|
||||
const LayerRow = muiThemeable()(ThemableLayerRow);
|
||||
export default LayerRow;
|
||||
|
@@ -1,3 +1,5 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Table,
|
||||
@@ -12,21 +14,25 @@ import newNameGenerator from '../Utils/NewNameGenerator';
|
||||
import { mapReverseFor } from '../Utils/MapFor';
|
||||
import styles from './styles';
|
||||
import LayerRow from './LayerRow';
|
||||
import AddLayerRow from './AddLayerRow';
|
||||
import EffectsListDialog from '../EffectsList/EffectsListDialog';
|
||||
import BackgroundColorRow from './BackgroundColorRow';
|
||||
import { Column, Line } from '../UI/Grid';
|
||||
import Add from 'material-ui/svg-icons/content/add';
|
||||
import RaisedButton from 'material-ui/RaisedButton';
|
||||
|
||||
const SortableAddLayerRow = SortableElement(AddLayerRow);
|
||||
const SortableLayerRow = SortableElement(LayerRow);
|
||||
|
||||
class LayersListBody extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
nameErrors: {},
|
||||
};
|
||||
}
|
||||
type LayersListBodyState = {|
|
||||
nameErrors: { [string]: boolean },
|
||||
|};
|
||||
|
||||
class LayersListBody extends Component<*, LayersListBodyState> {
|
||||
state = {
|
||||
nameErrors: {},
|
||||
};
|
||||
|
||||
render() {
|
||||
const { layersContainer } = this.props;
|
||||
const { layersContainer, onEditEffects } = this.props;
|
||||
|
||||
const layersCount = layersContainer.getLayersCount();
|
||||
const containerLayersList = mapReverseFor(0, layersCount, i => {
|
||||
@@ -40,6 +46,8 @@ class LayersListBody extends Component {
|
||||
layer={layer}
|
||||
layerName={layerName}
|
||||
nameError={this.state.nameErrors[layerName]}
|
||||
effectsCount={layer.getEffectsCount()}
|
||||
onEditEffects={() => onEditEffects(layer)}
|
||||
onBlur={event => {
|
||||
const newName = event.target.value;
|
||||
if (layerName === newName) return;
|
||||
@@ -70,7 +78,7 @@ class LayersListBody extends Component {
|
||||
});
|
||||
}}
|
||||
isVisible={layer.getVisibility()}
|
||||
onChangeVisibility={(e, visible) => {
|
||||
onChangeVisibility={visible => {
|
||||
layer.setVisibility(visible);
|
||||
this.forceUpdate();
|
||||
}}
|
||||
@@ -78,31 +86,17 @@ class LayersListBody extends Component {
|
||||
);
|
||||
});
|
||||
|
||||
const addRow = (
|
||||
<SortableAddLayerRow
|
||||
index={layersContainer.getLayersCount()}
|
||||
key={'add-layer-row'}
|
||||
disabled
|
||||
onAdd={() => {
|
||||
const name = newNameGenerator('Layer', name =>
|
||||
layersContainer.hasLayerNamed(name)
|
||||
);
|
||||
layersContainer.insertNewLayer(
|
||||
name,
|
||||
layersContainer.getLayersCount()
|
||||
);
|
||||
this.forceUpdate();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<TableBody
|
||||
displayRowCheckbox={false}
|
||||
deselectOnClickaway={true}
|
||||
showRowHover={true}
|
||||
>
|
||||
{containerLayersList.concat(addRow)}
|
||||
{containerLayersList}
|
||||
<BackgroundColorRow
|
||||
layout={layersContainer}
|
||||
onBackgroundColorChanged={() => this.forceUpdate()}
|
||||
/>
|
||||
</TableBody>
|
||||
);
|
||||
}
|
||||
@@ -111,55 +105,117 @@ class LayersListBody extends Component {
|
||||
const SortableLayersListBody = SortableContainer(LayersListBody);
|
||||
SortableLayersListBody.muiName = 'TableBody';
|
||||
|
||||
export default class LayersList extends Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
type Props = {|
|
||||
freezeUpdate: boolean,
|
||||
layersContainer: gdLayout,
|
||||
onRemoveLayer: (layerName: string, cb: (done: boolean) => void) => void,
|
||||
onRenameLayer: (
|
||||
oldName: string,
|
||||
newName: string,
|
||||
cb: (done: boolean) => void
|
||||
) => void,
|
||||
|};
|
||||
type State = {|
|
||||
effectsEditedLayer: ?gdLayer,
|
||||
|};
|
||||
|
||||
export default class LayersList extends Component<Props, State> {
|
||||
state = {
|
||||
effectsEditedLayer: null,
|
||||
};
|
||||
|
||||
defaultProps = {
|
||||
onRemoveLayer: (layerName: string, cb: (done: boolean) => void) => cb(true),
|
||||
onRenameLayer: (
|
||||
oldName: string,
|
||||
newName: string,
|
||||
cb: (done: boolean) => void
|
||||
) => cb(true),
|
||||
};
|
||||
|
||||
shouldComponentUpdate(nextProps: Props) {
|
||||
// Rendering the component can be costly as it iterates over
|
||||
// every layers, so the prop freezeUpdate allow to ask the component to stop
|
||||
// updating, for example when hidden.
|
||||
return !nextProps.freezeUpdate;
|
||||
}
|
||||
|
||||
_editEffects = (effectsEditedLayer: ?gdLayer) => {
|
||||
this.setState({
|
||||
effectsEditedLayer,
|
||||
});
|
||||
};
|
||||
|
||||
_addLayer = () => {
|
||||
const { layersContainer } = this.props;
|
||||
const name = newNameGenerator('Layer', name =>
|
||||
layersContainer.hasLayerNamed(name)
|
||||
);
|
||||
layersContainer.insertNewLayer(name, layersContainer.getLayersCount());
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { effectsEditedLayer } = this.state;
|
||||
|
||||
// Force the list to be mounted again if layersContainer
|
||||
// has been changed. Avoid accessing to invalid objects that could
|
||||
// crash the app.
|
||||
const listKey = this.props.layersContainer.ptr;
|
||||
|
||||
return (
|
||||
<Table selectable={false}>
|
||||
<TableHeader displaySelectAll={false} adjustForCheckbox={false}>
|
||||
<TableRow>
|
||||
<TableHeaderColumn style={styles.handleColumn} />
|
||||
<TableHeaderColumn>Layer name</TableHeaderColumn>
|
||||
<TableHeaderColumn style={styles.visibleColumn}>
|
||||
Visible
|
||||
</TableHeaderColumn>
|
||||
<TableRowColumn style={styles.toolColumn} />
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<SortableLayersListBody
|
||||
key={listKey}
|
||||
layersContainer={this.props.layersContainer}
|
||||
onRemoveLayer={this.props.onRemoveLayer}
|
||||
onRenameLayer={this.props.onRenameLayer}
|
||||
onSortEnd={({ oldIndex, newIndex }) => {
|
||||
const layersCount = this.props.layersContainer.getLayersCount();
|
||||
this.props.layersContainer.moveLayer(
|
||||
layersCount - 1 - oldIndex,
|
||||
layersCount - 1 - newIndex
|
||||
);
|
||||
this.forceUpdate();
|
||||
}}
|
||||
helperClass="sortable-helper"
|
||||
useDragHandle
|
||||
lockToContainerEdges
|
||||
/>
|
||||
</Table>
|
||||
<React.Fragment>
|
||||
<Table selectable={false}>
|
||||
<TableHeader displaySelectAll={false} adjustForCheckbox={false}>
|
||||
<TableRow>
|
||||
<TableHeaderColumn style={styles.handleColumn} />
|
||||
<TableHeaderColumn>Layer name</TableHeaderColumn>
|
||||
<TableHeaderColumn style={styles.effectsColumn}>
|
||||
Effects
|
||||
</TableHeaderColumn>
|
||||
<TableRowColumn style={styles.toolColumn} />
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<SortableLayersListBody
|
||||
key={listKey}
|
||||
layersContainer={this.props.layersContainer}
|
||||
onEditEffects={layer => this._editEffects(layer)}
|
||||
onRemoveLayer={this.props.onRemoveLayer}
|
||||
onRenameLayer={this.props.onRenameLayer}
|
||||
onSortEnd={({ oldIndex, newIndex }) => {
|
||||
const layersCount = this.props.layersContainer.getLayersCount();
|
||||
this.props.layersContainer.moveLayer(
|
||||
layersCount - 1 - oldIndex,
|
||||
layersCount - 1 - newIndex
|
||||
);
|
||||
this.forceUpdate();
|
||||
}}
|
||||
helperClass="sortable-helper"
|
||||
useDragHandle
|
||||
/>
|
||||
</Table>
|
||||
<Column>
|
||||
<Line justifyContent="flex-end" expand>
|
||||
<RaisedButton
|
||||
label={<Trans>Add a layer</Trans>}
|
||||
primary
|
||||
onClick={this._addLayer}
|
||||
labelPosition="before"
|
||||
icon={<Add />}
|
||||
/>
|
||||
</Line>
|
||||
</Column>
|
||||
{effectsEditedLayer && (
|
||||
<EffectsListDialog
|
||||
effectsContainer={effectsEditedLayer}
|
||||
onApply={() =>
|
||||
this.setState({
|
||||
effectsEditedLayer: null,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LayersList.defaultProps = {
|
||||
onRemoveLayer: (layerName, cb) => cb(true),
|
||||
onRenameLayer: (oldName, newName, cb) => cb(true),
|
||||
};
|
||||
|
@@ -5,10 +5,11 @@ export default {
|
||||
paddingLeft: 8,
|
||||
paddingRight: 0,
|
||||
},
|
||||
visibleColumn: {
|
||||
width: 48,
|
||||
effectsColumn: {
|
||||
width: 100,
|
||||
textAlign: 'center',
|
||||
},
|
||||
toolColumn: {
|
||||
width: 48,
|
||||
width: 96,
|
||||
},
|
||||
};
|
||||
|
@@ -79,7 +79,7 @@ export const create = (authentification: Authentification) => {
|
||||
i18n={i18n}
|
||||
eventsFunctionsExtensionsState={eventsFunctionsExtensionsState}
|
||||
previewLauncher={<LocalPreviewLauncher />}
|
||||
exportDialog={<ExportDialog exporters={getLocalExporters()} />}
|
||||
renderExportDialog={(props) => <ExportDialog {...props} exporters={getLocalExporters()} />}
|
||||
createDialog={
|
||||
<CreateProjectDialog
|
||||
examplesComponent={LocalExamples}
|
||||
|
@@ -97,7 +97,7 @@ const contributors = [
|
||||
{ name: 'Diego Schiavon', description: 'Indiegogo Ubuntu contributor' },
|
||||
{ name: 'conceptgame', description: 'Indiegogo super contributor' },
|
||||
{
|
||||
name: 'Jose David Coartas Correa',
|
||||
name: 'Jose David Cuartas Correa',
|
||||
description:
|
||||
'Author of Digitopolis (a book on how to make games with GDevelop4)',
|
||||
},
|
||||
|