Compare commits

...

162 Commits

Author SHA1 Message Date
Florian Rival
807a75a265 Update productName for newIDE 2018-01-19 00:24:07 +01:00
Florian Rival
6e1b44ea56 Bump newIDE version 2018-01-18 23:31:41 +01:00
Florian Rival
d870a54f58 Fix error when handling download-progress of an update 2018-01-18 23:31:00 +01:00
Florian Rival
9f464a3f23 Bump newIDE version 2018-01-18 23:12:37 +01:00
Florian Rival
ed4e84d665 Add Export icon in newIDE main toolbar & fix profile refreshing after getting a new subscription 2018-01-18 23:12:08 +01:00
Florian Rival
1e4ab96233 Bump newIDE version 2018-01-18 00:04:27 +01:00
Florian Rival
380c034ff5 Merge pull request #433 from 4ian/feature/online-cordova-export
Packaging for Android with GDevelop online build service + various improvements
2018-01-18 00:02:51 +01:00
Florian Rival
7c9abe432d Fix AboutDialog story in storybook and use retro default avatars 2018-01-18 00:02:05 +01:00
Florian Rival
2ca853c22c Add HelpButton in ProfileDialog and disabled icon field in ProjectPropertiesDialog 2018-01-17 23:55:40 +01:00
Florian Rival
b23d55612a Add display of user Gravatar in ProfileDetails 2018-01-17 22:36:51 +01:00
Florian Rival
b261faacfc Freeze GDCore/libGD.js and newIDE version to 4.0.96 (don't read it from Git tags) 2018-01-17 00:21:07 +01:00
Florian Rival
9984b32876 Add link to help page specific to each exporters 2018-01-16 23:42:46 +01:00
Florian Rival
d63ba75430 Change wording in CreateProfile 2018-01-16 23:33:44 +01:00
Florian Rival
2db903ed94 Fix profile refreshing after login/account creation 2018-01-16 22:00:45 +01:00
Florian Rival
3f9bc4d335 Fix upload and Android export for large files 2018-01-16 21:25:36 +01:00
Florian Rival
3df95d0e21 Switch authentification to use Firebase 2018-01-14 17:59:09 +01:00
Florian Rival
f445695f6a Add auto-update support to GDevelop 5 2018-01-13 17:17:48 +01:00
Florian Rival
6f3163d7bd Rework ExportDialog to have a list of exporters to choose from 2018-01-13 14:54:10 +01:00
Florian Rival
5421eae23d Add buttons to switch between exporters of the same kind 2018-01-11 00:04:31 +01:00
Florian Rival
a0e82ee22f Add button to download logs in LocalOnlineCordovaExport 2018-01-10 23:25:18 +01:00
Florian Rival
1fbf822769 Move GDevelop hosting API configuration to GDevelopServices/ApiConfigs.js 2018-01-09 23:52:44 +01:00
Florian Rival
6b0f037722 Add a comment about working with Auth0 during development 2018-01-09 23:52:44 +01:00
Florian Rival
ae231d2fe6 Update packageName of fixtures/example games 2018-01-09 23:52:44 +01:00
Florian Rival
2e95899da7 Ensure user is asked for authenticating again if needed 2018-01-09 23:52:44 +01:00
Florian Rival
3f45b38dfc [WIP] Make SubscriptionDialog working with GDevelop Services and Stripe
TODO: Properly handle the refresh of ProfileDialog
2018-01-09 23:52:44 +01:00
Florian Rival
daf4d36348 [WIP] Add SubscriptionDialog 2018-01-09 23:52:44 +01:00
Florian Rival
477cd16f23 Add storybook stories for Profile components 2018-01-09 23:52:44 +01:00
Florian Rival
83eded0056 [WIP] Improve LocalOnlineCordovaExport progress handling 2018-01-09 23:52:44 +01:00
Florian Rival
338de9c149 [WIP] Add LimitDisplayer and display subscriptions limits in LocalOnlineCordovaExport 2018-01-09 23:52:44 +01:00
Florian Rival
6d99d4e661 [WIP] Add happy path for LocalOnlineCordovaExport 2018-01-09 23:52:44 +01:00
Florian Rival
4e5a8060c8 [WIP] Add Archiver to create zip files for LocalOnlineCordovaExport 2018-01-09 23:52:44 +01:00
Florian Rival
eb938e39ff [WIP] Add LocalOnlineCordovaExport (with no real calls to the build service for now) 2018-01-09 23:52:44 +01:00
Florian Rival
cff8604a5f Fix flow error/incompatibility in auth0-lock's node_modules 2018-01-09 23:52:44 +01:00
Florian Rival
2afa54bcd2 [WIP] Add SubscriptionDetails, ProfileDetails and UsagesDetails in ProfileDialog 2018-01-09 23:52:44 +01:00
Florian Rival
4da6025b6f Move configuration of BrowserS3PreviewLauncher in ApiConfigs 2018-01-09 23:52:44 +01:00
Florian Rival
c4f64444e7 [WIP] Add a Profile in newIDE with login/logout based on Auth0 2018-01-09 23:52:44 +01:00
Florian Rival
b0205b296f Merge pull request #432 from 4ian/fix/various
Various improvements for GDJS (memory + libs update)
2018-01-09 23:48:15 +01:00
Florian Rival
e0c8b3cc81 Add comment about RuntimScene.unloadScene and memory management 2018-01-09 23:47:47 +01:00
Florian Rival
80ca92336e Fix filters and refactor Text style update for Pixi v4 2018-01-09 23:03:26 +01:00
Florian Rival
6c0681e4ca Merge branch 'mem' of https://github.com/dos1/GD into dos1-mem 2018-01-07 20:06:18 +01:00
Sebastian Krzyszkowiak
26e8503dc8 GDJS: HowlerSoundManager: use XMLHttpRequest to preload
Howler decodes the whole file, which causes massive memory spike
on load when the project contains lots of longer music files.
We could use HTML5 audio to avoid decoding, but then the load event
actually means "preloaded enough to start playing without buffering".

We don't need to decode here, putting the file into browser cache
should be enough, so let's just download the file with XHR.
2018-01-06 02:09:01 +01:00
Sebastian Krzyszkowiak
c1ce78efde GDJS: howler-sound-manager: use .unload() method when destroying to free the resources used by sounds 2018-01-06 02:08:54 +01:00
Sebastian Krzyszkowiak
2f52c2f062 GDJS: store global volume value in HowlerSoundManager to avoid rounding errors (#430) 2018-01-05 16:59:16 +01:00
Sebastian Krzyszkowiak
24d11a2446 GDJS: update howler.js to 2.0.7 2018-01-04 23:44:57 +01:00
Sebastian Krzyszkowiak
e1d4ae5bbd GDJS: update Pixi to 4.6.2 2018-01-04 23:44:32 +01:00
Sebastian Krzyszkowiak
dcfe346f54 GDJS: RuntimeScene: clean up more variables on unload, so they can be garbage collected 2018-01-04 23:44:13 +01:00
Florian Rival
3906db9efe Make UserUUID generation security/exception errors-proof 2017-12-26 16:47:00 +01:00
Florian Rival
ba5244d95d Bump newIDE version 2017-12-26 16:46:32 +01:00
Florian Rival
52db730870 Add ErrorBounday for handling editor crash and EmptyEventsPlaceholder for empty events sheets 2017-12-26 14:47:46 +01:00
Florian Rival
82d2278ebd Bump newIDE version 2017-12-25 23:55:48 +01:00
Florian Rival
30d08ac72d Add button linking to help about creating new themes in newIDE 2017-12-25 17:48:38 +01:00
Florian Rival
6f1e71c8e5 Update README about creating themes for newIDE 2017-12-25 17:41:00 +01:00
Florian Rival
385be9b5f5 Make EventsSheet color scheme customizable and add Monokai inspired dark color scheme 2017-12-25 17:33:25 +01:00
Florian Rival
25cb041d78 Improve DarkTheme to fix Raised buttons readability 2017-12-25 14:58:36 +01:00
Florian Rival
18fa9cd659 Merge pull request #429 from 4ian/feature/upgrade-mui-react
Upgrade to React 16 and Material-UI 0.20
2017-12-24 17:33:58 +01:00
Florian Rival
ce760541c5 Upgrade to React 16.2 2017-12-24 16:51:04 +01:00
Florian Rival
db51b652f0 Remove useless refs created warnings with React 16 2017-12-24 15:05:46 +01:00
Florian Rival
4222d98aa9 Update instructions parameters color scheme 2017-12-24 15:05:20 +01:00
Florian Rival
484f1e5dcb Fix typing 2017-12-24 14:30:43 +01:00
Florian Rival
7304b94a77 Fix warnings/React issues with refs in EventsSheet 2017-12-24 14:07:20 +01:00
Florian Rival
ee5ec7df72 Fix InstancePropertiesEditor not refreshed when an instance is deleted/moved 2017-12-24 12:49:20 +01:00
Florian Rival
9a1e4bdf7c Update react-i18next dependency of newIDE 2017-12-24 12:40:26 +01:00
Florian Rival
a43cca7629 Update react-dnd dependency of newIDE 2017-12-24 12:31:08 +01:00
Florian Rival
0cdabab2ec Update react-color dependency of newIDE 2017-12-24 12:24:49 +01:00
Florian Rival
28b968ea99 Update newIDE to React 15.6.2 2017-12-24 12:21:32 +01:00
Florian Rival
5160033092 Upgrade storybook and remove useless newIDE dependencies 2017-12-24 12:00:41 +01:00
Florian Rival
ce086c07b9 Upgrade material-ui to v0.20 and upgrade other dependencies 2017-12-24 11:49:40 +01:00
Florian Rival
bf9348488d Merge pull request #428 from 4ian/feature/themable-newIDE
Add a Dark theme to newIDE
2017-12-23 23:18:38 +01:00
Florian Rival
70b3e0701b Fix StartPage tests 2017-12-23 23:14:44 +01:00
Florian Rival
dc3d890937 Add comment explaining how to create a new theme 2017-12-23 23:14:02 +01:00
Florian Rival
f68be32e2d Update logo in newIDE StartPage and tweak colors 2017-12-23 23:11:10 +01:00
Florian Rival
3758e7af03 Fix editors not opening in SceneEditor in newIDE 2017-12-23 22:40:50 +01:00
Florian Rival
5ab1858349 [WIP] Refactor EventsSheet to make it partially themable 2017-12-23 21:50:12 +01:00
Florian Rival
54353bcb24 [WIP] Add PreferencesDialog and make the whole newIDE interface themable 2017-12-23 21:06:55 +01:00
Florian Rival
de7640558d Fix Clock Skew related errors while uploading to S3 from certain computers 2017-12-23 13:52:33 +01:00
Florian Rival
2e17b16516 Make newIDE context menus dynamic (greyed-out items if not applicable, checkbox for grid) 2017-12-23 13:51:36 +01:00
Sebastian Krzyszkowiak
345f9f1786 Run CopyRuntimeToGD.sh with bash explicitly (#427)
The script contains bashisms which fail on systems where sh is
symlinked to dash instead of bash.
2017-12-22 11:12:02 +01:00
Florian Rival
8c9214ce5e Bump newIDE version 2017-12-20 22:44:49 +01:00
Florian Rival
fc9cc8046d Merge pull request #426 from 4ian/feature/newide-js-code-event
Add support for Javascript Code Event in newIDE
2017-12-20 22:33:12 +01:00
Florian Rival
e592bb855d Add support for objects parameter in JsCodeEvent in newIDE 2017-12-20 22:30:53 +01:00
Florian Rival
f8407264da Add basic support for JsCodeEvent in newIDE 2017-12-19 21:16:27 +01:00
Florian Rival
202fe2b69a Add FpsLimiter to limit framerate on InstancesEditor of newIDE, to avoid killing CPU/battery 2017-12-18 00:05:31 +01:00
Florian Rival
d14a93f9d0 Remove ok button in MainFrame Snackbar 2017-12-17 19:37:13 +01:00
Florian Rival
1d1ccdbf06 Fix unmounting and related focus issue of editors in newIDE 2017-12-17 16:16:29 +01:00
Florian Rival
92fd647316 Add button to open external events or layout events from a LinkEvent in newIDE 2017-12-16 18:09:30 +01:00
Florian Rival
d8ca506200 Add flow typing to MainFrame 2017-12-16 17:18:09 +01:00
Florian Rival
928d6eda98 Update VSCode settings 2017-12-16 16:53:53 +01:00
Florian Rival
d5e6755694 Move all providers from MainFrame to a dedicated component 2017-12-16 16:53:37 +01:00
Florian Rival
a1431126ec Merge pull request #424 from 4ian/feature/clipboard-project-manager
Clipboard support for ProjectManager in newIDE
2017-12-16 16:17:14 +01:00
Florian Rival
13c8caeb8c Increase flow coverage 2017-12-16 16:12:10 +01:00
Florian Rival
b2b5ee0e19 Add a TODO about style refactoring 2017-12-16 14:28:32 +01:00
Florian Rival
ff74ecd7c9 Run prettier on newIDE codebase 2017-12-16 14:26:05 +01:00
Florian Rival
0ac3e79156 Fix flow typing in newIDE and update flow to latest version 2017-12-16 14:24:25 +01:00
Florian Rival
3a69936e2e Add copy/cut/paste support for External Layouts and Events in newIDE 2017-12-16 14:02:40 +01:00
Florian Rival
e738d5dd5f Add copy/cut/paste support for Layout in newIDE 2017-12-16 00:14:18 +01:00
Florian Rival
ed8ee21c04 Reduce GroupEvent height 2017-12-15 23:22:47 +01:00
Florian Rival
87220a941a Bump newIDE version 2017-12-15 23:19:23 +01:00
Florian Rival
4425d13c7a Rename objectWithScope to objectWithContext 2017-12-13 00:19:31 +01:00
Florian Rival
b818e5f493 Add support for copy/cut/paste objects 2017-12-12 23:57:37 +01:00
Lizard-13
403563ba32 Support keyboard top row numbers in HTML5 games (#422) 2017-12-11 14:29:14 +01:00
Florian Rival
156ca68116 Make newIDE save project as formatted JSON 2017-12-10 23:03:16 +01:00
Florian Rival
de3677a6c3 Add AnimationPreview in SpriteEditor 2017-12-10 23:02:51 +01:00
Florian Rival
3b76dfe9f4 Fix LocalCordovaExport storybook and increase expression selector height 2017-12-09 20:08:23 +01:00
Florian Rival
6e38ee6d16 Move newIDE test files next to the test source files (no __tests__ folder) 2017-12-09 18:05:59 +01:00
Florian Rival
454657b00f Add link to Tank Shooter beginner tutorial in newIDE 2017-12-09 17:47:54 +01:00
Florian Rival
aad0c4e909 Fix newIDE yarn.lock 2017-12-09 17:27:53 +01:00
Florian Rival
d106ee9ac1 Merge pull request #421 from 4ian/feature/newide-expressions-autocomplete
Expression field with list of available functions for newIDE
2017-12-09 17:16:41 +01:00
Florian Rival
5b1e6e4381 Don't show ExpressionSelector when ExpressionField is displayed inline 2017-12-09 16:50:27 +01:00
Florian Rival
e1106c6145 Fix GenericExpressionField validation and functions button positioning 2017-12-07 00:33:42 +01:00
Florian Rival
0c5caf9986 Update StringField to use GenericExpressionField, with same features as ExpressionField 2017-12-07 00:22:59 +01:00
Florian Rival
2fdcd6c639 Fix formatting of expressions with code-only parameters 2017-12-07 00:04:56 +01:00
Florian Rival
8332adf07b [WIP] Fix Expression object/behavior functions formatting and BehaviorField 2017-12-06 23:43:20 +01:00
Florian Rival
fb40e908c0 [WIP] Add ExpressionParametersEditor to edit parameters of an expression to insert
TODO: Properly format objects and behaviors function calls.
2017-12-06 00:22:21 +01:00
Florian Rival
b6ef67568a [WIP] Add the function in the text field when an expression is chosen 2017-12-05 22:07:56 +01:00
Florian Rival
9c591ec3b1 [WIP] Add ExpressionSelector, refactor InstructionSelector and switched to lodash monolithic build 2017-12-04 21:49:31 +01:00
Lizard-13
495900c083 Add point inside object condition (#418)
* Add point inside object condition

* Update names and comments
2017-12-04 09:25:56 +01:00
Florian Rival
7874e2af27 [WIP] Add auto-completion to ExpressionField in newIDE 2017-12-04 00:03:46 +01:00
Florian Rival
19b37b7111 Update Electron latest stable version for newIDE 2017-12-03 22:53:01 +01:00
Florian Rival
8593249bc6 Add link to the Itch.io publishing help page 2017-12-03 12:28:11 +01:00
Florian Rival
6d483ec887 Update electron-builder and fix analytics send when exporting with Cordova in newIDE 2017-11-28 23:02:40 +01:00
Florian Rival
e1a28f0f90 Update newIDE readme 2017-11-28 00:43:22 +01:00
Florian Rival
c2ba76a821 Bump newIDE version 2017-11-28 00:40:40 +01:00
Florian Rival
c4f8134d89 Add Cordova and Cocos2d-JS exports in newIDE 2017-11-28 00:39:19 +01:00
Florian Rival
59416fd0cd Avoid clearing the output directory when exporting a HTML5 game 2017-11-27 23:58:15 +01:00
Florian Rival
ef7ed24114 Merge pull request #417 from 4ian/feature/open-scene-editor-on-open
Automatic opening of 1st scene in newIDE + Sprite frames selection/deletion
2017-11-27 22:12:16 +01:00
Florian Rival
07dd2bcb5c Add Checkbox to select sprites and context menu to delete selected sprites 2017-11-26 19:55:53 +01:00
Florian Rival
81dfeb3ab1 Open scene or project manager (according to layouts count) after project opening in newIDE 2017-11-24 20:08:38 +01:00
Florian Rival
09d558744f Refactor project opening in MainFrame and add test for StartPage 2017-11-24 19:54:09 +01:00
Florian Rival
abe7dd7ccd Adapt StartPage button when a project is opened 2017-11-24 08:39:06 +01:00
Florian Rival
0da624dc18 Fix variables with multiline values rendering in newIDE + add a storybook stories for this 2017-11-23 00:36:21 +01:00
Florian Rival
a082585b4e Don't make Game Settings initially opened in ProjectManager 2017-11-21 01:07:10 +01:00
Florian Rival
d1582723ba Fix hint text in ProjectPropertiesDialog 2017-11-21 01:06:07 +01:00
Florian Rival
829a88f290 Add ProjectPropertiesDialog, editor for global variables and minor fixes 2017-11-21 00:58:07 +01:00
Florian Rival
79f2e57fa9 Add ExternalEventsField, used to choose events to import in LinkEvent 2017-11-19 23:42:42 +01:00
Florian Rival
322787ba0e Merge pull request #416 from 4ian/feature/points-editor
Add Points editor
2017-11-19 20:53:27 +01:00
Florian Rival
7bd2dab47c Clean up PointsEditor and related 2017-11-19 20:38:59 +01:00
Florian Rival
dd1446cfb0 Add Toggles to have all animations/sprites sharing the same points in PointsEditor 2017-11-19 18:00:57 +01:00
Florian Rival
cf96db75ea Add HelpButton to ExportDialog 2017-11-19 15:47:08 +01:00
Florian Rival
da4f350cdb [WIP] Add points editor and hitboxes editor buttons in a SpriteEditor toolbar 2017-11-19 15:44:59 +01:00
Florian Rival
862c012bb6 [WIP] Ensure position of PointsEditor dialog remains centered after adding points 2017-11-19 15:26:18 +01:00
Florian Rival
72a65cb1d2 [WIP] Add default position handling for center point in PointsEditor 2017-11-19 02:30:25 +01:00
Florian Rival
22379ae31b [WIP] Add PointsPreview in PointsEditor
TODO: Properly handle automatic points
2017-11-19 01:39:18 +01:00
Florian Rival
73e0ba8264 Merge pull request #415 from Lizard-13/patch-1
Polygon fix
2017-11-18 16:20:15 +01:00
Lizard-13
2b0e28f456 Polygon fix
Just a little fix for an error it's throwing me (not sure how it doesn't fails for every convex polygon)  :)
2017-11-18 11:38:59 -03:00
Florian Rival
151a9b1a74 [WIP] Add ImageThumbnail in PointsEditor and a link to it in SpriteEditor 2017-11-17 00:29:55 +01:00
Florian Rival
172d7f049e [WIP] Improve PointsEditor 2017-11-16 00:14:49 +01:00
Florian Rival
5ef703eff4 [WIP] Add PointsEditor to edit the points of a Sprite in newIDE 2017-11-15 01:25:48 +01:00
Florian Rival
98d970de30 Add polyfill for self and patch XMLHttpRequest.send for Cocos2d-JS 3.15 and below 2017-11-14 23:32:49 +01:00
Florian Rival
8896fb280a Try to fix FontFace support in Edge/IE/Safari 2017-11-14 22:44:11 +01:00
Florian Rival
8df5ab9e62 Add a very basic Shopify extension (HTML5 only) to checkout products in a shop 2017-11-12 23:19:19 +01:00
Florian Rival
eb32d51999 Add Semaphore CI badge (quicker CI for newIDE and GDJS tests) 2017-11-12 19:34:57 +01:00
Florian Rival
8901809f88 Avoid potential issue with LayersList when project/layout props are changed 2017-11-12 18:16:37 +01:00
Florian Rival
1de22ef53b Ensure the proper tab is focused after opening, even when the tab is already opened 2017-11-12 01:48:47 +01:00
Florian Rival
d328ae0c7e Increase the width of the text field to set the font size in TextEditor 2017-11-12 01:32:20 +01:00
Florian Rival
dabb0f9dad Remove GDevApp link in README as it is replaced by newIDE 2017-11-12 01:24:10 +01:00
Florian Rival
5adcfc5bad Fix Flow error 2017-11-11 19:12:20 +01:00
Florian Rival
b03bf86a35 Add error messages when an expression is invalid in ExpressionField or StringField in newIDE 2017-11-11 18:33:49 +01:00
Florian Rival
db1b1eadf1 Refactor ExpressionParser and ParserCallbacks with getters for errors 2017-11-11 15:40:22 +01:00
Florian Rival
939266b18b Add more help buttons in newIDE and various minor fixes 2017-11-09 21:31:28 +01:00
Florian Rival
b68a9d8b47 Rename newIDE executable from GDevelop IDE to GDevelop 5 2017-11-09 20:51:05 +01:00
Florian Rival
af1a028203 Merge pull request #413 from 4ian/fix/react-mosaic-dragging
Fix dropping objects from ExternalEditor in newIDE due to react-mosaic blocking drop + help buttons and various small improvements
2017-11-09 00:14:38 +01:00
Florian Rival
c697d5fb99 Upgrade react-mosaic-component so that it does not conflict with HTML5/external drag'n'drop 2017-11-08 23:47:47 +01:00
284 changed files with 22091 additions and 4485 deletions

View File

@@ -62,7 +62,12 @@
"__tuple": "cpp",
"hash_map": "cpp",
"hash_set": "cpp",
"system_error": "cpp"
"system_error": "cpp",
"__nullptr": "cpp",
"__functional_base": "cpp",
"__functional_base_03": "cpp",
"chrono": "cpp",
"ratio": "cpp"
},
"files.exclude": {
"Binaries/*build*": true,

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

View File

@@ -500,7 +500,7 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(const gd::String & parame
gd::ExpressionParser parser(parameter);
if ( !parser.ParseMathExpression(platform, project, scene, callbacks) )
{
cout << "Error :" << parser.firstErrorStr << " in: "<< parameter << endl;
cout << "Error :" << parser.GetFirstError() << " in: "<< parameter << endl;
argOutput = "0";
}
@@ -514,7 +514,7 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(const gd::String & parame
gd::ExpressionParser parser(parameter);
if ( !parser.ParseStringExpression(platform, project, scene, callbacks) )
{
cout << "Error in text expression" << parser.firstErrorStr << endl;
cout << "Error in text expression" << parser.GetFirstError() << endl;
argOutput = "\"\"";
}

View File

@@ -169,8 +169,8 @@ bool CallbacksForGeneratingExpressionCode::OnSubMathExpression(const gd::Platfor
if ( !parser.ParseMathExpression(platform, project, layout, callbacks) )
{
#if defined(GD_IDE_ONLY)
firstErrorStr = callbacks.firstErrorStr;
firstErrorPos = callbacks.firstErrorPos;
firstErrorStr = callbacks.GetFirstError();
firstErrorPos = callbacks.GetFirstErrorPosition();
#endif
return false;
}
@@ -188,8 +188,8 @@ bool CallbacksForGeneratingExpressionCode::OnSubTextExpression(const gd::Platfor
if ( !parser.ParseStringExpression(platform, project, layout, callbacks) )
{
#if defined(GD_IDE_ONLY)
firstErrorStr = callbacks.firstErrorStr;
firstErrorPos = callbacks.firstErrorPos;
firstErrorStr = callbacks.GetFirstError();
firstErrorPos = callbacks.GetFirstErrorPosition();
#endif
return false;
}

View File

@@ -31,21 +31,31 @@ public:
virtual ~ExpressionParser() {};
/**
* Parse the expression, calling each functor when necessary
* \brief Parse the expression, calling each functor when necessary
* \return True if expression was correctly parsed.
*/
bool ParseMathExpression(const gd::Platform & platform, const gd::Project & project, const gd::Layout & layout, gd::ParserCallbacks & callbacks);
/**
* Parse the expression, calling each functor when necessary
* \brief Parse the expression, calling each functor when necessary
* \return True if expression was correctly parsed.
*/
bool ParseStringExpression(const gd::Platform & platform, const gd::Project & project, const gd::Layout & layout, gd::ParserCallbacks & callbacks);
gd::String firstErrorStr;
size_t firstErrorPos;
/**
* \brief Return the description of the error that was found
*/
const gd::String & GetFirstError() { return firstErrorStr; }
/**
* \brief Return the position of the error that was found
* \return The position, or gd::String::npos if no error is found
*/
size_t GetFirstErrorPosition() { return firstErrorPos; }
private:
gd::String firstErrorStr;
size_t firstErrorPos;
/**
* Tool function to add a parameter
@@ -103,8 +113,21 @@ public:
virtual bool OnSubMathExpression(const gd::Platform & platform, const gd::Project & project, const gd::Layout & layout, gd::Expression & expression) = 0;
virtual bool OnSubTextExpression(const gd::Platform & platform, const gd::Project & project, const gd::Layout & layout, gd::Expression & expression) = 0;
/**
* \brief Return the description of the error that was found
*/
const gd::String & GetFirstError() { return firstErrorStr; }
/**
* \brief Return the position of the error that was found
* \return The position, or gd::String::npos if no error is found
*/
size_t GetFirstErrorPosition() { return firstErrorPos; }
protected:
gd::String firstErrorStr;
size_t firstErrorPos;
private:
/**
* \brief Set the return type of the expression: Done by ExpressionParser according to

View File

@@ -30,7 +30,7 @@ namespace gd
gd::VariableParser parser(parameter);
if ( !parser.Parse(callbacks) )
cout << "Error :" << parser.firstErrorStr << " in: "<< parameter << endl;
cout << "Error :" << parser.GetFirstError() << " in: "<< parameter << endl;
\endcode
*
* Here is the parsed grammar: <br>
@@ -61,6 +61,17 @@ public:
*/
bool Parse(VariableParserCallbacks & callbacks);
/**
* \brief Return the description of the error that was found
*/
const gd::String & GetFirstError() { return firstErrorStr; }
/**
* \brief Return the position of the error that was found
* \return The position, or gd::String::npos if no error is found
*/
size_t GetFirstErrorPosition() { return firstErrorPos; }
gd::String firstErrorStr;
size_t firstErrorPos;

View File

@@ -621,6 +621,18 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(gd:
.AddParameter("objectList", _("Objects"))
.MarkAsSimple();
obj.AddCondition("CollisionPoint",
_("Point inside object"),
_("Test if a point is inside the object collision masks."),
_("_PARAM1_;_PARAM2_ is inside _PARAM0_"),
_("Collision"),
"res/conditions/collisionPoint24.png",
"res/conditions/collisionPoint.png")
.AddParameter("object", _("Object"))
.AddParameter("expression", _("X position of the point"))
.AddParameter("expression", _("Y position of the point"))
.MarkAsSimple();
obj.AddExpression("X", _("X position"), _("X position of the object"), _("Position"), "res/actions/position.png")
.AddParameter("object", _("Object"));

View File

@@ -131,7 +131,7 @@ public:
* \warning If the image has not been loaded (using LoadImage) and the center point is set as automatic,
* the returned point won't be correct.
*/
inline Point & GetCenter() { automaticCentre = false; return centre; }
inline Point & GetCenter() { return centre; }
/**
* \brief Return true if the center point is automatically computed.

View File

@@ -477,8 +477,8 @@ void EditExpressionDialog::TextModified(wxStyledTextEvent& event)
gd::ExpressionParser expressionParser(expression);
if ( !expressionParser.ParseMathExpression(project.GetCurrentPlatform(), project, layout, callbacks) )
{
errorTxt->SetLabel(expressionParser.firstErrorStr);
lastErrorPos = expressionParser.firstErrorPos;
errorTxt->SetLabel(expressionParser.GetFirstError());
lastErrorPos = expressionParser.GetFirstErrorPosition();
}
else
{

View File

@@ -516,8 +516,8 @@ void EditStrExpressionDialog::TextModified(wxStyledTextEvent& event)
gd::ExpressionParser expressionParser(text);
if ( !expressionParser.ParseStringExpression(project.GetCurrentPlatform(), project, layout, callbacks) )
{
errorTxt->SetLabel(expressionParser.firstErrorStr);
lastErrorPos = expressionParser.firstErrorPos;
errorTxt->SetLabel(expressionParser.GetFirstError());
lastErrorPos = expressionParser.GetFirstErrorPosition();
}
else
{

View File

@@ -28,8 +28,8 @@ bool CallbacksForExpressionCorrectnessTesting::OnSubMathExpression(const gd::Pla
if ( !parser.ParseMathExpression(platform, project, layout, callbacks) )
{
#if defined(GD_IDE_ONLY)
firstErrorStr = callbacks.firstErrorStr;
firstErrorPos = callbacks.firstErrorPos;
firstErrorStr = callbacks.GetFirstError();
firstErrorPos = callbacks.GetFirstErrorPosition();
#endif
return false;
}
@@ -45,8 +45,8 @@ bool CallbacksForExpressionCorrectnessTesting::OnSubTextExpression(const gd::Pla
if ( !parser.ParseStringExpression(platform, project, layout, callbacks) )
{
#if defined(GD_IDE_ONLY)
firstErrorStr = callbacks.firstErrorStr;
firstErrorPos = callbacks.firstErrorPos;
firstErrorStr = callbacks.GetFirstError();
firstErrorPos = callbacks.GetFirstErrorPosition();
#endif
return false;
}

View File

@@ -143,13 +143,13 @@ void InstructionSentenceFormatter::LoadTypesFormattingFromConfig()
{
//Load default configuration
typesFormatting.clear();
typesFormatting["expression"].SetColor(99, 0, 0).SetBold();
typesFormatting["object"].SetColor(19, 81, 0).SetBold();
typesFormatting["behavior"].SetColor(19, 81, 0).SetBold();
typesFormatting["operator"].SetColor(64, 81, 79).SetBold();
typesFormatting["objectvar"].SetColor(44, 69, 99).SetBold();
typesFormatting["scenevar"].SetColor(44, 69, 99).SetBold();
typesFormatting["globalvar"].SetColor(44, 69, 99).SetBold();
typesFormatting["expression"].SetColor(27, 143, 1).SetBold();
typesFormatting["object"].SetColor(182, 97, 10).SetBold();
typesFormatting["behavior"].SetColor(119, 119, 119).SetBold();
typesFormatting["operator"].SetColor(55, 131, 211).SetBold();
typesFormatting["objectvar"].SetColor(131, 55, 162).SetBold();
typesFormatting["scenevar"].SetColor(131, 55, 162).SetBold();
typesFormatting["globalvar"].SetColor(131, 55, 162).SetBold();
//Load any existing custom configuration
#if !defined(GD_NO_WX_GUI)

View File

@@ -40,6 +40,7 @@ IF (NOT EMSCRIPTEN)
ENDIF()
ADD_SUBDIRECTORY(PlatformBehavior)
ADD_SUBDIRECTORY(PrimitiveDrawing)
ADD_SUBDIRECTORY(Shopify)
IF (NOT EMSCRIPTEN)
ADD_SUBDIRECTORY(SoundObject)
ENDIF()

View File

@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 2.6)
cmake_policy(SET CMP0015 NEW)
project(Shopify)
gd_add_extension_includes()
#Defines
###
gd_add_extension_definitions(Shopify)
#The targets
###
include_directories(.)
file(GLOB source_files *)
gd_add_extension_target(Shopify "${source_files}" "JsPlatform")
#Linker files for the IDE extension
###
gd_extension_link_libraries(Shopify)

View File

@@ -0,0 +1,93 @@
/**
GDevelop - Shopify Extension
Copyright (c)2017 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Tools/Localization.h"
void DeclareShopifyExtension(gd::PlatformExtension & extension)
{
extension.SetExtensionInformation("Shopify",
_("Shopify"),
_("Interact with products and generate URLs for checkouts with your Shopify shop."),
"Florian Rival",
"Open source (MIT License)");
#if defined(GD_IDE_ONLY)
extension.AddAction("BuildClient",
_("Initialize a shop"),
_("Initialize a shop with your credentials. Call this action first, and then use the shop name in the other actions to interact with products."),
_("Initialize shop _PARAM1_ (domain: _PARAM2_, appId: _PARAM3_)"),
_("Shopify"),
"JsPlatform/Extensions/Shopifyicon24.png",
"JsPlatform/Extensions/Shopifyicon16.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("string", _("Shop name"))
.AddParameter("string", _("Domain (xxx.myshopify.com)"))
.AddParameter("string", _("App Id"))
.AddParameter("string", _("Access Token"));
extension.AddAction("GetCheckoutUrlForProduct",
_("Get the URL for buying a product"),
_("Get the URL for buying a product from a shop. The URL will be stored in the variable that you specify. You can then use the action to open an URL to redirect the player to the checkout."),
_("Get the URL for product #_PARAM2_ (quantity: _PARAM3_, variant: _PARAM4_) from shop _PARAM1_, and store it in _PARAM5_ (or _PARAM6_ in case of error)"),
_("Shopify"),
"JsPlatform/Extensions/Shopifyicon24.png",
"JsPlatform/Extensions/Shopifyicon16.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("string", _("Shop name (initialized with \"Initialize a shop\" action)"))
.AddParameter("string", _("Product id"))
.AddParameter("expression", _("Quantity"))
.AddParameter("expression", _("Variant (0 by default)")).SetDefaultValue("0")
.AddParameter("scenevar", _("Variable where the URL for checkout must be stored"))
.AddParameter("scenevar", _("Variable containing the error (if any)"));
#endif
}
/**
* \brief This class declares information about the JS extension.
*/
class ShopifyJsExtension : public gd::PlatformExtension
{
public:
/**
* \brief Constructor of an extension declares everything the extension contains: objects, actions, conditions and expressions.
*/
ShopifyJsExtension()
{
DeclareShopifyExtension(*this);
GetAllActions()["Shopify::BuildClient"].codeExtraInformation
.SetIncludeFile("Extensions/Shopify/shopify-buy.umd.polyfilled.min.js")
.AddIncludeFile("Extensions/Shopify/shopifytools.js")
.SetFunctionName("gdjs.evtTools.shopify.buildClient");
GetAllActions()["Shopify::GetCheckoutUrlForProduct"].codeExtraInformation
.SetIncludeFile("Extensions/Shopify/shopify-buy.umd.polyfilled.min.js")
.AddIncludeFile("Extensions/Shopify/shopifytools.js")
.SetFunctionName("gdjs.evtTools.shopify.getCheckoutUrlForProduct");
StripUnimplementedInstructionsAndExpressions();
GD_COMPLETE_EXTENSION_COMPILATION_INFORMATION();
};
};
#if defined(EMSCRIPTEN)
extern "C" gd::PlatformExtension * CreateGDJSShopifyExtension() {
return new ShopifyJsExtension;
}
#else
/**
* Used by GDevelop to create the extension class
* -- Do not need to be modified. --
*/
extern "C" gd::PlatformExtension * GD_EXTENSION_API CreateGDJSExtension() {
return new ShopifyJsExtension;
}
#endif
#endif

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,63 @@
gdjs.ShopifyClientsManager = function() {
};
gdjs.ShopifyClientsManager.set = function(runtimeScene, name, shopifyClient) {
var game = runtimeScene.getGame();
if (!game.shopifyClients) {
game.shopifyClients = {};
}
game.shopifyClients[name] = shopifyClient;
}
gdjs.ShopifyClientsManager.get = function(runtimeScene, name) {
var game = runtimeScene.getGame();
if (!game.shopifyClients) {
game.shopifyClients = {};
}
return game.shopifyClients[name];
}
/**
* @namespace gdjs.evtTools
* @class shopify
* @static
* @private
*/
gdjs.evtTools.shopify = {};
gdjs.evtTools.shopify.buildClient = function(runtimeScene, name, domain, appId, accessToken) {
if (typeof ShopifyBuy === 'undefined') return;
var config = new ShopifyBuy.Config({
accessToken: accessToken,
domain: domain,
appId: appId
});
var shopifyClient = ShopifyBuy.buildClient(config);
gdjs.ShopifyClientsManager.set(runtimeScene, name, shopifyClient);
};
gdjs.evtTools.shopify.getCheckoutUrlForProduct = function(runtimeScene, name,
productId, quantity, variantIndex, successVariable, errorVariable) {
errorVariable.setString("");
successVariable.setString("");
var shopifyClient = gdjs.ShopifyClientsManager.get(runtimeScene, name);
shopifyClient.fetchProduct(productId)
.then(function (product) {
if (variantIndex < 0 || variantIndex >= product.variants.length) {
errorVariable.setString("The product has no variant.");
return;
}
var variant = product.variants[variantIndex];
var checkoutURL = variant.checkoutUrl(quantity);
successVariable.setString(checkoutURL);
}, function (error) {
errorVariable.setString("Unable to get the product that was requested.");
});
};

View File

@@ -29,13 +29,21 @@ gdjs.TextRuntimeObjectPixiRenderer.prototype.ensureUpToDate = function() {
gdjs.TextRuntimeObjectPixiRenderer.prototype.updateStyle = function() {
var fontName = "\"gdjs_font_" + this._object._fontName + "\"";
var style = { align:"left" };
style.font = "";
if ( this._object._italic ) style.font += "italic ";
if ( this._object._bold ) style.font += "bold ";
style.font += this._object._characterSize + "px " + fontName;
style.fill = "rgb("+this._object._color[0]+","+this._object._color[1]+","+this._object._color[2]+")";
this._text.style = style;
var style = this._text.style;
style.fontStyle = this._object._italic ? 'italic' : 'normal';
style.fontWeight = this._object._bold ? 'bold' : 'normal';
style.fontSize = this._object._characterSize;
style.fontFamily = fontName;
style.fill = gdjs.rgbToHexNumber(
this._object._color[0],
this._object._color[1],
this._object._color[2]
);
// Manually ask the PIXI object to re-render as we changed a style property
// see http://www.html5gamedevs.com/topic/16924-change-text-style-post-render/
this._text.dirty = true;
};
gdjs.TextRuntimeObjectPixiRenderer.prototype.updatePosition = function() {

View File

@@ -346,7 +346,7 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(const gd::String & parame
gd::VariableParser parser(parameter);
if ( !parser.Parse(callbacks) )
{
cout << "Error :" << parser.firstErrorStr << " in: "<< parameter << endl;
cout << "Error :" << parser.GetFirstError() << " in: "<< parameter << endl;
argOutput = "runtimeContext->GetSceneVariables().GetBadVariable()";
}
}
@@ -357,7 +357,7 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(const gd::String & parame
gd::VariableParser parser(parameter);
if ( !parser.Parse(callbacks) )
{
cout << "Error :" << parser.firstErrorStr << " in: "<< parameter << endl;
cout << "Error :" << parser.GetFirstError() << " in: "<< parameter << endl;
argOutput = "runtimeContext->GetGameVariables().GetBadVariable()";
}
}
@@ -373,7 +373,7 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(const gd::String & parame
gd::VariableParser parser(parameter);
if ( !parser.Parse(callbacks) )
{
cout << "Error :" << parser.firstErrorStr << " in: "<< parameter << endl;
cout << "Error :" << parser.GetFirstError() << " in: "<< parameter << endl;
argOutput = "runtimeContext->GetGameVariables().GetBadVariable()";
}
}

View File

@@ -109,7 +109,7 @@ void VariableCodeGenerationCallbacks::OnChildSubscript(gd::String stringExpressi
if ( !parser.ParseStringExpression(codeGenerator.GetPlatform(),
codeGenerator.GetProject(), codeGenerator.GetLayout(), callbacks) )
{
cout << "Error in text expression" << parser.firstErrorStr << endl;
cout << "Error in text expression" << parser.GetFirstError() << endl;
argumentCode = "\"\"";
}

View File

@@ -69,6 +69,7 @@ BaseObjectExtension::BaseObjectExtension()
objectActions["Rebondir"].SetFunctionName("SeparateObjectsWithForces").SetIncludeFile("GDCpp/Extensions/Builtin/ObjectTools.h");
objectActions["Ecarter"].SetFunctionName("SeparateObjectsWithoutForces").SetIncludeFile("GDCpp/Extensions/Builtin/ObjectTools.h");
objectActions["SeparateFromObjects"].SetFunctionName("SeparateFromObjects").SetIncludeFile("GDCpp/Extensions/Builtin/ObjectTools.h");
objectConditions["CollisionPoint"].SetFunctionName("IsCollidingWithPoint");
objectExpressions["X"].SetFunctionName("GetX");

View File

@@ -128,3 +128,19 @@ CollisionResult GD_API PolygonCollisionTest(Polygon2d & p1, Polygon2d & p2)
return result;
}
bool GD_API IsPointInsidePolygon(Polygon2d & poly, float x, float y)
{
bool inside = false;
sf::Vector2f vi, vj;
for (std::size_t i = 0, j = poly.vertices.size()-1; i < poly.vertices.size(); j = i++)
{
vi = poly.vertices[i];
vj = poly.vertices[j];
if ( ((vi.y>y) != (vj.y>y)) && (x < (vj.x-vi.x) * (y-vi.y) / (vj.y-vi.y) + vi.x) )
inside = !inside;
}
return inside;
}

View File

@@ -33,5 +33,16 @@ struct CollisionResult
*/
CollisionResult GD_API PolygonCollisionTest(Polygon2d & p1, Polygon2d & p2);
/**
* Check if a point is inside a polygon.
*
* Uses PNPOLY by W. Randolph Franklin (https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html)
*
* \return true if the point is inside the polygon
*
* \ingroup GameEngine
*/
bool GD_API IsPointInsidePolygon(Polygon2d & poly, float x, float y);
#endif // POLYGONCOLLISION_H

View File

@@ -379,6 +379,17 @@ bool RuntimeObject::IsCollidingWith(RuntimeObject * obj2)
return false;
}
bool RuntimeObject::IsCollidingWithPoint(float pointX, float pointY){
vector<Polygon2d> hitBoxes = GetHitBoxes();
for (std::size_t i = 0; i < hitBoxes.size(); ++i)
{
if ( IsPointInsidePolygon(hitBoxes[i], pointX, pointY) )
return true;
}
return false;
}
void RuntimeObject::SeparateObjectsWithoutForces( std::map <gd::String, std::vector<RuntimeObject*> *> pickedObjectLists)
{
vector<RuntimeObject*> objects2;

View File

@@ -223,6 +223,14 @@ public:
*/
bool IsCollidingWith(RuntimeObject * other);
/**
* \brief Check if a point is inside the object collision hitboxes.
* \param pointX The point x coordinate.
* \param pointY The point y coordinate.
* \return true if the point is inside the object collision hitboxes.
*/
bool IsCollidingWithPoint(float pointX, float pointY);
/**
* \brief Check collision with each object of the list using their hitboxes, and move the object
* according to the sum of the move vector returned by each collision test.

View File

@@ -103,7 +103,7 @@ ELSE()
ELSE()
add_custom_target(GDJS_Runtime
ALL
COMMAND sh "CopyRuntimeToGD.sh" ${GD_base_dir}/Binaries/Output/${CMAKE_BUILD_TYPE}_${CMAKE_SYSTEM_NAME}/JsPlatform/Runtime
COMMAND bash "CopyRuntimeToGD.sh" ${GD_base_dir}/Binaries/Output/${CMAKE_BUILD_TYPE}_${CMAKE_SYSTEM_NAME}/JsPlatform/Runtime
WORKING_DIRECTORY ${GD_base_dir}/GDJS/scripts)
ENDIF()
ENDIF()

View File

@@ -539,7 +539,7 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(const gd::String & parame
gd::VariableParser parser(parameter);
if ( !parser.Parse(callbacks) )
{
cout << "Error :" << parser.firstErrorStr << " in: "<< parameter << endl;
cout << "Error :" << parser.GetFirstError() << " in: "<< parameter << endl;
argOutput = "gdjs.VariablesContainer.badVariable";
}
}
@@ -550,7 +550,7 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(const gd::String & parame
gd::VariableParser parser(parameter);
if ( !parser.Parse(callbacks) )
{
cout << "Error :" << parser.firstErrorStr << " in: "<< parameter << endl;
cout << "Error :" << parser.GetFirstError() << " in: "<< parameter << endl;
argOutput = "gdjs.VariablesContainer.badVariable";
}
}
@@ -566,7 +566,7 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(const gd::String & parame
gd::VariableParser parser(parameter);
if ( !parser.Parse(callbacks) )
{
cout << "Error :" << parser.firstErrorStr << " in: "<< parameter << endl;
cout << "Error :" << parser.GetFirstError() << " in: "<< parameter << endl;
argOutput = "gdjs.VariablesContainer.badVariable";
}
}

View File

@@ -110,7 +110,7 @@ void VariableCodeGenerationCallbacks::OnChildSubscript(gd::String stringExpressi
if ( !parser.ParseStringExpression(codeGenerator.GetPlatform(),
codeGenerator.GetProject(), codeGenerator.GetLayout(), callbacks) )
{
cout << "Error in text expression" << parser.firstErrorStr << endl;
cout << "Error in text expression" << parser.GetFirstError() << endl;
argumentCode = "\"\"";
}

View File

@@ -71,6 +71,7 @@ BaseObjectExtension::BaseObjectExtension()
objectConditions["ObjectVariableChildExists"].SetFunctionName("variableChildExists").SetIncludeFile("runtimeobject.js");
objectActions["ObjectVariableRemoveChild"].SetFunctionName("variableRemoveChild").SetIncludeFile("runtimeobject.js");
objectActions["ObjectVariableClearChildren"].SetFunctionName("variableClearChildren").SetIncludeFile("runtimeobject.js");
objectConditions["CollisionPoint"].SetFunctionName("isCollidingWithPoint").SetIncludeFile("runtimeobject.js");
objectExpressions["X"].SetFunctionName("getX");
objectExpressions["Y"].SetFunctionName("getY");

View File

@@ -136,6 +136,7 @@ gd::PlatformExtension * CreateGDJSTextEntryObjectExtension();
gd::PlatformExtension * CreateGDJSInventoryExtension();
gd::PlatformExtension * CreateGDJSLinkedObjectsExtension();
gd::PlatformExtension * CreateGDJSSystemInfoExtension();
gd::PlatformExtension * CreateGDJSShopifyExtension();
}
#endif
@@ -181,6 +182,7 @@ JsPlatform::JsPlatform() :
AddExtension(std::shared_ptr<gd::PlatformExtension>(CreateGDJSInventoryExtension())); std::cout.flush();
AddExtension(std::shared_ptr<gd::PlatformExtension>(CreateGDJSLinkedObjectsExtension())); std::cout.flush();
AddExtension(std::shared_ptr<gd::PlatformExtension>(CreateGDJSSystemInfoExtension())); std::cout.flush();
AddExtension(std::shared_ptr<gd::PlatformExtension>(CreateGDJSShopifyExtension())); std::cout.flush();
#endif
std::cout << "done." << std::endl;
};

View File

@@ -109,7 +109,6 @@ bool Exporter::ExportWholePixiProject(gd::Project & project, gd::String exportDi
//Prepare the export directory
fs.MkDir(exportDir);
fs.ClearDir(exportDir);
std::vector<gd::String> includesFiles;
gd::Project exportedProject = project;

View File

@@ -1,3 +1,24 @@
// "Polyfill" self which is not defined in Cocos2d-JS (v3.10)
// and used by some library like Shopify SDK.
if (typeof self === 'undefined') {
console.log("Add polyfill for 'self'");
self = window;
}
// Patch XMLHttpRequest.send as Cocos2d-JS v3.15 and below are not supporting
// passing null or undefined as parameter, even if it is compliant to do so.
if (typeof XMLHttpRequest !== 'undefined') {
console.log("Patching XMLHttpRequest for Cocos2d-JS v3.15 and below");
var originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(body) {
console.log("(Using patched XMLHttpRequest.send)");
if (body === null || body === undefined) {
return originalSend.call(this);
}
originalSend.apply(this, arguments);
}
}
cc.game.onStart = function(){
if(!cc.sys.isNative && document.getElementById("cocosLoading")) //If referenced loading.js, please remove it
document.body.removeChild(document.getElementById("cocosLoading"));

View File

@@ -100,6 +100,17 @@ gdjs.evtTools.input.keysNameToCode = {
"x": 88,
"y": 89,
"z": 90,
"Num0": 48,
"Num1": 49,
"Num2": 50,
"Num3": 51,
"Num4": 52,
"Num5": 53,
"Num6": 54,
"Num7": 55,
"Num8": 56,
"Num9": 57,
"Numpad0": 96,
"Numpad1": 97,

View File

@@ -17,7 +17,7 @@ var gdjs = gdjs || {
};
/**
* Convert a rgb color value to a hex value.
* Convert a rgb color value to a hex string.
* @note No "#" or "0x" are added.
* @static
*/
@@ -25,6 +25,14 @@ gdjs.rgbToHex = function(r, g, b) {
return "" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
};
/**
* Convert a rgb color value to a hex value.
* @static
*/
gdjs.rgbToHexNumber = function(r, g, b) {
return (r << 16) + (g << 8) + b;
}
/**
* Get a random integer between 0 and max.
* @method random

View File

@@ -75,6 +75,8 @@ gdjs.HowlerSoundManager = function(resources)
this._resources = resources;
this._availableResources = {}; //Map storing "audio" resources for faster access.
this._globalVolume = 100;
this._sounds = {};
this._musics = {};
this._freeSounds = []; //Sounds without an assigned channel.
@@ -121,7 +123,7 @@ gdjs.HowlerSoundManager = function(resources)
sound.play();
}
}
that._pausedSounds = [];
that._pausedSounds.length = 0;
that._paused = false;
}, false);
});
@@ -203,7 +205,7 @@ gdjs.HowlerSoundManager.prototype.playSound = function(soundName, loop, volume,
gdjs.HowlerSoundManager.prototype.playSoundOnChannel = function(soundName, channel, loop, volume, pitch) {
var oldSound = this._sounds[channel];
if (oldSound) {
oldSound.stop();
oldSound.unload();
}
var soundFile = this._getFileFromSoundName(soundName);
@@ -244,7 +246,7 @@ gdjs.HowlerSoundManager.prototype.playMusic = function(soundName, loop, volume,
gdjs.HowlerSoundManager.prototype.playMusicOnChannel = function(soundName, channel, loop, volume, pitch) {
var oldMusic = this._musics[channel];
if (oldMusic) {
oldMusic.stop();
oldMusic.unload();
}
var soundFile = this._getFileFromSoundName(soundName);
@@ -268,41 +270,43 @@ gdjs.HowlerSoundManager.prototype.getMusicOnChannel = function(channel) {
};
gdjs.HowlerSoundManager.prototype.setGlobalVolume = function(volume) {
Howler.volume(volume/100);
this._globalVolume = volume;
if (this._globalVolume > 100) this._globalVolume = 100;
if (this._globalVolume < 0) this._globalVolume = 0;
Howler.volume(this._globalVolume/100);
};
gdjs.HowlerSoundManager.prototype.getGlobalVolume = function() {
return Howler.volume()*100;
return this._globalVolume;
};
gdjs.HowlerSoundManager.prototype.clearAll = function() {
for (var i = 0;i<this._freeSounds.length;++i) {
if (this._freeSounds[i]) this._freeSounds[i].stop();
if (this._freeSounds[i]) this._freeSounds[i].unload();
}
for (var i = 0;i<this._freeMusics.length;++i) {
if (this._freeMusics[i]) this._freeMusics[i].stop();
if (this._freeMusics[i]) this._freeMusics[i].unload();
}
this._freeSounds.length = 0;
this._freeMusics.length = 0;
for (var p in this._sounds) {
if (this._sounds.hasOwnProperty(p) && this._sounds[p]) {
this._sounds[p].stop();
this._sounds[p].unload();
delete this._sounds[p];
}
}
for (var p in this._musics) {
if (this._musics.hasOwnProperty(p) && this._musics[p]) {
this._musics[p].stop();
this._musics[p].unload();
delete this._musics[p];
}
}
this._pausedSounds = [];
this._pausedSounds.length = 0;
}
gdjs.HowlerSoundManager.prototype.preloadAudio = function(onProgress, onComplete, resources) {
resources = resources || this._resources;
var files = [];
for(var i = 0, len = resources.length;i<len;++i) {
var res = resources[i];
@@ -319,10 +323,8 @@ gdjs.HowlerSoundManager.prototype.preloadAudio = function(onProgress, onComplete
var loaded = 0;
function onLoad(audioFile) {
console.log("loaded" + audioFile);
loaded++;
if (loaded === files.length) {
console.log("All audio loaded");
return onComplete();
}
@@ -332,13 +334,12 @@ gdjs.HowlerSoundManager.prototype.preloadAudio = function(onProgress, onComplete
var that = this;
for(var i = 0;i<files.length;++i) {
(function(audioFile) {
console.log("Loading" + audioFile)
var sound = new Howl({
src: [audioFile], //TODO: ogg, mp3...
preload: true,
onload: onLoad.bind(that, audioFile),
onloaderror: onLoad.bind(that, audioFile)
});
var sound = new XMLHttpRequest();
sound.addEventListener('load', onLoad.bind(that, audioFile));
sound.addEventListener('error', onLoad.bind(that, audioFile));
sound.addEventListener('abort', onLoad.bind(that, audioFile));
sound.open('GET', audioFile);
sound.send();
})(files[i]);
}
}

File diff suppressed because one or more lines are too long

View File

@@ -22,13 +22,13 @@ gdjs.NightPixiFilter = function() {
opacity: { type: '1f', value: 1 }
};
PIXI.AbstractFilter.call(this,
PIXI.Filter.call(this,
vertexShader,
fragmentShader,
uniforms
);
}
gdjs.NightPixiFilter.prototype = Object.create(PIXI.AbstractFilter.prototype);
gdjs.NightPixiFilter.prototype = Object.create(PIXI.Filter.prototype);
gdjs.NightPixiFilter.prototype.constructor = gdjs.NightPixiFilter;
gdjs.LightNightPixiFilter = function() {
@@ -51,13 +51,13 @@ gdjs.LightNightPixiFilter = function() {
opacity: { type: '1f', value: 1 }
};
PIXI.AbstractFilter.call(this,
PIXI.Filter.call(this,
vertexShader,
fragmentShader,
uniforms
);
}
gdjs.LightNightPixiFilter.prototype = Object.create(PIXI.AbstractFilter.prototype);
gdjs.LightNightPixiFilter.prototype = Object.create(PIXI.Filter.prototype);
gdjs.LightNightPixiFilter.prototype.constructor = gdjs.LightNightPixiFilter;
gdjs.PixiFiltersTools._filters = {
@@ -70,7 +70,7 @@ gdjs.PixiFiltersTools._filters = {
if (parameterName !== 'intensity' &&
parameterName !== 'opacity') return;
filter.uniforms[parameterName].value = value;
filter.uniforms[parameterName] = value;
},
},
LightNight: {
@@ -81,7 +81,7 @@ gdjs.PixiFiltersTools._filters = {
updateParameter: function(filter, parameterName, value) {
if (parameterName !== 'opacity') return;
filter.uniforms.opacity.value = value;
filter.uniforms.opacity = value;
},
},
Sepia: {

File diff suppressed because one or more lines are too long

View File

@@ -68,13 +68,13 @@ gdjs.Polygon.prototype.computeEdges = function() {
else v2 = this.vertices[i + 1];
this.edges[i][0] = v2[0] - v1[0];
this.edges[i][1] = v2[1] - v1[1];
this.edges[i][1] = v2[1] - v1[1];
}
};
gdjs.Polygon.prototype.isConvex = function() {
this.computeEdges();
edgesLen = this.edges.length;
var edgesLen = this.edges.length;
if ( edgesLen < 3 ) {
return false;
@@ -87,7 +87,7 @@ gdjs.Polygon.prototype.isConvex = function() {
if ( (zCrossProduct > 0) !== zProductIsPositive ) return false;
}
var lastZCrossProduct = this.edges[edgesLen-1][0]*this.edges[0][1] - this.edges[edgesLen][1]*this.edges[0][0];
var lastZCrossProduct = this.edges[edgesLen-1][0]*this.edges[0][1] - this.edges[edgesLen-1][1]*this.edges[0][0];
if ( (lastZCrossProduct > 0) !== zProductIsPositive ) return false;
return true;
@@ -258,3 +258,29 @@ gdjs.Polygon.distance = function(minA, maxA, minB, maxB)
if (minA < minB) return minB - maxA;
else return minA - maxB;
}
/**
* Check if a point is inside a polygon.
*
* Uses <a href="https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html">PNPOLY</a> by W. Randolph Franklin.
*
* @method isPointInside
* @static
* @param poly {Polygon} The polygon to test
* @param x {Number} The point x coordinate
* @param y {Number} The point y coordinate
* @return {Boolean} true if the point is inside the polygon
*/
gdjs.Polygon.isPointInside = function(poly, x, y)
{
var inside = false;
var vi, vj;
for (var i = 0, j = poly.vertices.length-1; i < poly.vertices.length; j = i++) {
vi = poly.vertices[i];
vj = poly.vertices[j];
if ( ((vi[1]>y) != (vj[1]>y)) && (x < (vj[0]-vi[0]) * (y-vi[1]) / (vj[1]-vi[1]) + vi[0]) )
inside = !inside;
}
return inside;
};

View File

@@ -1202,6 +1202,23 @@ gdjs.RuntimeObject.prototype.cursorOnObject = function(runtimeScene) {
return false;
};
/**
* \brief Check if a point is inside the object collision hitboxes.
* @method isCollidingWithPoint
* @param pointX The point x coordinate.
* @param pointY The point y coordinate.
* @return true if the point is inside the object collision hitboxes.
*/
gdjs.RuntimeObject.prototype.isCollidingWithPoint = function(pointX, pointY) {
var hitBoxes = this.getHitBoxes();
for(var i = 0; i < this.hitBoxes.length; ++i) {
if ( gdjs.Polygon.isPointInside(hitBoxes[i], pointX, pointY) )
return true;
}
return false;
}
/**
* Get the identifier associated to an object name :<br>

View File

@@ -146,6 +146,28 @@ gdjs.RuntimeScene.prototype.unloadScene = function() {
for(var i = 0;i < gdjs.callbacksRuntimeSceneUnloaded.length;++i) {
gdjs.callbacksRuntimeSceneUnloaded[i](this);
}
// It should not be necessary to reset these variables, but this help
// ensuring that all memory related to the RuntimeScene is released immediately.
this._layers = new Hashtable();
this._variables = new gdjs.VariablesContainer();
this._initialBehaviorSharedData = new Hashtable();
this._objects = new Hashtable();
this._instances = new Hashtable();
this._instancesCache = new Hashtable();
this._initialObjectsData = null;
this._eventsFunction = null;
this._objectsCtor = new Hashtable();
this._allInstancesList = [];
this._instancesRemoved = [];
this._renderer = new gdjs.RuntimeSceneRenderer(this, this._runtimeGame ? this._runtimeGame.getRenderer() : null);
this._lastId = 0;
this._eventsContext = null;
this._isLoaded = false;
this.onCanvasResized();
};
/**

View File

@@ -1,3 +1,4 @@
#!/bin/bash
#Get the destination, or copy by default to release directory
DESTINATION=../../Binaries/Output/Release_Linux/JsPlatform/Runtime/
if [ "$(uname)" == "Darwin" ]; then
@@ -14,4 +15,4 @@ mkdir -p "$DESTINATION"
cp -R ../Runtime/* "$DESTINATION"
rsync -r -u --include=*.js --include=*/ --exclude=* ../../Extensions/ "$DESTINATION"/Extensions/
echo "✅ Copied GDJS and extensions runtime files (*.js) to '$DESTINATION'."
echo "✅ Copied GDJS and extensions runtime files (*.js) to '$DESTINATION'."

View File

@@ -1,114 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<Project>
<GDVersion Major="3" Minor="0" Build="11297" Revision="57008" />
<Info winExecutableFilename="" winExecutableIconFile="" linuxExecutableFilename="" macExecutableFilename="" useExternalSourceFiles="false">
<Nom value="Projet" />
<Auteur value="" />
<Extensions>
<Extension name="BuiltinObject" />
<Extension name="BuiltinAudio" />
<Extension name="BuiltinVariables" />
<Extension name="BuiltinTime" />
<Extension name="BuiltinMouse" />
<Extension name="BuiltinKeyboard" />
<Extension name="BuiltinJoystick" />
<Extension name="BuiltinCamera" />
<Extension name="BuiltinWindow" />
<Extension name="BuiltinFile" />
<Extension name="BuiltinNetwork" />
<Extension name="BuiltinScene" />
<Extension name="BuiltinAdvanced" />
<Extension name="Sprite" />
<Extension name="BuiltinCommonInstructions" />
<Extension name="BuiltinCommonConversions" />
<Extension name="BuiltinStringInstructions" />
<Extension name="BuiltinMathematicalTools" />
<Extension name="BuiltinExternalLayouts" />
<Extension name="TextObject" />
</Extensions>
<Platforms current="GDevelop JS platform">
<Platform name="GDevelop JS platform" />
<Platform name="GDevelop C++ platform" />
</Platforms>
<WindowW value="800" />
<WindowH value="600" />
<Portable />
<LatestCompilationDirectory value="" />
<FPSmax value="60" />
<FPSmin value="10" />
<verticalSync value="false" />
</Info>
<Resources>
<Resources />
<ResourceFolders />
</Resources>
<Objects />
<ObjectGroups />
<Variables />
<Scenes firstScene="">
<Scene nom="Nouvelle sc<73>ne" mangledName="Nouvelle_32sc__4524ne" r="209.000000" v="209.000000" b="209.000000" titre="" oglFOV="90.000000" oglZNear="1.000000" oglZFar="500.000000" standardSortMethod="true" stopSoundsOnStartup="true" disableInputWhenNotFocused="true">
<UISettings gridWidth="32.000000" grid="false" snap="true" gridHeight="32.000000" gridR="158.000000" gridG="180.000000" gridB="255.000000" zoomFactor="1.000000" windowMask="true" associatedLayout="" />
<GroupesObjets />
<Objets>
<Objet nom="NouvelObjet" type="TextObject::Text" smoothed="true" bold="false" italic="false" underlined="false">
<Variables />
<String value="Heeello" />
<Font value="BOD_CB.TTF" />
<CharacterSize value="50" />
<Color r="255" g="128" b="0" />
</Objet>
</Objets>
<Layers>
<Layer Name="" Visibility="true">
<Camera DefaultSize="true" Width="0.000000" Height="0.000000" DefaultViewport="true" ViewportLeft="0.000000" ViewportTop="0.000000" ViewportRight="1.000000" ViewportBottom="1.000000" />
</Layer>
</Layers>
<Variables />
<BehaviorsSharedDatas />
<Positions>
<Objet nom="NouvelObjet" x="297.000000" y="245.000000" plan="1" layer="" angle="0.000000" personalizedSize="false" width="0.000000" height="0.000000" locked="false">
<floatInfos />
<stringInfos />
<InitialVariables />
</Objet>
</Positions>
<Events>
<Event disabled="false" folded="false">
<Type value="BuiltinCommonInstructions::Standard" />
<Conditions />
<Actions>
<Action>
<Type value="MettreXY" />
<Parametre value="NouvelObjet" />
<Parametre value="=" />
<Parametre value="300" />
<Parametre value="=" />
<Parametre value="300" />
</Action>
<Action>
<Type value="TextObject::Angle" />
<Parametre value="NouvelObjet" />
<Parametre value="+" />
<Parametre value="1" />
</Action>
<Action>
<Type value="TextObject::Opacity" />
<Parametre value="NouvelObjet" />
<Parametre value="=" />
<Parametre value="128+cos(TimeFromStart())*128" />
</Action>
<Action>
<Type value="TextObject::Size" />
<Parametre value="NouvelObjet" />
<Parametre value="=" />
<Parametre value="30+cos(TimeFromStart())*30" />
</Action>
</Actions>
</Event>
</Events>
</Scene>
</Scenes>
<ExternalEvents />
<ExternalLayouts />
<ExternalSourceFiles />
</Project>

View File

@@ -0,0 +1 @@
{"firstLayout": "","gdVersion": {"build": 96,"major": 4,"minor": 0,"revision": 89},"properties": {"folderProject": false,"linuxExecutableFilename": "","macExecutableFilename": "","packageName": "","projectFile": "/Users/florian/Projects/F/GD/GDJS/tests/games/Text.json","useExternalSourceFiles": false,"winExecutableFilename": "","winExecutableIconFile": "","name": "Projet","author": "","windowWidth": 800,"windowHeight": 600,"latestCompilationDirectory": "","maxFPS": 60,"minFPS": 10,"verticalSync": false,"extensions": [{"name": "BuiltinObject"},{"name": "BuiltinAudio"},{"name": "BuiltinVariables"},{"name": "BuiltinTime"},{"name": "BuiltinMouse"},{"name": "BuiltinKeyboard"},{"name": "BuiltinJoystick"},{"name": "BuiltinCamera"},{"name": "BuiltinWindow"},{"name": "BuiltinFile"},{"name": "BuiltinNetwork"},{"name": "BuiltinScene"},{"name": "BuiltinAdvanced"},{"name": "Sprite"},{"name": "BuiltinCommonInstructions"},{"name": "BuiltinCommonConversions"},{"name": "BuiltinStringInstructions"},{"name": "BuiltinMathematicalTools"},{"name": "BuiltinExternalLayouts"},{"name": "TextObject"}],"platforms": [{"name": "GDevelop JS platform"},{"name": "GDevelop C++ platform"}],"currentPlatform": "GDevelop C++ platform"},"resources": {"resources": [],"resourceFolders": []},"objects": [],"objectsGroups": [],"variables": [],"layouts": [{"b": 209,"disableInputWhenNotFocused": true,"mangledName": "Nouvelle_32sc_232ne","name": "Nouvelle scène","oglFOV": 90,"oglZFar": 500,"oglZNear": 1,"r": 209,"standardSortMethod": true,"stopSoundsOnStartup": true,"title": "","v": 209,"uiSettings": {"grid": false,"gridB": 255,"gridG": 180,"gridHeight": 32,"gridOffsetX": 0,"gridOffsetY": 0,"gridR": 158,"gridWidth": 32,"snap": true,"windowMask": true,"zoomFactor": 1},"objectsGroups": [],"variables": [],"instances": [{"angle": 0,"customSize": false,"height": 0,"layer": "","locked": false,"name": "NouvelObjet","width": 0,"x": 297,"y": 245,"zOrder": 1,"numberProperties": [],"stringProperties": [],"initialVariables": []}],"objects": [{"bold": false,"italic": false,"name": "NouvelObjet","smoothed": true,"type": "TextObject::Text","underlined": false,"variables": [],"behaviors": [],"string": "Heeello","font": "BOD_CB.TTF","characterSize": 50,"color": {"b": 0,"g": 128,"r": 255}}],"events": [{"disabled": false,"folded": false,"type": "BuiltinCommonInstructions::Standard","conditions": [],"actions": [{"type": {"inverted": false,"value": "MettreXY"},"parameters": ["NouvelObjet","=","300","=","300"],"subInstructions": []},{"type": {"inverted": false,"value": "TextObject::Angle"},"parameters": ["NouvelObjet","+","1"],"subInstructions": []},{"type": {"inverted": false,"value": "TextObject::Opacity"},"parameters": ["NouvelObjet","=","128+cos(TimeFromStart())*128"],"subInstructions": []},{"type": {"inverted": false,"value": "TextObject::Size"},"parameters": ["NouvelObjet","=","30+cos(TimeFromStart())*30"],"subInstructions": []}],"events": []}],"layers": [{"name": "","visibility": true,"cameras": [{"defaultSize": true,"defaultViewport": true,"height": 0,"viewportBottom": 1,"viewportLeft": 0,"viewportRight": 1,"viewportTop": 0,"width": 0}],"effects": []}],"behaviorsSharedData": []}],"externalEvents": [],"externalLayouts": [],"externalSourceFiles": []}

View File

@@ -531,7 +531,7 @@ void InstructionSelectorDialog::OnOkBtClick(wxCommandEvent& event)
||(instructionMetadata.parameters[i].type == "layer" && !expressionParser.ParseStringExpression(game.GetCurrentPlatform(), game, scene, callbacks))
||(instructionMetadata.parameters[i].type == "expression" && !expressionParser.ParseMathExpression(game.GetCurrentPlatform(), game, scene, callbacks)))
{
message = expressionParser.firstErrorStr;
message = expressionParser.GetFirstError();
parametersHaveErrors = true;
ParaEdit[i]->SetBackgroundColour(wxColour(255, 194, 191));

View File

@@ -4,7 +4,7 @@ GDevelop is a full featured, open source game development software, allowing to
![GDevelop in action, used to add a trigger in a platformer game](https://raw.githubusercontent.com/4ian/GD/master/Core/docs/images/demo.gif "GDevelop in action, used to add a trigger in a platformer game")
Getting started [![Build Status](https://travis-ci.org/4ian/GD.svg?branch=master)](https://travis-ci.org/4ian/GD) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/4ian/GD?branch=master&svg=true)](https://ci.appveyor.com/project/4ian/gd)
Getting started [![Build Status](https://semaphoreci.com/api/v1/4ian/gd/branches/master/badge.svg)](https://semaphoreci.com/4ian/gd) [![Build Status](https://travis-ci.org/4ian/GD.svg?branch=master)](https://travis-ci.org/4ian/GD) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/4ian/GD?branch=master&svg=true)](https://ci.appveyor.com/project/4ian/gd)
---------------
| ❔ I want to... | 🚀 What to do |
@@ -49,7 +49,6 @@ Links
### Related projects and games
* [GDevelop.js](https://github.com/4ian/GDevelop.js) is a binding to use GDevelop engine in Javascript. Used for newIDE.
* [GDevApp.com](https://gdevapp.com) is a radically innovative online game creator, compatible with GDevelop. It is based on GDevelop.js and can be used on any browser, including iOS and Android.
* [Lil BUB's HELLO EARTH](http://lilbub.com/game) is a retro 8-bit mobile video game featuring [Lil BUB](http://lilbub.com). It's created with GDevelop and made up of equal parts science, magic, and heart.
![Lil Bub](http://compilgames.net/assets/bub/screenshots-background.jpg "GDevelop logo")

View File

@@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 2.6)
cmake_policy(SET CMP0015 NEW)
project(GDVersion)
set(GD_VERSION_STR "4.0.96")
if(FULL_VERSION_NUMBER)
set(GENERATE_VERSION_SCRIPT ${PROJECT_SOURCE_DIR}/GenerateVersionFull.cmake)
@@ -11,5 +12,5 @@ endif()
add_custom_target(GDVersion
ALL
COMMAND ${CMAKE_COMMAND} -P ${GENERATE_VERSION_SCRIPT} ${PROJECT_SOURCE_DIR}/../Core/GDCore/Tools/
COMMAND ${CMAKE_COMMAND} -P ${GENERATE_VERSION_SCRIPT} ${PROJECT_SOURCE_DIR}/../Core/GDCore/Tools/ ${GD_VERSION_STR}
)

View File

@@ -1,13 +1,19 @@
find_package(Git)
if(GIT_FOUND)
EXECUTE_PROCESS(
COMMAND ${GIT_EXECUTABLE} describe --tags
OUTPUT_VARIABLE GD_VERSION_STR
RESULT_VARIABLE GIT_DESCRIBE_RESULT
ERROR_VARIABLE GIT_DESCRIBE_ERROR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Retrieving GDevelop version from Git tags is disabled as GDevelop 5
# is being developed and we still want the old IDE version to stay at 4.
# The old IDE has the same version number as GDCore/libGD.js
# Hence, version of GDevelop 4 is manually specified in CMakeLists.txt
# EXECUTE_PROCESS(
# COMMAND ${GIT_EXECUTABLE} describe --tags
# OUTPUT_VARIABLE GD_VERSION_STR
# RESULT_VARIABLE GIT_DESCRIBE_RESULT
# ERROR_VARIABLE GIT_DESCRIBE_ERROR
# OUTPUT_STRIP_TRAILING_WHITESPACE
# )
set(GD_VERSION_STR ${CMAKE_ARGV4})
set(VERSIONPRIV_PATH "${CMAKE_ARGV3}/VersionPriv.h")
set(ORIGINAL_CONTENT " ")

View File

@@ -3,13 +3,19 @@ find_package(Git)
message(WARNING "You're not using the full version number. It's not suitable for public releases and builds!")
if(GIT_FOUND)
EXECUTE_PROCESS(
COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=0 # Only get the lastest tag's name
OUTPUT_VARIABLE GD_VERSION_STR
RESULT_VARIABLE GIT_DESCRIBE_RESULT
ERROR_VARIABLE GIT_DESCRIBE_ERROR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Retrieving GDevelop version from Git tags is disabled as GDevelop 5
# is being developed and we still want the old IDE version to stay at 4.
# The old IDE has the same version number as GDCore/libGD.js
# Hence, version of GDevelop 4 is manually specified in CMakeLists.txt
# EXECUTE_PROCESS(
# COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=0 # Only get the lastest tag's name
# OUTPUT_VARIABLE GD_VERSION_STR
# RESULT_VARIABLE GIT_DESCRIBE_RESULT
# ERROR_VARIABLE GIT_DESCRIBE_ERROR
# OUTPUT_STRIP_TRAILING_WHITESPACE
# )
set(GD_VERSION_STR ${CMAKE_ARGV4})
set(VERSIONPRIV_PATH "${CMAKE_ARGV3}/VersionPriv.h")
set(ORIGINAL_CONTENT " ")

View File

@@ -61,18 +61,29 @@ cd newIDE/app
yarn test #or npm run test
```
### Theming
It's possible to create new themes for the UI. See [this file](https://github.com/4ian/GD/blob/master/newIDE/app/src/UI/Theme/index.js) to declare a new theme. You can take a look at the [default theme](https://github.com/4ian/GD/blob/master/newIDE/app/src/UI/Theme/DefaultTheme/index.js), including the [styling of the Events Sheets](https://github.com/4ian/GD/blob/master/newIDE/app/src/UI/Theme/DefaultTheme/EventsSheet.css).
## Building and deploying the standalone app
### Desktop version
First, update version number which is read in `newIDE/electron-app/app/package.json`.
```bash
cd newIDE/electron-app
yarn build #or npm run build
```
This will build and package the Electron app for Windows, macOS and Linux (according to your OS).
The output are stored inside `newIDE/electron-app/dist` and copied to `Binaries/Output/Release_XXX`.
Version number is read from `newIDE/electron-app/app/package.json`.
This will build and package the Electron app for Windows, macOS and Linux (according to your OS). The output are stored inside `newIDE/electron-app/dist`.
To build artifacts for all platforms and publish to a draft GitHub release:
```
GH_TOKEN=xxx yarn build --mac --win --linux tar.gz --publish always
```
### Webapp version
@@ -86,10 +97,9 @@ yarn deploy #or npm run deploy
This new editor is still in development and is missing some features:
- [ ] Support for translations (See an [example of a component that can be translated](https://github.com/4ian/GD/blob/master/newIDE/app/src/MainFrame/Toolbar.js#L44))
- [ ] [Autocompletion of expressions and parameters in Events editor](https://trello.com/c/mAROBTR8/46-expression-editor-auto-complete-for-the-new-ide).
- [ ] [Points and collision mask editor](https://trello.com/c/2Kzwj61r/47-points-and-collision-masks-editors-for-sprite-objects-in-the-new-ide)
- [ ] [Collision mask editor](https://trello.com/c/2Kzwj61r/47-collision-masks-editors-for-sprite-objects-in-the-new-ide)
- [ ] Support for native games
- [ ] Export with Cocos2d-JS to Android and iOS.
- [ ] More [documentation](http://wiki.compilgames.net/doku.php/gdevelop5/start) about how to package for iOS/Android with Cordova/PhoneGap Build or Cocos2d-JS.
- [ ] Search in events
- [ ] More [examples](https://github.com/4ian/GD/blob/master/newIDE/app/src/ProjectCreation/BrowserExamples.js)
- [ ] More [tutorials](http://wiki.compilgames.net/doku.php/gdevelop5/start)

View File

@@ -1,5 +1,7 @@
[ignore]
<PROJECT_ROOT>/resources/.*
<PROJECT_ROOT>/node_modules/auth0-lock/node_modules/fbjs/flow/include/PromiseMap.js
<PROJECT_ROOT>/node_modules/protobufjs/src/bower.json
[include]

View File

@@ -5,6 +5,7 @@ node_modules
# testing
coverage
flow-coverage
# production
build
@@ -23,3 +24,4 @@ public/res
public/CppPlatform
public/JsPlatform
resources/GDJS

18
newIDE/app/flow-typed/libGD.js vendored Normal file
View File

@@ -0,0 +1,18 @@
// @flow
//TODO: These types could be generated from GDevelop.js instead of being
//manually written here.
type EmscriptenObject = Object & {
ptr: Number
};
declare type gdProject = EmscriptenObject;
declare type gdLayout = EmscriptenObject;
declare type gdExternalLayout = EmscriptenObject;
declare type gdExternalEvents = EmscriptenObject;
declare type gdSerializerElement = EmscriptenObject;
declare type gdInitialInstance = EmscriptenObject;
declare type gdBaseEvent = EmscriptenObject;
//Represents all objects that have serializeTo and unserializeFrom methods.
declare type gdSerializable = EmscriptenObject;

4207
newIDE/app/flow-typed/npm/lodash_v4.x.x.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,94 @@
// flow-typed signature: 6c6a5771bbdffe188d60637063b5f9a4
// flow-typed version: 6533cd10ce/react-i18next_v6.x.x/flow_>=v0.53.x
declare module "react-i18next" {
declare type TFunction = (key?: ?string, data?: ?Object) => string;
declare type Locales = string | Array<string>;
declare type TranslatorProps = {
t: TFunction,
i18nLoadedAt: Date,
i18n: Object
};
declare type Translator<OP, P> = (
component: React$ComponentType<P>
) => Class<React$Component<OP, *>>;
declare type TranslateOptions = $Shape<{
wait: boolean,
nsMode: "default" | "fallback",
bindi18n: false | string,
bindStore: false | string,
withRef: boolean,
translateFuncName: string,
i18n: Object
}>;
declare function translate<OP, P>(
locales?: Locales,
options?: TranslateOptions
): Translator<OP, P>;
declare type I18nProps = {
i18n?: Object,
ns?: string | Array<string>,
children: (t: TFunction, { i18n: Object, t: TFunction }) => React$Node,
initialI18nStore?: Object,
initialLanguage?: string
};
declare var I18n: React$ComponentType<I18nProps>;
declare type InterpolateProps = {
className?: string,
dangerouslySetInnerHTMLPartElement?: string,
i18n?: Object,
i18nKey?: string,
options?: Object,
parent?: string,
style?: Object,
t?: TFunction,
useDangerouslySetInnerHTML?: boolean
};
declare var Interpolate: React$ComponentType<InterpolateProps>;
declare type TransProps = {
count?: number,
parent?: string,
i18n?: Object,
i18nKey?: string,
t?: TFunction
};
declare var Trans: React$ComponentType<TransProps>;
declare type ProviderProps = { i18n: Object, children: React$Element<*> };
declare var I18nextProvider: React$ComponentType<ProviderProps>;
declare type NamespacesProps = {
components: Array<React$ComponentType<*>>,
i18n: { loadNamespaces: Function }
};
declare function loadNamespaces(props: NamespacesProps): Promise<void>;
declare var reactI18nextModule: {
type: "3rdParty",
init: (instance: Object) => void
};
declare var defaultOptions: {
wait: false,
withRef: false,
bindI18n: "languageChanged loaded",
bindStore: "added removed",
translateFuncName: "t",
nsMode: "default"
};
declare function setDefaults(options: TranslateOptions): void;
declare function getDefaults(): TranslateOptions;
declare function getI18n(): Object;
declare function setI18n(instance: Object): void;
}

View File

@@ -5,66 +5,74 @@
"license": "MIT",
"homepage": ".",
"devDependencies": {
"flow-bin": "^0.58.0",
"flow-bin": "^0.61.0",
"flow-coverage-report": "^0.4.0",
"follow-redirects": "^1.2.3",
"prettier": "1.7.0",
"react-scripts": "1.0.6",
"react-scripts": "1.0.17",
"shelljs": "^0.7.7"
},
"dependencies": {
"@storybook/react": "3.1.3",
"@storybook/react": "3.2.19",
"aws-sdk": "^2.100.0",
"axios": "^0.16.1",
"classnames": "^2.2.5",
"element-closest": "^2.0.2",
"blueimp-md5": "^2.10.0",
"classnames": "2.2.5",
"date-fns": "^1.29.0",
"element-closest": "2.0.2",
"firebase": "^4.8.2",
"flat": "2.0.1",
"fontfaceobserver": "2.0.13",
"i18next": "^10.0.3",
"keen-tracking": "1.1.3",
"lodash.assignin": "^4.2.0",
"lodash.compact": "^3.0.1",
"lodash.findindex": "^4.6.0",
"lodash.flatten": "^4.4.0",
"lodash.keys": "^4.2.0",
"lodash.update": "^4.10.2",
"lodash.values": "^4.3.0",
"material-ui": "0.18.3",
"material-ui-search-bar": "0.4.0",
"lodash": "4.17.4",
"material-ui": "0.20",
"material-ui-search-bar": "0.4.1",
"pixi-simple-gesture": "0.2.2",
"pixi.js": "3.0.11",
"prop-types": "^15.5.10",
"raven-js": "^3.19.1",
"react": "15.4.2",
"react-addons-css-transition-group": "15.4.2",
"react-addons-perf": "15.4.2",
"react-color": "2.11.7",
"react-dnd": "2.3.0",
"react-dnd-html5-backend": "2.3.0",
"react-dom": "15.4.2",
"react-i18next": "^6.0.6",
"react-measure": "1.4.6",
"react-mosaic-component": "4ian/react-mosaic#fix/is-dragging",
"react-sortable-hoc": "^0.6.3",
"react-sortable-tree": "^0.1.21",
"react-tap-event-plugin": "2.0.1",
"react-test-renderer": "15.4.2",
"react-virtualized": "9.3.0",
"slugs": "^0.1.3",
"react": "^16.2.0",
"react-color": "2.13.8",
"react-dnd": "2.5.4",
"react-dnd-html5-backend": "2.5.4",
"react-dom": "^16.2.0",
"react-error-boundary": "^1.2.0",
"react-i18next": "6.2.0",
"react-measure": "1.4.7",
"react-mosaic-component": "1.0.3",
"react-sortable-hoc": "0.6.8",
"react-sortable-tree": "1.5.3",
"react-test-renderer": "16.2.0",
"react-virtualized": "9.14.1",
"slugs": "0.1.3",
"source-map-explorer": "^1.4.0",
"wait-promise": "^0.4.1"
"wait-promise": "0.4.1"
},
"scripts": {
"postinstall": "npm run import-resources",
"import-resources": "cd scripts && node import-libGD.js && node import-res-folder.js && node import-GDJS-Runtime.js",
"start": "npm run import-resources && react-scripts start",
"build": "npm run import-resources && react-scripts build",
"postinstall": "npm run import-resources && cd node_modules/react-mosaic-component && yarn && yarn build",
"format": "prettier --write \"src/**/*.js\"",
"test": "react-scripts test --env=jsdom",
"flow": "flow",
"analyze-source-map": "source-map-explorer build/static/js/main.*",
"storybook": "start-storybook -p 9009 -s public",
"build-storybook": "build-storybook -s public"
"analyze-test-coverage": "react-scripts test --env=jsdom --coverage",
"analyze-flow-coverage": "flow-coverage-report",
"analyze-source-map": "source-map-explorer build/static/js/main.*"
},
"eslintConfig": {
"extends": "react-app"
},
"flow-coverage-report": {
"includeGlob": [
"src/**/*.js"
],
"type": [
"text",
"html",
"json"
]
}
}

View File

@@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<title>GDevelop</title>
<title>GDevelop 5</title>
<style>
html, body {
@@ -43,6 +43,10 @@
';path=/;expires='+new Date(0).toUTCString();i=d.indexOf('.');if(i<0)break;d=d.slice(i+1)}}};
})(window,document,window['_fs_namespace'],'script','user');
</script>
<!-- Stripe.com Checkout -->
<script src="https://checkout.stripe.com/checkout.js"></script>
</head>
<body>
<!-- Root div used for React `App` component rendering-->

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,18 @@
/**
* Return the help page for the given behavior
* @param {*} behavior
*/
export const getBehaviorHelpPagePath = behavior => {
if (!behavior) return null;
switch (behavior.getTypeName()) {
case 'DraggableBehavior::Draggable':
return '/behaviors/draggable';
case 'PlatformBehavior::PlatformerObjectBehavior':
return '/behaviors/platformer';
case 'PlatformBehavior::PlatformBehavior':
return '/behaviors/platformer';
default:
return '';
}
};

View File

@@ -1,10 +1,11 @@
import React, { Component } from 'react';
import Dialog from '../UI/Dialog';
import HelpButton from '../UI/HelpButton';
import FlatButton from 'material-ui/FlatButton';
import Avatar from 'material-ui/Avatar';
import { List, ListItem } from 'material-ui/List';
import { mapFor } from '../Utils/MapFor';
import flatten from 'lodash.flatten';
import flatten from 'lodash/flatten';
const styles = {
icon: { borderRadius: 0 },
@@ -64,13 +65,19 @@ export default class NewBehaviorDialog extends Component {
if (!open || !project) return null;
const actions = [
<FlatButton label="Close" primary={false} onClick={onClose} />,
<FlatButton
key="close"
label="Close"
primary={false}
onClick={onClose}
/>,
];
return (
<Dialog
title="Add a new behavior to the object"
actions={actions}
secondaryActions={<HelpButton helpPagePath="/behaviors" />}
open={open}
noMargin
autoScrollBodyContent

View File

@@ -5,10 +5,12 @@ import Delete from 'material-ui/svg-icons/action/delete';
import IconButton from 'material-ui/IconButton';
import EmptyMessage from '../UI/EmptyMessage';
import MiniToolbar from '../UI/MiniToolbar';
import HelpIcon from '../UI/HelpIcon';
import PropertiesEditor from '../PropertiesEditor';
import propertiesMapToSchema from '../PropertiesEditor/PropertiesMapToSchema';
import newNameGenerator from '../Utils/NewNameGenerator';
import NewBehaviorDialog from './NewBehaviorDialog';
import { getBehaviorHelpPagePath } from './BehaviorsHelpPagePaths';
const styles = {
addBehaviorLine: {
@@ -169,6 +171,9 @@ export default class BehaviorsEditor extends Component {
>
<Delete />
</IconButton>
<HelpIcon
helpPagePath={getBehaviorHelpPagePath(behavior)}
/>
</span>
</MiniToolbar>
<div style={styles.propertiesContainer}>

View File

@@ -1,10 +0,0 @@
export const selectableArea = 'selectable';
export const selectedArea = 'selected';
export const largeSelectableArea = 'large-selectable';
export const largeSelectedArea = 'large-selected';
export const background = 'background';
export const container = 'gd-events-sheet';
export const eventsTree = 'events-tree';

View File

@@ -0,0 +1,16 @@
// @flow
import * as React from 'react';
import PlaceholderMessage from '../UI/PlaceholderMessage';
import HelpButton from '../UI/HelpButton';
const EmptyEventsPlaceholder = () => (
<PlaceholderMessage>
<p>
There are no events here. Events are composed of conditions and actions.
</p>
<p>Add your first event using the first buttons of the toolbar.</p>
<HelpButton helpPagePath="/events" />
</PlaceholderMessage>
);
export default EmptyEventsPlaceholder;

View File

@@ -0,0 +1,15 @@
export const selectableArea = 'selectable';
export const selectedArea = 'selected';
export const largeSelectableArea = 'large-selectable';
export const largeSelectedArea = 'large-selected';
export const executableEventContainer = 'executable-event-container';
export const actionsContainer = 'actions-container';
export const conditionsContainer = 'conditions-container';
export const subInstructionsContainer = 'sub-instructions-container';
export const instructionParameter = 'instruction-parameter';
export const background = 'background';
export const eventsTree = 'events-tree';

View File

@@ -0,0 +1,50 @@
// @flow
type WatchedComponent = {
onHeightsChanged: (Function) => void
};
/**
* Store the height of events and notify a component whenever
* heights have changed.
* Needed for EventsTree as we need to tell it when heights have changed
* so it can recompute the internal row heights of the react-virtualized List.
*/
export default class EventHeightsCache {
eventHeights = {};
updateTimeoutId: ?number = null;
component: ?WatchedComponent = null;
constructor(component: WatchedComponent) {
this.component = component;
}
_notifyComponent() {
if (this.updateTimeoutId) {
return; // An update is already scheduled.
}
// Notify the component, on the next tick, that heights have changed
this.updateTimeoutId = setTimeout(() => {
if (this.component) {
this.component.onHeightsChanged(() => (this.updateTimeoutId = null));
} else {
this.updateTimeoutId = null;
}
}, 0);
}
setEventHeight(event: gdBaseEvent, height: number) {
const cachedHeight = this.eventHeights[event.ptr];
if (!cachedHeight || cachedHeight !== height) {
// console.log(event.ptr, 'has a new height', height, 'old:', cachedHeight);
this._notifyComponent();
}
this.eventHeights[event.ptr] = height;
}
getEventHeight(event: gdBaseEvent): number {
return this.eventHeights[event.ptr] || 60;
}
}

View File

@@ -6,6 +6,7 @@ import ForEachEvent from './Renderers/ForEachEvent';
import RepeatEvent from './Renderers/RepeatEvent';
import WhileEvent from './Renderers/WhileEvent';
import LinkEvent from './Renderers/LinkEvent';
import JsCodeEvent from './Renderers/JsCodeEvent';
export default {
components: {
@@ -17,6 +18,7 @@ export default {
'BuiltinCommonInstructions::Repeat': RepeatEvent,
'BuiltinCommonInstructions::While': WhileEvent,
'BuiltinCommonInstructions::Link': LinkEvent,
'BuiltinCommonInstructions::JsCode': JsCodeEvent,
},
getEventComponent: function(event) {
if (this.components.hasOwnProperty(event.getType()))

View File

@@ -1,8 +1,8 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { mapFor } from '../Utils/MapFor';
import { mapFor } from '../../Utils/MapFor';
import classNames from 'classnames';
import { selectedArea, selectableArea } from './ClassNames';
import { selectedArea, selectableArea, subInstructionsContainer, instructionParameter } from './ClassNames';
import InstructionsList from './InstructionsList';
const gd = global.gd;
const instrFormatter = gd.InstructionSentenceFormatter.get();
@@ -20,12 +20,6 @@ const styles = {
paddingLeft: 2,
paddingRight: 2,
},
subInstructionsList: {
marginLeft: 9,
marginTop: 1,
borderRight: 'none',
borderLeft: '1px solid #d3d3d3',
},
};
export default class Instruction extends Component {
@@ -61,29 +55,20 @@ export default class Instruction extends Component {
{mapFor(0, formattedTexts.size(), i => {
const formatting = formattedTexts.getTextFormatting(i);
const parameterIndex = formatting.getUserData();
const isParameter =
parameterIndex >= 0 && parameterIndex < parametersCount;
if (!isParameter)
return <span key={i}>{formattedTexts.getString(i)}</span>;
const parameterType = metadata.getParameter(parameterIndex).getType();
return (
<span
key={i}
style={{
color:
'rgb(' +
formatting.getColorRed() +
',' +
formatting.getColorGreen() +
',' +
formatting.getColorBlue() +
')',
fontWeight: formatting.isBold() ? 'bold' : 'normal',
fontStyle: formatting.isItalic() ? 'italic' : 'normal',
}}
className={classNames({
[selectableArea]: true,
[instructionParameter]: true,
[parameterType]: true,
})}
onClick={domEvent =>
this.props.onParameterClick(domEvent, parameterIndex)}
@@ -149,7 +134,7 @@ export default class Instruction extends Component {
{this._renderInstructionText(metadata)}
{metadata.canHaveSubInstructions() && (
<InstructionsList
style={styles.subInstructionsList}
extraClassName={subInstructionsContainer}
instrsList={instruction.getSubInstructions()}
areConditions={this.props.isCondition}
selection={this.props.selection}

View File

@@ -1,20 +1,11 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Instruction from './Instruction.js';
import { mapFor } from '../Utils/MapFor';
import { isInstructionSelected } from './SelectionHandler';
import Instruction from './Instruction';
import { mapFor } from '../../Utils/MapFor';
import { isInstructionSelected } from '../SelectionHandler';
import { actionsContainer, conditionsContainer } from './ClassNames';
const styles = {
conditionsContainer: {
paddingLeft: 5,
paddingRight: 5,
background: '#f1f2f2',
borderRight: '1px solid #d3d3d3',
},
actionsContainer: {
paddingLeft: 5,
paddingRight: 5,
},
addButton: {
cursor: 'pointer',
},
@@ -43,16 +34,26 @@ export default class InstructionsList extends Component {
};
render() {
const instructionsListContext = {
isCondition: this.props.areConditions,
instrsList: this.props.instrsList,
};
const {
addButtonLabel,
areConditions,
extraClassName,
instrsList,
onAddNewInstruction,
onInstructionClick,
onInstructionContextMenu,
onInstructionDoubleClick,
onInstructionsListContextMenu,
onParameterClick,
selection,
style,
} = this.props;
const instructions = mapFor(0, this.props.instrsList.size(), i => {
const instruction = this.props.instrsList.get(i);
const instructions = mapFor(0, instrsList.size(), i => {
const instruction = instrsList.get(i);
const instructionContext = {
isCondition: this.props.areConditions,
instrsList: this.props.instrsList,
isCondition: areConditions,
instrsList: instrsList,
instruction,
indexInList: i,
};
@@ -60,44 +61,49 @@ export default class InstructionsList extends Component {
return (
<Instruction
instruction={instruction}
isCondition={this.props.areConditions}
instrsList={this.props.instrsList}
isCondition={areConditions}
instrsList={instrsList}
index={i}
key={instruction.ptr}
selected={isInstructionSelected(this.props.selection, instruction)}
onClick={() => this.props.onInstructionClick(instructionContext)}
selected={isInstructionSelected(selection, instruction)}
onClick={() => onInstructionClick(instructionContext)}
onDoubleClick={() =>
this.props.onInstructionDoubleClick(instructionContext)}
onInstructionDoubleClick(instructionContext)}
onContextMenu={(x, y) =>
this.props.onInstructionContextMenu(x, y, instructionContext)}
onInstructionContextMenu(x, y, instructionContext)}
onParameterClick={(domEvent, parameterIndex) =>
this.props.onParameterClick({
onParameterClick({
...instructionContext,
parameterIndex,
domEvent,
})}
selection={this.props.selection}
onAddNewSubInstruction={this.props.onAddNewInstruction}
onSubInstructionClick={this.props.onInstructionClick}
onSubInstructionDoubleClick={this.props.onInstructionDoubleClick}
onSubInstructionContextMenu={this.props.onInstructionContextMenu}
selection={selection}
onAddNewSubInstruction={onAddNewInstruction}
onSubInstructionClick={onInstructionClick}
onSubInstructionDoubleClick={onInstructionDoubleClick}
onSubInstructionContextMenu={onInstructionContextMenu}
onSubInstructionsListContextMenu={
this.props.onInstructionsListContextMenu
onInstructionsListContextMenu
}
onSubParameterClick={this.props.onParameterClick}
onSubParameterClick={onParameterClick}
/>
);
});
const containerStyle = this.props.areConditions
? styles.conditionsContainer
: styles.actionsContainer;
const addButtonLabel = this.props.areConditions
const instructionsListContext = {
isCondition: areConditions,
instrsList: instrsList,
};
const addButtonDefaultLabel = areConditions
? 'Add condition'
: 'Add action';
return (
<div style={{ ...containerStyle, ...this.props.style }}>
<div
className={`${areConditions
? conditionsContainer
: actionsContainer} ${extraClassName || ''}`}
style={style}
>
{instructions}
<a
style={styles.addButton}
@@ -105,14 +111,14 @@ export default class InstructionsList extends Component {
onClick={this.onAddNewInstruction}
onContextMenu={e => {
e.stopPropagation();
this.props.onInstructionsListContextMenu(
onInstructionsListContextMenu(
e.clientX,
e.clientY,
instructionsListContext
);
}}
>
{this.props.addButtonLabel || addButtonLabel}
{addButtonLabel || addButtonDefaultLabel}
</a>
</div>
);

View File

@@ -2,7 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
import { rgbToHex } from '../../Utils/ColorTransformer';
import { rgbToHex } from '../../../Utils/ColorTransformer';
import {
largeSelectedArea,
largeSelectableArea,
@@ -37,13 +37,9 @@ export default class CommentEvent extends Component {
onUpdate: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
this.state = {
editing: false,
};
}
state = {
editing: false,
};
edit = () => {
this.setState(

View File

@@ -6,16 +6,16 @@ import {
largeSelectedArea,
largeSelectableArea,
selectableArea,
executableEventContainer,
} from '../ClassNames';
import InlinePopover from '../InlinePopover';
import ObjectField from '../InstructionEditor/ParameterFields/ObjectField';
import InlinePopover from '../../InlinePopover';
import ObjectField from '../../InstructionEditor/ParameterFields/ObjectField';
const gd = global.gd;
const styles = {
container: {
display: 'flex',
flexDirection: 'column',
borderBottom: '1px solid #d3d3d3',
},
instructionsContainer: {
display: 'flex',
@@ -75,6 +75,7 @@ export default class ForEachEvent extends Component {
className={classNames({
[largeSelectableArea]: true,
[largeSelectedArea]: this.props.selected,
[executableEventContainer]: true,
})}
>
<div

View File

@@ -11,7 +11,7 @@ const gd = global.gd;
const styles = {
container: {
height: 60,
height: 40,
display: 'flex',
alignItems: 'center',
padding: 5,

View File

@@ -0,0 +1,192 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
import InlinePopover from '../../InlinePopover';
import ObjectField from '../../InstructionEditor/ParameterFields/ObjectField';
import {
largeSelectedArea,
largeSelectableArea,
selectableArea,
} from '../ClassNames';
import { getHelpLink } from '../../../Utils/HelpLink';
import Window from '../../../Utils/Window';
const gd = global.gd;
const fontFamily = '"Lucida Console", Monaco, monospace';
const styles = {
container: {
minHeight: 30,
display: 'flex',
flexDirection: 'column',
backgroundColor: 'white',
},
wrappingText: {
fontFamily,
paddingLeft: 5,
paddingRight: 5,
margin: 0,
},
text: {
flex: 1,
whiteSpace: 'pre-line',
margin: 0,
paddingLeft: 4 * 5,
paddingRight: 5,
fontFamily,
},
textArea: {
paddingLeft: 4 * 5,
paddingRight: 5,
flex: 1,
boxSizing: 'border-box',
width: '100%',
fontSize: 14,
fontFamily,
},
comment: {
color: '#777',
},
commentLink: {
cursor: 'pointer',
color: '#777',
textDecoration: 'underline',
},
};
export default class JsCodeEvent extends Component {
state = {
editing: false,
anchorEl: null,
};
edit = () => {
this.setState(
{
editing: true,
height: this._container.offsetHeight,
},
() => {
const input = ReactDOM.findDOMNode(this._input);
input.focus();
input.value = gd.asJsCodeEvent(this.props.event).getInlineCode();
}
);
};
endEditing = () => {
const jsCodeEvent = gd.asJsCodeEvent(this.props.event);
jsCodeEvent.setInlineCode(ReactDOM.findDOMNode(this._input).value);
this.setState(
{
editing: false,
},
() => this.props.onUpdate()
);
};
editObject = domEvent => {
this.setState({
editingObject: true,
anchorEl: domEvent.currentTarget,
});
};
endObjectEditing = () => {
this.setState({
editingObject: false,
anchorEl: null,
});
};
openHelp = () => {
Window.openExternalURL(getHelpLink('/events/js-code'));
};
render() {
const jsCodeEvent = gd.asJsCodeEvent(this.props.event);
const parameterObjects = jsCodeEvent.getParameterObjects();
const objects = (
<span
className={classNames({
[selectableArea]: true,
})}
onClick={this.editObject}
>
{parameterObjects
? `, objects /*${parameterObjects}*/`
: ' /* No objects selected, only pass the scene as argument */'}
</span>
);
const functionStart = (
<p style={styles.wrappingText}>
<span>{'(function(runtimeScene'}</span>
{objects}
<span>{') {'}</span>
</p>
);
const functionEnd = (
<p style={styles.wrappingText}>
<span>{'})(runtimeScene'}</span>
{objects}
<span>{');'}</span>
<span style={styles.comment}>
{' // '}
<a onClick={this.openHelp} style={styles.commentLink}>
Read the documentation and help
</a>
</span>
</p>
);
return (
<div
style={styles.container}
className={classNames({
[largeSelectableArea]: true,
[largeSelectedArea]: this.props.selected,
})}
ref={container => (this._container = container)}
>
{functionStart}
{!this.state.editing ? (
<p
className={classNames({
[selectableArea]: true,
})}
onClick={this.edit}
key="p"
style={styles.text}
>
{jsCodeEvent.getInlineCode()}
</p>
) : (
<textarea
key="textarea"
type="text"
style={{ ...styles.textArea, height: this.state.height }}
onBlur={this.endEditing}
ref={input => (this._input = input)}
/>
)}
{functionEnd}
<InlinePopover
open={this.state.editingObject}
anchorEl={this.state.anchorEl}
onRequestClose={this.endObjectEditing}
>
<ObjectField
project={this.props.project}
layout={this.props.layout}
value={parameterObjects}
onChange={text => {
jsCodeEvent.setParameterObjects(text);
this.props.onUpdate();
}}
isInline
/>
</InlinePopover>
</div>
);
}
}

View File

@@ -1,13 +1,16 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import OpenInNew from 'material-ui/svg-icons/action/open-in-new';
import IconButton from 'material-ui/IconButton';
import classNames from 'classnames';
import {
largeSelectedArea,
largeSelectableArea,
selectableArea,
} from '../ClassNames';
import InlinePopover from '../InlinePopover';
import DefaultField from '../InstructionEditor/ParameterFields/DefaultField';
import InlinePopover from '../../InlinePopover';
import ExternalEventsField from '../../InstructionEditor/ParameterFields/ExternalEventsField';
import { showWarningBox } from '../../../UI/Messages/MessageBox';
const gd = global.gd;
const styles = {
@@ -27,6 +30,8 @@ export default class LinkEvent extends Component {
event: PropTypes.object.isRequired,
};
_externalEventsField = null;
constructor(props) {
super(props);
@@ -37,10 +42,31 @@ export default class LinkEvent extends Component {
}
edit = domEvent => {
this.setState({
editing: true,
anchorEl: domEvent.currentTarget,
});
this.setState(
{
editing: true,
anchorEl: domEvent.currentTarget,
},
() => {
if (this._externalEventsField) this._externalEventsField.focus();
}
);
};
openTarget = () => {
const { project, event, onOpenLayout, onOpenExternalEvents } = this.props;
const linkEvent = gd.asLinkEvent(event);
const target = linkEvent.getTarget();
if (project.hasExternalEventsNamed(target)) {
onOpenExternalEvents(target);
} else if (project.hasLayoutNamed(target)) {
onOpenLayout(target);
} else {
showWarningBox(
'The specified external events do not exist in the game. Be sure that the name is correctly spelled or create them using the project manager.'
);
}
};
endEditing = () => {
@@ -51,7 +77,7 @@ export default class LinkEvent extends Component {
};
render() {
var linkEvent = gd.asLinkEvent(this.props.event);
const linkEvent = gd.asLinkEvent(this.props.event);
const target = linkEvent.getTarget();
return (
@@ -73,20 +99,24 @@ export default class LinkEvent extends Component {
{target || '< Enter the name of external events >'}
</i>
</span>
<IconButton onClick={this.openTarget} disabled={!target}>
<OpenInNew />
</IconButton>
<InlinePopover
open={this.state.editing}
anchorEl={this.state.anchorEl}
onRequestClose={this.endEditing}
>
<DefaultField
<ExternalEventsField
project={this.props.project}
layout={this.props.layout}
value={target}
onChange={text => {
linkEvent.setTarget(text);
this.props.onUpdate();
}}
isInline
ref={externalEventsField =>
(this._externalEventsField = externalEventsField)}
/>
</InlinePopover>
</div>

View File

@@ -6,16 +6,16 @@ import {
largeSelectedArea,
largeSelectableArea,
selectableArea,
executableEventContainer,
} from '../ClassNames';
import InlinePopover from '../InlinePopover';
import DefaultField from '../InstructionEditor/ParameterFields/DefaultField';
import InlinePopover from '../../InlinePopover';
import DefaultField from '../../InstructionEditor/ParameterFields/DefaultField';
const gd = global.gd;
const styles = {
container: {
display: 'flex',
flexDirection: 'column',
borderBottom: '1px solid #d3d3d3',
},
instructionsContainer: {
display: 'flex',
@@ -75,6 +75,7 @@ export default class RepeatEvent extends Component {
className={classNames({
[largeSelectableArea]: true,
[largeSelectedArea]: this.props.selected,
[executableEventContainer]: true,
})}
>
<div

View File

@@ -1,14 +1,17 @@
import React, { Component } from 'react';
import InstructionsList from '../InstructionsList.js';
import InstructionsList from '../InstructionsList';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { largeSelectedArea, largeSelectableArea } from '../ClassNames';
import {
largeSelectedArea,
largeSelectableArea,
executableEventContainer,
} from '../ClassNames';
const gd = global.gd;
const styles = {
container: {
display: 'flex',
borderBottom: '1px solid #d3d3d3',
},
actionsList: {
flex: 1,
@@ -40,6 +43,7 @@ export default class StandardEvent extends Component {
className={classNames({
[largeSelectableArea]: true,
[largeSelectedArea]: this.props.selected,
[executableEventContainer]: true,
})}
>
<InstructionsList

View File

@@ -1,15 +1,18 @@
import React, { Component } from 'react';
import InstructionsList from '../InstructionsList.js';
import InstructionsList from '../InstructionsList';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { largeSelectedArea, largeSelectableArea } from '../ClassNames';
import {
largeSelectedArea,
largeSelectableArea,
executableEventContainer,
} from '../ClassNames';
const gd = global.gd;
const styles = {
container: {
display: 'flex',
flexDirection: 'column',
borderBottom: '1px solid #d3d3d3',
},
instructionsContainer: {
display: 'flex',
@@ -45,6 +48,7 @@ export default class ForEachEvent extends Component {
className={classNames({
[largeSelectableArea]: true,
[largeSelectedArea]: this.props.selected,
[executableEventContainer]: true,
})}
>
<div>While these conditions are true:</div>

View File

Before

Width:  |  Height:  |  Size: 175 B

After

Width:  |  Height:  |  Size: 175 B

View File

@@ -1,60 +1,19 @@
import React, { Component } from 'react';
import muiThemeable from 'material-ui/styles/muiThemeable';
import findIndex from 'lodash/findIndex';
import {
SortableTreeWithoutDndContext as SortableTree,
SortableTreeWithoutDndContext,
getNodeAtPath,
} from 'react-sortable-tree';
import EventsRenderingService from '../EventsRenderingService';
import { mapFor } from '../../Utils/MapFor';
import { eventsTree } from '../ClassNames';
import findIndex from 'lodash.findindex';
import { getInitialSelection, isEventSelected } from '../SelectionHandler';
import EventsRenderingService from './EventsRenderingService';
import EventHeightsCache from './EventHeightsCache';
import { eventsTree } from './ClassNames';
import './style.css';
const indentWidth = 22;
/**
* Store the height of events and notify a component whenever
* heights have changed.
* Needed for EventsTree as we need to tell it when heights have changed
* so it can recompute the internal row heights of the react-virtualized List.
*/
class EventHeightsCache {
eventHeights = {};
component = null;
constructor(component) {
this.component = component;
}
_notifyComponent() {
if (this.updateTimeoutId) {
return; // An update is already scheduled.
}
// Notify the component, on the next tick, that heights have changed
this.updateTimeoutId = setTimeout(() => {
if (this.component) {
this.component.onHeightsChanged(() => (this.updateTimeoutId = null));
} else {
this.updateTimeoutId = null;
}
}, 0);
}
setEventHeight(event, height) {
const cachedHeight = this.eventHeights[event.ptr];
if (!cachedHeight || cachedHeight !== height) {
// console.log(event.ptr, 'has a new height', height, 'old:', cachedHeight);
this._notifyComponent();
}
this.eventHeights[event.ptr] = height;
}
getEventHeight(event) {
return this.eventHeights[event.ptr] || 60;
}
}
/**
* The component containing an event.
* It will report the rendered event height so that the EventsTree can
@@ -107,6 +66,8 @@ class EventContainer extends Component {
this.props.onInstructionsListContextMenu
}
onParameterClick={this.props.onParameterClick}
onOpenExternalEvents={this.props.onOpenExternalEvents}
onOpenLayout={this.props.onOpenLayout}
/>
)}
</div>
@@ -116,11 +77,19 @@ class EventContainer extends Component {
const getNodeKey = ({ treeIndex }) => treeIndex;
const ThemableSortableTree = ({ muiTheme, ...otherProps }) => (
<SortableTreeWithoutDndContext
className={`${eventsTree} ${muiTheme.eventsSheetRootClassName}`}
{...otherProps}
/>
);
const SortableTree = muiThemeable()(ThemableSortableTree);
/**
* Display a tree of event. Builtin on react-sortable-tree so that event
* can be drag'n'dropped and events rows are virtualized.
*/
export default class EventsTree extends Component {
export default class ThemableEventsTree extends Component {
static defaultProps = {
selection: getInitialSelection(),
};
@@ -143,7 +112,7 @@ export default class EventsTree extends Component {
*/
onHeightsChanged(cb) {
this.forceUpdate(() => {
this._list.wrappedInstance.recomputeRowHeights();
if (this._list) this._list.wrappedInstance.recomputeRowHeights();
if (cb) cb();
});
}
@@ -154,14 +123,14 @@ export default class EventsTree extends Component {
*/
forceEventsUpdate(cb) {
this.setState(this._eventsToTreeData(this.props.events), () => {
this._list.wrappedInstance.recomputeRowHeights();
if (this._list) this._list.wrappedInstance.recomputeRowHeights();
if (cb) cb();
});
}
scrollToEvent(event) {
const row = this._getEventRow(event);
if (row !== -1) this._list.wrappedInstance.scrollToRow(row);
if (row !== -1 && this._list) this._list.wrappedInstance.scrollToRow(row);
}
_getEventRow(searchedEvent) {
@@ -262,15 +231,18 @@ export default class EventsTree extends Component {
onEventContextMenu={(x, y) => this.props.onEventContextMenu(x, y, node)}
onInstructionContextMenu={this.props.onInstructionContextMenu}
onInstructionsListContextMenu={this.props.onInstructionsListContextMenu}
onOpenExternalEvents={this.props.onOpenExternalEvents}
onOpenLayout={this.props.onOpenLayout}
/>
);
};
render() {
const { height } = this.props;
return (
<div style={{ height: this.props.height || 400 }}>
<div style={{ height: height || 400 }}>
<SortableTree
className={eventsTree}
treeData={this.state.treeData}
scaffoldBlockPxWidth={indentWidth}
onChange={() => {}}
@@ -279,6 +251,8 @@ export default class EventsTree extends Component {
canDrop={this._canDrop}
rowHeight={({ index }) => {
const event = this.state.flatData[index];
if (!event) return 0;
return this.eventsHeightsCache.getEventHeight(event);
}}
reactVirtualizedListProps={{

View File

@@ -1,10 +1,5 @@
/* This overwrite the default react-sortable-tree css to display events list */
.gd-events-sheet .events-tree {
font-family: -apple-system, ".SFNSText-Regular", "San Francisco", Roboto, "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif;
font-size: 14px;
}
/**
* Draggable handle on the left of an event
*/
@@ -16,6 +11,10 @@
border-radius: 0;
}
.gd-events-sheet .events-tree {
height: 100%;
}
/**
* Container of an event line (including the scaffolding lines and the event).
*/
@@ -33,7 +32,7 @@
}
.gd-events-sheet .rst__row {
/* The "landing pad" highlight box is done with positionL absolute.
/* The "landing pad" highlight box is done with position: absolute.
* Ensure it will cover all the event row but not the scaffolding lines.
*/
position: relative;
@@ -119,15 +118,6 @@
border: 1px transparent solid;
}
.gd-events-sheet .selectable:hover {
background-color: rgba(0,0,0, 0.1);
}
.gd-events-sheet .selectable.selected {
background-color: rgba(0,0,0, 0.1);
border: 1px #4ab0e4 solid !important;
}
/**
* Large selectable area (events)
*/
@@ -135,7 +125,3 @@
box-sizing: border-box;
border: 1px transparent solid;
}
.gd-events-sheet .large-selectable.large-selected {
border: 1px #4ab0e4 solid !important;
}

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -4,14 +4,10 @@ import ParameterRenderingService from './InstructionEditor/ParameterRenderingSer
const gd = global.gd;
export default class InlineParameterEditor extends Component {
constructor(props) {
super(props);
this.state = {
isValid: false,
parameterMetadata: null,
};
}
state = {
isValid: false,
parameterMetadata: null,
};
componentWillReceiveProps(newProps) {
if (
@@ -79,10 +75,11 @@ export default class InlineParameterEditor extends Component {
project={this.props.project}
layout={this.props.layout}
value={this.props.instruction.getParameter(this.props.parameterIndex)}
instruction={this.props.instruction}
instructionOrExpression={this.props.instruction}
key={this.props.instruction.ptr}
onChange={this.props.onChange}
ref={field => (this._field = field)}
parameterRenderingService={ParameterRenderingService}
isInline
/>
</InlinePopover>

View File

@@ -30,13 +30,13 @@ export default class InstructionEditorDialog extends React.Component {
<FlatButton
label="Cancel"
primary={false}
onTouchTap={this.props.onCancel}
onClick={this.props.onCancel}
/>,
<FlatButton
label="Ok"
primary={true}
keyboardFocused={false}
onTouchTap={this.props.onSubmit}
onClick={this.props.onSubmit}
/>,
];

View File

@@ -0,0 +1,35 @@
// @flow
import update from 'lodash/update';
import compact from 'lodash/compact';
import { type InstructionOrExpressionInformation } from './InstructionOrExpressionInformation.flow.js';
const GROUP_DELIMITER = '/';
export type InstructionOrExpressionTreeNode =
| InstructionOrExpressionInformation
| {
[string]: InstructionOrExpressionTreeNode,
};
export const createTree = (
allExpressions: Array<InstructionOrExpressionInformation>
): InstructionOrExpressionTreeNode => {
const tree = {};
allExpressions.forEach(
(expressionInfo: InstructionOrExpressionInformation) => {
update(
tree,
compact(expressionInfo.fullGroupName.split(GROUP_DELIMITER)),
groupInfo => {
const existingGroupInfo = groupInfo || {};
return {
...existingGroupInfo,
[expressionInfo.displayedName]: expressionInfo,
};
}
);
}
);
return tree;
};

View File

@@ -0,0 +1,149 @@
// @flow weak
const gd = global.gd;
const GROUP_DELIMITER = '/';
const enumerateExtensionExpressions = (
prefix,
expressions,
objectMetadata,
behaviorMetadata
) => {
const allExpressions = [];
//Get the map containing the metadata of the expression provided by the extension...
var expressionsTypes = expressions.keys();
//... and add each instruction
for (var j = 0; j < expressionsTypes.size(); ++j) {
var exprMetadata = expressions.get(expressionsTypes.get(j));
if (!exprMetadata.isShown()) {
//Skip hidden expressions
continue;
}
var parameters = [];
for (var i = 0; i < exprMetadata.getParametersCount(); i++) {
if (objectMetadata && i === 0) continue;
if (behaviorMetadata && i <= 1) continue; //Skip object and behavior parameters
if (exprMetadata.getParameter(i).isCodeOnly()) continue;
parameters.push(exprMetadata.getParameter(i));
}
const displayedName = exprMetadata.getFullName();
const groupName = exprMetadata.getGroup();
const fullGroupName = prefix + groupName;
allExpressions.push({
type: expressionsTypes.get(j),
name: expressionsTypes.get(j),
displayedName,
fullGroupName,
metadata: exprMetadata,
parameters: parameters,
objectMetadata: objectMetadata,
behaviorMetadata: behaviorMetadata,
});
}
return allExpressions;
};
export const enumerateExpressions = type => {
const freeExpressions = [];
const objectsExpressions = [];
const behaviorsExpressions = [];
const allExtensions = gd
.asPlatform(gd.JsPlatform.get())
.getAllPlatformExtensions();
for (var i = 0; i < allExtensions.size(); ++i) {
var extension = allExtensions.get(i);
var allObjectsTypes = extension.getExtensionObjectsTypes();
var allBehaviorsTypes = extension.getBehaviorsTypes();
let prefix = '';
if (allObjectsTypes.size() > 0 || allBehaviorsTypes.size() > 0) {
prefix =
extension.getName() === 'BuiltinObject'
? 'Common expressions for all objects'
: extension.getFullName();
prefix += GROUP_DELIMITER;
}
//Check which type of expression we want to autocomplete.
var allFreeExpressionsGetter = extension.getAllExpressions;
var allObjectExpressionsGetter = extension.getAllExpressionsForObject;
var allBehaviorExpressionsGetter = extension.getAllExpressionsForBehavior;
if (type === 'string') {
allFreeExpressionsGetter = extension.getAllStrExpressions;
allObjectExpressionsGetter = extension.getAllStrExpressionsForObject;
allBehaviorExpressionsGetter = extension.getAllStrExpressionsForBehavior;
}
//Free expressions
freeExpressions.push.apply(
freeExpressions,
enumerateExtensionExpressions(
prefix,
allFreeExpressionsGetter.call(extension)
)
);
//Objects expressions:
for (var j = 0; j < allObjectsTypes.size(); ++j) {
var objMetadata = extension.getObjectMetadata(allObjectsTypes.get(j));
objectsExpressions.push.apply(
objectsExpressions,
enumerateExtensionExpressions(
prefix,
allObjectExpressionsGetter.call(extension, allObjectsTypes.get(j)),
objMetadata
)
);
}
//Behaviors expressions:
for (var k = 0; k < allBehaviorsTypes.size(); ++k) {
var autoMetadata = extension.getBehaviorMetadata(
allBehaviorsTypes.get(k)
);
behaviorsExpressions.push.apply(
behaviorsExpressions,
enumerateExtensionExpressions(
prefix,
allBehaviorExpressionsGetter.call(
extension,
allBehaviorsTypes.get(k)
),
undefined,
autoMetadata
)
);
}
}
return {
allExpressions: [
...freeExpressions,
...objectsExpressions,
...behaviorsExpressions,
],
freeExpressions,
objectsExpressions,
behaviorsExpressions,
};
};
export const filterExpressions = (list, searchText) => {
if (!searchText) return list;
const lowercaseSearchText = searchText.toLowerCase();
return list.filter(enumeratedExpression => {
return (
enumeratedExpression.type.toLowerCase().indexOf(lowercaseSearchText) !==
-1
);
});
};

View File

@@ -0,0 +1,48 @@
import {
enumerateExpressions,
filterExpressions,
} from './EnumerateExpressions';
import { createTree } from './CreateTree';
import isObject from 'lodash/isObject';
describe('EnumerateObjects', () => {
it('can enumerate and filter expressions', () => {
const { freeExpressions, objectsExpressions } = enumerateExpressions(
'number'
);
// Should find atan, atan2, atanh math function
expect(filterExpressions(freeExpressions, 'atan')).toHaveLength(3);
// Should find abs math function
expect(filterExpressions(freeExpressions, 'abs')).toHaveLength(1);
expect(filterExpressions(freeExpressions, 'MouseX')).toHaveLength(1);
expect(filterExpressions(freeExpressions, 'MouseY')).toHaveLength(1);
expect(filterExpressions(objectsExpressions, 'PointX')).toHaveLength(1);
});
it('can create the tree of some instructions', () => {
const stripMetadata = obj => {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
if (
key === 'objectMetadata' ||
key === 'behaviorMetadata' ||
key === 'metadata' ||
key === 'parameters'
) {
delete obj[key];
} else if (isObject(obj[key])) {
stripMetadata(obj[key]);
}
}
}
return obj;
};
const { objectsExpressions } = enumerateExpressions('number');
expect(stripMetadata(createTree(objectsExpressions))).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,95 @@
// @flow
import { type InstructionOrExpressionInformation } from './InstructionOrExpressionInformation.flow.js';
const gd = global.gd;
const GROUP_DELIMITER = '/';
const enumerateExtensionInstructions = (
groupPrefix: string,
extensionInstructions
) => {
//Get the map containing the metadata of the instructions provided by the extension...
var instructionsTypes = extensionInstructions.keys();
const allInstructions = [];
//... and add each instruction
for (let j = 0; j < instructionsTypes.size(); ++j) {
const instrMetadata = extensionInstructions.get(instructionsTypes.get(j));
if (instrMetadata.isHidden()) continue;
const displayedName = instrMetadata.getFullName();
const groupName = instrMetadata.getGroup();
const fullGroupName = groupPrefix + groupName;
allInstructions.push({
type: instructionsTypes.get(j),
displayedName,
fullGroupName,
});
}
return allInstructions;
};
export const enumerateInstructions = (
isCondition: boolean
): Array<InstructionOrExpressionInformation> => {
let allInstructions = [];
const allExtensions = gd
.asPlatform(gd.JsPlatform.get())
.getAllPlatformExtensions();
for (let i = 0; i < allExtensions.size(); ++i) {
const extension = allExtensions.get(i);
const allObjectsTypes = extension.getExtensionObjectsTypes();
const allBehaviorsTypes = extension.getBehaviorsTypes();
let prefix = '';
if (allObjectsTypes.size() > 0 || allBehaviorsTypes.size() > 0) {
prefix =
extension.getName() === 'BuiltinObject'
? 'Common ' +
(isCondition ? 'conditions' : 'action') +
' for all objects'
: extension.getFullName();
prefix += GROUP_DELIMITER;
}
//Free instructions
allInstructions = [
...allInstructions,
...enumerateExtensionInstructions(
prefix,
isCondition ? extension.getAllConditions() : extension.getAllActions()
),
];
//Objects instructions:
for (let j = 0; j < allObjectsTypes.size(); ++j) {
allInstructions = [
...allInstructions,
...enumerateExtensionInstructions(
prefix,
isCondition
? extension.getAllConditionsForObject(allObjectsTypes.get(j))
: extension.getAllActionsForObject(allObjectsTypes.get(j))
),
];
}
//Behaviors instructions:
for (let j = 0; j < allBehaviorsTypes.size(); ++j) {
allInstructions = [
...allInstructions,
...enumerateExtensionInstructions(
prefix,
isCondition
? extension.getAllConditionsForBehavior(allBehaviorsTypes.get(j))
: extension.getAllActionsForBehavior(allBehaviorsTypes.get(j))
),
];
}
}
return allInstructions;
};

View File

@@ -0,0 +1,19 @@
import { createTree } from './CreateTree';
import { enumerateInstructions } from './EnumerateInstructions';
describe('EnumerateInstructions', () => {
it('can enumerate instructions being conditions', () => {
const instructions = enumerateInstructions(true);
expect(instructions).toMatchSnapshot();
});
it('can enumerate instructions being actions', () => {
const instructions = enumerateInstructions(false);
expect(instructions).toMatchSnapshot();
});
it('can create the tree of instructions', () => {
const instructions = enumerateInstructions('number');
expect(createTree(instructions)).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,31 @@
// @flow
import React, { Component } from 'react';
import { enumerateExpressions } from './EnumerateExpressions';
import InstructionOrExpressionSelector from './index';
import { createTree, type InstructionOrExpressionTreeNode } from './CreateTree';
import { type InstructionOrExpressionInformation } from './InstructionOrExpressionInformation.flow.js';
export default class ExpressionSelector extends Component<*, *> {
instructionsInfo: Array<InstructionOrExpressionInformation> = [];
instructionsInfoTree: ?InstructionOrExpressionTreeNode = null;
static defaultProps = {
expressionType: 'number',
};
componentWillMount() {
const { allExpressions } = enumerateExpressions(this.props.expressionType);
this.instructionsInfo = allExpressions;
this.instructionsInfoTree = createTree(allExpressions);
}
render() {
return (
<InstructionOrExpressionSelector
instructionsInfo={this.instructionsInfo}
instructionsInfoTree={this.instructionsInfoTree}
{...this.props}
/>
);
}
}

View File

@@ -0,0 +1,12 @@
//@flow
export type InstructionOrExpressionInformation = {
type: string,
name?: string, //For expressions
displayedName: string,
fullGroupName: string,
metadata: Object,
parameters?: Array<any>,
objectMetadata?: Object, //For expressions
behaviorMetadata?: Object, //For expressions
};

View File

@@ -0,0 +1,32 @@
// @flow
import React, { Component } from 'react';
import InstructionOrExpressionSelector from './index';
import { createTree, type InstructionOrExpressionTreeNode } from './CreateTree';
import { enumerateInstructions } from './EnumerateInstructions';
import { type InstructionOrExpressionInformation } from './InstructionOrExpressionInformation.flow.js';
type Props = {
isCondition: boolean,
// And props from InstructionOrExpressionSelector
};
export default class InstructionSelector extends Component<Props, *> {
instructionsInfo: Array<InstructionOrExpressionInformation> = [];
instructionsInfoTree: ?InstructionOrExpressionTreeNode = null;
componentWillMount() {
const allInstructions = enumerateInstructions(this.props.isCondition);
this.instructionsInfo = allInstructions;
this.instructionsInfoTree = createTree(allInstructions);
}
render() {
return (
<InstructionOrExpressionSelector
instructionsInfo={this.instructionsInfo}
instructionsInfoTree={this.instructionsInfoTree}
{...this.props}
/>
);
}
}

View File

@@ -0,0 +1,180 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EnumerateObjects can create the tree of some instructions 1`] = `
Object {
"Common expressions for all objects": Object {
"Angle": Object {
"Angle": Object {
"displayedName": "Angle",
"fullGroupName": "Common expressions for all objects/Angle",
"name": "Angle",
"type": "Angle",
},
},
"Movement": Object {
"Average X coordinates of forces": Object {
"displayedName": "Average X coordinates of forces",
"fullGroupName": "Common expressions for all objects/Movement",
"name": "ForceX",
"type": "ForceX",
},
"Average Y coordinates of forces": Object {
"displayedName": "Average Y coordinates of forces",
"fullGroupName": "Common expressions for all objects/Movement",
"name": "ForceY",
"type": "ForceY",
},
"Average angle of the forces": Object {
"displayedName": "Average angle of the forces",
"fullGroupName": "Common expressions for all objects/Movement",
"name": "ForceAngle",
"type": "ForceAngle",
},
"Average length of the forces": Object {
"displayedName": "Average length of the forces",
"fullGroupName": "Common expressions for all objects/Movement",
"name": "ForceLength",
"type": "ForceLength",
},
},
"Position": Object {
"Distance between two objects": Object {
"displayedName": "Distance between two objects",
"fullGroupName": "Common expressions for all objects/Position",
"name": "Distance",
"type": "Distance",
},
"Square distance between two objects": Object {
"displayedName": "Square distance between two objects",
"fullGroupName": "Common expressions for all objects/Position",
"name": "SqDistance",
"type": "SqDistance",
},
"X position": Object {
"displayedName": "X position",
"fullGroupName": "Common expressions for all objects/Position",
"name": "X",
"type": "X",
},
"Y position": Object {
"displayedName": "Y position",
"fullGroupName": "Common expressions for all objects/Position",
"name": "Y",
"type": "Y",
},
},
"Size": Object {
"Height": Object {
"displayedName": "Height",
"fullGroupName": "Common expressions for all objects/Size",
"name": "Height",
"type": "Height",
},
"Width": Object {
"displayedName": "Width",
"fullGroupName": "Common expressions for all objects/Size",
"name": "Width",
"type": "Width",
},
},
"Variables": Object {
"Object's variable": Object {
"displayedName": "Object's variable",
"fullGroupName": "Common expressions for all objects/Variables",
"name": "Variable",
"type": "Variable",
},
"Object's variable number of children": Object {
"displayedName": "Object's variable number of children",
"fullGroupName": "Common expressions for all objects/Variables",
"name": "VariableChildCount",
"type": "VariableChildCount",
},
},
"Visibility": Object {
"Z order": Object {
"displayedName": "Z order",
"fullGroupName": "Common expressions for all objects/Visibility",
"name": "ZOrder",
"type": "ZOrder",
},
},
},
"Sprite": Object {
"Animations and images": Object {
"Animation": Object {
"displayedName": "Animation",
"fullGroupName": "Sprite/Animations and images",
"name": "Animation",
"type": "Animation",
},
"Animation speed scale": Object {
"displayedName": "Animation speed scale",
"fullGroupName": "Sprite/Animations and images",
"name": "AnimationSpeedScale",
"type": "AnimationSpeedScale",
},
"Image": Object {
"displayedName": "Image",
"fullGroupName": "Sprite/Animations and images",
"name": "Sprite",
"type": "Sprite",
},
},
"Direction": Object {
"Direction": Object {
"displayedName": "Direction",
"fullGroupName": "Sprite/Direction",
"name": "Direction",
"type": "Direction",
},
},
"Position": Object {
"X position of a point": Object {
"displayedName": "X position of a point",
"fullGroupName": "Sprite/Position",
"name": "PointX",
"type": "PointX",
},
"Y position of a point": Object {
"displayedName": "Y position of a point",
"fullGroupName": "Sprite/Position",
"name": "PointY",
"type": "PointY",
},
},
"Size": Object {
"Scale of the height of an object": Object {
"displayedName": "Scale of the height of an object",
"fullGroupName": "Sprite/Size",
"name": "ScaleY",
"type": "ScaleY",
},
"Scale of the width of an object": Object {
"displayedName": "Scale of the width of an object",
"fullGroupName": "Sprite/Size",
"name": "ScaleX",
"type": "ScaleX",
},
},
},
"Text object": Object {
"Opacity": Object {
"Opacity": Object {
"displayedName": "Opacity",
"fullGroupName": "Text object/Opacity",
"name": "Opacity",
"type": "Opacity",
},
},
"Rotation": Object {
"Angle": Object {
"displayedName": "Angle",
"fullGroupName": "Text object/Rotation",
"name": "Angle",
"type": "Angle",
},
},
},
}
`;

View File

@@ -0,0 +1,141 @@
import React, { Component } from 'react';
import { List, ListItem, makeSelectable } from 'material-ui/List';
import SearchBar from 'material-ui-search-bar';
import keys from 'lodash/keys';
import muiThemeable from 'material-ui/styles/muiThemeable';
const SelectableList = makeSelectable(List);
const styles = {
searchBar: {
margin: '0 auto',
backgroundColor: 'transparent',
},
groupListItemNestedList: {
padding: 0,
},
};
class ThemableInstructionOrExpressionSelector extends Component {
state = {
search: '',
searchResults: [],
};
componentDidMount() {
this._searchBar.focus();
}
focus = () => {
if (this._searchBar) this._searchBar.focus();
};
_matchCritera(instructionInfo, lowercaseSearch) {
const { displayedName, fullGroupName } = instructionInfo;
return (
displayedName.toLowerCase().indexOf(lowercaseSearch) !== -1 ||
fullGroupName.toLowerCase().indexOf(lowercaseSearch) !== -1
);
}
_computeSearchResults = search => {
const lowercaseSearch = this.state.search.toLowerCase();
return keys(this.props.instructionsInfo)
.map(key => {
return this.props.instructionsInfo[key];
})
.filter(instructionInfo =>
this._matchCritera(instructionInfo, lowercaseSearch)
);
};
_onSubmitSearch = () => {
const { searchResults } = this.state;
if (!searchResults.length) return;
this.props.onChoose(searchResults[0].type, searchResults[0]);
};
_renderTree(instructionInfoTree) {
const { muiTheme } = this.props;
return Object.keys(instructionInfoTree).map(key => {
const instructionOrGroup = instructionInfoTree[key];
if (instructionOrGroup.hasOwnProperty('type')) {
return (
<ListItem
key={key}
primaryText={key}
value={instructionOrGroup.type}
onClick={() => {
this.props.onChoose(instructionOrGroup.type, instructionOrGroup);
}}
/>
);
} else {
return (
<ListItem
key={key}
style={{borderBottom: `1px solid ${muiTheme.listItem.separatorColor}`}}
nestedListStyle={styles.groupListItemNestedList}
primaryText={<div style={{color: muiTheme.listItem.groupTextColor}}>{key}</div>}
primaryTogglesNestedList={true}
autoGenerateNestedIndicator={true}
nestedItems={this._renderTree(instructionOrGroup)}
/>
);
}
});
}
_renderSearchResults = () => {
return this.state.searchResults.map(instructionInfo => {
return (
<ListItem
key={instructionInfo.type}
style={styles.listItem}
primaryText={instructionInfo.displayedName}
secondaryText={instructionInfo.fullGroupName}
value={instructionInfo.type}
onClick={() => {
this.props.onChoose(instructionInfo.type, instructionInfo);
}}
/>
);
});
};
render() {
const { muiTheme, selectedType, instructionsInfoTree, style } = this.props;
return (
<div
style={{
backgroundColor: muiTheme.list.itemsBackgroundColor,
...style,
}}
>
<SearchBar
onChange={text =>
this.setState({
search: text,
searchResults: this._computeSearchResults(text),
})}
onRequestSearch={this._onSubmitSearch}
style={styles.searchBar}
ref={searchBar => (this._searchBar = searchBar)}
/>
<SelectableList value={selectedType}>
{this.state.search
? this._renderSearchResults()
: this._renderTree(instructionsInfoTree)}
</SelectableList>
</div>
);
}
}
const InstructionOrExpressionSelector = muiThemeable()(
ThemableInstructionOrExpressionSelector
);
export default InstructionOrExpressionSelector;

View File

@@ -49,7 +49,7 @@ export default class InstructionParametersEditor extends Component {
<EmptyMessage>
{this.props.isCondition
? 'Choose a condition on the left'
: 'Choose a condition on the right'}
: 'Choose an action on the left'}
</EmptyMessage>
</div>
);
@@ -98,12 +98,13 @@ export default class InstructionParametersEditor extends Component {
project={project}
layout={layout}
value={instruction.getParameter(i)}
instruction={instruction}
instructionOrExpression={instruction}
key={i}
onChange={value => {
instruction.setParameter(i, value);
this.forceUpdate();
}}
parameterRenderingService={ParameterRenderingService}
/>
);
})}

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