Compare commits

...

314 Commits

Author SHA1 Message Date
AlexandreS
13cf9b1d0b Show error box when there's an error logging in with provider (#6284)
Do not show in changelog
2024-01-26 16:28:48 +01:00
Clément Pasteau
6fc0198298 Bump version to 5.3.188 (#6283) 2024-01-26 16:26:11 +01:00
github-actions[bot]
0999ba611d Update translations [skip ci] (#6265)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2024-01-26 16:25:56 +01:00
Clément Pasteau
119d1af76a Game template bundles can be displayed and bought (#6275)
Do not show in changelog
2024-01-26 16:17:58 +01:00
AlexandreS
d6b4dacb1e Add possibility to sign up using Google, GitHub and Apple (#6249) 2024-01-26 15:46:18 +01:00
D8H
9edb3cfe91 Fix missing collision masks on new Sprite objects (#6282) 2024-01-26 15:25:12 +01:00
D8H
d44a1c3537 Generate particle textures only once (#6280) 2024-01-26 14:33:52 +01:00
AlexandreS
8cc84e5728 Fix legacy plans not showing in the user profile
Also: adds back the possibility to cancel a legacy subscription
2024-01-26 14:17:59 +01:00
D8H
76130b43d4 Handle export of assets with animations sharing frames (#6274) 2024-01-26 13:46:42 +01:00
D8H
40b0823b91 Fix a memory leak in the 2D particle emitter (#6278) 2024-01-26 12:23:13 +01:00
D8H
9b447e08e2 Forbid life-cycle functions to be renamed (#6269) 2024-01-25 14:53:07 +01:00
Aurélien Vivet
10314a1911 Fix focus not set in deletion confirmation dialog (#6270) 2024-01-25 14:16:17 +01:00
D8H
d0195719c2 Fix the 2 columns layout that may show hidden properties (#6268) 2024-01-25 12:18:52 +01:00
D8H
dd8c040381 Fix a crash when displaying the contextual menu of titles in object group lists (#6263) 2024-01-25 11:45:17 +01:00
D8H
5e0c8a92aa Fix a crash in the sprite editor of a child-object (#6266) 2024-01-25 11:44:31 +01:00
github-actions[bot]
977c102ddc Update translations [skip ci] (#6264)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2024-01-25 10:58:54 +01:00
Clément Pasteau
849d79d5d7 Avoid creating multiple canvas for ThreeJS (#6259) 2024-01-25 10:58:24 +01:00
D8H
28f7c9ae0b Fix a crash when the scale tween action is used in a 2D game (#6260) 2024-01-25 10:49:34 +01:00
github-actions[bot]
38e58327fa Update translations [skip ci] (#6244)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2024-01-24 11:22:04 +01:00
Clément Pasteau
3cc29efaa2 Remove report button on editor crash to avoid cluttering Github issues (#6247)
Do not show in changelog
2024-01-24 11:14:38 +01:00
Clément Pasteau
65eb76fb57 use newer electron builder (#6227)
Co-authored-by: romw314 <106016361+romw314@users.noreply.github.com>
2024-01-24 11:14:19 +01:00
AlexandreS
9eb721662f Remove useless margins (#6242)
Don't show in changelog
2024-01-23 14:22:39 +01:00
github-actions[bot]
b10b131010 Update translations [skip ci] (#6238)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2024-01-23 14:13:37 +01:00
Tristan Rhodes
b5fd1bb351 Improved wording "Stop music and sounds at the beginning of this scene" (#6240) 2024-01-23 14:05:31 +01:00
Aurélien Vivet
f1c9521625 Fix sentence of the Draw a Torus action (#6239) 2024-01-23 11:00:28 +01:00
github-actions[bot]
6ceb3c2c10 Update translations [skip ci] (#6234)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2024-01-22 15:38:34 +01:00
AlexandreS
43827876cd Fix subscription changes (#6236)
Don't show in changelog
2024-01-22 15:37:15 +01:00
AlexandreS
62b746300a Adapt credit banner button color to each theme (#6237)
Don't show in changelog
2024-01-22 15:29:33 +01:00
AlexandreS
769ebcd91c Fix credit banner text color on light themes (#6235)
Don't show in changelog
2024-01-22 11:12:57 +01:00
Florian Rival
70d5de16bf Fix Windows code signing (#6233) 2024-01-21 22:03:44 +01:00
Clément Pasteau
7fbe1bd23d Bump version to 5.3.187 (#6214) 2024-01-18 17:58:12 +01:00
github-actions[bot]
de8a679e31 Update translations [skip ci] (#6217)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2024-01-18 17:57:31 +01:00
Clément Pasteau
a1a4029b35 Introduce GDevelop credits and Marketing Campaigns (#6209)
* Credits can now be purchased from your Profile
* They can be used to purchase marketing campaigns, in order to promote your game on gd.games, with GDevelop social networks, or even within GDevelop itself.
* In the upcoming months, credits will unlock more and more exclusive perks in the app
2024-01-18 17:20:28 +01:00
Clément Pasteau
a377467031 Revert "Type JsExtensions files with typescript" (#6220)
Don't show in changelog
2024-01-18 16:01:28 +01:00
AlexandreS
dd090fd1d7 Abort login with providers (#6215)
Don't show in changelog
2024-01-18 12:32:01 +01:00
D8H
26ee9b3891 Avoid to add useless attributes on serialized extensions (#6216)
Don't show in changelog
2024-01-18 09:53:53 +01:00
Arthur Pacaud (arthuro555)
ad18eab4ae Type JsExtension.js files with TypeScript (#6200)
* Also improve typing of the C++ Core classes in TypeScript.

Only show in developer changelog
2024-01-18 09:53:24 +01:00
github-actions[bot]
007fc36a2e Update translations [skip ci] (#6184)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2024-01-18 09:36:40 +01:00
D8H
c8ebfde85b Fix platformer characters turn back speed when the deceleration is greater than the acceleration (#6208) 2024-01-17 18:32:14 +01:00
Vladyslav Pohorielov
d0005ba2cb Add support for Spine objects (2D skeletal animation) (#5927)
* This allows to use animatable 2D objects created with Spine (purchase a licence on [their website](https://esotericsoftware.com/) if you're interested).
* 2D skeletal animation allows for very smooth animations, and keep resource usage low compared to animations made of frames like Sprite objects. It's perfect for 2D games and can be used for animated characters, avatars, UI elements.
* Many thanks to @f0nar and @LousyMolars for their work on this new feature and associated game examples!

---------

Co-authored-by: Vladyslav Pohorielov <vpohorielov@playtika.com>
Co-authored-by: Gleb Volkov <glebusheg@gmail.com>
Co-authored-by: Florian Rival <Florian.rival@gmail.com>
Co-authored-by: Davy Hélard <davy.helard@gmail.com>
2024-01-17 18:19:08 +01:00
D8H
f623b352ee Hide deprecated properties of the platformer character behind a button (#6199) 2024-01-17 10:53:55 +01:00
D8H
16e2d8a005 Move some properties of the platformer character behavior in 2 columns (#6197) 2024-01-16 12:22:13 +01:00
D8H
7654883cb1 Allow to use orthographic camera in 3D layers (#6187) 2024-01-15 23:05:23 +01:00
D8H
3ae5db2a49 Fix scale and camera zoom tweens from setting NaN when the initial value was 0 (#6178) 2024-01-15 14:44:10 +01:00
Arthur Pacaud (arthuro555)
9ed002c879 Add expression to read the authenticated user unique identifier (#6192)
* This is useful if you want to interface with a third party or in-house solution to store user data.
2024-01-15 14:00:14 +01:00
Florian Rival
d4283c2350 Add new icon for 3D model objects (#6186) 2024-01-12 18:24:43 +01:00
AlexandreS
5a176d21e7 Add SSO to login/sign up (#6167)
Feature hidden in live environment.

Don't show in changelog
2024-01-12 14:45:16 +01:00
D8H
bfdfd7f0fb Improve the new UI of the extension editor (#6165)
- Add icons for behavior and object in the function tree
- Move the indicators on the right.
- Remove the big button.
- Fix the effect icon.
- Add checkboxes for the visibility.
- Rename on double click.
- Fix indentation.
- Hide the 3 dots button.

- Don't show in changelog
2024-01-12 13:11:16 +01:00
AlexandreS
aae75f2232 Fix use of Warning icon in tooltips (#6183)
Don't show in changelog
2024-01-12 13:04:20 +01:00
github-actions[bot]
977092c0a3 Update translations [skip ci] (#6179)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2024-01-12 12:38:05 +01:00
D8H
556688cedb Add pagination to asset store results (#6174) 2024-01-11 17:52:46 +01:00
github-actions[bot]
ce93dc5310 Update translations [skip ci] (#6170)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2024-01-10 15:57:04 +01:00
Clément Pasteau
d6d425db4f Show premium game templates from the same author on a template's page (#6168) 2024-01-10 15:42:51 +01:00
AlexandreS
2737e75639 Fix merge issue (#6169)
Don't show in changelog
2024-01-10 15:35:31 +01:00
github-actions[bot]
36eab18133 Update translations [skip ci] (#6164)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2024-01-09 16:30:55 +01:00
Clément Pasteau
48acbb12ee Decode filenames from any URLs before using them (#6018)
Only show in developer changelog
2024-01-09 16:00:09 +01:00
Florian Rival
21904e46f1 Avoid an extra click to purchase an asset pack or game template (#6161) 2024-01-09 15:32:33 +01:00
AlexandreS
94753ac053 Get subscription plans from server (#6128)
Don't show in changelog
2024-01-09 15:28:28 +01:00
github-actions[bot]
b160ee9b27 Update translations [skip ci] (#6147)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2024-01-09 15:15:24 +01:00
D8H
f76e8a72b6 Make the function property editor better adapt to small size (#6158) 2024-01-08 18:11:18 +01:00
D8H
5bc342688d Fix the cameras position to keep them centered on the object they follow when the game resolution is changed (#3821)
- Also fix the camera position for pixel-art games when a zoom and a game resolution change is used to have big pixels
2024-01-08 12:43:50 +01:00
D8H
3e6204c0eb Remember the size of mosaic nodes when they are uncollapsed (#6156)
- Don't show in changelog
2024-01-08 12:42:31 +01:00
D8H
7b1c340ad0 Fix game crashing at loading when video files are missing (#6155) 2024-01-08 12:08:48 +01:00
D8H
fac724dc3f Fix the indentation of extensions exported from the web-app (#6148) 2024-01-05 11:47:54 +01:00
D8H
9b4151f64c Add a button to export all the objetcs of a scene to submit them to the asset store (#4848)
* The dialog can be reached from the hidden drop-down menu of "Scene objects".
2024-01-05 11:09:27 +01:00
github-actions[bot]
6b5ab6c811 Update translations [skip ci] (#6130)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2024-01-05 10:06:13 +01:00
Clément Pasteau
5943092b0c Fix correctly taking folders into account when adding assets from the Home Store (#6146) 2024-01-05 10:05:44 +01:00
D8H
deb0c5ffc3 Display events functions of extensions in a tree view (#6133) 2024-01-04 18:14:44 +01:00
Clément Pasteau
c56fa03bf6 Fix crash when trying to access a child of a non existing structure child in an expression (#6144) 2024-01-04 16:20:49 +01:00
D8H
0d49d449db Import several animations to a sprite object in one go (#6035)
- Sprite animations are automatically created when several image files are selected and they ends with an animation name and an optional frame index (most naming conversions should work).
2024-01-04 11:02:04 +01:00
Clément Pasteau
fdd702cd09 Fix renaming a layer with external layouts (#6140)
* Instances of external layouts and of the attached scene are now all moved properly to the renamed layer
2024-01-04 10:31:34 +01:00
Clément Pasteau
5a8e4a7ca9 Allow confirming and canceling confirmation dialogs with keyboard shortcuts (#6138) 2024-01-03 14:52:36 +01:00
D8H
2e4e91c21e Add tween actions (position, rotation and depth) for 3D objects (#6122) 2024-01-02 10:03:59 +01:00
AlexandreS
6ece930809 Use axios error extractor where possible (#6132)
Only show in developer changelog
2023-12-27 19:40:16 +01:00
AlexandreS
c5baa81977 Display Game template categories with chips (#6129)
Don't show in changelog
2023-12-27 16:10:19 +01:00
github-actions[bot]
d24be38874 Update translations [skip ci] (#6109)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2023-12-27 10:35:14 +01:00
AlexandreS
1efffbbb78 Fix: Avoid project commit error by retrying call to server (#6127) 2023-12-26 12:12:25 +01:00
D8H
090d76a368 Show a drop-down list for string with choices parameters (#6120) 2023-12-22 15:31:43 +01:00
D8H
d44a9375de Add variables autocompletion for groups in variable fields (#6110) 2023-12-21 12:20:14 +01:00
AlexandreS
5a2a3893f9 Remove dashboard from profile dialog (#6113) 2023-12-21 12:08:23 +01:00
AlexandreS
0a6b0dc785 Use cloud storage provider internal name (#6108)
Don't show in changelog
2023-12-20 12:15:04 +01:00
github-actions[bot]
02e99726dc Update translations [skip ci] (#6103)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2023-12-20 11:55:13 +01:00
AlexandreS
843055d8df Add error message when versions fail to load (#6105)
Don't show in changelog
2023-12-20 11:40:07 +01:00
AlexandreS
f7fda5cb5e Fix placeholder thumbnail in objects lists for prefabs (#6107) 2023-12-20 11:37:28 +01:00
AlexandreS
e529642aec Fix program opening count logic when user has never acknowledged the feature (#6106)
Don't show in changelog
2023-12-20 10:01:35 +01:00
AlexandreS
c8e10d7043 Bump newIDE version (#6104) 2023-12-19 12:25:29 +01:00
AlexandreS
5f0de0e9a7 Adapt extract changelog script to currently used format (#6102)
Only show in developer changelog
2023-12-19 12:18:25 +01:00
github-actions[bot]
e51638ce4b Update translations [skip ci] (#6101)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2023-12-19 12:17:54 +01:00
AlexandreS
0fbd6a606a Add possibility to restore a previous version of a cloud project (#6022)
- Display all the project versions (paginated)
- Allow to open any version and name it for better tracking
- Make it possible to restore a given version
2023-12-19 11:50:28 +01:00
github-actions[bot]
2fa543c3db Update translations [skip ci] (#6083)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2023-12-18 16:56:41 +01:00
AlexandreS
38e35c9695 Prevent crash on new object dialog (#6099) 2023-12-18 16:50:21 +01:00
Florian Rival
ad13a1a101 Fix changes done in an extension not applied when clicking on 'Share' immediately without a preview or navigation in the editor (#6098) 2023-12-18 16:02:27 +01:00
AlexandreS
cb9d98d027 Wait 10 program openings before displaying GamesDashboard info (#6097)
Don't show in changelog
2023-12-18 15:51:23 +01:00
D8H
064c3f1572 Fix warning in the object list (#6091)
- don't show in changelog
2023-12-15 18:46:40 +01:00
D8H
9e5320f9d4 Add an item menu to add sub-folders in object folders (#6090) 2023-12-15 17:56:19 +01:00
Clément Pasteau
a1826d355d Rework subscriptions display in Profile (#6089) 2023-12-15 17:27:48 +01:00
D8H
32a3a094d1 Add new objects under the selected one in the list (#6087) 2023-12-15 16:51:50 +01:00
D8H
ee7dc2654b Fix parameter value choices autocompletion (#6086) 2023-12-15 12:02:24 +01:00
Clément Pasteau
64ffad3c0a Bump to 5.3.185 (#6082) 2023-12-14 14:06:01 +01:00
github-actions[bot]
dcc62f078f Update translations [skip ci] (#6076)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2023-12-14 13:54:43 +01:00
D8H
d920f05dbc Fix animation scanning in the 3D model object editor (#6080) 2023-12-14 13:28:38 +01:00
AlexandreS
ac82be800b Fix: update file metadata when saving cloud project triggers new commit (#6074) 2023-12-13 16:50:50 +01:00
Aurélien Vivet
2c92ae4042 Fix typo (#6075) 2023-12-13 16:35:36 +01:00
Clément Pasteau
465e934605 Bump version to 5.3.184 (#6072) 2023-12-13 14:34:04 +01:00
github-actions[bot]
248ba7675e Update translations [skip ci] (#6070)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2023-12-13 14:19:15 +01:00
AlexandreS
fd2b59ba45 Revert "Use python3 to update bindings (#6068)" (#6071)
This reverts commit 6edf63e98f.

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

Only show in developer changelog
2023-11-22 17:19:13 +01:00
AlexandreS
4d8cf56922 Do not change homepage tab at opening if an item from the asset store is requested (#5936) 2023-11-22 16:29:44 +01:00
github-actions[bot]
1a6e0ba5a1 Update translations [skip ci] (#5918)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2023-11-22 14:04:18 +01:00
AlexandreS
ec1ebcbf5b Fix desktop app not opening on Mac (#5934)
Don't show in changelog
2023-11-22 11:48:07 +01:00
Arthur Pacaud (arthuro555)
4ee9ccd7a9 Allow using resources as behavior properties (#5256)
Only show in developer changelog
2023-11-22 11:17:35 +01:00
AlexandreS
1ac248bfa4 Replace parcel watcher with chokidar (#5932) 2023-11-22 09:35:29 +01:00
D8H
65b78d4db7 Fix duplication of the "create" action in the search results (#5930) 2023-11-21 18:14:35 +01:00
D8H
639d90d743 Move deprecated physics actions at the bottom of search results (#5925) 2023-11-21 18:13:38 +01:00
D8H
45d0a78656 Simplify the wording of some actions names (#5929) 2023-11-21 18:12:09 +01:00
Florian Rival
0a0811e355 Fix crash with 'Put the object around another' action when target object was not existing (#5931) 2023-11-21 17:11:37 +01:00
D8H
a8f9df3dac Use descriptions to search actions and conditions (#5928) 2023-11-21 10:36:54 +01:00
Clément Pasteau
35db56d778 Fix Debugger sometimes crashing (#5926)
* Remove pixi effects renderer from the debugger payload, which was causing it to fail.
2023-11-20 16:06:41 +01:00
D8H
27efe8e3dd Improving the grouping of some behaviors actions and conditions (#5923) 2023-11-19 16:13:19 +01:00
github-actions[bot]
3584ee2aaf Update translations (#5916) 2023-11-17 14:49:31 +01:00
D8H
19a762fb60 Fix a memory and CPU leak when a finished tween is replaced (#5917) 2023-11-17 11:44:12 +01:00
AlexandreS
8a6dd8b940 Bump newIDE version (#5914) 2023-11-17 09:55:10 +01:00
AlexandreS
2173b49b19 New get started flow and customize content based on user survey (#5781) 2023-11-17 09:54:49 +01:00
github-actions[bot]
caf2752acb Update translations [skip ci] (#5912)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-11-16 17:35:40 +01:00
Clément Pasteau
e3291b515d Check existence of elements before destroying (#5913)
Do not show in changelog
2023-11-16 15:55:58 +01:00
Clément Pasteau
c1627b5ab2 Add error id to issue to fully track its trace (#5909)
Do not show in changelog
2023-11-16 11:55:47 +01:00
github-actions[bot]
7db5b97c45 Update translations [skip ci] (#5905)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2023-11-16 10:03:50 +01:00
D8H
425a0e92ca Fix camera flickering when "pixel rounding" is enabled (#5907) 2023-11-16 15:48:47 +09:00
AlexandreS
c2dfe579af Exclude project files (when configured to save scenes and events in different files) from watcher (#5885)
Also, exclude git files when using git versioning.
2023-11-15 16:15:15 +01:00
AlexandreS
4ea6fb78bd Improve objects list (#5895)
- Remove spell check on inputs
- Unselect item when a parent is closed (and the item becomes not visible)
- Allow to remove folder with all the objects contained in it (recursively)
- Improve selected row color 
- Add hover effects on rows
2023-11-15 09:53:15 +01:00
github-actions[bot]
8e668db6ea Update translations [skip ci] (#5900)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2023-11-14 17:59:53 +01:00
D8H
88063a4cad Show instructions about objects with the other object instructions (#5828) 2023-11-13 17:05:49 +01:00
AlexandreS
b180f5032e Fix getThumbanil static methods (#5902)
Don't show in changelog
2023-11-13 13:59:04 +01:00
AlexandreS
29e7f7d7a4 Remove build section ref and effect (#5901)
Don't show in changelog
2023-11-13 10:07:22 +01:00
AlexandreS
547f1e4bce Prevent dragging of treeview row placeholder item (#5899) 2023-11-13 09:42:18 +01:00
github-actions[bot]
9e176d91ec Update translations [skip ci] (#5879)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2023-11-13 09:41:27 +01:00
Mehrab
cac59d4727 Fix the grid/mask checkbox not updated when toggled on the web-app (#5897) 2023-11-12 17:51:34 +01:00
D8H
4f98ffa9ab Fix memory some memory leaks in the scene editor (#5853) 2023-11-10 16:14:54 +01:00
Clément Pasteau
7229406cfb Add errorBoundaries on all main components to prevent crashing the whole app (#5889)
Do not show in changelog
2023-11-10 10:04:01 +01:00
Clément Pasteau
d39cfd16a2 Prevent possible crashes of the editor in multiple part of the app (#5883)
Do not show in changelog
2023-11-09 17:02:11 +01:00
AlexandreS
2965f374bb Improve resources watching performance (#5882)
- Try at reducing Windows devices resources use
- Remove any `.git` folder from being watched (for project using versioning with git)
2023-11-08 09:17:11 +01:00
Clément Pasteau
2d5b2a49e7 Fix a possible crash of the instance editor when updating a sprite (#5881)
* Fix calling methods on Texture frame which can be null when an image is updated
2023-11-07 11:24:57 +01:00
Clément Pasteau
05df9cb599 Show back preview of list item on drag instead of full row for objects and groups (#5880) 2023-11-06 17:23:36 +01:00
D8H
7b28d2525e Fix a broken link in the extension list page (#5878) 2023-11-06 10:06:54 +01:00
github-actions[bot]
533e61cdbc Update translations [skip ci] (#5869)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-11-06 09:29:39 +01:00
Clément Pasteau
3a823113f1 Improve issue template (#5872)
Do not show in changelog
2023-11-03 17:17:15 +01:00
Clément Pasteau
17bc8b584d Show error in error boundary for easier debugging (#5855)
Do not show in changelog
2023-11-03 17:14:25 +01:00
AlexandreS
dbaf8a6569 Clear selection before updating list (#5873)
Don't show in changelog
2023-11-03 17:12:32 +01:00
Clément Pasteau
fcbc538c30 Reduce logs for web previews (#5854)
Do not show in changelog
2023-11-03 16:29:37 +01:00
Clément Pasteau
decd96c52f Only allow opening project folder in Resources tab if local project (#5867) 2023-11-02 18:04:20 +01:00
AlexandreS
8dbf9c99ce Bump newIDE version (#5868) 2023-11-02 14:35:50 +01:00
D8H
1318523804 Fix a crash when objects are used after being deleted (#5865) 2023-11-02 13:09:54 +01:00
github-actions[bot]
8ef5e2c1ed Update translations [skip ci] (#5850)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2023-11-02 11:50:33 +01:00
AlexandreS
d945426068 Fix custom loading screen not displaying (#5864) 2023-11-02 11:34:45 +01:00
AlexandreS
8eb07a492f Fix treeview search crash when there is no global object (#5866) 2023-11-02 11:14:39 +01:00
Florian Rival
37c9dd95ce Allow installing up to 200 assets at the same time (#5851) 2023-11-02 11:02:40 +01:00
AlexandreS
a6d837cbe6 Use new electron notarize tool (#5857)
Only show in developer changelog
2023-10-31 13:36:10 +01:00
AlexandreS
f6539626d3 Fix cloud projects preview (#5852) 2023-10-30 16:53:22 +01:00
AlexandreS
e0df9b0ff3 Bump newIDE version (#5849) 2023-10-30 15:21:43 +01:00
Jake
0b50231027 Update Solarized Dark theme mosaic toolbar color (#5809) 2023-10-30 15:11:15 +01:00
github-actions[bot]
94c9924a88 Update translations [skip ci] (#5848) 2023-10-30 14:54:33 +01:00
D8H
581cc5837a Fix some memory leaks in 2D object renderers (#5843) 2023-10-30 14:42:15 +01:00
TRP
ff77d107b2 Add new shape in shape painter: polygon (#5836) 2023-10-30 14:28:38 +01:00
AlexandreS
3a5198cb34 Remove possibility to rename item on click a short delay after item has been selected (#5847)
Users noticed that they unwillingly edited object names when they only wanted to click/double-click/drag the object
2023-10-30 13:53:27 +01:00
github-actions[bot]
c92025549d Update translations [skip ci] (#5811)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2023-10-30 09:53:06 +01:00
Florian Rival
600b307e7e Fix AppVeyor build (Python 3.12 not having distutils anymore) (#5846)
Don't show in changelog
2023-10-29 18:26:43 +01:00
D8H
297fade0bd Remove unused imports (#5844)
Don't show in changelog
2023-10-28 14:35:46 +02:00
D8H
84d7500eab Add the 3D particle emitter in the object list (#5840) 2023-10-27 11:51:05 +02:00
D8H
f99c4ab948 Move the Z-order action and condition to the "Layer" group (#5834) 2023-10-26 11:02:47 +02:00
D8H
5fe4f23c83 Improve the search to give as many results as possible (#5839) 2023-10-26 11:01:57 +02:00
Florian Rival
8820bd45e4 Fix typo 2023-10-26 09:09:55 +02:00
D8H
5e16968f37 Fix "Scale Z" group in actions/conditions editor (#5835) 2023-10-25 18:09:39 +02:00
TRP
6b6179ff22 Add new shape in Shape Painter: Chamfer rectangle (#5817) 2023-10-25 17:55:42 +02:00
AlexandreS
94bcd87a9f Add stop method on video and use it when object is destroyed (#5833)
Fixes bug where video are not restarted when switching scenes
2023-10-25 13:15:10 +02:00
D8H
356a1974ef Improve descriptions for impulse and force actions of the Physics behavior (#5832) 2023-10-25 12:38:09 +02:00
AlexandreS
345bad9876 Clean forgotten import (#5831)
Don't show in changelog
2023-10-25 11:17:54 +02:00
AlexandreS
0b8d843a73 Mobile version optimizations (#5830)
Fixes a bug where scene toolbar does not update on instance selection.
Don't show in changelog
2023-10-25 09:35:45 +02:00
D8H
9f0c987ec7 Fix scale actions group (#5825) 2023-10-23 17:05:11 +02:00
D8H
0e79209d2b Fix a regression on the object selection in the instruction editor (#5824)
- don't show in changelog
2023-10-23 12:26:57 +02:00
D8H
f991c09c39 Fix a crash when only the Ease expression is used from Tween extension (#5821) 2023-10-23 09:57:22 +02:00
D8H
6e24dfa9b8 Fix object selection for deprecated instructions (#5816) 2023-10-20 17:13:20 +02:00
D8H
a3cd00dc94 Fix a typo in the scale tween action (#5815) 2023-10-20 15:47:29 +02:00
Florian Rival
300c011151 Bump newIDE version (#5813) 2023-10-20 15:24:18 +02:00
D8H
230d410469 Fix missing exported files for scene tweens (#5812) 2023-10-20 15:20:16 +02:00
D8H
402e54f706 Fix the Ease expression of the Tween extension (#5810)
Don't show in changelog
2023-10-20 11:07:19 +02:00
github-actions[bot]
dc29c27272 Update translations [skip ci] (#5803) 2023-10-20 09:01:47 +02:00
Florian Rival
462bb84c51 Fix exporting not shown on iOS (#5808) 2023-10-19 17:26:25 +02:00
AlexandreS
a775e79bae Catch instance rendering process in scene editor (#5806)
Don't show in changelog
2023-10-19 16:32:57 +02:00
Florian Rival
bc8c867f23 Fix parameters not accessible in expressions for 'Action With Operator' functions (#5805) 2023-10-19 15:55:55 +02:00
AlexandreS
f34bf2b7b4 Focus confirm delete button (#5804)
Don't show in changelog
2023-10-19 15:52:18 +02:00
Florian Rival
e2d482e1aa Fix long formulas/expressions going out of the screen in the events sheet 2023-10-19 14:51:21 +02:00
AlexandreS
0724ae34d9 Use electron remote shell to open project folder (#5801)
Don't show in changelog
2023-10-19 14:47:31 +02:00
github-actions[bot]
2fc1c2fd86 Update translations [skip ci] (#5774) 2023-10-19 12:19:58 +02:00
D8H
9a42ae11ad Tween extension rework (#5710)
Fix bugs and change implementation for a better maintainability.
2023-10-19 11:24:32 +02:00
Florian Rival
32773e06f6 Improve error message when exception at runtime (#5800) 2023-10-19 10:20:08 +02:00
Florian Rival
00508df014 Don't show an undeclared variable as an error in action/condition editor 2023-10-18 17:22:07 +02:00
Florian Rival
c3cf5b7002 Display an error message in game when an exception/crash is detected (#5799) 2023-10-18 17:19:11 +02:00
AlexandreS
df7fab84b8 Bump newIDE version (#5798) 2023-10-18 16:39:32 +02:00
AlexandreS
95a0012c5e Load texture in runtime even if filename does not have an extension (#5797)
Don't show in changelog
2023-10-18 16:38:25 +02:00
AlexandreS
4bf644f9f2 Add "Help > About GDevelop" menu option for non-Mac users (#5796)
---------

Co-authored-by: Tristan Rhodes <tristan.rhodes@gmail.com>
2023-10-18 13:01:02 +02:00
AlexandreS
79cad1da3a Fix error message not cleared after choosing a color in a color picker (#5794) 2023-10-18 12:47:57 +02:00
AlexandreS
ffc6f5786b Do not clear selection when panning with middle mouse button (#5795) 2023-10-18 12:24:08 +02:00
Florian Rival
07df65f8c2 Fix a potential crash by ensuring selection of instances does not contain deleted global objects (#5791)
- Also keep the selection of objects/folders when changing between scene tabs (unless they have been deleted)
2023-10-18 11:12:19 +02:00
D8H
61f8f0ed66 Fix Bitmap text resource action parameter type (#5793) 2023-10-18 10:55:20 +02:00
D8H
c24e2e5ace Use similar wording for angle conditions (#5792)
* Don't show in changelog
2023-10-17 18:45:17 +02:00
D8H
25c72a6d1f Optimize evaluation of several forces on 1 object (#5789) 2023-10-17 17:09:27 +02:00
AlexandreS
6153b23e7d Improve data sent to automatic error report (#5790)
Also, attempt to fix a crash at GDevelop editor opening.
2023-10-17 16:48:47 +02:00
Florian Rival
1b7ccfded3 Fix warning 2023-10-17 14:00:37 +02:00
Florian Rival
ec57a0a80d Improve the Share/Publish dialog: larger window, clearer sections and wording (#5785) 2023-10-17 11:28:29 +02:00
D8H
95a9e37aba Add animation time control for sprites and 3D models (#5777) 2023-10-17 10:02:49 +02:00
D8H
787274f7fa Fix 3D distance sorting issues by using a higher default near plane (#5784) 2023-10-17 09:55:23 +02:00
D8H
ceb24f0edb Unify text expressions conditions and actions between objects (#5778) 2023-10-16 18:17:50 +02:00
D8H
028bf7a70d Fix Physics behavior forces effect under 60 fps (#5783) 2023-10-16 17:50:52 +02:00
AlexandreS
08471d0356 Use componentWillReceiveProps rather than componentWillUpdate (#5782)
Don't show in changelog
2023-10-16 17:15:27 +02:00
AlexandreS
921add6665 Organize your objects into folders (#5655)
You can now create folders as a means to organize your objects when editing a scene or an external layout.

Thanks to @Silver-Streak, Matt Pin, @arthuro555, SnailMail, @TheGemDev, Peeble, Rasterisko for all the help designing and testing the feature.

Note that object tags have been dropped in this version since they were not practical to use thus not widespread used.
2023-10-16 17:04:20 +02:00
AlexandreS
dabff06891 Add error message when trying to login without connection to internet (#5780) 2023-10-16 11:37:43 +02:00
AlexandreS
967033c407 Fix scene loading for video resources (#5772)
Listen error event on base texture loading
Also makes file watcher listen on addition and removal of files.
2023-10-13 15:03:57 +02:00
Florian Rival
0809749ddc Hide expressions GetArgumentAsString and GetArgumentAsNumber as they are now replaced by just being able to write the parameter name in an expression (#5773) 2023-10-13 10:14:28 +02:00
github-actions[bot]
8a27801355 Update translations [skip ci] (#5766)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2023-10-12 18:30:18 +02:00
AlexandreS
20cba9d7ed Fix linter and types (#5770)
Don't show in changelog
2023-10-12 18:00:31 +02:00
AlexandreS
3878b93c94 Fix: Prevent loading of forbidden GIF file from crashing a game loading screen (#5769) 2023-10-12 17:01:10 +02:00
D8H
645d5331cb Add a setting to show warnings about deprecated instructions (#5755) 2023-10-12 16:55:02 +02:00
AlexandreS
91db750632 Reload resources in the editor when the resource changes (#5631)
GDevelop now refreshes resources (images, videos, 3D models, fonts, etc.) when a change is detected on the filesystem so that you can immediately see the changes.

This allows to efficiently use GDevelop alongside other tools (Tiled or LDtk for instance).

Notes:
- This logically only affects the desktop version for projects saved on the filesystem;
- You can deactivate it in the preferences.
2023-10-12 16:01:01 +02:00
Clément Pasteau
740485b54a Activate support for unicode characters (including emojis) for all names (objects, groups, variables, functions, parameters) on all projects (#5703) 2023-10-12 15:25:37 +02:00
Florian Rival
e960fe7c5b Fix unicode/emojis not supported in function parameter names (#5768) 2023-10-12 15:20:12 +02:00
AlexandreS
0633c7b474 Fix: Prevent root variable from having white spaces (#5767) 2023-10-12 15:13:18 +02:00
AlexandreS
704bfd40cb Fix example opening wrongfully opening "Create from scratch" dialog (#5765) 2023-10-12 14:34:24 +02:00
github-actions[bot]
310abe7a13 Update translations [skip ci] (#5759)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2023-10-12 14:11:12 +02:00
Daniel R
b0f9bed273 Add string condition operators: starts with, ends with and contains (#4970)
- Thanks @danired
2023-10-12 13:33:32 +02:00
Clément Pasteau
766a62ad9b Invalidate cache when updating Panel Sprite opacity (#5764)
* This fixes a bug where creating a panel sprite with low opacity would prevent changing its opacity afterwards
2023-10-12 13:27:08 +02:00
D8H
ec6f669d94 Add HSL adjustment, motion blur and shockwave effects (#5760) 2023-10-12 10:49:29 +02:00
D8H
36a2408be0 Fix particles angle when the speed is negative (#5762) 2023-10-12 10:48:23 +02:00
Clément Pasteau
1343210a2b Fix "Locate file" for resource to open the folder in front of the app (#5758) 2023-10-11 15:52:27 +02:00
github-actions[bot]
1e76fedd7b Update translations [skip ci] (#5757)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-10-11 15:15:25 +02:00
Clément Pasteau
3923641988 Bump to 5.2.176 (#5756) 2023-10-11 10:42:18 +02:00
github-actions[bot]
a7d488626c Update translations [skip ci] (#5751)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-10-11 10:42:04 +02:00
Clément Pasteau
33f20b9bde Fix adding multiple assets after navigating into folder, correctly taking the assets into account (#5752) 2023-10-11 10:23:03 +02:00
AlexandreS
6c4cd6343f Fix Ctrl+Z closing project on Azerty keyboards (#5753)
Use event which property rather than code property when possible
2023-10-11 10:03:56 +02:00
Clément Pasteau
f1c04df0ee Do not sort animations by name in the Animations Dropdown for consistency (#5754) 2023-10-10 18:49:44 +02:00
D8H
9b466ff574 Fix particle emitter rotation speed (#5747) 2023-10-10 18:42:28 +02:00
Clément Pasteau
2b0969600e Deactivate accessibility in the editor too (#5750)
Do not show in changelog
2023-10-10 17:19:32 +02:00
github-actions[bot]
7ed1eee8f9 Update translations [skip ci] (#5748)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-10-10 17:11:17 +02:00
Clément Pasteau
9b9b729b42 Improve error message and component when crash (#5749)
Do not show in changelog
2023-10-10 15:27:23 +02:00
github-actions[bot]
530969d4b0 Update translations [skip ci] (#5735)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2023-10-10 14:19:42 +02:00
Clément Pasteau
11ef87b9ee Force window events to not be passive, so they can be prevented on mobile. (#5745)
This fixes a bug where mouse events where triggered on mobile, introducing unwanted interactions.
2023-10-10 14:17:16 +02:00
Clément Pasteau
e1857e2e1e Deactivate PixiJS accessibility plugin (#5746)
* For developer changelog
* Better remove it, as this isn't used and adds unnecessary elements in the Dom.
2023-10-10 14:16:57 +02:00
Florian Rival
d1385e5fc2 Fix completions in expressions not being case insensitive and limited for objects/variables/properties/parameters (#5742) 2023-10-10 10:48:33 +02:00
AlexandreS
9298026179 Add possibility to set Z offset when creating instances from external layout (#5704) 2023-10-10 09:40:10 +02:00
Florian Rival
913e367f6f Allow to properly use variables/properties/parameters in brackets to access a structure variable (#5738)
* This means expressions like `MyStructure[SomeIndexVariable]` will work properly for a structure if `SomeIndexVariable` is a string variable. Gdevelop now properly uses the type of the variable/property/parameter (to avoid considering what's inside the brackets as a number if it's a string). Remember to declare your variables in the variables editor so that you can use them in expressions.
2023-10-09 18:21:49 +02:00
AlexandreS
1bc27bdd8b Fix shape painter drawing stars crashing games 2023-10-09 10:55:04 +02:00
Florian Rival
4d8b39df95 Add more tests for new variable syntax
Don't show in changelog
2023-10-08 19:02:55 +02:00
Florian Rival
e6970319b9 Fix usage of string properties and parameters using the new simplified syntax (#5737)
* Properties or parameters holding a string where wrongly interpreted as a number using the new syntax.
2023-10-08 17:41:34 +02:00
Florian Rival
cdf21ebd4c Allow to use the new variable syntax for groups (#5730)
* You can write `MyObjectGroup.SomeVariable` in the expressions as long as all the objects of the groups have this variable declared in the objects variable editor.
2023-10-08 11:59:35 +02:00
1070 changed files with 105327 additions and 55701 deletions

View File

@@ -30,7 +30,7 @@ jobs:
- run:
name: Install Emscripten (for GDevelop.js)
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 1.39.6 && ./emsdk activate 1.39.6 && cd ..
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 3.1.21 && ./emsdk activate 3.1.21 && cd ..
# GDevelop.js dependencies
- restore_cache:
@@ -107,7 +107,7 @@ jobs:
- run:
name: Install Emscripten (for GDevelop.js)
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 1.39.6 && ./emsdk activate 1.39.6 && cd ..
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 3.1.21 && ./emsdk activate 3.1.21 && cd ..
- run:
name: Install system dependencies for Electron builder
@@ -127,7 +127,8 @@ jobs:
# Build GDevelop.js (and run tests to ensure it works)
- run:
name: Build GDevelop.js
command: cd GDevelop.js && source ../emsdk/emsdk_env.sh && npm run build && npm test && cd ..
# Use "--runInBand" as it's faster and avoid deadlocks on CircleCI Linux machines (probably because limited in processes number).
command: cd GDevelop.js && source ../emsdk/emsdk_env.sh && npm run build && npm test -- --runInBand && cd ..
# GDevelop IDE dependencies (after building GDevelop.js to avoid downloading a pre-built version)
- run:
@@ -184,7 +185,7 @@ jobs:
- run:
name: Install Emscripten (for GDevelop.js)
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 1.39.6 && ./emsdk activate 1.39.6 && cd ..
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 3.1.21 && ./emsdk activate 3.1.21 && cd ..
# GDevelop.js dependencies
- restore_cache:
@@ -200,7 +201,8 @@ jobs:
# Build GDevelop.js (and run tests to ensure it works)
- run:
name: Build GDevelop.js
command: cd GDevelop.js && source ../emsdk/emsdk_env.sh && npm run build && npm test && cd ..
# Use "--runInBand" as it's faster and avoid deadlocks on CircleCI Linux machines (probably because limited in processes number).
command: cd GDevelop.js && source ../emsdk/emsdk_env.sh && npm run build && npm test -- --runInBand && cd ..
- save_cache:
paths:

View File

@@ -0,0 +1,37 @@
name: 💥 Automatic crash report
description: Do not use this template for bug reports. This template is only for automatic crash reports.
body:
- type: textarea
id: description
attributes:
label: Describe what you were doing when the crash happened
description: If applicable, add screenshots to help explain your problem.
placeholder: |
1. Went to '...'
2. Clicked on '...'
3. Scrolled down to '...'
4. Saw error
- type: input
id: gdevelop_version
attributes:
label: GDevelop version
description: |
The version of GDevelop used. Leave the prefilled value.
validations:
required: true
- type: textarea
id: platform_info
attributes:
label: Platform info
description: |
The platform you are using GDevelop on. Leave the prefilled value.
- type: textarea
id: error_stack
attributes:
label: Additional error context
description: Additonal context about the problem. Leave the prefilled value.
- type: textarea
id: component_stack
attributes:
label: Additional component context
description: Additonal context about the problem. Leave the prefilled value.

View File

@@ -2,18 +2,21 @@ name: 🐛Bug report
description: Create a bug report about GDevelop or the game engine
body:
- type: checkboxes
id: searched_issues
attributes:
label: Is there an existing issue for this?
options:
- label: I have searched the [existing issues](https://github.com/4ian/GDevelop/issues)
required: true
- label: I have searched the [existing issues](https://github.com/4ian/GDevelop/issues)
required: true
- type: textarea
id: description
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: reproduction_steps
attributes:
label: Steps to reproduce
description: |
@@ -27,6 +30,7 @@ body:
validations:
required: true
- type: dropdown
id: platform
attributes:
label: GDevelop platform
description: Which platform of GDevelop are you using?
@@ -38,6 +42,7 @@ body:
validations:
required: true
- type: input
id: gdevelop_version
attributes:
label: GDevelop version
description: |
@@ -47,6 +52,7 @@ body:
validations:
required: true
- type: textarea
id: platform_info
attributes:
label: Platform info
value: |
@@ -66,6 +72,7 @@ body:
</details>
- type: textarea
id: additional_context
attributes:
label: Additional context
description: Add any other context about the problem here.

View File

@@ -14,7 +14,7 @@ tasks:
init: |
sudo apt-get update
sudo apt install cmake python-is-python3 python3-distutils -y
git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 1.39.6 && ./emsdk activate 1.39.6 && cd ..
git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 3.1.21 && ./emsdk activate 3.1.21 && cd ..
cd GDevelop.js
npm install
source ../emsdk/emsdk_env.sh && npm run build -- --dev

View File

@@ -39,7 +39,7 @@ install:
- cd ..
# Install Emscripten (for GDevelop.js)
- git clone https://github.com/juj/emsdk.git
- cd emsdk && ./emsdk install 1.39.6 && ./emsdk activate 1.39.6 && cd ..
- cd emsdk && ./emsdk install 3.1.21 && ./emsdk activate 3.1.21 && cd ..
# Install GDevelop.js dependencies
- cd GDevelop.js && npm install && cd ..
# Build GDevelop.js

View File

@@ -113,7 +113,8 @@
"memory_resource": "cpp",
"__bits": "cpp",
"__verbose_abort": "cpp",
"variant": "cpp"
"variant": "cpp",
"charconv": "cpp"
},
"files.exclude": {
"Binaries/*build*": true,

View File

@@ -164,7 +164,7 @@ void LinkEvent::UnserializeFrom(gd::Project& project,
}
bool LinkEvent::AcceptVisitor(gd::EventVisitor &eventVisitor) {
return BaseEvent::AcceptVisitor(eventVisitor) |
return BaseEvent::AcceptVisitor(eventVisitor) ||
eventVisitor.VisitLinkEvent(*this);
}

View File

@@ -46,10 +46,6 @@ void EffectsCodeGenerator::GenerateEffectsIncludeFiles(
// TODO Add unit tests on this function.
// TODO Merge with UsedExtensionsFinder.
// Default lights rely on the fact that UsedExtensionsFinder doesn't find
// extension usages for effects. This has the happy side effect of not
// including Three.js when no 3D object are in the scenes.
// We need to make something explicit to avoid future bugs.
// See also gd::Project::ExposeResources for a method that traverse the whole
// project (this time for resources) and

View File

@@ -69,8 +69,36 @@ gd::String EventsCodeGenerator::GenerateRelationalOperatorCall(
}
}
return callStartString + "(" + argumentsStr + ") " + relationalOperator +
" " + rhs;
auto lhs = callStartString + "(" + argumentsStr + ")";
return GenerateRelationalOperation(relationalOperator, lhs, rhs);
}
/**
* @brief Generate a relational operation
*
* @param relationalOperator the operator
* @param lhs the left hand operand
* @param rhs the right hand operand
* @return gd::String
*/
gd::String EventsCodeGenerator::GenerateRelationalOperation(
const gd::String& relationalOperator,
const gd::String& lhs,
const gd::String& rhs) {
return lhs + " " + GenerateRelationalOperatorCodes(relationalOperator) + " " + rhs;
}
const gd::String EventsCodeGenerator::GenerateRelationalOperatorCodes(const gd::String &operatorString) {
if (operatorString == "=") {
return "==";
}
if (operatorString != "<" && operatorString != ">" &&
operatorString != "<=" && operatorString != ">=" && operatorString != "!=" &&
operatorString != "startsWith" && operatorString != "endsWith" && operatorString != "contains") {
cout << "Warning: Bad relational operator: Set to == by default." << endl;
return "==";
}
return operatorString;
}
/**
@@ -638,18 +666,6 @@ gd::String EventsCodeGenerator::GenerateActionsListCode(
return outputCode;
}
const gd::String EventsCodeGenerator::GenerateRelationalOperatorCodes(const gd::String &operatorString) {
if (operatorString == "=") {
return "==";
}
if (operatorString != "<" && operatorString != ">" &&
operatorString != "<=" && operatorString != ">=" && operatorString != "!=") {
cout << "Warning: Bad relational operator: Set to == by default." << endl;
return "==";
}
return operatorString;
}
gd::String EventsCodeGenerator::GenerateParameterCodes(
const gd::Expression& parameter,
const gd::ParameterMetadata& metadata,
@@ -674,7 +690,7 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
argOutput =
GenerateObject(parameter.GetPlainString(), metadata.GetType(), context);
} else if (metadata.GetType() == "relationalOperator") {
argOutput += GenerateRelationalOperatorCodes(parameter.GetPlainString());
argOutput += parameter.GetPlainString();
argOutput = "\"" + argOutput + "\"";
} else if (metadata.GetType() == "operator") {
argOutput += parameter.GetPlainString();
@@ -698,6 +714,8 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
metadata.GetType() == "tilesetResource" ||
metadata.GetType() == "videoResource" ||
metadata.GetType() == "model3DResource" ||
metadata.GetType() == "atlasResource" ||
metadata.GetType() == "spineResource" ||
// Deprecated, old parameter names:
metadata.GetType() == "password" || metadata.GetType() == "musicfile" ||
metadata.GetType() == "soundfile" || metadata.GetType() == "police") {
@@ -1212,13 +1230,13 @@ gd::String EventsCodeGenerator::GeneratePropertyGetter(const gd::PropertiesConta
const gd::NamedPropertyDescriptor& property,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
return "getProperty" + property.GetName() + "()";
return "getProperty" + property.GetName() + "As" + type + "()";
}
gd::String EventsCodeGenerator::GenerateParameterGetter(const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
return "getParameter" + parameter.GetName() + "()";
return "getParameter" + parameter.GetName() + "As" + type + "()";
}
EventsCodeGenerator::EventsCodeGenerator(const gd::Project& project_,

View File

@@ -12,8 +12,8 @@
#include "GDCore/Events/Event.h"
#include "GDCore/Events/Instruction.h"
#include "GDCore/String.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/String.h"
namespace gd {
class EventsList;
class Expression;
@@ -58,8 +58,9 @@ class GD_CORE_API EventsCodeGenerator {
* \brief Construct a code generator for the specified
* objects/groups and platform
*/
EventsCodeGenerator(const gd::Platform& platform,
const gd::ProjectScopedContainers& projectScopedContainers_);
EventsCodeGenerator(
const gd::Platform& platform,
const gd::ProjectScopedContainers& projectScopedContainers_);
virtual ~EventsCodeGenerator(){};
/**
@@ -472,10 +473,15 @@ class GD_CORE_API EventsCodeGenerator {
*/
size_t GenerateSingleUsageUniqueIdForEventsList();
virtual gd::String GenerateRelationalOperation(
const gd::String& relationalOperator,
const gd::String& lhs,
const gd::String& rhs);
protected:
virtual const gd::String GenerateRelationalOperatorCodes(
const gd::String& operatorString);
protected:
/**
* \brief Generate the code for a single parameter.
*
@@ -546,7 +552,9 @@ class GD_CORE_API EventsCodeGenerator {
};
virtual gd::String GenerateVariableValueAs(const gd::String& type) {
return type == "string" ? ".getAsString()" : ".getAsNumber()";
return type == "number|string" ? ".getAsNumberOrString()"
: type == "string" ? ".getAsString()"
: ".getAsNumber()";
}
/**
@@ -577,14 +585,16 @@ class GD_CORE_API EventsCodeGenerator {
return "fakeObjectListOf_" + objectName;
}
virtual gd::String GeneratePropertyGetter(const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& type,
gd::EventsCodeGenerationContext& context);
virtual gd::String GeneratePropertyGetter(
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& type,
gd::EventsCodeGenerationContext& context);
virtual gd::String GenerateParameterGetter(const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context);
virtual gd::String GenerateParameterGetter(
const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context);
/**
* \brief Generate the code to reference an object which is
@@ -665,7 +675,8 @@ class GD_CORE_API EventsCodeGenerator {
* The default implementation generates C-style code : It wraps the predicate
* inside parenthesis and add a !.
*/
virtual gd::String GenerateNegatedPredicate(const gd::String& predicate) const {
virtual gd::String GenerateNegatedPredicate(
const gd::String& predicate) const {
return "!(" + predicate + ")";
};
@@ -726,6 +737,7 @@ class GD_CORE_API EventsCodeGenerator {
const std::vector<gd::String>& arguments,
const gd::String& callStartString,
std::size_t startFromArgument = 0);
gd::String GenerateOperatorCall(const gd::InstructionMetadata& instrInfos,
const std::vector<gd::String>& arguments,
const gd::String& callStartString,

View File

@@ -103,7 +103,7 @@ void ExpressionCodeGenerator::OnVisitVariableNode(VariableNode& node) {
// This "translation" from the type to an enum could be avoided
// if all types were moved to an enum.
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetObjectsContainersList(),
codeGenerator.GetProjectScopedContainers(),
rootType,
node);
@@ -191,7 +191,7 @@ void ExpressionCodeGenerator::OnVisitVariableBracketAccessorNode(
return;
}
ExpressionCodeGenerator generator("string", "", codeGenerator, context);
ExpressionCodeGenerator generator("number|string", "", codeGenerator, context);
node.expression->Visit(generator);
output +=
codeGenerator.GenerateVariableBracketAccessor(generator.GetOutput());
@@ -200,7 +200,7 @@ void ExpressionCodeGenerator::OnVisitVariableBracketAccessorNode(
void ExpressionCodeGenerator::OnVisitIdentifierNode(IdentifierNode& node) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetObjectsContainersList(),
codeGenerator.GetProjectScopedContainers(),
rootType,
node);
@@ -271,7 +271,7 @@ void ExpressionCodeGenerator::OnVisitIdentifierNode(IdentifierNode& node) {
void ExpressionCodeGenerator::OnVisitFunctionCallNode(FunctionCallNode& node) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetObjectsContainersList(),
codeGenerator.GetProjectScopedContainers(),
rootType,
node);
@@ -502,7 +502,7 @@ gd::String ExpressionCodeGenerator::GenerateDefaultValue(
void ExpressionCodeGenerator::OnVisitEmptyNode(EmptyNode& node) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetObjectsContainersList(),
codeGenerator.GetProjectScopedContainers(),
rootType,
node);
output += GenerateDefaultValue(type);
@@ -511,7 +511,7 @@ void ExpressionCodeGenerator::OnVisitEmptyNode(EmptyNode& node) {
void ExpressionCodeGenerator::OnVisitObjectFunctionNameNode(
ObjectFunctionNameNode& node) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetObjectsContainersList(),
codeGenerator.GetProjectScopedContainers(),
rootType,
node);
output += GenerateDefaultValue(type);

View File

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

View File

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

View File

@@ -115,21 +115,23 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAdvancedExtension(
.AddExpression(
"GetArgumentAsNumber",
_("Get function parameter value"),
_("Get function parameter (also called \"argument\") value."),
_("Get function parameter (also called \"argument\") value. You don't need this most of the time as you can simply write the parameter name in an expression."),
"",
"res/function16.png")
.AddParameter("functionParameterName", _("Parameter name"), "number,string,boolean")
.SetRelevantForFunctionEventsOnly();
.SetRelevantForFunctionEventsOnly()
.SetHidden();
extension
.AddStrExpression(
"GetArgumentAsString",
_("Get function parameter text"),
_("Get function parameter (also called \"argument\") text."),
_("Get function parameter (also called \"argument\") text. You don't need this most of the time as you can simply write the parameter name in an expression."),
"",
"res/function16.png")
.AddParameter("functionParameterName", _("Parameter name"), "number,string,boolean")
.SetRelevantForFunctionEventsOnly();
.SetRelevantForFunctionEventsOnly()
.SetHidden();
extension
.AddCondition(

View File

@@ -49,6 +49,7 @@ class GD_CORE_API BuiltinExtensionsImplementer {
static void ImplementsAnimatableExtension(gd::PlatformExtension& extension);
static void ImplementsEffectExtension(gd::PlatformExtension& extension);
static void ImplementsOpacityExtension(gd::PlatformExtension& extension);
static void ImplementsTextContainerExtension(gd::PlatformExtension& extension);
};
} // namespace gd

View File

@@ -396,7 +396,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
_("Z order"),
_("Modify the Z-order of an object"),
_("the z-order"),
_("Z order"),
_("Layers and cameras"),
"res/actions/planicon24.png",
"res/actions/planicon.png")
@@ -550,7 +550,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
_("Z-order"),
_("Compare the Z-order of the specified object."),
_("the Z-order"),
_("Z-order"),
_("Layer"),
"res/conditions/planicon24.png",
"res/conditions/planicon.png")
@@ -617,6 +617,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"number", ParameterOptions::MakeNewOptions())
.MarkAsAdvanced();
// Deprecated
obj.AddCondition("AngleOfDisplacement",
_("Angle of movement (using forces)"),
_("Compare the angle of movement of an object according to "
@@ -626,7 +627,20 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
_("Movement using forces"),
"res/conditions/vitesse24.png",
"res/conditions/vitesse.png")
.SetHidden()
.AddParameter("object", _("Object"))
.AddParameter("expression", _("Angle, in degrees"))
.AddParameter("expression", _("Tolerance, in degrees"))
.MarkAsAdvanced();
obj.AddCondition("IsTotalForceAngleAround",
_("Angle of movement (using forces)"),
_("Compare the angle of movement of an object according to "
"the forces applied on it."),
_("Angle of movement of _PARAM0_ is _PARAM1_ ± _PARAM2_°"),
_("Movement using forces"),
"res/conditions/vitesse24.png",
"res/conditions/vitesse.png")
.AddParameter("object", _("Object"))
.AddParameter("expression", _("Angle, in degrees"))
.AddParameter("expression", _("Tolerance, in degrees"))
@@ -1137,7 +1151,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
obj.AddExpression("ZOrder",
_("Z-order"),
_("Z-order of an object"),
_("Visibility"),
"",
"res/actions/planicon.png")
.AddParameter("object", _("Object"));
@@ -1267,8 +1281,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
_("Enable an effect on the object"),
_("Enable effect _PARAM1_ on _PARAM0_: _PARAM2_"),
_("Effects"),
"res/actions/effect24.png",
"res/actions/effect.png")
"res/actions/effect_black.svg",
"res/actions/effect_black.svg")
.AddParameter("object", _("Object"))
.AddParameter("objectEffectName", _("Effect name"))
.AddParameter("yesorno", _("Enable?"))
@@ -1283,8 +1297,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"names) in the effects window."),
_("Set _PARAM2_ to _PARAM3_ for effect _PARAM1_ of _PARAM0_"),
_("Effects"),
"res/actions/effect24.png",
"res/actions/effect.png")
"res/actions/effect_black.svg",
"res/actions/effect_black.svg")
.AddParameter("object", _("Object"))
.AddParameter("objectEffectName", _("Effect name"))
.AddParameter("objectEffectParameterName", _("Property name"))
@@ -1301,8 +1315,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"names) in the effects window."),
_("Set _PARAM2_ to _PARAM3_ for effect _PARAM1_ of _PARAM0_"),
_("Effects"),
"res/actions/effect24.png",
"res/actions/effect.png")
"res/actions/effect_black.svg",
"res/actions/effect_black.svg")
.AddParameter("object", _("Object"))
.AddParameter("objectEffectName", _("Effect name"))
.AddParameter("objectEffectParameterName", _("Property name"))
@@ -1318,8 +1332,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"names) in the effects window."),
_("Enable _PARAM2_ for effect _PARAM1_ of _PARAM0_: _PARAM3_"),
_("Effects"),
"res/actions/effect24.png",
"res/actions/effect.png")
"res/actions/effect_black.svg",
"res/actions/effect_black.svg")
.AddParameter("object", _("Object"))
.AddParameter("objectEffectName", _("Effect name"))
.AddParameter("objectEffectParameterName", _("Property name"))
@@ -1333,8 +1347,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
_("Check if the effect on an object is enabled."),
_("Effect _PARAM1_ of _PARAM0_ is enabled"),
_("Effects"),
"res/actions/effect24.png",
"res/actions/effect.png")
"res/actions/effect_black.svg",
"res/actions/effect_black.svg")
.AddParameter("object", _("Object"))
.AddParameter("objectEffectName", _("Effect name"))
.MarkAsSimple()
@@ -1610,7 +1624,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
_("Cast a ray from _PARAM1_;_PARAM2_, angle: _PARAM3_ and max "
"distance: _PARAM4_px, against _PARAM0_, and save the "
"result in _PARAM5_, _PARAM6_"),
"",
_("Collision"),
"res/conditions/raycast24.png",
"res/conditions/raycast.png")
.AddParameter("objectList", _("Objects to test against the ray"))
@@ -1641,7 +1655,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
_("Cast a ray from _PARAM1_;_PARAM2_ to _PARAM3_;_PARAM4_ "
"against _PARAM0_, and save the "
"result in _PARAM5_, _PARAM6_"),
"",
_("Collision"),
"res/conditions/raycast24.png",
"res/conditions/raycast.png")
.AddParameter("objectList", _("Objects to test against the ray"))

View File

@@ -27,7 +27,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
extension.AddInstructionOrExpressionGroupMetadata(_("Layers and cameras"))
.SetIcon("res/conditions/camera24.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Effects"))
.SetIcon("res/actions/effect24.png");
.SetIcon("res/actions/effect_black.svg");
extension
.AddExpressionAndConditionAndAction(
@@ -327,7 +327,6 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
.AddParameter("expression", _("Camera number (default : 0)"), "", true)
.SetDefaultValue("0");
// TODO Deprecated: hide this action in a future release.
extension
.AddAction(
"FixCamera",
@@ -339,6 +338,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
"",
"res/actions/camera24.png",
"res/actions/camera.png")
.SetHidden()
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("objectPtr", _("Object"))
.AddParameter("expression",
@@ -386,7 +386,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
_("Center the camera on an object"),
_("Center the camera on the specified object."),
_("Center camera on _PARAM1_ (layer: _PARAM3_)"),
"",
_("Layers and cameras"),
"res/actions/camera24.png",
"res/actions/camera.png")
.AddCodeOnlyParameter("currentScene", "")
@@ -450,8 +450,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
"names) in the effects window."),
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of layer _PARAM1_"),
_("Effects"),
"res/actions/effect24.png",
"res/actions/effect.png")
"res/actions/effect_black.svg",
"res/actions/effect_black.svg")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("layer", _("Layer"), "", true)
.SetDefaultValue("\"\"")
@@ -469,8 +469,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
"names) in the effects window."),
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of layer _PARAM1_"),
_("Effects"),
"res/actions/effect24.png",
"res/actions/effect.png")
"res/actions/effect_black.svg",
"res/actions/effect_black.svg")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("layer", _("Layer"), "", true)
.SetDefaultValue("\"\"")
@@ -488,8 +488,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
"names) in the effects window."),
_("Enable _PARAM3_ for effect _PARAM2_ of layer _PARAM1_: _PARAM4_"),
_("Effects"),
"res/actions/effect24.png",
"res/actions/effect.png")
"res/actions/effect_black.svg",
"res/actions/effect_black.svg")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("layer", _("Layer"), "", true)
.SetDefaultValue("\"\"")
@@ -504,8 +504,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
_("The effect on a layer is enabled"),
_("Effect _PARAM2_ on layer _PARAM1_ is enabled"),
_(""),
"res/actions/effect24.png",
"res/actions/effect.png")
"res/actions/effect_black.svg",
"res/actions/effect_black.svg")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("layer", _("Layer"), "", true)
.SetDefaultValue("\"\"")
@@ -518,8 +518,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
_("Enable an effect on a layer"),
_("Enable effect _PARAM2_ on layer _PARAM1_: _PARAM3_"),
_("Effects"),
"res/actions/effect24.png",
"res/actions/effect.png")
"res/actions/effect_black.svg",
"res/actions/effect_black.svg")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("layer", _("Layer"), "", true)
.SetDefaultValue("\"\"")
@@ -548,7 +548,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
extension
.AddAction(
"ChangeLayerTimeScale",
_("Change layer time scale"),
_("Layer time scale"),
_("Change the time scale applied to the objects of the layer."),
_("Set the time scale of layer _PARAM1_ to _PARAM2_"),
"",
@@ -594,7 +594,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
extension
.AddAction(
"SetLayerAmbientLightColor",
_("Set the ambient light color"),
_("Ambient light color"),
_("Set the ambient light color of the lighting layer in format "
"\"R;G;B\" string."),
_("Set the ambient color of the lighting layer _PARAM1_ to _PARAM2_"),

View File

@@ -23,6 +23,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Animatable capability"))
.SetIcon("res/actions/animation24.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Animations and images"))
.SetIcon("res/actions/animation24.png");
@@ -42,7 +44,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
"number",
"Index",
_("Animation (by number)"),
_("the number of the animation played by the object (the number from "
_("the animation played by the object using the animation number (from "
"the animations list)"),
_("the number of the animation"),
_("Animations and images"),
@@ -53,6 +55,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
"number", gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Animation index")))
.MarkAsSimple();
aut.GetAllExpressions()["Index"].SetGroup("");
aut.AddExpressionAndConditionAndAction(
"string",
@@ -69,6 +72,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
"objectAnimationName", gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Animation name")))
.MarkAsSimple();
aut.GetAllStrExpressions()["Name"].SetGroup("");
aut.AddScopedAction("PauseAnimation",
_("Pause the animation"),
@@ -107,6 +111,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
"number", gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Speed scale")))
.MarkAsSimple();
aut.GetAllExpressions()["SpeedScale"].SetGroup("");
aut.AddScopedCondition("IsAnimationPaused",
_("Animation paused"),
@@ -130,6 +135,31 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "AnimatableBehavior")
.MarkAsSimple();
aut.AddExpressionAndConditionAndAction(
"number",
"ElapsedTime",
_("Animation elapsed time"),
_("the elapsed time from the beginning of the animation (in seconds)"),
_("the animation elapsed time"),
_("Animations and images"),
"res/actions/animation24.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "AnimatableBehavior")
.UseStandardParameters(
"number", gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Elapsed time (in seconds)")))
.MarkAsAdvanced();
aut.GetAllExpressions()["ElapsedTime"].SetGroup("");
aut.AddExpression(
"Duration",
_("Animation duration"),
_("Return the current animation duration (in seconds)."),
_("Animations and images"),
"res/actions/animation24.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "AnimatableBehavior");
}
} // namespace gd

View File

@@ -24,7 +24,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Effects"))
.SetIcon("res/actions/effect24.png");
.SetIcon("res/actions/effect_black.svg");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"EffectBehavior",
@@ -32,7 +32,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
"Effect",
_("Apply visual effects to objects."),
"",
"res/actions/effect24.png",
"res/actions/effect_black.svg",
"EffectBehavior",
std::make_shared<gd::Behavior>(),
std::make_shared<gd::BehaviorsSharedData>())
@@ -43,8 +43,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
_("Enable an effect on the object"),
_("Enable effect _PARAM2_ on _PARAM0_: _PARAM3_"),
_("Effects"),
"res/actions/effect24.png",
"res/actions/effect.png")
"res/actions/effect_black.svg",
"res/actions/effect_black.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
.AddParameter("objectEffectName", _("Effect name"))
@@ -58,8 +58,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
"names) in the effects window."),
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of _PARAM0_"),
_("Effects"),
"res/actions/effect24.png",
"res/actions/effect.png")
"res/actions/effect_black.svg",
"res/actions/effect_black.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
.AddParameter("objectEffectName", _("Effect name"))
@@ -75,8 +75,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
"names) in the effects window."),
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of _PARAM0_"),
_("Effects"),
"res/actions/effect24.png",
"res/actions/effect.png")
"res/actions/effect_black.svg",
"res/actions/effect_black.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
.AddParameter("objectEffectName", _("Effect name"))
@@ -91,8 +91,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
"names) in the effects window."),
_("Enable _PARAM3_ for effect _PARAM2_ of _PARAM0_: _PARAM4_"),
_("Effects"),
"res/actions/effect24.png",
"res/actions/effect.png")
"res/actions/effect_black.svg",
"res/actions/effect_black.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
.AddParameter("objectEffectName", _("Effect name"))
@@ -105,8 +105,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
_("Check if the effect on an object is enabled."),
_("Effect _PARAM2_ of _PARAM0_ is enabled"),
_("Effects"),
"res/actions/effect24.png",
"res/actions/effect.png")
"res/actions/effect_black.svg",
"res/actions/effect_black.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
.AddParameter("objectEffectName", _("Effect name"))

View File

@@ -24,7 +24,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsFlippableExtension(
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Effects"))
.SetIcon("res/actions/effect24.png");
.SetIcon("res/actions/effect_black.svg");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"FlippableBehavior",

View File

@@ -23,6 +23,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsOpacityExtension(
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Opacity capability"))
.SetIcon("res/actions/opacity24.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Visibility"))
.SetIcon("res/actions/opacity24.png");
@@ -54,6 +56,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsOpacityExtension(
_("Opacity (0-255)")))
.SetFunctionName("setOpacity")
.SetGetter("getOpacity");
aut.GetAllExpressions()["Value"].SetGroup("");
}
} // namespace gd

View File

@@ -23,6 +23,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Scalable capability"))
.SetIcon("res/actions/scale24_black.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Size"))
.SetIcon("res/actions/scale24_black.png");
@@ -44,7 +46,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
_("Scale"),
_("the scale of the object (default scale is 1)"),
_("the scale"),
_("Scale"),
_("Size"),
"res/actions/scale24_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ScalableBehavior")
@@ -53,6 +55,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Scale (1 by default)")))
.MarkAsAdvanced();
aut.GetAllExpressions()["Value"].SetGroup("");
aut.AddExpressionAndConditionAndAction(
"number",
@@ -60,7 +63,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
_("Scale on X axis"),
_("the scale on X axis of the object (default scale is 1)"),
_("the scale on X axis"),
_("Scale"),
_("Size"),
"res/actions/scaleWidth24_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ScalableBehavior")
@@ -69,6 +72,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Scale (1 by default)")))
.MarkAsAdvanced();
aut.GetAllExpressions()["X"].SetGroup("");
aut.AddExpressionAndConditionAndAction(
"number",
@@ -76,7 +80,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
_("Scale on Y axis"),
_("the scale on Y axis of the object (default scale is 1)"),
_("the scale on Y axis"),
_("Scale"),
_("Size"),
"res/actions/scaleHeight24_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ScalableBehavior")
@@ -85,6 +89,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Scale (1 by default)")))
.MarkAsAdvanced();
aut.GetAllExpressions()["Y"].SetGroup("");
}
} // namespace gd

View File

@@ -0,0 +1,58 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the GNU Lesser General Public
* License.
*/
#include "GDCore/Extensions/Builtin/AllBuiltinExtensions.h"
#include "GDCore/Extensions/Metadata/MultipleInstructionMetadata.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/BehaviorsSharedData.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Tools/Localization.h"
using namespace std;
namespace gd {
void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTextContainerExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("TextContainerCapability",
_("Text capability"),
_("Animate objects."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Text capability"))
.SetIcon("res/conditions/text24_black.png");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"TextContainerBehavior",
_("Text capability"),
"Text",
_("Access objects text."),
"",
"res/conditions/text24_black.png",
"TextContainerBehavior",
std::make_shared<gd::Behavior>(),
std::make_shared<gd::BehaviorsSharedData>())
.SetHidden();
aut.AddExpressionAndConditionAndAction(
"string",
"Value",
_("Text"),
_("the text"),
_("the text"),
"",
"res/conditions/text24_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TextContainerBehavior")
.UseStandardParameters(
"string", gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Text")))
.MarkAsSimple();
aut.GetAllStrExpressions()["Value"].SetGroup("");
}
} // namespace gd

View File

@@ -38,6 +38,8 @@ BuiltinExtensionsImplementer::ImplementsExternalLayoutsExtension(
.SetDefaultValue("0")
.AddParameter("expression", _("Y position of the origin"), "", true)
.SetDefaultValue("0")
.AddParameter("expression", _("Z position of the origin"), "", true)
.SetDefaultValue("0")
.MarkAsAdvanced();
}

View File

@@ -5,6 +5,7 @@
*/
#include "AllBuiltinExtensions.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Extensions/Metadata/MultipleInstructionMetadata.h"
using namespace std;
namespace gd {
@@ -57,7 +58,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
extension
.AddCondition("DoesSceneExist",
_("Does scene exist"),
_("Check if scene exists."),
_("Check if a scene exists."),
_("Scene _PARAM1_ exists"),
"",
"res/actions/texte.png",
@@ -163,6 +164,45 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
"res/actions/window.png")
.SetHelpPath("/interface/scene-editor/events")
.AddCodeOnlyParameter("currentScene", "");
extension
.AddAction("PrioritizeLoadingOfScene",
_("Preload scene"),
_("Preload a scene resources as soon as possible in background."),
_("Preload scene _PARAM1_ in background"),
"",
"res/actions/hourglass_black.svg",
"res/actions/hourglass_black.svg")
.SetHelpPath("/all-features/resources-loading")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("sceneName", _("Name of the new scene"))
.MarkAsAdvanced();
extension.AddExpressionAndCondition("number",
"SceneLoadingProgress",
_("Scene loading progress"),
_("The progress of resources loading in background for a scene (between 0 and 1)."),
_("_PARAM0_ loading progress"),
_(""),
"res/actions/hourglass_black.svg")
.SetHelpPath("/all-features/resources-loading")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("sceneName", _("Scene name"))
.UseStandardParameters("number", ParameterOptions::MakeNewOptions())
.MarkAsAdvanced();
extension
.AddCondition("AreSceneAssetsLoaded",
_("Scene preloaded"),
_("Check if scene resources have finished to load in background."),
_("Scene _PARAM1_ was preloaded in background"),
"",
"res/actions/hourglass_black.svg",
"res/actions/hourglass_black.svg")
.SetHelpPath("/all-features/resources-loading")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("sceneName", _("Scene name"))
.MarkAsAdvanced();
}
} // namespace gd

View File

@@ -30,7 +30,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
.AddObject<SpriteObject>("Sprite",
_("Sprite"),
_("Animated object which can be used for "
"most elements of a game"),
"most elements of a game."),
"CppPlatform/Extensions/spriteicon.png")
.SetCategoryFullName(_("General"))
.AddDefaultBehavior("EffectCapability::EffectBehavior")
@@ -408,6 +408,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
.SetHidden()
.MarkAsSimple();
// Deprecated
obj.AddCondition("ScaleWidth",
_("Scale on X axis"),
_("Compare the scale of the width of an object."),
@@ -415,7 +416,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
_("Size"),
"res/conditions/scaleWidth24_black.png",
"res/conditions/scaleWidth_black.png")
.SetHidden()
.AddParameter("object", _("Object"), "Sprite")
.UseStandardRelationalOperatorParameters(
"number",
@@ -423,6 +424,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
_("Scale (1 by default)")))
.MarkAsAdvanced();
// Deprecated
obj.AddCondition("ScaleHeight",
_("Scale on Y axis"),
_("Compare the scale of the height of an object."),
@@ -430,7 +432,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
_("Size"),
"res/conditions/scaleHeight24_black.png",
"res/conditions/scaleHeight_black.png")
.SetHidden()
.AddParameter("object", _("Object"), "Sprite")
.UseStandardRelationalOperatorParameters(
"number",

View File

@@ -87,8 +87,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsWindowExtension(
extension
.AddAction(
"SetWindowSize",
_("Change the size of the game window"),
_("This action changes the size of the game window. Note that this "
_("Game window size"),
_("Changes the size of the game window. Note that this "
"will only work on platform supporting this operation: games "
"running in browsers or on mobile phones can not update their "
"window size. Game resolution can still be updated."),

View File

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

View File

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

View File

@@ -191,6 +191,16 @@ class GD_CORE_API MultipleInstructionMetadata : public AbstractFunctionMetadata
return *this;
}
/**
* \see gd::InstructionMetadata::SetHelpPath
*/
MultipleInstructionMetadata &SetHelpPath(const gd::String &path) {
if (expression) expression->SetHelpPath(path);
if (condition) condition->SetHelpPath(path);
if (action) action->SetHelpPath(path);
return *this;
}
/**
* \see gd::InstructionMetadata::MarkAsSimple
*/

View File

@@ -303,12 +303,24 @@ class GD_CORE_API ObjectMetadata : public InstructionOrExpressionContainerMetada
return *this;
}
/**
* \brief Return true if the instruction must be hidden in the IDE.
* \brief Return true if the object must be hidden in the IDE.
*/
bool IsHidden() const { return hidden; }
/**
* \brief Declare a usage of the 3D renderer.
*/
ObjectMetadata &MarkAsRenderedIn3D() {
isRenderedIn3D = true;
return *this;
}
/**
* \brief Return true if the object uses the 3D renderer.
*/
bool IsRenderedIn3D() const { return isRenderedIn3D; }
std::map<gd::String, gd::InstructionMetadata> conditionsInfos;
std::map<gd::String, gd::InstructionMetadata> actionsInfos;
std::map<gd::String, gd::ExpressionMetadata> expressionsInfos;
@@ -329,6 +341,7 @@ class GD_CORE_API ObjectMetadata : public InstructionOrExpressionContainerMetada
gd::String categoryFullName;
std::set<gd::String> defaultBehaviorTypes;
bool hidden = false;
bool isRenderedIn3D = false;
std::shared_ptr<gd::ObjectConfiguration>
blueprintObject; ///< The "blueprint" object to be copied when a new

View File

@@ -60,10 +60,10 @@ void ParameterMetadataTools::ParametersToObjectsContainer(
}
}
void ParameterMetadataTools::ForEachParameterWithPrefix(
void ParameterMetadataTools::ForEachParameterMatchingSearch(
const std::vector<const std::vector<gd::ParameterMetadata>*>&
parametersVectorsList,
const gd::String& prefix,
const gd::String& search,
std::function<void(const gd::ParameterMetadata&)> cb) {
for (auto it = parametersVectorsList.rbegin();
it != parametersVectorsList.rend();
@@ -71,7 +71,7 @@ void ParameterMetadataTools::ForEachParameterWithPrefix(
const std::vector<gd::ParameterMetadata>* parametersVector = *it;
for (const auto& parameterMetadata: *parametersVector) {
if (parameterMetadata.GetName().find(prefix) == 0) cb(parameterMetadata);
if (parameterMetadata.GetName().FindCaseInsensitive(search) != gd::String::npos) cb(parameterMetadata);
}
}
}

View File

@@ -27,9 +27,9 @@ class GD_CORE_API ParameterMetadataTools {
const std::vector<gd::ParameterMetadata>& parameters,
gd::ObjectsContainer& outputObjectsContainer);
static void ForEachParameterWithPrefix(
static void ForEachParameterMatchingSearch(
const std::vector<const std::vector<gd::ParameterMetadata>*>& parametersVectorsList,
const gd::String& prefix,
const gd::String& search,
std::function<void(const gd::ParameterMetadata&)> cb);
static bool Has(

View File

@@ -217,7 +217,9 @@ class GD_CORE_API ValueTypeMetadata {
parameterType == "jsonResource" ||
parameterType == "tilemapResource" ||
parameterType == "tilesetResource" ||
parameterType == "model3DResource";
parameterType == "model3DResource" ||
parameterType == "atlasResource" ||
parameterType == "spineResource";
}
return false;
}

View File

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

View File

@@ -4,7 +4,6 @@
* reserved. This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#include "DependenciesAnalyzer.h"
#include <algorithm>
#include "GDCore/Events/Builtin/LinkEvent.h"
@@ -29,9 +28,9 @@ DependenciesAnalyzer::DependenciesAnalyzer(const gd::Project& project_,
bool DependenciesAnalyzer::Analyze() {
if (layout)
return Analyze(layout->GetEvents(), true);
return Analyze(layout->GetEvents());
else if (externalEvents)
return Analyze(externalEvents->GetEvents(), true);
return Analyze(externalEvents->GetEvents());
std::cout << "ERROR: DependenciesAnalyzer called without any layout or "
"external events.";
@@ -40,63 +39,38 @@ bool DependenciesAnalyzer::Analyze() {
DependenciesAnalyzer::~DependenciesAnalyzer() {}
bool DependenciesAnalyzer::Analyze(const gd::EventsList& events, bool isOnTopLevel) {
bool DependenciesAnalyzer::Analyze(const gd::EventsList& events) {
for (unsigned int i = 0; i < events.size(); ++i) {
const gd::LinkEvent* linkEvent = dynamic_cast<const gd::LinkEvent*>(&events[i]);
if (linkEvent) {
DependenciesAnalyzer analyzer(*this);
gd::String linked = linkEvent->GetTarget();
if (project.HasExternalEventsNamed(linked)) {
if (std::find(parentExternalEvents.begin(),
parentExternalEvents.end(),
linked) != parentExternalEvents.end())
return false; // Circular dependency!
externalEventsDependencies.insert(
linked); // There is a direct dependency
if (!isOnTopLevel) notTopLevelExternalEventsDependencies.insert(linked);
analyzer.AddParentExternalEvents(linked);
if (!analyzer.Analyze(project.GetExternalEvents(linked).GetEvents(),
isOnTopLevel))
linked) != parentExternalEvents.end()) {
// Circular dependency!
return false;
}
bool wasDependencyJustAdded = externalEventsDependencies.insert(linked).second;
if (wasDependencyJustAdded) {
parentExternalEvents.push_back(linked);
if (!Analyze(project.GetExternalEvents(linked).GetEvents()))
return false;
parentExternalEvents.pop_back();
}
} else if (project.HasLayoutNamed(linked)) {
if (std::find(parentScenes.begin(), parentScenes.end(), linked) !=
parentScenes.end())
return false; // Circular dependency!
scenesDependencies.insert(linked); // There is a direct dependency
if (!isOnTopLevel) notTopLevelScenesDependencies.insert(linked);
analyzer.AddParentScene(linked);
if (!analyzer.Analyze(project.GetLayout(linked).GetEvents(),
isOnTopLevel))
parentScenes.end()) {
// Circular dependency!
return false;
}
// Update with indirect dependencies.
scenesDependencies.insert(analyzer.GetScenesDependencies().begin(),
analyzer.GetScenesDependencies().end());
externalEventsDependencies.insert(
analyzer.GetExternalEventsDependencies().begin(),
analyzer.GetExternalEventsDependencies().end());
sourceFilesDependencies.insert(
analyzer.GetSourceFilesDependencies().begin(),
analyzer.GetSourceFilesDependencies().end());
notTopLevelScenesDependencies.insert(
analyzer.GetNotTopLevelScenesDependencies().begin(),
analyzer.GetNotTopLevelScenesDependencies().end());
notTopLevelExternalEventsDependencies.insert(
analyzer.GetNotTopLevelExternalEventsDependencies().begin(),
analyzer.GetNotTopLevelExternalEventsDependencies().end());
if (!isOnTopLevel) {
notTopLevelScenesDependencies.insert(
analyzer.GetScenesDependencies().begin(),
analyzer.GetScenesDependencies().end());
notTopLevelExternalEventsDependencies.insert(
analyzer.GetExternalEventsDependencies().begin(),
analyzer.GetExternalEventsDependencies().end());
}
bool wasDependencyJustAdded = scenesDependencies.insert(linked).second;
if (wasDependencyJustAdded) {
parentScenes.push_back(linked);
if (!Analyze(project.GetLayout(linked).GetEvents()))
return false;
parentScenes.pop_back();
}
}
}
@@ -112,45 +86,9 @@ bool DependenciesAnalyzer::Analyze(const gd::EventsList& events, bool isOnTopLev
// Analyze sub events dependencies
if (events[i].CanHaveSubEvents()) {
if (!Analyze(events[i].GetSubEvents(), false)) return false;
if (!Analyze(events[i].GetSubEvents())) return false;
}
}
return true;
}
gd::String DependenciesAnalyzer::ExternalEventsCanBeCompiledForAScene() {
if (!externalEvents) {
std::cout << "ERROR: ExternalEventsCanBeCompiledForAScene called without "
"external events set!"
<< std::endl;
return "";
}
gd::String sceneName;
for (unsigned int i = 0; i < project.GetLayoutsCount(); ++i) {
// For each layout, compute the dependencies and the dependencies which are
// not coming from a top level event.
DependenciesAnalyzer analyzer(project, project.GetLayout(i));
if (!analyzer.Analyze()) continue; // Analyze failed -> Cyclic dependencies
const std::set<gd::String>& dependencies =
analyzer.GetExternalEventsDependencies();
const std::set<gd::String>& notTopLevelDependencies =
analyzer.GetNotTopLevelExternalEventsDependencies();
// Check if the external events is a dependency, and that is is only present
// as a link on the top level.
if (dependencies.find(externalEvents->GetName()) != dependencies.end() &&
notTopLevelDependencies.find(externalEvents->GetName()) ==
notTopLevelDependencies.end()) {
if (!sceneName.empty())
return ""; // External events can be compiled only if one scene is
// including them.
else
sceneName = project.GetLayout(i).GetName();
}
}
return sceneName; // External events can be compiled and used for the scene.
}
#endif

View File

@@ -39,11 +39,6 @@ class GD_CORE_API DependenciesAnalyzer {
/**
* \brief Constructor for analyzing the dependencies of external events.
*
* You can also call then
* DependenciesAnalyzer::ExternalEventsCanBeCompiledForAScene to check if the
* external events can be compiled separately and called by a scene. \see
* DependenciesAnalyzer::ExternalEventsCanBeCompiledForAScene
*/
DependenciesAnalyzer(const gd::Project& project_,
const gd::ExternalEvents& externalEvents);
@@ -60,18 +55,6 @@ class GD_CORE_API DependenciesAnalyzer {
*/
bool Analyze();
/**
* Check if the external events (passed in the constructor) can be compiled
* and called by a single scene:<br> This is possible when the link calling
* the external events does not have any parent event and when this situation
* occurs only in a single scene and not in another.
*
* \return The name of the scene which is able to call the compiled external
* events. If empty, no scene is able to call them. (So external events have
* to be included directly by links).
*/
gd::String ExternalEventsCanBeCompiledForAScene();
/**
* \brief Return the scenes being dependencies of the scene or external events
* passed in the constructor.
@@ -96,25 +79,6 @@ class GD_CORE_API DependenciesAnalyzer {
return sourceFilesDependencies;
};
/**
* \brief Return the scenes being dependencies of the scene or external events
* passed in the constructor, but being not top level dependencies: The links
* including them are not a top level events (i.e: They have a parent event).
*/
const std::set<gd::String>& GetNotTopLevelScenesDependencies() const {
return notTopLevelScenesDependencies;
};
/**
* \brief Return the external events being dependencies of the scene or
* external events passed in the constructor, but being not top level
* dependencies: The links including them are not a top level events (i.e:
* They have a parent event).
*/
const std::set<gd::String>& GetNotTopLevelExternalEventsDependencies() const {
return notTopLevelExternalEventsDependencies;
};
private:
/**
* \brief Analyze the dependencies of the events.
@@ -124,32 +88,11 @@ class GD_CORE_API DependenciesAnalyzer {
* (they have no parents). \return false if a circular dependency exists, true
* otherwise.
*/
bool Analyze(const gd::EventsList& events, bool isOnTopLevel);
void AddParentScene(gd::String parentScene) {
parentScenes.push_back(parentScene);
};
void AddParentExternalEvents(gd::String parentExternalEvents_) {
parentExternalEvents.push_back(parentExternalEvents_);
};
/**
* Return true if all links pointing to external events called \a
* externalEventsName are only at the top level of \a events. The function
* return false as soon as it discover a link to external events which is not
* at the top level ( i.e: It has a parent event ).
*
* \warning The function assumes that there are not cyclic dependencies.
*/
bool CheckIfExternalEventsIsLinkedOnlyAtTopLevel(
const gd::String& externalEventsName,
std::vector<std::shared_ptr<gd::BaseEvent> >& events);
bool Analyze(const gd::EventsList& events);
std::set<gd::String> scenesDependencies;
std::set<gd::String> externalEventsDependencies;
std::set<gd::String> sourceFilesDependencies;
std::set<gd::String> notTopLevelScenesDependencies;
std::set<gd::String> notTopLevelExternalEventsDependencies;
std::vector<gd::String>
parentScenes; ///< Used to check for circular dependencies.
std::vector<gd::String>

View File

@@ -58,7 +58,7 @@ class GD_CORE_API ExpressionObjectsAnalyzer
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers.GetObjectsContainersList(), rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ParameterMetadata::IsExpression("variable", type)) {
// Nothing to do (this can't reference an object)
@@ -88,7 +88,7 @@ class GD_CORE_API ExpressionObjectsAnalyzer
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers.GetObjectsContainersList(), rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ParameterMetadata::IsObject(type)) {
context.AddObjectName(projectScopedContainers, node.identifierName);
} else if (gd::ParameterMetadata::IsExpression("variable", type)) {

View File

@@ -226,7 +226,7 @@ void EventsIdentifiersFinder::FindArgumentsInEventsAndDependencies(
eventWorker.Launch(layout.GetEvents(),
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout));
DependenciesAnalyzer dependenciesAnalyzer = DependenciesAnalyzer(project, layout);
DependenciesAnalyzer dependenciesAnalyzer(project, layout);
dependenciesAnalyzer.Analyze();
for (const gd::String& externalEventName : dependenciesAnalyzer.GetExternalEventsDependencies()) {
const gd::ExternalEvents& externalEvents = project.GetExternalEvents(externalEventName);

View File

@@ -81,8 +81,7 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
const auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(platform, objectsContainersList, rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
// Nothing to do (this can't reference an object)
@@ -115,7 +114,7 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers.GetObjectsContainersList(), rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ParameterMetadata::IsObject(type) &&
node.identifierName == objectName) {
hasDoneRenaming = true;
@@ -217,8 +216,7 @@ class GD_CORE_API ExpressionObjectFinder : public ExpressionParser2NodeWorker {
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
const auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(platform, objectsContainersList, rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
// Nothing to do (this can't reference an object)
@@ -250,7 +248,7 @@ class GD_CORE_API ExpressionObjectFinder : public ExpressionParser2NodeWorker {
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers.GetObjectsContainersList(), rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ParameterMetadata::IsObject(type) &&
node.identifierName == searchedObjectName) {
hasObject = true;

View File

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

View File

@@ -258,7 +258,7 @@ void EventsVariablesFinder::FindArgumentsInEventsAndDependencies(
eventWorker.Launch(layout.GetEvents(),
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout));
DependenciesAnalyzer dependenciesAnalyzer = DependenciesAnalyzer(project, layout);
DependenciesAnalyzer dependenciesAnalyzer(project, layout);
dependenciesAnalyzer.Analyze();
for (const gd::String& externalEventName : dependenciesAnalyzer.GetExternalEventsDependencies()) {
const gd::ExternalEvents& externalEvents = project.GetExternalEvents(externalEventName);

View File

@@ -11,13 +11,16 @@
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Events/Parsers/ExpressionParser2Node.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Events/Parsers/GrammarTerminals.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/Extensions/Metadata/ValueTypeMetadata.h"
#include "GDCore/IDE/Events/ExpressionNodeLocationFinder.h"
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
#include "GDCore/IDE/Events/ExpressionVariableOwnerFinder.h"
#include "GDCore/IDE/Events/ExpressionVariableParentFinder.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Project/Variable.h"
@@ -395,12 +398,10 @@ class GD_CORE_API ExpressionCompletionFinder
protected:
void OnVisitSubExpressionNode(SubExpressionNode& node) override {
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, rootType, node);
platform, projectScopedContainers, rootType, node);
AddCompletionsForAllIdentifiersWithPrefix("", type);
AddCompletionsForAllIdentifiersMatchingSearch("", type);
completions.push_back(
ExpressionCompletionDescription::ForExpressionWithPrefix(
type, "", searchedPosition + 1, searchedPosition + 1));
@@ -409,12 +410,10 @@ class GD_CORE_API ExpressionCompletionFinder
// No completions.
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, rootType, node);
platform, projectScopedContainers, rootType, node);
AddCompletionsForAllIdentifiersWithPrefix("", type);
AddCompletionsForAllIdentifiersMatchingSearch("", type);
completions.push_back(
ExpressionCompletionDescription::ForExpressionWithPrefix(
type, "", searchedPosition + 1, searchedPosition + 1));
@@ -488,90 +487,143 @@ class GD_CORE_API ExpressionCompletionFinder
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, rootType, node);
platform, projectScopedContainers, rootType, node);
// Only attempt to complete with the children of the variable
// if it's the last child (no more `.AnotherVariable` written after).
bool eagerlyCompleteIfExactMatch = node.child == nullptr;
if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
const gd::VariablesContainer* variablesContainer = nullptr;
if (type == "globalvar") {
variablesContainer =
projectScopedContainers.GetVariablesContainersList()
.GetTopMostVariablesContainer();
} else if (type == "scenevar") {
variablesContainer =
projectScopedContainers.GetVariablesContainersList()
.GetBottomMostVariablesContainer();
if (type == "globalvar" || type == "scenevar") {
const auto* variablesContainer =
type == "globalvar"
? projectScopedContainers.GetVariablesContainersList()
.GetTopMostVariablesContainer()
: projectScopedContainers.GetVariablesContainersList()
.GetBottomMostVariablesContainer();
if (variablesContainer) {
AddCompletionsForVariablesMatchingSearch(*variablesContainer,
node.name,
node.nameLocation,
eagerlyCompleteIfExactMatch);
}
} else if (type == "objectvar") {
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
platform,
objectsContainersList,
// Variable fields doesn't use expression completion,
// so the object will be found inside the expression itself.
"",
node);
variablesContainer =
objectsContainersList.GetObjectOrGroupVariablesContainer(
objectName);
}
platform, objectsContainersList, rootObjectName, node);
if (variablesContainer) {
AddCompletionsForVariablesWithPrefix(
*variablesContainer, node.name, node.nameLocation);
AddCompletionsForObjectOrGroupVariablesMatchingSearch(
objectsContainersList,
objectName,
node.name,
node.nameLocation,
eagerlyCompleteIfExactMatch);
}
} else {
AddCompletionsForObjectsAndVariablesWithPrefix(
node.name, type, node.nameLocation);
AddCompletionsForObjectsAndVariablesMatchingSearch(
node.name, type, node.nameLocation, eagerlyCompleteIfExactMatch);
}
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
// No completions
VariableAndItsParent variableAndItsParent =
gd::ExpressionVariableParentFinder::GetLastParentOfNode(
platform, projectScopedContainers, node);
// If no child, we're at the end of a variable (like `GrandChild` in
// `Something.Child.GrandChild`) so we can complete eagerly children if we
// can.
gd::String eagerlyCompleteForVariableName =
node.child == nullptr ? node.name : "";
AddCompletionsForChildrenVariablesOf(variableAndItsParent,
node.nameLocation,
eagerlyCompleteForVariableName);
}
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override {
// No completions
}
VariableBracketAccessorNode& node) override {}
void OnVisitIdentifierNode(IdentifierNode& node) override {
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, rootType, node);
platform, projectScopedContainers, rootType, node);
if (gd::ParameterMetadata::IsObject(type)) {
// Only show completions of objects if an object is required.
AddCompletionsForObjectWithPrefix(
AddCompletionsForObjectMatchingSearch(
node.identifierName, type, node.location);
} else if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
const gd::VariablesContainer* variablesContainer = nullptr;
if (type == "globalvar") {
variablesContainer =
projectScopedContainers.GetVariablesContainersList()
.GetTopMostVariablesContainer();
} else if (type == "scenevar") {
variablesContainer =
projectScopedContainers.GetVariablesContainersList()
.GetBottomMostVariablesContainer();
if (type == "globalvar" || type == "scenevar") {
const auto* variablesContainer =
type == "globalvar"
? projectScopedContainers.GetVariablesContainersList()
.GetTopMostVariablesContainer()
: projectScopedContainers.GetVariablesContainersList()
.GetBottomMostVariablesContainer();
if (variablesContainer) {
if (IsCaretOn(node.identifierNameDotLocation) ||
IsCaretOn(node.childIdentifierNameLocation)) {
// Complete a potential child variable:
if (variablesContainer->Has(node.identifierName)) {
AddCompletionsForChildrenVariablesOf(
&variablesContainer->Get(node.identifierName),
node.childIdentifierNameLocation,
node.childIdentifierName);
}
} else {
// Complete a root variable of the scene or project.
// Don't attempt to complete children variables if there is
// already a dot written (`MyVariable.`).
bool eagerlyCompleteIfPossible =
!node.identifierNameDotLocation.IsValid();
AddCompletionsForVariablesMatchingSearch(
*variablesContainer,
node.identifierName,
node.identifierNameLocation,
eagerlyCompleteIfPossible);
}
}
} else if (type == "objectvar") {
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
platform,
objectsContainersList,
// Variable fields doesn't use expression completion,
// so the object will be found inside the expression itself.
"",
node);
variablesContainer =
objectsContainersList.GetObjectOrGroupVariablesContainer(
objectName);
}
platform, objectsContainersList, rootObjectName, node);
if (variablesContainer) {
AddCompletionsForVariablesWithPrefix(*variablesContainer,
node.identifierName,
node.identifierNameLocation);
if (IsCaretOn(node.identifierNameDotLocation) ||
IsCaretOn(node.childIdentifierNameLocation)) {
// Complete a potential child variable:
const auto* variablesContainer =
objectsContainersList.GetObjectOrGroupVariablesContainer(
objectName);
if (variablesContainer &&
variablesContainer->Has(node.identifierName)) {
AddCompletionsForChildrenVariablesOf(
&variablesContainer->Get(node.identifierName),
node.childIdentifierNameLocation,
node.childIdentifierName);
}
} else {
// Complete a root variable of the object.
// Don't attempt to complete children variables if there is
// already a dot written (`MyVariable.`).
bool eagerlyCompleteIfPossible =
!node.identifierNameDotLocation.IsValid();
AddCompletionsForObjectOrGroupVariablesMatchingSearch(
objectsContainersList,
objectName,
node.identifierName,
node.identifierNameLocation,
eagerlyCompleteIfPossible);
}
}
} else {
// Object function, behavior name, variable, object variable.
if (IsCaretOn(node.identifierNameLocation)) {
// Is this the proper position?
AddCompletionsForAllIdentifiersWithPrefix(
node.identifierName, type, node.identifierNameLocation);
// Don't attempt to complete children variables if there is
// already a dot written (`MyVariable.`).
bool eagerlyCompleteIfPossible =
!node.identifierNameDotLocation.IsValid();
AddCompletionsForAllIdentifiersMatchingSearch(
node.identifierName,
type,
node.identifierNameLocation,
eagerlyCompleteIfPossible);
if (!node.identifierNameDotLocation.IsValid()) {
completions.push_back(
ExpressionCompletionDescription::ForExpressionWithPrefix(
@@ -582,46 +634,69 @@ class GD_CORE_API ExpressionCompletionFinder
}
} else if (IsCaretOn(node.identifierNameDotLocation) ||
IsCaretOn(node.childIdentifierNameLocation)) {
const gd::String& objectName = node.identifierName;
// Might be:
// - An object variable, object behavior or object expression.
// - Or a variable with a child.
projectScopedContainers.MatchIdentifierWithName<void>(
node.identifierName,
[&]() {
// This is an object.
const gd::String& objectName = node.identifierName;
AddCompletionsForObjectOrGroupVariablesMatchingSearch(
objectsContainersList,
objectName,
node.childIdentifierName,
node.childIdentifierNameLocation,
true);
// Might be an object variable, object behavior or object expression:
const auto* variablesContainer =
projectScopedContainers.GetObjectsContainersList()
.GetObjectOrGroupVariablesContainer(objectName);
if (variablesContainer) {
AddCompletionsForVariablesWithPrefix(
*variablesContainer,
node.childIdentifierName,
node.childIdentifierNameLocation);
}
completions.push_back(
ExpressionCompletionDescription::ForBehaviorWithPrefix(
node.childIdentifierName,
node.childIdentifierNameLocation.GetStartPosition(),
node.childIdentifierNameLocation.GetEndPosition(),
objectName));
completions.push_back(
ExpressionCompletionDescription::ForExpressionWithPrefix(
type,
node.childIdentifierName,
node.childIdentifierNameLocation.GetStartPosition(),
node.childIdentifierNameLocation.GetEndPosition(),
objectName));
},
[&]() {
// This is a variable.
VariableAndItsParent variableAndItsParent =
gd::ExpressionVariableParentFinder::GetLastParentOfNode(
platform, projectScopedContainers, node);
completions.push_back(
ExpressionCompletionDescription::ForBehaviorWithPrefix(
node.childIdentifierName,
node.childIdentifierNameLocation.GetStartPosition(),
node.childIdentifierNameLocation.GetEndPosition(),
objectName));
completions.push_back(
ExpressionCompletionDescription::ForExpressionWithPrefix(
type,
node.childIdentifierName,
node.childIdentifierNameLocation.GetStartPosition(),
node.childIdentifierNameLocation.GetEndPosition(),
objectName));
AddCompletionsForChildrenVariablesOf(
variableAndItsParent,
node.childIdentifierNameLocation,
node.childIdentifierName);
},
[&]() {
// Ignore properties here.
// There is no support for "children" of properties.
},
[&]() {
// Ignore parameters here.
// There is no support for "children" of parameters.
},
[&]() {
// Ignore unrecognised identifiers here.
});
}
}
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, rootType, node);
platform, projectScopedContainers, rootType, node);
if (!node.behaviorFunctionName.empty() ||
node.behaviorNameNamespaceSeparatorLocation.IsValid()) {
// Behavior function (or behavior function being written, with the
// function name missing)
if (IsCaretOn(node.objectNameLocation)) {
AddCompletionsForObjectWithPrefix(
AddCompletionsForObjectMatchingSearch(
node.objectName, type, node.objectNameLocation);
} else if (IsCaretOn(node.objectNameDotLocation) ||
IsCaretOn(node.objectFunctionOrBehaviorNameLocation)) {
@@ -645,7 +720,7 @@ class GD_CORE_API ExpressionCompletionFinder
} else {
// Object function or behavior name
if (IsCaretOn(node.objectNameLocation)) {
AddCompletionsForObjectWithPrefix(
AddCompletionsForObjectMatchingSearch(
node.objectName, type, node.objectNameLocation);
} else if (IsCaretOn(node.objectNameDotLocation) ||
IsCaretOn(node.objectFunctionOrBehaviorNameLocation)) {
@@ -666,17 +741,15 @@ class GD_CORE_API ExpressionCompletionFinder
}
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, rootType, node);
platform, projectScopedContainers, rootType, node);
bool isCaretOnParenthesis = IsCaretOn(node.openingParenthesisLocation) ||
IsCaretOn(node.closingParenthesisLocation);
if (!node.behaviorName.empty()) {
// Behavior function
if (IsCaretOn(node.objectNameLocation)) {
AddCompletionsForObjectWithPrefix(
AddCompletionsForObjectMatchingSearch(
node.objectName, type, node.objectNameLocation);
} else if (IsCaretOn(node.objectNameDotLocation) ||
IsCaretOn(node.behaviorNameLocation)) {
@@ -700,7 +773,7 @@ class GD_CORE_API ExpressionCompletionFinder
} else if (!node.objectName.empty()) {
// Object function
if (IsCaretOn(node.objectNameLocation)) {
AddCompletionsForObjectWithPrefix(
AddCompletionsForObjectMatchingSearch(
node.objectName, type, node.objectNameLocation);
} else {
// Add completions for behaviors, because we could imagine that the user
@@ -738,12 +811,11 @@ class GD_CORE_API ExpressionCompletionFinder
}
}
void OnVisitEmptyNode(EmptyNode& node) override {
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, rootType, node);
platform, projectScopedContainers, rootType, node);
AddCompletionsForAllIdentifiersWithPrefix(node.text, type, node.location);
AddCompletionsForAllIdentifiersMatchingSearch(
node.text, type, node.location);
completions.push_back(
ExpressionCompletionDescription::ForExpressionWithPrefix(
type,
@@ -762,12 +834,98 @@ class GD_CORE_API ExpressionCompletionFinder
(inclusive && searchedPosition <= location.GetEndPosition())));
}
void AddCompletionsForVariablesWithPrefix(
const gd::VariablesContainer& variablesContainer,
const gd::String& prefix,
/**
* A slightly less strict check than `gd::Project::IsNameSafe` as child
* variables can be completed even if they start with a number.
*/
bool IsIdentifierSafe(const gd::String& name) {
if (name.empty()) return false;
for (auto character : name) {
if (!GrammarTerminals::IsAllowedInIdentifier(character)) {
return false;
}
}
return true;
}
void AddCompletionsForChildrenVariablesOf(
VariableAndItsParent variableAndItsParent,
const ExpressionParserLocation& location,
gd::String eagerlyCompleteForVariableName = "") {
if (variableAndItsParent.parentVariable) {
AddCompletionsForChildrenVariablesOf(variableAndItsParent.parentVariable,
location,
eagerlyCompleteForVariableName);
} else if (variableAndItsParent.parentVariablesContainer) {
AddCompletionsForVariablesMatchingSearch(
*variableAndItsParent.parentVariablesContainer, "", location);
}
}
void AddCompletionsForChildrenVariablesOf(
const gd::Variable* variable,
const ExpressionParserLocation& location,
gd::String eagerlyCompleteForVariableName = "") {
if (!variable) return;
if (variable->GetType() == gd::Variable::Structure) {
for (const auto& name : variable->GetAllChildrenNames()) {
if (!IsIdentifierSafe(name)) continue;
const auto& childVariable = variable->GetChild(name);
ExpressionCompletionDescription description(
ExpressionCompletionDescription::Variable,
location.GetStartPosition(),
location.GetEndPosition());
description.SetCompletion(name);
description.SetVariableType(childVariable.GetType());
completions.push_back(description);
if (name == eagerlyCompleteForVariableName) {
AddEagerCompletionForVariableChildren(childVariable, name, location);
}
}
} else {
// TODO: we could do a "comment only completion" to indicate that nothing
// can/should be completed?
}
}
void AddEagerCompletionForVariableChildren(
const gd::Variable& variable,
const gd::String& variableName,
const ExpressionParserLocation& location) {
variablesContainer.ForEachVariableWithPrefix(
prefix,
if (variable.GetType() == gd::Variable::Structure) {
gd::String prefix = variableName + ".";
for (const auto& name : variable.GetAllChildrenNames()) {
gd::String completion =
IsIdentifierSafe(name)
? (prefix + name)
: (variableName + "[" +
gd::ExpressionParser2NodePrinter::PrintStringLiteral(name) +
"]");
const auto& childVariable = variable.GetChild(name);
ExpressionCompletionDescription description(
ExpressionCompletionDescription::Variable,
location.GetStartPosition(),
location.GetEndPosition());
description.SetCompletion(completion);
description.SetVariableType(childVariable.GetType());
completions.push_back(description);
}
}
}
void AddCompletionsForVariablesMatchingSearch(
const gd::VariablesContainer& variablesContainer,
const gd::String& search,
const ExpressionParserLocation& location,
bool eagerlyCompleteIfExactMatch = false) {
variablesContainer.ForEachVariableMatchingSearch(
search,
[&](const gd::String& variableName, const gd::Variable& variable) {
ExpressionCompletionDescription description(
ExpressionCompletionDescription::Variable,
@@ -776,34 +934,66 @@ class GD_CORE_API ExpressionCompletionFinder
description.SetCompletion(variableName);
description.SetVariableType(variable.GetType());
completions.push_back(description);
if (eagerlyCompleteIfExactMatch && variableName == search) {
AddEagerCompletionForVariableChildren(
variable, variableName, location);
}
});
}
void AddCompletionsForObjectWithPrefix(
const gd::String& prefix,
const gd::String& type,
const ExpressionParserLocation& location) {
projectScopedContainers.GetObjectsContainersList().ForEachNameWithPrefix(
prefix,
[&](const gd::String& name,
const gd::ObjectConfiguration* objectConfiguration) {
void AddCompletionsForObjectOrGroupVariablesMatchingSearch(
const gd::ObjectsContainersList& objectsContainersList,
const gd::String& objectOrGroupName,
const gd::String& search,
const ExpressionParserLocation& location,
bool eagerlyCompleteIfExactMatch) {
objectsContainersList.ForEachObjectOrGroupVariableMatchingSearch(
objectOrGroupName,
search,
[&](const gd::String& variableName, const gd::Variable& variable) {
ExpressionCompletionDescription description(
ExpressionCompletionDescription::Object,
ExpressionCompletionDescription::Variable,
location.GetStartPosition(),
location.GetEndPosition());
description.SetObjectConfiguration(objectConfiguration);
description.SetCompletion(name);
description.SetType(type);
description.SetCompletion(variableName);
description.SetVariableType(variable.GetType());
completions.push_back(description);
if (eagerlyCompleteIfExactMatch && variableName == search) {
AddEagerCompletionForVariableChildren(
variable, variableName, location);
}
});
}
void AddCompletionsForObjectsAndVariablesWithPrefix(
const gd::String& prefix,
void AddCompletionsForObjectMatchingSearch(
const gd::String& search,
const gd::String& type,
const ExpressionParserLocation& location) {
projectScopedContainers.ForEachIdentifierWithPrefix(
prefix,
projectScopedContainers.GetObjectsContainersList()
.ForEachNameMatchingSearch(
search,
[&](const gd::String& name,
const gd::ObjectConfiguration* objectConfiguration) {
ExpressionCompletionDescription description(
ExpressionCompletionDescription::Object,
location.GetStartPosition(),
location.GetEndPosition());
description.SetObjectConfiguration(objectConfiguration);
description.SetCompletion(name);
description.SetType(type);
completions.push_back(description);
});
}
void AddCompletionsForObjectsAndVariablesMatchingSearch(
const gd::String& search,
const gd::String& type,
const ExpressionParserLocation& location,
bool eagerlyCompleteIfExactMatch) {
projectScopedContainers.ForEachIdentifierMatchingSearch(
search,
[&](const gd::String& objectName,
const ObjectConfiguration* objectConfiguration) {
ExpressionCompletionDescription description(
@@ -823,6 +1013,11 @@ class GD_CORE_API ExpressionCompletionFinder
description.SetCompletion(variableName);
description.SetVariableType(variable.GetType());
completions.push_back(description);
if (eagerlyCompleteIfExactMatch && variableName == search) {
AddEagerCompletionForVariableChildren(
variable, variableName, location);
}
},
[&](const gd::NamedPropertyDescriptor& property) {
// Ignore properties here.
@@ -832,20 +1027,21 @@ class GD_CORE_API ExpressionCompletionFinder
});
}
void AddCompletionsForAllIdentifiersWithPrefix(const gd::String& prefix,
const gd::String& type) {
AddCompletionsForAllIdentifiersWithPrefix(
prefix,
void AddCompletionsForAllIdentifiersMatchingSearch(const gd::String& search,
const gd::String& type) {
AddCompletionsForAllIdentifiersMatchingSearch(
search,
type,
ExpressionParserLocation(searchedPosition + 1, searchedPosition + 1));
}
void AddCompletionsForAllIdentifiersWithPrefix(
const gd::String& prefix,
void AddCompletionsForAllIdentifiersMatchingSearch(
const gd::String& search,
const gd::String& type,
const ExpressionParserLocation& location) {
projectScopedContainers.ForEachIdentifierWithPrefix(
prefix,
const ExpressionParserLocation& location,
bool eagerlyCompleteIfExactMatch = false) {
projectScopedContainers.ForEachIdentifierMatchingSearch(
search,
[&](const gd::String& objectName,
const ObjectConfiguration* objectConfiguration) {
ExpressionCompletionDescription description(
@@ -865,6 +1061,11 @@ class GD_CORE_API ExpressionCompletionFinder
description.SetCompletion(variableName);
description.SetVariableType(variable.GetType());
completions.push_back(description);
if (eagerlyCompleteIfExactMatch && variableName == search) {
AddEagerCompletionForVariableChildren(
variable, variableName, location);
}
},
[&](const gd::NamedPropertyDescriptor& property) {
ExpressionCompletionDescription description(
@@ -892,11 +1093,14 @@ class GD_CORE_API ExpressionCompletionFinder
const gd::String& rootType_,
size_t searchedPosition_,
gd::ExpressionNode* maybeParentNodeAtLocation_)
: platform(platform_),
: searchedPosition(searchedPosition_),
maybeParentNodeAtLocation(maybeParentNodeAtLocation_),
platform(platform_),
projectScopedContainers(projectScopedContainers_),
rootType(rootType_),
searchedPosition(searchedPosition_),
maybeParentNodeAtLocation(maybeParentNodeAtLocation_){};
rootObjectName("") // Always empty, might be changed if variable fields
// in the editor are changed to use completion.
{};
std::vector<ExpressionCompletionDescription> completions;
size_t searchedPosition;
@@ -905,6 +1109,7 @@ class GD_CORE_API ExpressionCompletionFinder
const gd::Platform& platform;
const gd::ProjectScopedContainers& projectScopedContainers;
const gd::String rootType;
const gd::String rootObjectName;
};
} // namespace gd

View File

@@ -14,13 +14,12 @@
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
#include "GDCore/Project/Layout.h" // For GetTypeOfObject and GetTypeOfBehavior
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Tools/Localization.h"
namespace gd {
class Expression;
class ObjectsContainer;
class ObjectsContainersList;
class Platform;
class ParameterMetadata;
class ExpressionMetadata;
@@ -41,10 +40,10 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
* operations.
*/
static const gd::String GetType(const gd::Platform &platform,
const gd::ObjectsContainersList &objectsContainersList,
const gd::ProjectScopedContainers &projectScopedContainers,
gd::ExpressionNode& node) {
gd::ExpressionLeftSideTypeFinder typeFinder(
platform, objectsContainersList);
platform, projectScopedContainers);
node.Visit(typeFinder);
return typeFinder.GetType();
}
@@ -53,9 +52,9 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
protected:
ExpressionLeftSideTypeFinder(const gd::Platform &platform_,
const gd::ObjectsContainersList &objectsContainersList_)
const gd::ProjectScopedContainers &projectScopedContainers_)
: platform(platform_),
objectsContainersList(objectsContainersList_),
projectScopedContainers(projectScopedContainers_),
type("unknown") {};
const gd::String &GetType() {
@@ -67,6 +66,14 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
}
void OnVisitOperatorNode(OperatorNode& node) override {
node.leftHandSide->Visit(*this);
// The type is decided by the first operand, unless it can (`number|string`)
// or should (`unknown`) be refined, in which case we go for the right
// operand (which got visited knowing the type of the first operand, so it's
// equal or strictly more precise than the left operand).
if (type == "unknown" || type == "number|string") {
node.rightHandSide->Visit(*this);
}
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
node.factor->Visit(*this);
@@ -83,7 +90,7 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata(
platform, objectsContainersList, node);
platform, projectScopedContainers.GetObjectsContainersList(), node);
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
type = "unknown";
}
@@ -93,12 +100,99 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
}
void OnVisitVariableNode(VariableNode& node) override {
type = "unknown";
projectScopedContainers.MatchIdentifierWithName<void>(node.name,
[&]() {
// This represents an object.
// We could store it to explore the type of the variable, but in practice this
// is only called for structures/arrays with 2 levels, and we don't support structure
// type identification for now.
},
[&]() {
// This is a variable.
// We could store it to explore the type of the variable, but in practice this
// is only called for structures/arrays with 2 levels, and we don't support structure
// type identification for now.
}, [&]() {
// This is a property with more than one child - this is unsupported.
}, [&]() {
// This is a parameter with more than one child - this is unsupported.
}, [&]() {
// This is something else.
type = "unknown";
});
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
type = "unknown";
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
type = "unknown";
projectScopedContainers.MatchIdentifierWithName<void>(node.identifierName,
[&]() {
// It's an object variable.
if (projectScopedContainers.GetObjectsContainersList()
.HasObjectOrGroupWithVariableNamed(
node.identifierName, node.childIdentifierName)
== ObjectsContainersList::VariableExistence::DoesNotExist) {
type = "unknown";
return;
}
auto variableType =
projectScopedContainers.GetObjectsContainersList()
.GetTypeOfObjectOrGroupVariable(node.identifierName,
node.childIdentifierName);
ReadTypeFromVariable(variableType);
},
[&]() {
// It's a variable.
const auto& variable =
projectScopedContainers.GetVariablesContainersList().Get(
node.identifierName);
if (node.childIdentifierName.empty()) {
ReadTypeFromVariable(variable.GetType());
} else {
if (!variable.HasChild(node.childIdentifierName)) {
type = "unknown";
return;
}
ReadTypeFromVariable(
variable.GetChild(node.childIdentifierName).GetType());
}
}, [&]() {
// This is a property.
const gd::NamedPropertyDescriptor& property = projectScopedContainers
.GetPropertiesContainersList().Get(node.identifierName).second;
if (property.GetType() == "Number") {
type = "number";
} else if (property.GetType() == "Boolean") {
// Nothing - we don't know the precise type (this could be used a string or as a number)
} else {
// Assume type is String or equivalent.
type = "string";
}
}, [&]() {
// It's a parameter.
const auto& parametersVectorsList = projectScopedContainers.GetParametersVectorsList();
const auto& parameter = gd::ParameterMetadataTools::Get(parametersVectorsList, node.identifierName);
const auto& valueTypeMetadata = parameter.GetValueTypeMetadata();
if (valueTypeMetadata.IsNumber()) {
type = "number";
} else if (valueTypeMetadata.IsString()) {
type = "string";
} else if (valueTypeMetadata.IsBoolean()) {
// Nothing - we don't know the precise type (this could be used as a string or as a number).
} else {
type = "unknown";
}
}, [&]() {
// This is something else.
type = "unknown";
});
}
void OnVisitEmptyNode(EmptyNode& node) override {
type = "unknown";
@@ -108,10 +202,18 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
}
private:
void ReadTypeFromVariable(gd::Variable::Type variableType) {
if (variableType == gd::Variable::Number) {
type = "number";
} else if (variableType == gd::Variable::String) {
type = "string";
}
}
gd::String type;
const gd::Platform &platform;
const gd::ObjectsContainersList &objectsContainersList;
const gd::ProjectScopedContainers &projectScopedContainers;
const gd::String rootType;
};

View File

@@ -15,13 +15,12 @@
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
#include "GDCore/Project/Layout.h" // For GetTypeOfObject and GetTypeOfBehavior
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Tools/Localization.h"
namespace gd {
class Expression;
class ObjectsContainer;
class ObjectsContainersList;
class Platform;
class ParameterMetadata;
class ExpressionMetadata;
@@ -51,11 +50,11 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
* sub-expression that a given node represents.
*/
static const gd::String GetType(const gd::Platform &platform,
const gd::ObjectsContainersList &objectsContainersList,
const gd::ProjectScopedContainers &projectScopedContainers,
const gd::String &rootType,
gd::ExpressionNode& node) {
gd::ExpressionTypeFinder typeFinder(
platform, objectsContainersList, rootType);
platform, projectScopedContainers, rootType);
node.Visit(typeFinder);
return typeFinder.GetType();
}
@@ -64,10 +63,10 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
protected:
ExpressionTypeFinder(const gd::Platform &platform_,
const gd::ObjectsContainersList &objectsContainersList_,
const gd::ProjectScopedContainers &projectScopedContainers_,
const gd::String &rootType_)
: platform(platform_),
objectsContainersList(objectsContainersList_),
projectScopedContainers(projectScopedContainers_),
rootType(rootType_),
type(ExpressionTypeFinder::unknownType),
child(nullptr) {};
@@ -113,8 +112,12 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
}
auto leftSideType = gd::ExpressionLeftSideTypeFinder::GetType(
platform,
objectsContainersList,
projectScopedContainers,
node);
// If we can infer a definitive number or string type, use it.
// Otherwise, we only know that it's number or string, and this can even
// be used as is at runtime.
if (leftSideType == ExpressionTypeFinder::numberType
|| leftSideType == ExpressionTypeFinder::stringType) {
type = leftSideType;
@@ -126,7 +129,7 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
if (child == nullptr) {
const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata(
platform, objectsContainersList, node);
platform, projectScopedContainers.GetObjectsContainersList(), node);
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
VisitParent(node);
}
@@ -138,7 +141,7 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
const gd::ParameterMetadata* parameterMetadata =
gd::MetadataProvider::GetFunctionCallParameterMetadata(
platform,
objectsContainersList,
projectScopedContainers.GetObjectsContainersList(),
node,
*child);
if (parameterMetadata == nullptr || parameterMetadata->GetType().empty()) {
@@ -159,7 +162,7 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
else if (rootType == ExpressionTypeFinder::numberOrStringType) {
auto leftSideType = gd::ExpressionLeftSideTypeFinder::GetType(
platform,
objectsContainersList,
projectScopedContainers,
node);
if (leftSideType == ExpressionTypeFinder::numberType
|| leftSideType == ExpressionTypeFinder::stringType) {
@@ -183,7 +186,7 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
ExpressionNode *child;
const gd::Platform &platform;
const gd::ObjectsContainersList &objectsContainersList;
const gd::ProjectScopedContainers &projectScopedContainers;
const gd::String rootType;
};

View File

@@ -89,20 +89,43 @@ bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
const auto& propertiesContainersList = projectScopedContainers.GetPropertiesContainersList();
const auto& parametersVectorsList = projectScopedContainers.GetParametersVectorsList();
// Unless we find something precise (like a variable or property or parameter with a known type),
// we consider this node will be of the type required by the parent.
childType = parentType;
return projectScopedContainers.MatchIdentifierWithName<bool>(identifier.identifierName,
[&]() {
// This represents an object.
if (identifier.childIdentifierName.empty()) {
RaiseTypeError(_("An object variable or expression should be entered."),
identifier.identifierNameLocation);
return true; // We should have found a variable.
}
if (!objectsContainersList.HasObjectOrGroupWithVariableNamed(identifier.identifierName, identifier.childIdentifierName)) {
auto variableExistence = objectsContainersList.HasObjectOrGroupWithVariableNamed(identifier.identifierName, identifier.childIdentifierName);
if (variableExistence == gd::ObjectsContainersList::DoesNotExist) {
RaiseTypeError(_("This variable does not exist on this object or group."),
identifier.identifierNameLocation);
identifier.childIdentifierNameLocation);
return true; // We should have found a variable.
}
else if (variableExistence == gd::ObjectsContainersList::ExistsOnlyOnSomeObjectsOfTheGroup) {
RaiseTypeError(_("This variable only exists on some objects of the group. It must be declared for all objects."),
identifier.childIdentifierNameLocation);
return true; // We should have found a variable.
}
else if (variableExistence == gd::ObjectsContainersList::GroupIsEmpty) {
RaiseTypeError(_("This group is empty. Add an object to this group first."),
identifier.identifierNameLocation);
return true; // We should have found a variable.
}
auto variableType = objectsContainersList.GetTypeOfObjectOrGroupVariable(identifier.identifierName, identifier.childIdentifierName);
ReadChildTypeFromVariable(variableType);
return true; // We found a variable.
}, [&]() {
@@ -110,7 +133,6 @@ bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
// Try to identify a declared variable with the name (and maybe the child
// variable).
const gd::Variable& variable =
variablesContainersList.Get(identifier.identifierName);
@@ -118,6 +140,7 @@ bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
// Just the root variable is accessed, check it can be used in an
// expression.
validateVariableTypeForExpression(variable.GetType());
ReadChildTypeFromVariable(variable.GetType());
return true; // We found a variable.
} else {
@@ -131,6 +154,7 @@ bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
const gd::Variable& childVariable =
variable.GetChild(identifier.childIdentifierName);
ReadChildTypeFromVariable(childVariable.GetType());
return true; // We found a variable.
}
}, [&]() {
@@ -138,23 +162,44 @@ bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
if (!identifier.childIdentifierName.empty()) {
RaiseTypeError(_("Accessing a child variable of a property is not possible - just write the property name."),
identifier.childIdentifierNameLocation);
return true; // We found a property, even if the child is not allowed.
}
const gd::NamedPropertyDescriptor& property = projectScopedContainers
.GetPropertiesContainersList().Get(identifier.identifierName).second;
if (property.GetType() == "Number") {
childType = Type::Number;
} else if (property.GetType() == "Boolean") {
// Nothing - we don't know the precise type (this could be used a string or as a number)
} else {
// Assume type is String or equivalent.
childType = Type::String;
}
return true; // We found a property.
}, [&]() {
// This is a parameter.
if (!identifier.childIdentifierName.empty()) {
RaiseTypeError(_("Accessing a child variable of a parameter is not possible - just write the parameter name."),
identifier.childIdentifierNameLocation);
return true; // We found a parameter, even if the child is not allowed.
}
const auto& parameter = gd::ParameterMetadataTools::Get(parametersVectorsList, identifier.identifierName);
const auto& valueTypeMetadata = parameter.GetValueTypeMetadata();
if (!valueTypeMetadata.IsNumber() && !valueTypeMetadata.IsString() && !valueTypeMetadata.IsBoolean()) {
if (valueTypeMetadata.IsNumber()) {
childType = Type::Number;
} else if (valueTypeMetadata.IsString()) {
childType = Type::String;
} else if (valueTypeMetadata.IsBoolean()) {
// Nothing - we don't know the precise type (this could be used as a string or as a number).
} else {
RaiseTypeError(_("This parameter is not a string, number or boolean - it can't be used in an expression."),
identifier.identifierNameLocation);
return true; // We found a parameter, even though the type is incompatible.
}
@@ -218,11 +263,17 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(
}
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
RaiseError("invalid_function_name",
if (function.functionName.empty()) {
RaiseError("invalid_function_name",
_("Enter the name of the function to call."),
function.location);
} else {
RaiseError("invalid_function_name",
_("Cannot find an expression with this name: ") +
function.functionName + "\n" +
_("Double check that you've not made any typo in the name."),
function.location);
}
return returnType;
}

View File

@@ -103,9 +103,9 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
"example: \"Your name: \" + VariableString(PlayerName).", node.rightHandSide->location);
}
else if (node.op != '+') {
RaiseOperatorError(
_("You've used an operator that is not supported. Only + can be used "
"to concatenate texts."),
RaiseOperatorError(
_("You've used an operator that is not supported. Only + can be used "
"to concatenate texts."),
ExpressionParserLocation(node.leftHandSide->location.GetEndPosition() + 1, node.location.GetEndPosition()));
}
} else if (leftType == Type::Object) {
@@ -124,7 +124,11 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
node.rightHandSide->Visit(*this);
const Type rightType = childType;
childType = leftType;
// The type is decided by the first operand, unless it can (`number|string`)
// or should (`unknown`) be refined, in which case we go for the right
// operand (which got visited knowing the type of the first operand, so it's
// equal or strictly more precise than the left operand).
childType = (leftType == Type::Unknown || leftType == Type::NumberOrString) ? leftType : rightType;
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
ReportAnyError(node);
@@ -309,8 +313,10 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
RaiseTypeError(
_("You've entered a name, but this type was expected:") + " " + TypeToString(parentType),
node.location);
childType = parentType;
} else {
childType = parentType;
}
childType = parentType;
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
ReportAnyError(node);
@@ -379,6 +385,16 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
RaiseError("invalid_operator", message, location);
}
void ReadChildTypeFromVariable(gd::Variable::Type variableType) {
if (variableType == gd::Variable::Number) {
childType = Type::Number;
} else if (variableType == gd::Variable::String) {
childType = Type::String;
} else {
// Nothing - we don't know the precise type (this could be used as a string or as a number).
}
}
static Type StringToType(const gd::String &type);
static const gd::String &TypeToString(Type type);
static const gd::String unknownTypeString;

View File

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

View File

@@ -25,6 +25,9 @@ const UsedExtensionsResult UsedExtensionsFinder::ScanProject(gd::Project& projec
void UsedExtensionsFinder::DoVisitObject(gd::Object &object) {
auto metadata = gd::MetadataProvider::GetExtensionAndObjectMetadata(
project.GetCurrentPlatform(), object.GetType());
if (metadata.GetMetadata().IsRenderedIn3D()) {
result.MarkAsHaving3DObjects();
}
result.GetUsedExtensions().insert(metadata.GetExtension().GetName());
for (auto &&includeFile : metadata.GetMetadata().includeFiles) {
result.GetUsedIncludeFiles().insert(includeFile);
@@ -110,7 +113,7 @@ void UsedExtensionsFinder::OnVisitVariableNode(VariableNode& node) {
result.GetUsedExtensions().insert("BuiltinVariables");
auto type = gd::ExpressionTypeFinder::GetType(
project.GetCurrentPlatform(), GetObjectsContainersList(), rootType, node);
project.GetCurrentPlatform(), GetProjectScopedContainers(), rootType, node);
if (gd::ParameterMetadata::IsExpression("variable", type)) {
// Nothing to do (this can't reference an object)
@@ -154,7 +157,7 @@ void UsedExtensionsFinder::OnVisitVariableBracketAccessorNode(
// Add extensions bound to Objects/Behaviors/Functions
void UsedExtensionsFinder::OnVisitIdentifierNode(IdentifierNode &node) {
auto type = gd::ExpressionTypeFinder::GetType(
project.GetCurrentPlatform(), GetObjectsContainersList(), rootType, node);
project.GetCurrentPlatform(), GetProjectScopedContainers(), rootType, node);
if (gd::ParameterMetadata::IsObject(type) ||
GetObjectsContainersList().HasObjectOrGroupNamed(node.identifierName)) {
// An object or object variable is used.

View File

@@ -44,6 +44,13 @@ public:
return usedRequiredFiles;
}
/**
* \brief Return true when at least 1 object uses the 3D renderer.
*/
bool Has3DObjects() const {
return has3DObjects;
}
/**
* The extensions used by the project (or part of it).
*/
@@ -59,10 +66,15 @@ public:
*/
std::set<gd::String> &GetUsedRequiredFiles() { return usedRequiredFiles; }
void MarkAsHaving3DObjects() {
has3DObjects = true;
}
private:
std::set<gd::String> usedExtensions;
std::set<gd::String> usedIncludeFiles;
std::set<gd::String> usedRequiredFiles;
bool has3DObjects = false;
};
class GD_CORE_API UsedExtensionsFinder

View File

@@ -43,7 +43,6 @@ void ExtensionsLoader::LoadAllExtensions(const gd::String &directory,
struct dirent *lecture;
DIR *rep;
rep = opendir(directory.c_str());
int l = 0;
if (rep == NULL) {
cout << "Unable to open Extensions (" << directory << ") directory."
@@ -63,8 +62,6 @@ void ExtensionsLoader::LoadAllExtensions(const gd::String &directory,
LoadExtension(directory + "/" + lec, platform, forgiving);
librariesLoaded.push_back(directory + "/" + lec);
l++;
}
}
@@ -103,7 +100,6 @@ void ExtensionsLoader::ExtensionsLoadingDone(const gd::String &directory) {
struct dirent *lecture;
DIR *rep;
rep = opendir(directory.c_str());
int l = 0;
if (rep == NULL) {
cout << "Unable to open Extensions (" << directory << ") directory."
@@ -118,7 +114,6 @@ void ExtensionsLoader::ExtensionsLoadingDone(const gd::String &directory) {
lec.find(".xgd" + suffix, lec.length() - 4 - suffix.length()) !=
string::npos) {
librariesLoaded.push_back(directory + "/" + lec);
l++;
}
}

View File

@@ -0,0 +1,246 @@
/*
* GDevelop Core
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "ObjectAssetSerializer.h"
#include <algorithm>
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteObject.h"
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/IDE/Project/AssetResourcePathCleaner.h"
#include "GDCore/IDE/Project/ResourcesInUseHelper.h"
#include "GDCore/IDE/Project/ResourcesRenamer.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/CustomBehavior.h"
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/PropertyDescriptor.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Tools/Log.h"
namespace gd {
gd::String
ObjectAssetSerializer::GetObjectExtensionName(const gd::Object &object) {
const gd::String &type = object.GetType();
const auto separatorIndex =
type.find(PlatformExtension::GetNamespaceSeparator());
return separatorIndex != std::string::npos ? type.substr(0, separatorIndex)
: "";
}
void ObjectAssetSerializer::SerializeTo(
gd::Project &project, const gd::Object &object,
const gd::String &objectFullName, SerializerElement &element,
std::map<gd::String, std::vector<gd::String>> &resourcesFileNameMap) {
auto cleanObject = object.Clone();
cleanObject->GetVariables().Clear();
cleanObject->GetEffects().Clear();
for (auto &&behaviorName : cleanObject->GetAllBehaviorNames()) {
cleanObject->RemoveBehavior(behaviorName);
}
gd::String extensionName = GetObjectExtensionName(*cleanObject);
std::map<gd::String, gd::String> resourcesNameReverseMap;
gd::ObjectAssetSerializer::RenameObjectResourceFiles(
project, *cleanObject, "", objectFullName, resourcesFileNameMap,
resourcesNameReverseMap);
element.SetAttribute("id", "");
element.SetAttribute("name", "");
element.SetAttribute("license", "");
if (project.HasEventsFunctionsExtensionNamed(extensionName)) {
auto &extension = project.GetEventsFunctionsExtension(extensionName);
element.SetAttribute("description", extension.GetShortDescription());
}
element.SetAttribute("gdevelopVersion", "");
element.SetAttribute("version", "");
element.SetIntAttribute("animationsCount", 1);
element.SetIntAttribute("maxFramesCount", 1);
// TODO Find the right object dimensions.
element.SetIntAttribute("width", 0);
element.SetIntAttribute("height", 0);
SerializerElement &authorsElement = element.AddChild("authors");
authorsElement.ConsiderAsArrayOf("author");
SerializerElement &tagsElement = element.AddChild("tags");
tagsElement.ConsiderAsArrayOf("tag");
SerializerElement &objectAssetsElement = element.AddChild("objectAssets");
objectAssetsElement.ConsiderAsArrayOf("objectAsset");
SerializerElement &objectAssetElement =
objectAssetsElement.AddChild("objectAsset");
cleanObject->SerializeTo(objectAssetElement.AddChild("object"));
SerializerElement &resourcesElement =
objectAssetElement.AddChild("resources");
resourcesElement.ConsiderAsArrayOf("resource");
auto &resourcesManager = project.GetResourcesManager();
gd::ResourcesInUseHelper resourcesInUse(resourcesManager);
cleanObject->GetConfiguration().ExposeResources(resourcesInUse);
for (auto &&newResourceName : resourcesInUse.GetAllResources()) {
if (newResourceName.length() == 0) {
continue;
}
auto &resource = resourcesManager.GetResource(
resourcesNameReverseMap.find(newResourceName) !=
resourcesNameReverseMap.end()
? resourcesNameReverseMap[newResourceName]
: newResourceName);
SerializerElement &resourceElement = resourcesElement.AddChild("resource");
resource.SerializeTo(resourceElement);
// Override name and file because the project and the asset don't use the
// same one.
resourceElement.SetAttribute("kind", resource.GetKind());
resourceElement.SetAttribute("name", newResourceName);
auto &oldFilePath = resource.GetFile();
resourceElement.SetAttribute("file", newResourceName);
}
SerializerElement &requiredExtensionsElement =
objectAssetElement.AddChild("requiredExtensions");
requiredExtensionsElement.ConsiderAsArrayOf("requiredExtension");
if (project.HasEventsFunctionsExtensionNamed(extensionName)) {
SerializerElement &requiredExtensionElement =
requiredExtensionsElement.AddChild("requiredExtension");
requiredExtensionElement.SetAttribute("extensionName", extensionName);
requiredExtensionElement.SetAttribute("extensionVersion", "1.0.0");
}
// TODO This can be removed when the asset script no longer require it.
SerializerElement &customizationElement =
objectAssetElement.AddChild("customization");
customizationElement.ConsiderAsArrayOf("empty");
}
void ObjectAssetSerializer::RenameObjectResourceFiles(
gd::Project &project, gd::Object &object,
const gd::String &destinationDirectory, const gd::String &objectFullName,
std::map<gd::String, std::vector<gd::String>> &resourcesFileNameMap,
std::map<gd::String, gd::String> &resourcesNameReverseMap) {
std::map<gd::String, gd::String> cleanedResourcesFileNameMap;
gd::AssetResourcePathCleaner assetResourcePathCleaner(
project.GetResourcesManager(), cleanedResourcesFileNameMap,
resourcesNameReverseMap);
object.GetConfiguration().ExposeResources(assetResourcePathCleaner);
// Use asset store script naming conventions for sprite resource files.
if (object.GetConfiguration().GetType() == "Sprite") {
gd::SpriteObject &spriteConfiguration =
dynamic_cast<gd::SpriteObject &>(object.GetConfiguration());
/// Resource files may be duplicated because the files names allow the
/// asset store script to rebuild the animations.
std::map<gd::String, std::vector<gd::String>> normalizedFileNames;
for (std::size_t animationIndex = 0;
animationIndex < spriteConfiguration.GetAnimationsCount();
animationIndex++) {
auto &animation = spriteConfiguration.GetAnimation(animationIndex);
auto &direction = animation.GetDirection(0);
const gd::String &animationName =
animation.GetName().empty()
? gd::String::From(animationIndex)
: animation.GetName().FindAndReplace("_", " ", true);
// Search frames that share the same resource.
std::map<gd::String, std::vector<int>> frameIndexes;
for (std::size_t frameIndex = 0; frameIndex < direction.GetSpritesCount();
frameIndex++) {
auto &frame = direction.GetSprite(frameIndex);
if (frameIndexes.find(frame.GetImageName()) == frameIndexes.end()) {
std::vector<int> emptyVector;
frameIndexes[frame.GetImageName()] = emptyVector;
}
auto &indexes = frameIndexes[frame.GetImageName()];
indexes.push_back(frameIndex);
}
for (std::size_t frameIndex = 0; frameIndex < direction.GetSpritesCount();
frameIndex++) {
auto &frame = direction.GetSprite(frameIndex);
auto oldName = frame.GetImageName();
if (normalizedFileNames.find(oldName) == normalizedFileNames.end()) {
std::vector<gd::String> value;
normalizedFileNames[oldName] = value;
}
gd::String newName = objectFullName;
if (spriteConfiguration.GetAnimationsCount() > 1) {
newName += "_" + animationName;
}
if (direction.GetSpritesCount() > 1) {
newName += "_";
auto &indexes = frameIndexes[frame.GetImageName()];
for (size_t i = 0; i < indexes.size(); i++) {
newName += gd::String::From(indexes.at(i) + 1);
if (i < indexes.size() - 1) {
newName += ";";
}
}
}
gd::String extension = oldName.substr(oldName.find_last_of("."));
newName += extension;
frame.SetImageName(newName);
auto &newNames = normalizedFileNames[oldName];
if (find(newNames.begin(), newNames.end(), newName) == newNames.end()) {
newNames.push_back(newName);
}
}
}
for (std::map<gd::String, gd::String>::const_iterator it =
cleanedResourcesFileNameMap.begin();
it != cleanedResourcesFileNameMap.end(); ++it) {
if (!it->first.empty()) {
const gd::String &originFile = it->first;
const gd::String &destinationFile = it->second;
std::vector<gd::String> value;
for (auto &&normalizedFileName : normalizedFileNames[destinationFile]) {
value.push_back(normalizedFileName);
}
resourcesFileNameMap[originFile] = value;
}
}
auto clonedResourcesNameReverseMap = resourcesNameReverseMap;
resourcesNameReverseMap.clear();
for (std::map<gd::String, gd::String>::const_iterator it =
clonedResourcesNameReverseMap.begin();
it != clonedResourcesNameReverseMap.end(); ++it) {
if (!it->first.empty()) {
const gd::String& newResourceName = it->first;
const gd::String& oldResourceName = it->second;
for (auto&& normalizedFileName : normalizedFileNames[newResourceName]) {
resourcesNameReverseMap[normalizedFileName] =
oldResourceName;
}
}
}
}
else {
for (std::map<gd::String, gd::String>::const_iterator it =
cleanedResourcesFileNameMap.begin();
it != cleanedResourcesFileNameMap.end(); ++it) {
if (!it->first.empty()) {
const gd::String &originFile = it->first;
const gd::String &destinationFile = it->second;
std::vector<gd::String> value;
value.push_back(destinationFile);
resourcesFileNameMap[originFile] = value;
}
}
}
}
} // namespace gd

View File

@@ -0,0 +1,66 @@
/*
* GDevelop Core
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include <map>
#include <vector>
#include "GDCore/String.h"
namespace gd {
class Object;
class ExtensionDependency;
class PropertyDescriptor;
class Project;
class Layout;
class ArbitraryResourceWorker;
class InitialInstance;
class SerializerElement;
class EffectsContainer;
class AbstractFileSystem;
} // namespace gd
namespace gd {
/**
* \brief Serialize objects into an asset for the store.
*
* \ingroup IDE
*/
class GD_CORE_API ObjectAssetSerializer {
public:
/**
* \brief Serialize an object into an asset.
*
* \param project The project that contains the object and its resources.
* It's not actually modified.
* \param object The object to serialize as an asset.
* \param objectFullName The object name with spaces instead of PascalCase.
* \param element The element where the asset is serialize.
* \param resourcesFileNameMap The map from project resource file paths to
* asset resource file paths. Resource files of Sprite objects may be
* duplicated because the files names allow the asset store script to rebuild
* the animations.
*/
static void
SerializeTo(gd::Project &project, const gd::Object &object,
const gd::String &objectFullName, SerializerElement &element,
std::map<gd::String, std::vector<gd::String>> &resourcesFileNameMap);
~ObjectAssetSerializer(){};
private:
ObjectAssetSerializer(){};
static void RenameObjectResourceFiles(
gd::Project &project, gd::Object &object,
const gd::String &destinationDirectory, const gd::String &objectFullName,
std::map<gd::String, std::vector<gd::String>> &resourcesFileNameMap,
std::map<gd::String, gd::String> &resourcesNameReverseMap);
static gd::String GetObjectExtensionName(const gd::Object &object);
};
} // namespace gd

View File

@@ -52,6 +52,16 @@ void ArbitraryResourceWorker::ExposeModel3D(gd::String& resourceName){
// do.
};
void ArbitraryResourceWorker::ExposeAtlas(gd::String& resourceName){
// Nothing to do by default - each child class can define here the action to
// do.
};
void ArbitraryResourceWorker::ExposeSpine(gd::String& resourceName){
// Nothing to do by default - each child class can define here the action to
// do.
};
void ArbitraryResourceWorker::ExposeVideo(gd::String& videoName){
// Nothing to do by default - each child class can define here the action to
// do.
@@ -63,14 +73,10 @@ void ArbitraryResourceWorker::ExposeBitmapFont(gd::String& bitmapFontName){
};
void ArbitraryResourceWorker::ExposeAudio(gd::String& audioName) {
for (auto resources : GetResources()) {
if (!resources) continue;
if (resources->HasResource(audioName) &&
resources->GetResource(audioName).GetKind() == "audio") {
// Nothing to do, the audio is a reference to a proper resource.
return;
}
if (resourcesManager->HasResource(audioName) &&
resourcesManager->GetResource(audioName).GetKind() == "audio") {
// Nothing to do, the audio is a reference to a proper resource.
return;
}
// For compatibility with older projects (where events were referring to files
@@ -80,14 +86,10 @@ void ArbitraryResourceWorker::ExposeAudio(gd::String& audioName) {
};
void ArbitraryResourceWorker::ExposeFont(gd::String& fontName) {
for (auto resources : GetResources()) {
if (!resources) continue;
if (resources->HasResource(fontName) &&
resources->GetResource(fontName).GetKind() == "font") {
// Nothing to do, the font is a reference to a proper resource.
return;
}
if (resourcesManager->HasResource(fontName) &&
resourcesManager->GetResource(fontName).GetKind() == "font") {
// Nothing to do, the font is a reference to a proper resource.
return;
}
// For compatibility with older projects (where events were referring to files
@@ -96,12 +98,7 @@ void ArbitraryResourceWorker::ExposeFont(gd::String& fontName) {
ExposeFile(fontName);
};
void ArbitraryResourceWorker::ExposeResources(
gd::ResourcesManager* resourcesManager) {
if (!resourcesManager) return;
resourcesManagers.push_back(resourcesManager);
void ArbitraryResourceWorker::ExposeResources() {
std::vector<gd::String> resources = resourcesManager->GetAllResourceNames();
for (std::size_t i = 0; i < resources.size(); i++) {
if (resourcesManager->GetResource(resources[i]).UseFile())
@@ -110,9 +107,6 @@ void ArbitraryResourceWorker::ExposeResources(
}
void ArbitraryResourceWorker::ExposeEmbeddeds(gd::String& resourceName) {
if (resourcesManagers.empty()) return;
gd::ResourcesManager* resourcesManager = resourcesManagers[0];
gd::Resource& resource = resourcesManager->GetResource(resourceName);
if (!resource.GetMetadata().empty()) {
@@ -136,6 +130,7 @@ void ArbitraryResourceWorker::ExposeEmbeddeds(gd::String& resourceName) {
gd::String potentiallyUpdatedTargetResourceName = targetResourceName;
ExposeResourceWithType(targetResource.GetKind(), potentiallyUpdatedTargetResourceName);
ExposeEmbeddeds(potentiallyUpdatedTargetResourceName);
if (potentiallyUpdatedTargetResourceName != targetResourceName) {
// The resource name was renamed. Also update the mapping.
@@ -176,6 +171,7 @@ void ArbitraryResourceWorker::ExposeResourceWithType(
}
if (resourceType == "tilemap") {
ExposeTilemap(resourceName);
ExposeEmbeddeds(resourceName);
return;
}
if (resourceType == "tileset") {
@@ -184,12 +180,21 @@ void ArbitraryResourceWorker::ExposeResourceWithType(
}
if (resourceType == "json") {
ExposeJson(resourceName);
ExposeEmbeddeds(resourceName);
return;
}
if (resourceType == "video") {
ExposeVideo(resourceName);
return;
}
if (resourceType == "atlas") {
ExposeAtlas(resourceName);
return;
}
if (resourceType == "spine") {
ExposeSpine(resourceName);
return;
}
gd::LogError("Unexpected resource type: " + resourceType + " for: " + resourceName);
return;
}
@@ -243,10 +248,12 @@ bool ResourceWorkerInEventsWorker::DoVisitInstruction(gd::Instruction& instructi
} else if (parameterMetadata.GetType() == "jsonResource") {
gd::String updatedParameterValue = parameterValue;
worker.ExposeJson(updatedParameterValue);
worker.ExposeEmbeddeds(updatedParameterValue);
instruction.SetParameter(parameterIndex, updatedParameterValue);
} else if (parameterMetadata.GetType() == "tilemapResource") {
gd::String updatedParameterValue = parameterValue;
worker.ExposeTilemap(updatedParameterValue);
worker.ExposeEmbeddeds(updatedParameterValue);
instruction.SetParameter(parameterIndex, updatedParameterValue);
} else if (parameterMetadata.GetType() == "tilesetResource") {
gd::String updatedParameterValue = parameterValue;
@@ -256,19 +263,20 @@ bool ResourceWorkerInEventsWorker::DoVisitInstruction(gd::Instruction& instructi
gd::String updatedParameterValue = parameterValue;
worker.ExposeModel3D(updatedParameterValue);
instruction.SetParameter(parameterIndex, updatedParameterValue);
} else if (parameterMetadata.GetType() == "atlasResource") {
gd::String updatedParameterValue = parameterValue;
worker.ExposeAtlas(updatedParameterValue);
instruction.SetParameter(parameterIndex, updatedParameterValue);
} else if (parameterMetadata.GetType() == "spineResource") {
gd::String updatedParameterValue = parameterValue;
worker.ExposeSpine(updatedParameterValue);
instruction.SetParameter(parameterIndex, updatedParameterValue);
}
});
return false;
};
void LaunchResourceWorkerOnEvents(const gd::Project& project,
gd::EventsList& events,
gd::ArbitraryResourceWorker& worker) {
gd::ResourceWorkerInEventsWorker eventsWorker(project, worker);
eventsWorker.Launch(events);
}
gd::ResourceWorkerInEventsWorker
GetResourceWorkerOnEvents(const gd::Project &project,
gd::ArbitraryResourceWorker &worker) {
@@ -293,8 +301,8 @@ void ResourceWorkerInObjectsWorker::DoVisitBehavior(gd::Behavior &behavior){
gd::ResourceWorkerInObjectsWorker
GetResourceWorkerOnObjects(const gd::Project &project,
gd::ArbitraryResourceWorker &worker) {
gd::ResourceWorkerInObjectsWorker eventsWorker(project, worker);
return eventsWorker;
gd::ResourceWorkerInObjectsWorker resourcesWorker(project, worker);
return resourcesWorker;
}
} // namespace gd

View File

@@ -37,13 +37,14 @@ namespace gd {
* \see ResourcesMergingHelper
* \see gd::ResourcesInUseHelper
*
* \see gd::LaunchResourceWorkerOnEvents
* \see gd::GetResourceWorkerOnEvents
*
* \ingroup IDE
*/
class GD_CORE_API ArbitraryResourceWorker {
public:
ArbitraryResourceWorker(){};
public:
ArbitraryResourceWorker(gd::ResourcesManager &resourcesManager_)
: resourcesManager(&resourcesManager_){};
virtual ~ArbitraryResourceWorker();
/**
@@ -52,7 +53,7 @@ class GD_CORE_API ArbitraryResourceWorker {
* first to ensure that resources are known so that images, shaders & audio
* can make reference to them.
*/
void ExposeResources(gd::ResourcesManager *resourcesManager);
void ExposeResources();
/**
* \brief Expose a resource from a given type.
@@ -95,6 +96,16 @@ class GD_CORE_API ArbitraryResourceWorker {
* \brief Expose a 3D model, which is always a reference to a "model3D" resource.
*/
virtual void ExposeModel3D(gd::String &resourceName);
/**
* \brief Expose an atlas, which is always a reference to a "atlas" resource.
*/
virtual void ExposeAtlas(gd::String &resourceName);
/**
* \brief Expose an spine, which is always a reference to a "spine" resource.
*/
virtual void ExposeSpine(gd::String &resourceName);
/**
* \brief Expose a video, which is always a reference to a "video" resource.
@@ -122,10 +133,8 @@ class GD_CORE_API ArbitraryResourceWorker {
*/
virtual void ExposeEmbeddeds(gd::String &resourceName);
protected:
const std::vector<gd::ResourcesManager *> &GetResources() {
return resourcesManagers;
};
protected:
gd::ResourcesManager * resourcesManager;
private:
/**
@@ -133,8 +142,6 @@ class GD_CORE_API ArbitraryResourceWorker {
* exposed as file (see ExposeFile).
*/
void ExposeResource(gd::Resource &resource);
std::vector<gd::ResourcesManager *> resourcesManagers;
};
/**

View File

@@ -0,0 +1,74 @@
/*
* GDevelop Core
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "AssetResourcePathCleaner.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ResourcesManager.h"
#include "GDCore/String.h"
namespace gd {
void AssetResourcePathCleaner::ExposeImage(gd::String &imageName) {
ExposeResourceAsFile(imageName);
}
void AssetResourcePathCleaner::ExposeAudio(gd::String &audioName) {
ExposeResourceAsFile(audioName);
}
void AssetResourcePathCleaner::ExposeFont(gd::String &fontName) {
ExposeResourceAsFile(fontName);
}
void AssetResourcePathCleaner::ExposeJson(gd::String &jsonName) {
ExposeResourceAsFile(jsonName);
}
void AssetResourcePathCleaner::ExposeTilemap(gd::String &tilemapName) {
ExposeResourceAsFile(tilemapName);
}
void AssetResourcePathCleaner::ExposeTileset(gd::String &tilesetName) {
ExposeResourceAsFile(tilesetName);
}
void AssetResourcePathCleaner::ExposeVideo(gd::String &videoName) {
ExposeResourceAsFile(videoName);
}
void AssetResourcePathCleaner::ExposeBitmapFont(gd::String &bitmapFontName) {
ExposeResourceAsFile(bitmapFontName);
}
void AssetResourcePathCleaner::ExposeResourceAsFile(gd::String &resourceName) {
auto &resource = resourcesManager->GetResource(resourceName);
gd::String file = resource.GetFile();
ExposeFile(file);
resourcesNameReverseMap[file] = resourceName;
resourceName = file;
}
void AssetResourcePathCleaner::ExposeFile(gd::String &resourceFilePath) {
size_t slashPos = resourceFilePath.find_last_of("/");
size_t antiSlashPos = resourceFilePath.find_last_of("\\");
size_t baseNamePos =
slashPos == String::npos
? antiSlashPos == String::npos ? 0 : (antiSlashPos + 1)
: antiSlashPos == String::npos ? (slashPos + 1)
: slashPos > antiSlashPos ? (slashPos + 1)
: (antiSlashPos + 1);
gd::String baseName =
baseNamePos != 0
? resourceFilePath.substr(baseNamePos, resourceFilePath.length())
: resourceFilePath;
resourcesFileNameMap[resourceFilePath] = baseName;
resourceFilePath = baseName;
}
} // namespace gd

View File

@@ -0,0 +1,65 @@
/*
* GDevelop Core
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
#include "GDCore/IDE/Project/ResourcesMergingHelper.h"
#include "GDCore/String.h"
#include <map>
#include <memory>
#include <vector>
namespace gd {
class AbstractFileSystem;
class Project;
} // namespace gd
namespace gd {
/**
* \brief AssetResourcePathCleaner is used when exporting an object as an asset.
* It removes the folder from the path.
*
* \see ArbitraryResourceWorker
*
* \ingroup IDE
*/
class GD_CORE_API AssetResourcePathCleaner : public ArbitraryResourceWorker {
public:
AssetResourcePathCleaner(
gd::ResourcesManager &resourcesManager,
std::map<gd::String, gd::String> &resourcesFileNameMap_,
std::map<gd::String, gd::String> &resourcesNameReverseMap_)
: ArbitraryResourceWorker(resourcesManager),
resourcesFileNameMap(resourcesFileNameMap_),
resourcesNameReverseMap(resourcesNameReverseMap_){};
virtual ~AssetResourcePathCleaner(){};
void ExposeImage(gd::String &imageName) override;
void ExposeAudio(gd::String &audioName) override;
void ExposeFont(gd::String &fontName) override;
void ExposeJson(gd::String &jsonName) override;
void ExposeTilemap(gd::String &tilemapName) override;
void ExposeTileset(gd::String &tilesetName) override;
void ExposeVideo(gd::String &videoName) override;
void ExposeBitmapFont(gd::String &bitmapFontName) override;
void ExposeFile(gd::String &resource) override;
protected:
void ExposeResourceAsFile(gd::String &resourceName);
/**
* New file names that can be accessed by their original name.
*/
std::map<gd::String, gd::String> &resourcesFileNameMap;
/**
* Original resource names that can be accessed by their new name.
*/
std::map<gd::String, gd::String> &resourcesNameReverseMap;
};
} // namespace gd

View File

@@ -0,0 +1,19 @@
#include "ObjectsUsingResourceCollector.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"
namespace gd {
void ObjectsUsingResourceCollector::DoVisitObject(gd::Object& object) {
gd::ResourceNameMatcher resourceNameMatcher(*resourcesManager, resourceName);
object.GetConfiguration().ExposeResources(resourceNameMatcher);
if (resourceNameMatcher.AnyResourceMatches()) {
objectNames.push_back(object.GetName());
}
};
ObjectsUsingResourceCollector::~ObjectsUsingResourceCollector() {}
} // namespace gd

View File

@@ -0,0 +1,97 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include <vector>
#include "GDCore/IDE/Project/ArbitraryObjectsWorker.h"
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
#include "GDCore/String.h"
namespace gd {
class Object;
} // namespace gd
namespace gd {
class GD_CORE_API ObjectsUsingResourceCollector
: public ArbitraryObjectsWorker {
public:
ObjectsUsingResourceCollector(gd::ResourcesManager &resourcesManager_,
const gd::String &resourceName_)
: resourcesManager(&resourcesManager_), resourceName(resourceName_){};
virtual ~ObjectsUsingResourceCollector();
std::vector<gd::String>& GetObjectNames() { return objectNames; }
private:
void DoVisitObject(gd::Object& object) override;
std::vector<gd::String> objectNames;
gd::String resourceName;
gd::ResourcesManager *resourcesManager;
};
class GD_CORE_API ResourceNameMatcher : public ArbitraryResourceWorker {
public:
ResourceNameMatcher(gd::ResourcesManager &resourcesManager,
const gd::String &resourceName_)
: resourceName(resourceName_),
matchesResourceName(false), gd::ArbitraryResourceWorker(
resourcesManager){};
virtual ~ResourceNameMatcher(){};
bool AnyResourceMatches() { return matchesResourceName; }
void Reset() { matchesResourceName = false; }
private:
virtual void ExposeFile(gd::String& resource) override{
/*Don't care, we just read resource names*/
};
virtual void ExposeImage(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeAudio(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeFont(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeJson(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeTilemap(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeTileset(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeVideo(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeBitmapFont(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeModel3D(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeAtlas(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeSpine(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
void MatchResourceName(gd::String& otherResourceName) {
if (otherResourceName == resourceName) matchesResourceName = true;
}
gd::String resourceName;
bool matchesResourceName;
};
}; // namespace gd

View File

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

View File

@@ -25,8 +25,29 @@ bool ProjectResourcesCopier::CopyAllResourcesTo(
bool updateOriginalProject,
bool preserveAbsoluteFilenames,
bool preserveDirectoryStructure) {
if (updateOriginalProject) {
gd::ProjectResourcesCopier::CopyAllResourcesTo(
originalProject, originalProject, fs, destinationDirectory,
preserveAbsoluteFilenames, preserveDirectoryStructure);
} else {
gd::Project clonedProject = originalProject;
gd::ProjectResourcesCopier::CopyAllResourcesTo(
originalProject, clonedProject, fs, destinationDirectory,
preserveAbsoluteFilenames, preserveDirectoryStructure);
}
return true;
}
bool ProjectResourcesCopier::CopyAllResourcesTo(
gd::Project& originalProject,
gd::Project& clonedProject,
AbstractFileSystem& fs,
gd::String destinationDirectory,
bool preserveAbsoluteFilenames,
bool preserveDirectoryStructure) {
// Check if there are some resources with absolute filenames
gd::ResourcesAbsolutePathChecker absolutePathChecker(fs);
gd::ResourcesAbsolutePathChecker absolutePathChecker(originalProject.GetResourcesManager(), fs);
gd::ResourceExposer::ExposeWholeProjectResources(originalProject, absolutePathChecker);
auto projectDirectory = fs.DirNameFrom(originalProject.GetProjectFile());
@@ -34,24 +55,18 @@ bool ProjectResourcesCopier::CopyAllResourcesTo(
<< destinationDirectory << "..." << std::endl;
// Get the resources to be copied
gd::ResourcesMergingHelper resourcesMergingHelper(fs);
gd::ResourcesMergingHelper resourcesMergingHelper(
clonedProject.GetResourcesManager(), fs);
resourcesMergingHelper.SetBaseDirectory(projectDirectory);
resourcesMergingHelper.PreserveDirectoriesStructure(
preserveDirectoryStructure);
resourcesMergingHelper.PreserveAbsoluteFilenames(
preserveAbsoluteFilenames);
if (updateOriginalProject) {
gd::ResourceExposer::ExposeWholeProjectResources(originalProject, resourcesMergingHelper);
} else {
std::shared_ptr<gd::Project> project(new gd::Project(originalProject));
gd::ResourceExposer::ExposeWholeProjectResources(*project, resourcesMergingHelper);
}
resourcesMergingHelper.PreserveAbsoluteFilenames(preserveAbsoluteFilenames);
gd::ResourceExposer::ExposeWholeProjectResources(clonedProject,
resourcesMergingHelper);
// Copy resources
map<gd::String, gd::String>& resourcesNewFilename =
resourcesMergingHelper.GetAllResourcesOldAndNewFilename();
unsigned int i = 0;
for (map<gd::String, gd::String>::const_iterator it =
resourcesNewFilename.begin();
it != resourcesNewFilename.end();
@@ -71,8 +86,6 @@ bool ProjectResourcesCopier::CopyAllResourcesTo(
destinationFile + _("\"."));
}
}
++i;
}
return true;

View File

@@ -3,9 +3,10 @@
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef PROJECTRESOURCESCOPIER_H
#define PROJECTRESOURCESCOPIER_H
#pragma once
#include "GDCore/String.h"
namespace gd {
class Project;
class AbstractFileSystem;
@@ -47,8 +48,14 @@ class GD_CORE_API ProjectResourcesCopier {
bool updateOriginalProject,
bool preserveAbsoluteFilenames = true,
bool preserveDirectoryStructure = true);
private:
static bool CopyAllResourcesTo(gd::Project& originalProject,
gd::Project& clonedProject,
gd::AbstractFileSystem& fs,
gd::String destinationDirectory,
bool preserveAbsoluteFilenames = true,
bool preserveDirectoryStructure = true);
};
} // namespace gd
#endif // PROJECTRESOURCESCOPIER_H

View File

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

View File

@@ -0,0 +1,25 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "ResourcesInUseHelper.h"
namespace gd {
const std::vector<gd::String> ResourcesInUseHelper::resourceTypes = {
"image", "audio", "font", "json", "tilemap",
"tileset", "video", "bitmapFont", "model3D"};
const std::vector<gd::String> &ResourcesInUseHelper::GetAllResources() {
allResources.clear();
for (auto &&resourceType : gd::ResourcesInUseHelper::resourceTypes) {
for (auto &&resourceName : GetAll(resourceType)) {
allResources.push_back(resourceName);
}
}
return allResources;
}
} // namespace gd

View File

@@ -4,9 +4,7 @@
* reserved. This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#ifndef IMAGESUSEDINVENTORIZER_H
#define IMAGESUSEDINVENTORIZER_H
#pragma once
#include <set>
#include <vector>
@@ -33,10 +31,12 @@ std::set<gd::String> & usedImages = resourcesInUse.GetAllImages();
* \ingroup IDE
*/
class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
public:
ResourcesInUseHelper() : gd::ArbitraryResourceWorker(){};
public:
ResourcesInUseHelper(gd::ResourcesManager &resourcesManager)
: gd::ArbitraryResourceWorker(resourcesManager){};
virtual ~ResourcesInUseHelper(){};
const std::vector<gd::String>& GetAllResources();
std::set<gd::String>& GetAllImages() { return GetAll("image"); };
std::set<gd::String>& GetAllAudios() { return GetAll("audio"); };
std::set<gd::String>& GetAllFonts() { return GetAll("font"); };
@@ -46,6 +46,8 @@ class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
std::set<gd::String>& GetAllVideos() { return GetAll("video"); };
std::set<gd::String>& GetAllBitmapFonts() { return GetAll("bitmapFont"); };
std::set<gd::String>& GetAll3DModels() { return GetAll("model3D"); };
std::set<gd::String>& GetAllAtlases() { return GetAll("atlas"); };
std::set<gd::String>& GetAllSpines() { return GetAll("spine"); };
std::set<gd::String>& GetAll(const gd::String& resourceType) {
if (resourceType == "image") return allImages;
if (resourceType == "audio") return allAudios;
@@ -56,6 +58,8 @@ class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
if (resourceType == "video") return allVideos;
if (resourceType == "bitmapFont") return allBitmapFonts;
if (resourceType == "model3D") return allModel3Ds;
if (resourceType == "atlas") return allAtlases;
if (resourceType == "spine") return allSpines;
return emptyResources;
};
@@ -63,35 +67,42 @@ class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
virtual void ExposeFile(gd::String& resource) override{
/*Don't care, we just list resource names*/
};
virtual void ExposeImage(gd::String& imageResourceName) override {
allImages.insert(imageResourceName);
virtual void ExposeImage(gd::String& resourceName) override {
allImages.insert(resourceName);
};
virtual void ExposeAudio(gd::String& audioResourceName) override {
allAudios.insert(audioResourceName);
virtual void ExposeAudio(gd::String& resourceName) override {
allAudios.insert(resourceName);
};
virtual void ExposeFont(gd::String& fontResourceName) override {
allFonts.insert(fontResourceName);
virtual void ExposeFont(gd::String& resourceName) override {
allFonts.insert(resourceName);
};
virtual void ExposeJson(gd::String& jsonResourceName) override {
allJsons.insert(jsonResourceName);
virtual void ExposeJson(gd::String& resourceName) override {
allJsons.insert(resourceName);
};
virtual void ExposeTilemap(gd::String& tilemapResourceName) override {
allTilemaps.insert(tilemapResourceName);
virtual void ExposeTilemap(gd::String& resourceName) override {
allTilemaps.insert(resourceName);
};
virtual void ExposeTileset(gd::String& tilesetResourceName) override {
allTilesets.insert(tilesetResourceName);
virtual void ExposeTileset(gd::String& resourceName) override {
allTilesets.insert(resourceName);
};
virtual void ExposeVideo(gd::String& videoResourceName) override {
allVideos.insert(videoResourceName);
virtual void ExposeVideo(gd::String& resourceName) override {
allVideos.insert(resourceName);
};
virtual void ExposeBitmapFont(gd::String& bitmapFontResourceName) override {
allBitmapFonts.insert(bitmapFontResourceName);
virtual void ExposeBitmapFont(gd::String& resourceName) override {
allBitmapFonts.insert(resourceName);
};
virtual void ExposeModel3D(gd::String& resourceName) override {
allModel3Ds.insert(resourceName);
};
virtual void ExposeAtlas(gd::String& resourceName) override {
allAtlases.insert(resourceName);
};
virtual void ExposeSpine(gd::String& resourceName) override {
allSpines.insert(resourceName);
};
protected:
std::vector<gd::String> allResources;
std::set<gd::String> allImages;
std::set<gd::String> allAudios;
std::set<gd::String> allFonts;
@@ -101,10 +112,11 @@ class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
std::set<gd::String> allVideos;
std::set<gd::String> allBitmapFonts;
std::set<gd::String> allModel3Ds;
std::set<gd::String> allAtlases;
std::set<gd::String> allSpines;
std::set<gd::String> emptyResources;
static const std::vector<gd::String> resourceTypes;
};
} // namespace gd
#endif // IMAGESUSEDINVENTORIZER_H
#endif

View File

@@ -28,7 +28,7 @@ void ResourcesMergingHelper::ExposeFile(gd::String& resourceFilename) {
auto stripToFilenameOnly = [&]() {
fs.MakeAbsolute(resourceFullFilename, baseDirectory);
SetNewFilename(resourceFullFilename, fs.FileNameFrom(resourceFullFilename));
resourceFilename = oldFilenames[resourceFullFilename];
resourceFilename = newFilenames[resourceFullFilename];
};
// if we do not want to preserve the folders at all,
@@ -45,7 +45,7 @@ void ResourcesMergingHelper::ExposeFile(gd::String& resourceFilename) {
gd::String relativeFilename = resourceFullFilename;
if (fs.MakeRelative(relativeFilename, baseDirectory)) {
SetNewFilename(resourceFullFilename, relativeFilename);
resourceFilename = oldFilenames[resourceFullFilename];
resourceFilename = newFilenames[resourceFullFilename];
} else {
// The filename cannot be made relative. Consider that it is absolute.
// Just strip the filename to its file part
@@ -63,7 +63,7 @@ void ResourcesMergingHelper::ExposeFile(gd::String& resourceFilename) {
void ResourcesMergingHelper::SetNewFilename(gd::String oldFilename,
gd::String newFilename) {
if (oldFilenames.find(oldFilename) != oldFilenames.end()) return;
if (newFilenames.find(oldFilename) != newFilenames.end()) return;
// Extract baseName and extension from the new filename
size_t extensionPos = newFilename.find_last_of(".");
@@ -80,13 +80,13 @@ void ResourcesMergingHelper::SetNewFilename(gd::String oldFilename,
gd::NewNameGenerator::Generate(
baseName,
[this, extension](const gd::String& newBaseName) {
return newFilenames.find(newBaseName + extension) !=
newFilenames.end();
return oldFilenames.find(newBaseName + extension) !=
oldFilenames.end();
}) +
extension;
oldFilenames[oldFilename] = finalFilename;
newFilenames[finalFilename] = oldFilename;
newFilenames[oldFilename] = finalFilename;
oldFilenames[finalFilename] = oldFilename;
}
void ResourcesMergingHelper::SetBaseDirectory(

View File

@@ -28,11 +28,11 @@ namespace gd {
* \ingroup IDE
*/
class GD_CORE_API ResourcesMergingHelper : public ArbitraryResourceWorker {
public:
ResourcesMergingHelper(gd::AbstractFileSystem& fileSystem)
: ArbitraryResourceWorker(),
preserveDirectoriesStructure(false),
preserveAbsoluteFilenames(false),
public:
ResourcesMergingHelper(gd::ResourcesManager &resourcesManager,
gd::AbstractFileSystem &fileSystem)
: ArbitraryResourceWorker(resourcesManager),
preserveDirectoriesStructure(false), preserveAbsoluteFilenames(false),
fs(fileSystem){};
virtual ~ResourcesMergingHelper(){};
@@ -64,19 +64,25 @@ class GD_CORE_API ResourcesMergingHelper : public ArbitraryResourceWorker {
* the Base Directory.
*/
std::map<gd::String, gd::String>& GetAllResourcesOldAndNewFilename() {
return oldFilenames;
return newFilenames;
};
/**
* Resources merging helper collects all resources filenames and update these
* filenames.
*/
virtual void ExposeFile(gd::String& resource) override;
void ExposeFile(gd::String& resource) override;
protected:
void SetNewFilename(gd::String oldFilename, gd::String newFilename);
/**
* Original file names that can be accessed by their new name.
*/
std::map<gd::String, gd::String> oldFilenames;
/**
* New file names that can be accessed by their original name.
*/
std::map<gd::String, gd::String> newFilenames;
gd::String baseDirectory;
bool preserveDirectoriesStructure; ///< If set to true, the directory

View File

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

View File

@@ -0,0 +1,37 @@
/*
* GDevelop JS Platform
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "SceneResourcesFinder.h"
#include "GDCore/IDE/ResourceExposer.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Serialization/SerializerElement.h"
namespace gd {
std::set<gd::String> SceneResourcesFinder::FindProjectResources(gd::Project &project) {
gd::SceneResourcesFinder resourceWorker(project.GetResourcesManager());
gd::ResourceExposer::ExposeProjectResources(project, resourceWorker);
return resourceWorker.resourceNames;
}
std::set<gd::String> SceneResourcesFinder::FindSceneResources(gd::Project &project,
gd::Layout &layout) {
gd::SceneResourcesFinder resourceWorker(project.GetResourcesManager());
gd::ResourceExposer::ExposeLayoutResources(project, layout, resourceWorker);
return resourceWorker.resourceNames;
}
void SceneResourcesFinder::AddUsedResource(gd::String &resourceName) {
if (resourceName.empty()) {
return;
}
resourceNames.insert(resourceName);
}
} // namespace gd

View File

@@ -0,0 +1,93 @@
/*
* GDevelop JS Platform
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
#include "GDCore/String.h"
#include <set>
namespace gd {
class Project;
class Layout;
class SerializerElement;
} // namespace gd
namespace gd {
/**
* \brief Find resource usages in several parts of the project.
*
* \ingroup IDE
*/
class SceneResourcesFinder : private gd::ArbitraryResourceWorker {
public:
/**
* @brief Find resource usages in a given scenes.
*
* It doesn't include resources used globally.
*/
static std::set<gd::String> FindSceneResources(gd::Project &project,
gd::Layout &layout);
/**
* @brief Find resource that are used globally in the project.
*
* It doesn't include resources used in scenes.
*/
static std::set<gd::String> FindProjectResources(gd::Project &project);
virtual ~SceneResourcesFinder(){};
private:
SceneResourcesFinder(gd::ResourcesManager &resourcesManager)
: gd::ArbitraryResourceWorker(resourcesManager){};
void AddUsedResource(gd::String &resourceName);
void ExposeFile(gd::String &resourceFileName) override{
// Don't do anything: we're renaming resources, not the files they are
// pointing to.
};
void ExposeImage(gd::String &imageResourceName) override {
AddUsedResource(imageResourceName);
};
void ExposeAudio(gd::String &audioResourceName) override {
AddUsedResource(audioResourceName);
};
void ExposeFont(gd::String &fontResourceName) override {
AddUsedResource(fontResourceName);
};
void ExposeJson(gd::String &jsonResourceName) override {
AddUsedResource(jsonResourceName);
};
void ExposeTilemap(gd::String &tilemapResourceName) override {
AddUsedResource(tilemapResourceName);
};
void ExposeTileset(gd::String &tilesetResourceName) override {
AddUsedResource(tilesetResourceName);
};
void ExposeVideo(gd::String &videoResourceName) override {
AddUsedResource(videoResourceName);
};
void ExposeBitmapFont(gd::String &bitmapFontName) override {
AddUsedResource(bitmapFontName);
};
void ExposeModel3D(gd::String &resourceName) override {
AddUsedResource(resourceName);
};
void ExposeAtlas(gd::String &resourceName) override {
AddUsedResource(resourceName);
};
void ExposeSpine(gd::String &resourceName) override {
AddUsedResource(resourceName);
};
std::set<gd::String> resourceNames;
};
} // namespace gd

View File

@@ -20,6 +20,7 @@
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/String.h"
#include "GDCore/IDE/DependenciesAnalyzer.h"
namespace gd {
@@ -33,27 +34,8 @@ void ProjectBrowserHelper::ExposeProjectEvents(
// Add events based extensions
for (std::size_t e = 0; e < project.GetEventsFunctionsExtensionsCount();
e++) {
// Add (free) events functions
auto &eventsFunctionsExtension = project.GetEventsFunctionsExtension(e);
for (auto &&eventsFunction : eventsFunctionsExtension.GetInternalVector()) {
worker.Launch(eventsFunction->GetEvents());
}
// Add (behavior) events functions
for (auto &&eventsBasedBehavior :
eventsFunctionsExtension.GetEventsBasedBehaviors()
.GetInternalVector()) {
ExposeEventsBasedBehaviorEvents(project, *eventsBasedBehavior, worker);
}
// Add (object) events functions
for (auto &&eventsBasedObject :
eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) {
auto &objectEventsFunctions = eventsBasedObject->GetEventsFunctions();
for (auto &&eventsFunction : objectEventsFunctions.GetInternalVector()) {
worker.Launch(eventsFunction->GetEvents());
}
}
ProjectBrowserHelper::ExposeEventsFunctionsExtensionEvents(project, eventsFunctionsExtension, worker);
}
}
@@ -69,7 +51,7 @@ void ProjectBrowserHelper::ExposeProjectEventsWithoutExtensions(
}
}
void ProjectBrowserHelper::ExposeLayoutEvents(
void ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
gd::Project &project, gd::Layout &layout,
gd::ArbitraryEventsWorker &worker) {
@@ -85,7 +67,7 @@ void ProjectBrowserHelper::ExposeLayoutEvents(
}
}
void ProjectBrowserHelper::ExposeLayoutEvents(
void ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
gd::Project &project, gd::Layout &layout,
gd::ArbitraryEventsWorkerWithContext &worker) {
auto projectScopedContainers =
@@ -103,6 +85,32 @@ void ProjectBrowserHelper::ExposeLayoutEvents(
}
}
void ProjectBrowserHelper::ExposeLayoutEventsAndDependencies(
gd::Project &project, gd::Layout &layout,
gd::ArbitraryEventsWorker &worker) {
// Add layouts events
worker.Launch(layout.GetEvents());
DependenciesAnalyzer dependenciesAnalyzer(project, layout);
bool hasCircularDependencies = !dependenciesAnalyzer.Analyze();
if (hasCircularDependencies) {
// The analyzer stops when it finds circular dependencies so the dependencies are not complete.
// TODO Should the analyzer still continue to avoid side effect on thing that would not be code generation related?
// Maybe a boolean parameter should be added?
return;
}
for (const gd::String& externalEventName : dependenciesAnalyzer.GetExternalEventsDependencies()) {
gd::ExternalEvents& externalEvents = project.GetExternalEvents(externalEventName);
worker.Launch(externalEvents.GetEvents());
}
for (const gd::String& sceneName : dependenciesAnalyzer.GetScenesDependencies()) {
gd::Layout& dependencyLayout = project.GetLayout(sceneName);
worker.Launch(dependencyLayout.GetEvents());
}
}
void ProjectBrowserHelper::ExposeProjectEvents(
gd::Project &project, gd::ArbitraryEventsWorkerWithContext &worker) {
// See also gd::Project::ExposeResources for a method that traverse the whole
@@ -130,8 +138,43 @@ void ProjectBrowserHelper::ExposeProjectEvents(
// Add events based extensions
for (std::size_t e = 0; e < project.GetEventsFunctionsExtensionsCount();
e++) {
// Add (free) events functions
auto &eventsFunctionsExtension = project.GetEventsFunctionsExtension(e);
ProjectBrowserHelper::ExposeEventsFunctionsExtensionEvents(project, eventsFunctionsExtension, worker);
}
}
void ProjectBrowserHelper::ExposeEventsFunctionsExtensionEvents(
gd::Project &project, const gd::EventsFunctionsExtension &eventsFunctionsExtension,
gd::ArbitraryEventsWorker &worker) {
// Add (free) events functions
for (auto &&eventsFunction : eventsFunctionsExtension.GetInternalVector()) {
gd::ObjectsContainer globalObjectsAndGroups;
gd::ObjectsContainer objectsAndGroups;
gd::EventsFunctionTools::FreeEventsFunctionToObjectsContainer(
project, eventsFunctionsExtension, *eventsFunction,
globalObjectsAndGroups, objectsAndGroups);
worker.Launch(eventsFunction->GetEvents());
}
// Add (behavior) events functions
for (auto &&eventsBasedBehavior :
eventsFunctionsExtension.GetEventsBasedBehaviors()
.GetInternalVector()) {
ExposeEventsBasedBehaviorEvents(project, *eventsBasedBehavior, worker);
}
// Add (object) events functions
for (auto &&eventsBasedObject :
eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) {
ExposeEventsBasedObjectEvents(project, *eventsBasedObject, worker);
}
}
void ProjectBrowserHelper::ExposeEventsFunctionsExtensionEvents(
gd::Project &project, const gd::EventsFunctionsExtension &eventsFunctionsExtension,
gd::ArbitraryEventsWorkerWithContext &worker) {
// Add (free) events functions
for (auto &&eventsFunction : eventsFunctionsExtension.GetInternalVector()) {
gd::ObjectsContainer globalObjectsAndGroups;
gd::ObjectsContainer objectsAndGroups;
@@ -140,7 +183,7 @@ void ProjectBrowserHelper::ExposeProjectEvents(
globalObjectsAndGroups, objectsAndGroups);
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsAndGroups, objectsAndGroups);
projectScopedContainers.AddParameters(eventsFunction->GetParameters());
projectScopedContainers.AddParameters(eventsFunction->GetParametersForEvents(eventsFunctionsExtension));
worker.Launch(eventsFunction->GetEvents(), projectScopedContainers);
}
@@ -157,7 +200,6 @@ void ProjectBrowserHelper::ExposeProjectEvents(
eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) {
ExposeEventsBasedObjectEvents(project, *eventsBasedObject, worker);
}
}
}
void ProjectBrowserHelper::ExposeEventsBasedBehaviorEvents(
@@ -183,12 +225,27 @@ void ProjectBrowserHelper::ExposeEventsBasedBehaviorEvents(
gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsAndGroups, objectsAndGroups);
projectScopedContainers.AddPropertiesContainer(eventsBasedBehavior.GetSharedPropertyDescriptors());
projectScopedContainers.AddPropertiesContainer(eventsBasedBehavior.GetPropertyDescriptors());
projectScopedContainers.AddParameters(eventsFunction->GetParameters());
projectScopedContainers.AddParameters(eventsFunction->GetParametersForEvents(eventsBasedBehavior.GetEventsFunctions()));
worker.Launch(eventsFunction->GetEvents(), projectScopedContainers);
}
}
void ProjectBrowserHelper::ExposeEventsBasedObjectEvents(
gd::Project &project, const gd::EventsBasedObject &eventsBasedObject,
gd::ArbitraryEventsWorker &worker) {
auto &objectEventsFunctions = eventsBasedObject.GetEventsFunctions();
for (auto &&eventsFunction : objectEventsFunctions.GetInternalVector()) {
gd::ObjectsContainer globalObjectsAndGroups;
gd::ObjectsContainer objectsAndGroups;
gd::EventsFunctionTools::ObjectEventsFunctionToObjectsContainer(
project, eventsBasedObject, *eventsFunction, globalObjectsAndGroups,
objectsAndGroups);
worker.Launch(eventsFunction->GetEvents());
}
}
void ProjectBrowserHelper::ExposeEventsBasedObjectEvents(
gd::Project &project, const gd::EventsBasedObject &eventsBasedObject,
gd::ArbitraryEventsWorkerWithContext &worker) {
@@ -202,7 +259,7 @@ void ProjectBrowserHelper::ExposeEventsBasedObjectEvents(
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsAndGroups, objectsAndGroups);
projectScopedContainers.AddPropertiesContainer(eventsBasedObject.GetPropertyDescriptors());
projectScopedContainers.AddParameters(eventsFunction->GetParameters());
projectScopedContainers.AddParameters(eventsFunction->GetParametersForEvents(eventsBasedObject.GetEventsFunctions()));
worker.Launch(eventsFunction->GetEvents(), projectScopedContainers);
}
@@ -216,7 +273,7 @@ void ProjectBrowserHelper::ExposeProjectObjects(
// Layout objects
for (size_t i = 0; i < project.GetLayoutsCount(); i++) {
worker.Launch(project.GetLayout(i));
gd::ProjectBrowserHelper::ExposeLayoutObjects(project.GetLayout(i), worker);
}
// Event based objects children
@@ -232,6 +289,14 @@ void ProjectBrowserHelper::ExposeProjectObjects(
}
};
void ProjectBrowserHelper::ExposeLayoutObjects(gd::Layout &layout,
gd::ArbitraryObjectsWorker &worker) {
// In the future, layouts may have children object containers.
// Layout objects
worker.Launch(layout);
}
void ProjectBrowserHelper::ExposeProjectFunctions(
gd::Project &project, gd::ArbitraryEventsFunctionsWorker &worker) {

View File

@@ -60,18 +60,52 @@ public:
* \brief Call the specified worker on all events of a layout and
* its external events.
*/
static void ExposeLayoutEvents(gd::Project &project, gd::Layout &layout,
static void ExposeLayoutEventsAndExternalEvents(gd::Project &project, gd::Layout &layout,
gd::ArbitraryEventsWorker &worker);
/**
* \brief Call the specified worker on all events of a layout and
* its external events.
*/
static void ExposeLayoutEvents(gd::Project &project, gd::Layout &layout,
static void ExposeLayoutEventsAndExternalEvents(gd::Project &project, gd::Layout &layout,
gd::ArbitraryEventsWorkerWithContext &worker);
/**
* \brief Call the specified worker on all events of a layout and
* its dependencies according to EventLink (external events or other layout
* events).
*/
static void
ExposeLayoutEventsAndDependencies(gd::Project &project, gd::Layout &layout,
gd::ArbitraryEventsWorker &worker);
/**
* \brief Call the specified worker on all events of the event-based
* behavior
* extension.
*
* This should be the preferred way to traverse all the events of an events
* based extension.
*/
static void ExposeEventsFunctionsExtensionEvents(
gd::Project &project,
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
gd::ArbitraryEventsWorker &worker);
/**
* \brief Call the specified worker on all events of the event-based
* extension.
*
* This should be the preferred way to traverse all the events of an events
* based extension.
*/
static void ExposeEventsFunctionsExtensionEvents(
gd::Project &project,
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
gd::ArbitraryEventsWorkerWithContext &worker);
/**
* \brief Call the specified worker on all events of the event-based
* behavior.
*
* This should be the preferred way to traverse all the events of an events
* based behavior.
@@ -93,10 +127,22 @@ public:
/**
* \brief Call the specified worker on all events of the event-based
* behavior.
* object.
*
* This should be the preferred way to traverse all the events of an
* event-based behavior.
* event-based object.
*/
static void
ExposeEventsBasedObjectEvents(gd::Project &project,
const gd::EventsBasedObject &eventsBasedObject,
gd::ArbitraryEventsWorker &worker);
/**
* \brief Call the specified worker on all events of the event-based
* object.
*
* This should be the preferred way to traverse all the events of an
* event-based object.
*/
static void
ExposeEventsBasedObjectEvents(gd::Project &project,
@@ -112,6 +158,14 @@ public:
static void ExposeProjectObjects(gd::Project &project,
gd::ArbitraryObjectsWorker &worker);
/**
* \brief Call the specified worker on all ObjectContainers of the layout.
*
* This should be the preferred way to traverse all the objects of a layout.
*/
static void ExposeLayoutObjects(gd::Layout &layout,
gd::ArbitraryObjectsWorker &worker);
/**
* \brief Call the specified worker on all FunctionsContainers of the project
* (global, layouts...)

View File

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

View File

@@ -24,6 +24,7 @@
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/EffectMetadata.h"
#include "GDCore/IDE/Events/UsedExtensionsFinder.h"
namespace gd {
@@ -32,10 +33,9 @@ void ResourceExposer::ExposeWholeProjectResources(gd::Project& project, gd::Arbi
// traverse the whole project (this time for events) and ExposeProjectEffects
// (this time for effects).
gd::ResourcesManager* resourcesManager = &(project.GetResourcesManager());
// Expose any project resources as files.
worker.ExposeResources(resourcesManager);
worker.ExposeResources();
project.GetPlatformSpecificAssets().ExposeResources(worker);
// Expose event resources
@@ -73,6 +73,49 @@ void ResourceExposer::ExposeWholeProjectResources(gd::Project& project, gd::Arbi
worker.ExposeImage(loadingScreen.GetBackgroundImageResourceName());
}
void ResourceExposer::ExposeProjectResources(gd::Project& project, gd::ArbitraryResourceWorker& worker) {
// Expose global objects configuration resources
auto objectWorker = gd::GetResourceWorkerOnObjects(project, worker);
objectWorker.Launch(project);
}
void ResourceExposer::ExposeLayoutResources(
gd::Project &project, gd::Layout &layout,
gd::ArbitraryResourceWorker &worker) {
// Expose object configuration resources
auto objectWorker = gd::GetResourceWorkerOnObjects(project, worker);
gd::ProjectBrowserHelper::ExposeLayoutObjects(layout, objectWorker);
// Expose layer effect resources
for (std::size_t layerIndex = 0; layerIndex < layout.GetLayersCount();
layerIndex++) {
auto &layer = layout.GetLayer(layerIndex);
auto &effects = layer.GetEffects();
for (size_t effectIndex = 0; effectIndex < effects.GetEffectsCount();
effectIndex++) {
auto &effect = effects.GetEffect(effectIndex);
gd::ResourceExposer::ExposeEffectResources(project.GetCurrentPlatform(),
effect, worker);
}
}
// Expose event resources
auto eventWorker = gd::GetResourceWorkerOnEvents(project, worker);
gd::ProjectBrowserHelper::ExposeLayoutEventsAndDependencies(project, layout,
eventWorker);
// Exposed extension event resources
// Note that using resources in extensions is very unlikely and probably not
// worth the effort of something smart.
for (std::size_t e = 0; e < project.GetEventsFunctionsExtensionsCount();
e++) {
auto &eventsFunctionsExtension = project.GetEventsFunctionsExtension(e);
gd::ProjectBrowserHelper::ExposeEventsFunctionsExtensionEvents(project, eventsFunctionsExtension, eventWorker);
}
}
void ResourceExposer::ExposeEffectResources(
gd::Platform &platform, gd::Effect &effect,
gd::ArbitraryResourceWorker &worker) {
@@ -88,11 +131,13 @@ void ResourceExposer::ExposeEffectResources(
auto &resourceType = propertyDescriptor.GetExtraInfo()[0];
const gd::String &resourceName = effect.GetStringParameter(propertyName);
gd::String potentiallyUpdatedResourceName = resourceName;
worker.ExposeResourceWithType(resourceType,
potentiallyUpdatedResourceName);
if (potentiallyUpdatedResourceName != resourceName) {
effect.SetStringParameter(propertyName, potentiallyUpdatedResourceName);
if (!resourceName.empty()) {
gd::String potentiallyUpdatedResourceName = resourceName;
worker.ExposeResourceWithType(resourceType,
potentiallyUpdatedResourceName);
if (potentiallyUpdatedResourceName != resourceName) {
effect.SetStringParameter(propertyName, potentiallyUpdatedResourceName);
}
}
}
}

View File

@@ -10,6 +10,7 @@ class Platform;
class Project;
class ArbitraryResourceWorker;
class Effect;
class Layout;
} // namespace gd
namespace gd {
@@ -31,6 +32,25 @@ public:
static void ExposeWholeProjectResources(gd::Project &project,
gd::ArbitraryResourceWorker &worker);
/**
* @brief Expose only the resources used globally on a project.
*
* It doesn't include resources used in layouts.
*/
static void ExposeProjectResources(gd::Project &project,
gd::ArbitraryResourceWorker &worker);
/**
* @brief Expose the resources used in a given layout.
*
* It doesn't include resources used globally.
*/
static void ExposeLayoutResources(gd::Project &project, gd::Layout &layout,
gd::ArbitraryResourceWorker &worker);
/**
* @brief Expose the resources used in a given effect.
*/
static void ExposeEffectResources(gd::Platform &platform, gd::Effect &effect,
gd::ArbitraryResourceWorker &worker);
};

View File

@@ -16,8 +16,8 @@
#include "GDCore/IDE/Events/BehaviorTypeRenamer.h"
#include "GDCore/IDE/Events/CustomObjectTypeRenamer.h"
#include "GDCore/IDE/Events/EventsBehaviorRenamer.h"
#include "GDCore/IDE/Events/EventsRefactorer.h"
#include "GDCore/IDE/Events/EventsPropertyReplacer.h"
#include "GDCore/IDE/Events/EventsRefactorer.h"
#include "GDCore/IDE/Events/EventsVariableReplacer.h"
#include "GDCore/IDE/Events/ExpressionsParameterMover.h"
#include "GDCore/IDE/Events/ExpressionsRenamer.h"
@@ -138,7 +138,8 @@ void WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters(
}
}
VariablesChangeset WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
VariablesChangeset
WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
gd::Project &project,
const gd::SerializerElement &oldSerializedVariablesContainer,
const gd::VariablesContainer &newVariablesContainer) {
@@ -149,9 +150,9 @@ VariablesChangeset WholeProjectRefactorer::ComputeChangesetForVariablesContainer
if (oldVariablesContainer.GetPersistentUuid() !=
newVariablesContainer.GetPersistentUuid()) {
gd::LogWarning(
_("Called ComputeChangesetForVariablesContainer on variables containers "
"that are different - they can't be compared."));
gd::LogWarning(_(
"Called ComputeChangesetForVariablesContainer on variables containers "
"that are different - they can't be compared."));
return changeset;
}
@@ -192,14 +193,11 @@ VariablesChangeset WholeProjectRefactorer::ComputeChangesetForVariablesContainer
}
void WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
gd::Project &project,
const gd::VariablesContainer &newVariablesContainer,
const gd::VariablesChangeset& changeset) {
gd::Project &project, const gd::VariablesContainer &newVariablesContainer,
const gd::VariablesChangeset &changeset) {
gd::EventsVariableReplacer eventsVariableReplacer(
project.GetCurrentPlatform(),
newVariablesContainer,
changeset.oldToNewVariableNames,
changeset.removedVariableNames);
project.GetCurrentPlatform(), newVariablesContainer,
changeset.oldToNewVariableNames, changeset.removedVariableNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project,
eventsVariableReplacer);
}
@@ -743,14 +741,14 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorProperty(
EventsBasedBehavior::GetPropertyExpressionName(newPropertyName));
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {{oldPropertyName, newPropertyName}};
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {
{oldPropertyName, newPropertyName}};
std::unordered_set<gd::String> removedPropertyNames;
gd::EventsPropertyReplacer eventsPropertyReplacer(
project.GetCurrentPlatform(),
properties,
oldToNewPropertyNames,
removedPropertyNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project, eventsPropertyReplacer);
project.GetCurrentPlatform(), properties, oldToNewPropertyNames,
removedPropertyNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project,
eventsPropertyReplacer);
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
project,
@@ -813,14 +811,14 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorSharedProperty(
EventsBasedBehavior::GetSharedPropertyExpressionName(newPropertyName));
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {{oldPropertyName, newPropertyName}};
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {
{oldPropertyName, newPropertyName}};
std::unordered_set<gd::String> removedPropertyNames;
gd::EventsPropertyReplacer eventsPropertyReplacer(
project.GetCurrentPlatform(),
properties,
oldToNewPropertyNames,
removedPropertyNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project, eventsPropertyReplacer);
project.GetCurrentPlatform(), properties, oldToNewPropertyNames,
removedPropertyNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project,
eventsPropertyReplacer);
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
project,
@@ -870,14 +868,14 @@ void WholeProjectRefactorer::RenameEventsBasedObjectProperty(
EventsBasedObject::GetPropertyExpressionName(newPropertyName));
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {{oldPropertyName, newPropertyName}};
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {
{oldPropertyName, newPropertyName}};
std::unordered_set<gd::String> removedPropertyNames;
gd::EventsPropertyReplacer eventsPropertyReplacer(
project.GetCurrentPlatform(),
properties,
oldToNewPropertyNames,
removedPropertyNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project, eventsPropertyReplacer);
project.GetCurrentPlatform(), properties, oldToNewPropertyNames,
removedPropertyNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project,
eventsPropertyReplacer);
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
project,
@@ -1351,7 +1349,6 @@ void WholeProjectRefactorer::DoRenameBehavior(
gd::Project &project, const gd::String &oldBehaviorType,
const gd::String &newBehaviorType,
const gd::ProjectBrowser &projectBrowser) {
// Rename behavior in required behavior properties
auto requiredBehaviorRenamer =
gd::RequiredBehaviorRenamer(oldBehaviorType, newBehaviorType);
@@ -1378,7 +1375,6 @@ void WholeProjectRefactorer::DoRenameBehavior(
void WholeProjectRefactorer::DoRenameObject(
gd::Project &project, const gd::String &oldObjectType,
const gd::String &newObjectType, const gd::ProjectBrowser &projectBrowser) {
// Rename object type in objects lists.
auto customObjectTypeRenamer =
gd::CustomObjectTypeRenamer(oldObjectType, newObjectType);
@@ -1398,7 +1394,8 @@ void WholeProjectRefactorer::DoRenameObject(
void WholeProjectRefactorer::ObjectOrGroupRemovedInLayout(
gd::Project &project, gd::Layout &layout, const gd::String &objectName,
bool isObjectGroup, bool removeEventsAndGroups) {
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
auto projectScopedContainers = gd::ProjectScopedContainers::
MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
// Remove object in the current layout
if (removeEventsAndGroups) {
@@ -1447,7 +1444,8 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInLayout(
if (oldName == newName || newName.empty() || oldName.empty())
return;
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
auto projectScopedContainers = gd::ProjectScopedContainers::
MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
// Rename object in the current layout
gd::EventsRefactorer::RenameObjectInEvents(
@@ -1536,10 +1534,19 @@ void WholeProjectRefactorer::RenameLayer(gd::Project &project,
const gd::String &newName) {
if (oldName == newName || newName.empty() || oldName.empty())
return;
gd::ProjectElementRenamer projectElementRenamer(project.GetCurrentPlatform(),
"layer", oldName, newName);
gd::ProjectBrowserHelper::ExposeLayoutEvents(project, layout,
projectElementRenamer);
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
project, layout, projectElementRenamer);
layout.GetInitialInstances().MoveInstancesToLayer(oldName, newName);
std::vector<gd::String> externalLayoutsNames =
GetAssociatedExternalLayouts(project, layout);
for (gd::String name : externalLayoutsNames) {
auto &externalLayout = project.GetExternalLayout(name);
externalLayout.GetInitialInstances().MoveInstancesToLayer(oldName, newName);
}
}
void WholeProjectRefactorer::RenameLayerEffect(gd::Project &project,
@@ -1552,8 +1559,8 @@ void WholeProjectRefactorer::RenameLayerEffect(gd::Project &project,
gd::ProjectElementRenamer projectElementRenamer(
project.GetCurrentPlatform(), "layerEffectName", oldName, newName);
projectElementRenamer.SetLayerConstraint(layer.GetName());
gd::ProjectBrowserHelper::ExposeLayoutEvents(project, layout,
projectElementRenamer);
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
project, layout, projectElementRenamer);
}
void WholeProjectRefactorer::RenameObjectAnimation(gd::Project &project,
@@ -1566,8 +1573,8 @@ void WholeProjectRefactorer::RenameObjectAnimation(gd::Project &project,
gd::ProjectElementRenamer projectElementRenamer(
project.GetCurrentPlatform(), "objectAnimationName", oldName, newName);
projectElementRenamer.SetObjectConstraint(object.GetName());
gd::ProjectBrowserHelper::ExposeLayoutEvents(project, layout,
projectElementRenamer);
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
project, layout, projectElementRenamer);
}
void WholeProjectRefactorer::RenameObjectPoint(gd::Project &project,
@@ -1580,8 +1587,8 @@ void WholeProjectRefactorer::RenameObjectPoint(gd::Project &project,
gd::ProjectElementRenamer projectElementRenamer(
project.GetCurrentPlatform(), "objectPointName", oldName, newName);
projectElementRenamer.SetObjectConstraint(object.GetName());
gd::ProjectBrowserHelper::ExposeLayoutEvents(project, layout,
projectElementRenamer);
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
project, layout, projectElementRenamer);
}
void WholeProjectRefactorer::RenameObjectEffect(gd::Project &project,
@@ -1594,8 +1601,8 @@ void WholeProjectRefactorer::RenameObjectEffect(gd::Project &project,
gd::ProjectElementRenamer projectElementRenamer(
project.GetCurrentPlatform(), "objectEffectName", oldName, newName);
projectElementRenamer.SetObjectConstraint(object.GetName());
gd::ProjectBrowserHelper::ExposeLayoutEvents(project, layout,
projectElementRenamer);
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
project, layout, projectElementRenamer);
}
void WholeProjectRefactorer::ObjectOrGroupRemovedInEventsBasedObject(
@@ -1617,9 +1624,12 @@ void WholeProjectRefactorer::ObjectOrGroupRemovedInEventsFunction(
gd::ObjectsContainer &globalObjectsContainer,
gd::ObjectsContainer &objectsContainer, const gd::String &objectName,
bool isObjectGroup, bool removeEventsAndGroups) {
// In theory we should pass a ProjectScopedContainers to this function so it does not have to construct one.
// In practice, this is ok because we only deal with objects.
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsContainer, objectsContainer);
// In theory we should pass a ProjectScopedContainers to this function so it
// does not have to construct one. In practice, this is ok because we only
// deal with objects.
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(
globalObjectsContainer, objectsContainer);
if (removeEventsAndGroups) {
gd::EventsRefactorer::RemoveObjectInEvents(
@@ -1655,9 +1665,12 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction(
gd::ObjectsContainer &globalObjectsContainer,
gd::ObjectsContainer &objectsContainer, const gd::String &oldName,
const gd::String &newName, bool isObjectGroup) {
// In theory we should pass a ProjectScopedContainers to this function so it does not have to construct one.
// In practice, this is ok because we only deal with objects.
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsContainer, objectsContainer);
// In theory we should pass a ProjectScopedContainers to this function so it
// does not have to construct one. In practice, this is ok because we only
// deal with objects.
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(
globalObjectsContainer, objectsContainer);
gd::EventsRefactorer::RenameObjectInEvents(
project.GetCurrentPlatform(), projectScopedContainers,
@@ -1705,7 +1718,7 @@ void WholeProjectRefactorer::GlobalObjectOrGroupRemoved(
if (layout.HasObjectNamed(objectName))
continue;
ObjectOrGroupRemovedInLayout(project, layout, objectName, isObjectGroup,
ObjectOrGroupRemovedInLayout(project, layout, objectName, isObjectGroup,
removeEventsAndGroups);
}
}
@@ -1753,7 +1766,8 @@ size_t WholeProjectRefactorer::GetLayoutAndExternalLayoutLayerInstancesCount(
GetAssociatedExternalLayouts(project, layout);
for (gd::String name : externalLayoutsNames) {
auto &externalLayout = project.GetExternalLayout(name);
count += externalLayout.GetInitialInstances().GetLayerInstancesCount(layerName);
count +=
externalLayout.GetInitialInstances().GetLayerInstancesCount(layerName);
}
return count;
}

View File

@@ -5,6 +5,8 @@
*/
#include "CustomConfigurationHelper.h"
#include <map>
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/Project.h"
@@ -13,8 +15,6 @@
#include "GDCore/Serialization/Serializer.h"
#include "GDCore/Serialization/SerializerElement.h"
#include <map>
using namespace gd;
void CustomConfigurationHelper::InitializeContent(
@@ -25,7 +25,8 @@ void CustomConfigurationHelper::InitializeContent(
auto propertyType = property->GetType();
if (propertyType == "String" || propertyType == "Choice" ||
propertyType == "Color" || propertyType == "Behavior") {
propertyType == "Color" || propertyType == "Behavior" ||
propertyType == "resource") {
element.SetStringValue(property->GetValue());
} else if (propertyType == "Number") {
element.SetDoubleValue(property->GetValue().To<double>());
@@ -51,7 +52,8 @@ std::map<gd::String, gd::PropertyDescriptor> CustomConfigurationHelper::GetPrope
if (configurationContent.HasChild(propertyName)) {
if (propertyType == "String" || propertyType == "Choice" ||
propertyType == "Color" || propertyType == "Behavior") {
propertyType == "Color" || propertyType == "Behavior" ||
propertyType == "resource") {
newProperty.SetValue(
configurationContent.GetChild(propertyName).GetStringValue());
} else if (propertyType == "Number") {
@@ -59,8 +61,9 @@ std::map<gd::String, gd::PropertyDescriptor> CustomConfigurationHelper::GetPrope
configurationContent.GetChild(propertyName).GetDoubleValue()));
} else if (propertyType == "Boolean") {
newProperty.SetValue(
configurationContent.GetChild(propertyName).GetBoolValue() ? "true"
: "false");
configurationContent.GetChild(propertyName).GetBoolValue()
? "true"
: "false");
}
} else {
// No value was serialized for this property. `newProperty`
@@ -85,7 +88,8 @@ bool CustomConfigurationHelper::UpdateProperty(
const gd::String &propertyType = property.GetType();
if (propertyType == "String" || propertyType == "Choice" ||
propertyType == "Color" || propertyType == "Behavior") {
propertyType == "Color" || propertyType == "Behavior" ||
propertyType == "resource") {
element.SetStringValue(newValue);
} else if (propertyType == "Number") {
element.SetDoubleValue(newValue.To<double>());

View File

@@ -155,6 +155,10 @@ void CustomObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker& wor
worker.ExposeBitmapFont(newPropertyValue);
} else if (resourceType == "model3D") {
worker.ExposeModel3D(newPropertyValue);
} else if (resourceType == "atlas") {
worker.ExposeAtlas(newPropertyValue);
} else if (resourceType == "spine") {
worker.ExposeSpine(newPropertyValue);
}
if (newPropertyValue != oldPropertyValue) {

View File

@@ -17,7 +17,7 @@ EventsBasedObject::EventsBasedObject()
}
EventsBasedObject::~EventsBasedObject() {}
EventsBasedObject::EventsBasedObject(const gd::EventsBasedObject &_eventBasedObject)
: AbstractEventsBasedEntity(_eventBasedObject) {
// TODO Add a copy constructor in ObjectsContainer.
@@ -27,17 +27,26 @@ EventsBasedObject::EventsBasedObject(const gd::EventsBasedObject &_eventBasedObj
void EventsBasedObject::SerializeTo(SerializerElement& element) const {
element.SetAttribute("defaultName", defaultName);
if (isRenderedIn3D) {
element.SetBoolAttribute("is3D", true);
}
AbstractEventsBasedEntity::SerializeTo(element);
SerializeObjectsTo(element.AddChild("objects"));
SerializeFoldersTo(element.AddChild("objectsFolderStructure"));
}
void EventsBasedObject::UnserializeFrom(gd::Project& project,
const SerializerElement& element) {
const SerializerElement& element) {
defaultName = element.GetStringAttribute("defaultName");
isRenderedIn3D = element.GetBoolAttribute("is3D", false);
AbstractEventsBasedEntity::UnserializeFrom(project, element);
UnserializeObjectsFrom(project, element.GetChild("objects"));
if (element.HasChild("objectsFolderStructure")) {
UnserializeFoldersFrom(project, element.GetChild("objectsFolderStructure", 0));
}
AddMissingObjectsInRootFolder();
}
} // namespace gd

View File

@@ -72,6 +72,19 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity, public Ob
return *this;
}
/**
* \brief Declare a usage of the 3D renderer.
*/
EventsBasedObject& MarkAsRenderedIn3D(bool isRenderedIn3D_) {
isRenderedIn3D = isRenderedIn3D_;
return *this;
}
/**
* \brief Return true if the object uses the 3D renderer.
*/
bool IsRenderedIn3D() const { return isRenderedIn3D; }
void SerializeTo(SerializerElement& element) const override;
void UnserializeFrom(gd::Project& project,
@@ -79,6 +92,7 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity, public Ob
private:
gd::String defaultName;
bool isRenderedIn3D;
};
} // namespace gd

View File

@@ -19,7 +19,7 @@ Layer::Layer()
isLocked(false),
isLightingLayer(false),
followBaseLayerCamera(false),
camera3DNearPlaneDistance(0.1),
camera3DNearPlaneDistance(3),
camera3DFarPlaneDistance(10000),
camera3DFieldOfView(45),
ambientLightColorR(200),
@@ -39,6 +39,7 @@ void Layer::SetCameraCount(std::size_t n) {
void Layer::SerializeTo(SerializerElement& element) const {
element.SetAttribute("name", GetName());
element.SetAttribute("renderingType", GetRenderingType());
element.SetAttribute("cameraType", GetCameraType());
element.SetAttribute("visibility", GetVisibility());
element.SetAttribute("isLocked", IsLocked());
element.SetAttribute("isLightingLayer", IsLightingLayer());
@@ -78,6 +79,7 @@ void Layer::SerializeTo(SerializerElement& element) const {
void Layer::UnserializeFrom(const SerializerElement& element) {
SetName(element.GetStringAttribute("name", "", "Name"));
SetRenderingType(element.GetStringAttribute("renderingType", ""));
SetCameraType(element.GetStringAttribute("cameraType", "perspective"));
SetVisibility(element.GetBoolAttribute("visibility", true, "Visibility"));
SetLocked(element.GetBoolAttribute("isLocked", false));
SetLightingLayer(element.GetBoolAttribute("isLightingLayer", false));

View File

@@ -104,10 +104,17 @@ class GD_CORE_API Layer {
const gd::String& GetName() const { return name; }
const gd::String& GetRenderingType() const { return renderingType; }
void SetRenderingType(const gd::String& renderingType_) {
renderingType = renderingType_;
}
const gd::String& GetCameraType() const { return cameraType; }
void SetCameraType(const gd::String& cameraType_) {
cameraType = cameraType_;
}
/**
* \brief Change if layer is displayed or not
*/
@@ -268,6 +275,7 @@ class GD_CORE_API Layer {
gd::String name; ///< The name of the layer
gd::String renderingType; ///< The rendering type: "" (empty), "2d", "3d" or
///< "2d+3d".
gd::String cameraType;
bool isVisible; ///< True if the layer is visible
bool isLocked; ///< True if the layer is locked
bool isLightingLayer; ///< True if the layer is used to display lights and

View File

@@ -294,6 +294,7 @@ void Layout::SerializeTo(SerializerElement& element) const {
GetVariables().SerializeTo(element.AddChild("variables"));
GetInitialInstances().SerializeTo(element.AddChild("instances"));
SerializeObjectsTo(element.AddChild("objects"));
SerializeFoldersTo(element.AddChild("objectsFolderStructure"));
gd::EventsListSerialization::SerializeEventsTo(events,
element.AddChild("events"));
@@ -353,6 +354,11 @@ void Layout::UnserializeFrom(gd::Project& project,
project, GetEvents(), element.GetChild("events", 0, "Events"));
UnserializeObjectsFrom(project, element.GetChild("objects", 0, "Objets"));
if (element.HasChild("objectsFolderStructure")) {
UnserializeFoldersFrom(project, element.GetChild("objectsFolderStructure", 0));
}
AddMissingObjectsInRootFolder();
initialInstances.UnserializeFrom(
element.GetChild("instances", 0, "Positions"));
variables.UnserializeFrom(element.GetChild("variables", 0, "Variables"));
@@ -444,13 +450,15 @@ gd::String GD_CORE_API GetTypeOfObject(const gd::ObjectsContainer& project,
bool searchInGroups) {
gd::String type;
// Search in objects
// Search in objects.
if (layout.HasObjectNamed(name))
type = layout.GetObject(name).GetType();
else if (project.HasObjectNamed(name))
type = project.GetObject(name).GetType();
// Search in groups
// Search in groups.
// Currently, a group is considered as the "intersection" of all of its objects.
// Search "groups is the intersection of its objects" in the codebase.
else if (searchInGroups) {
for (std::size_t i = 0; i < layout.GetObjectGroups().size(); ++i) {
if (layout.GetObjectGroups()[i].GetName() == name) {
@@ -523,7 +531,7 @@ std::vector<gd::String> GD_CORE_API GetBehaviorNamesInObjectOrGroup(
const gd::ObjectsContainer &project, const gd::ObjectsContainer &layout,
const gd::String &objectOrGroupName, const gd::String &behaviorType,
bool searchInGroups) {
// Search in objects
// Search in objects.
if (layout.HasObjectNamed(objectOrGroupName)) {
auto &object = layout.GetObject(objectOrGroupName);
auto behaviorNames = object.GetAllBehaviorNames();
@@ -542,7 +550,9 @@ std::vector<gd::String> GD_CORE_API GetBehaviorNamesInObjectOrGroup(
return behaviorNames;
}
// Search in groups
// Search in groups.
// Currently, a group is considered as the "intersection" of all of its objects.
// Search "groups is the intersection of its objects" in the codebase.
const gd::ObjectsContainer *container;
if (layout.GetObjectGroups().Has(objectOrGroupName)) {
container = &layout;
@@ -554,12 +564,14 @@ std::vector<gd::String> GD_CORE_API GetBehaviorNamesInObjectOrGroup(
}
const vector<gd::String> &groupsObjects =
container->GetObjectGroups().Get(objectOrGroupName).GetAllObjectsNames();
// Empty groups don't contain any behavior.
if (groupsObjects.empty()) {
std::vector<gd::String> behaviorNames;
return behaviorNames;
}
// Compute the intersection of the behaviors of all objects.
auto behaviorNames = GetBehaviorNamesInObjectOrGroup(
project, layout, groupsObjects[0], behaviorType, false);
for (size_t i = 1; i < groupsObjects.size(); i++) {
@@ -587,7 +599,7 @@ bool GD_CORE_API HasBehaviorInObjectOrGroup(const gd::ObjectsContainer &project,
const gd::String &objectOrGroupName,
const gd::String &behaviorName,
bool searchInGroups) {
// Search in objects
// Search in objects.
if (layout.HasObjectNamed(objectOrGroupName)) {
return layout.GetObject(objectOrGroupName).HasBehaviorNamed(behaviorName);
}
@@ -599,7 +611,9 @@ bool GD_CORE_API HasBehaviorInObjectOrGroup(const gd::ObjectsContainer &project,
return false;
}
// Search in groups
// Search in groups.
// Currently, a group is considered as the "intersection" of all of its objects.
// Search "groups is the intersection of its objects" in the codebase.
const gd::ObjectsContainer *container;
if (layout.GetObjectGroups().Has(objectOrGroupName)) {
container = &layout;
@@ -610,10 +624,12 @@ bool GD_CORE_API HasBehaviorInObjectOrGroup(const gd::ObjectsContainer &project,
}
const vector<gd::String> &groupsObjects =
container->GetObjectGroups().Get(objectOrGroupName).GetAllObjectsNames();
// Empty groups don't contain any behavior.
if (groupsObjects.empty()) {
return false;
}
// Check that all objects have the behavior.
for (auto &&object : groupsObjects) {
if (!HasBehaviorInObjectOrGroup(project, layout, object, behaviorName,
@@ -629,7 +645,7 @@ bool GD_CORE_API IsDefaultBehavior(const gd::ObjectsContainer& project,
gd::String objectOrGroupName,
gd::String behaviorName,
bool searchInGroups) {
// Search in objects
// Search in objects.
if (layout.HasObjectNamed(objectOrGroupName)) {
auto &object = layout.GetObject(objectOrGroupName);
return object.HasBehaviorNamed(behaviorName) &&
@@ -645,7 +661,9 @@ bool GD_CORE_API IsDefaultBehavior(const gd::ObjectsContainer& project,
return false;
}
// Search in groups
// Search in groups.
// Currently, a group is considered as the "intersection" of all of its objects.
// Search "groups is the intersection of its objects" in the codebase.
const gd::ObjectsContainer *container;
if (layout.GetObjectGroups().Has(objectOrGroupName)) {
container = &layout;
@@ -656,10 +674,12 @@ bool GD_CORE_API IsDefaultBehavior(const gd::ObjectsContainer& project,
}
const vector<gd::String> &groupsObjects =
container->GetObjectGroups().Get(objectOrGroupName).GetAllObjectsNames();
// Empty groups don't contain any behavior.
if (groupsObjects.empty()) {
return false;
}
// Check that all objects have the same type.
for (auto &&object : groupsObjects) {
if (!IsDefaultBehavior(project, layout, object, behaviorName,
@@ -675,7 +695,7 @@ gd::String GD_CORE_API GetTypeOfBehaviorInObjectOrGroup(const gd::ObjectsContain
const gd::String& objectOrGroupName,
const gd::String& behaviorName,
bool searchInGroups) {
// Search in objects
// Search in objects.
if (layout.HasObjectNamed(objectOrGroupName)) {
auto &object = layout.GetObject(objectOrGroupName);
return object.HasBehaviorNamed(behaviorName) ?
@@ -691,7 +711,9 @@ gd::String GD_CORE_API GetTypeOfBehaviorInObjectOrGroup(const gd::ObjectsContain
return "";
}
// Search in groups
// Search in groups.
// Currently, a group is considered as the "intersection" of all of its objects.
// Search "groups is the intersection of its objects" in the codebase.
const gd::ObjectsContainer *container;
if (layout.GetObjectGroups().Has(objectOrGroupName)) {
container = &layout;
@@ -702,10 +724,12 @@ gd::String GD_CORE_API GetTypeOfBehaviorInObjectOrGroup(const gd::ObjectsContain
}
const vector<gd::String> &groupsObjects =
container->GetObjectGroups().Get(objectOrGroupName).GetAllObjectsNames();
// Empty groups don't contain any behavior.
if (groupsObjects.empty()) {
return "";
}
// Check that all objects have the behavior with the same type.
auto behaviorType = GetTypeOfBehaviorInObjectOrGroup(
project, layout, groupsObjects[0], behaviorName, false);
@@ -767,6 +791,8 @@ GetBehaviorsOfObject(const gd::ObjectsContainer& project,
}
// Search in groups
// Currently, a group is considered as the "intersection" of all of its objects.
// Search "groups is the intersection of its objects" in the codebase.
if (searchInGroups) {
for (std::size_t i = 0; i < layout.GetObjectGroups().size(); ++i) {
if (layout.GetObjectGroups()[i].GetName() == name) {

View File

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

View File

@@ -40,7 +40,6 @@ void Object::Init(const gd::Object& object) {
name = object.name;
assetStoreId = object.assetStoreId;
objectVariables = object.objectVariables;
tags = object.tags;
effectsContainer = object.effectsContainer;
behaviors.clear();
@@ -134,7 +133,6 @@ void Object::UnserializeFrom(gd::Project& project,
SetType(element.GetStringAttribute("type"));
assetStoreId = element.GetStringAttribute("assetStoreId");
name = element.GetStringAttribute("name", name, "nom");
tags = element.GetStringAttribute("tags");
objectVariables.UnserializeFrom(
element.GetChild("variables", 0, "Variables"));
@@ -207,7 +205,6 @@ void Object::SerializeTo(SerializerElement& element) const {
element.SetAttribute("name", GetName());
element.SetAttribute("assetStoreId", GetAssetStoreId());
element.SetAttribute("type", GetType());
element.SetAttribute("tags", GetTags());
objectVariables.SerializeTo(element.AddChild("variables"));
effectsContainer.SerializeTo(element.AddChild("effects"));

View File

@@ -120,14 +120,6 @@ class GD_CORE_API Object {
*/
const gd::String& GetType() const { return configuration->GetType(); }
/** \brief Change the tags of the object.
*/
void SetTags(const gd::String& tags_) { tags = tags_; }
/** \brief Return the tags of the object.
*/
const gd::String& GetTags() const { return tags; }
/** \brief Shortcut to check if the object is a 3D object.
*/
bool Is3DObject() const { return configuration->Is3DObject(); }
@@ -268,7 +260,6 @@ class GD_CORE_API Object {
///< object.
gd::VariablesContainer
objectVariables; ///< List of the variables of the object
gd::String tags; ///< Comma-separated list of tags
gd::EffectsContainer
effectsContainer; ///< The effects container for the object.
mutable gd::String persistentUuid; ///< A persistent random version 4 UUID,

View File

@@ -0,0 +1,248 @@
/*
* GDevelop Core
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/Project/ObjectFolderOrObject.h"
#include <memory>
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Tools/Log.h"
using namespace std;
namespace gd {
ObjectFolderOrObject ObjectFolderOrObject::badObjectFolderOrObject;
ObjectFolderOrObject::ObjectFolderOrObject()
: folderName("__NULL"), object(nullptr) {}
ObjectFolderOrObject::ObjectFolderOrObject(gd::String folderName_,
ObjectFolderOrObject* parent_)
: folderName(folderName_), parent(parent_), object(nullptr) {}
ObjectFolderOrObject::ObjectFolderOrObject(gd::Object* object_,
ObjectFolderOrObject* parent_)
: object(object_), parent(parent_) {}
ObjectFolderOrObject::~ObjectFolderOrObject() {}
bool ObjectFolderOrObject::HasObjectNamed(const gd::String& name) {
if (IsFolder()) {
return std::any_of(
children.begin(),
children.end(),
[&name](
std::unique_ptr<gd::ObjectFolderOrObject>& objectFolderOrObject) {
return objectFolderOrObject->HasObjectNamed(name);
});
}
if (!object) return false;
return object->GetName() == name;
}
ObjectFolderOrObject& ObjectFolderOrObject::GetObjectNamed(
const gd::String& name) {
if (object && object->GetName() == name) {
return *this;
}
if (IsFolder()) {
for (std::size_t j = 0; j < children.size(); j++) {
ObjectFolderOrObject& foundInChild = children[j]->GetObjectNamed(name);
if (&(foundInChild) != &badObjectFolderOrObject) {
return foundInChild;
}
}
}
return badObjectFolderOrObject;
}
void ObjectFolderOrObject::SetFolderName(const gd::String& name) {
if (!IsFolder()) return;
folderName = name;
}
ObjectFolderOrObject& ObjectFolderOrObject::GetChildAt(std::size_t index) {
if (index >= children.size()) return badObjectFolderOrObject;
return *children[index];
}
const ObjectFolderOrObject& ObjectFolderOrObject::GetChildAt(std::size_t index) const {
if (index >= children.size()) return badObjectFolderOrObject;
return *children[index];
}
ObjectFolderOrObject& ObjectFolderOrObject::GetObjectChild(
const gd::String& name) {
for (std::size_t j = 0; j < children.size(); j++) {
if (!children[j]->IsFolder()) {
if (children[j]->GetObject().GetName() == name) return *children[j];
};
}
return badObjectFolderOrObject;
}
void ObjectFolderOrObject::InsertObject(gd::Object* insertedObject,
std::size_t position) {
auto objectFolderOrObject =
gd::make_unique<ObjectFolderOrObject>(insertedObject, this);
if (position < children.size()) {
children.insert(children.begin() + position,
std::move(objectFolderOrObject));
} else {
children.push_back(std::move(objectFolderOrObject));
}
}
std::size_t ObjectFolderOrObject::GetChildPosition(
const ObjectFolderOrObject& child) const {
for (std::size_t j = 0; j < children.size(); j++) {
if (children[j].get() == &child) return j;
}
return gd::String::npos;
}
ObjectFolderOrObject& ObjectFolderOrObject::InsertNewFolder(
const gd::String& newFolderName, std::size_t position) {
auto newFolderPtr =
gd::make_unique<ObjectFolderOrObject>(newFolderName, this);
gd::ObjectFolderOrObject& newFolder = *(*(children.insert(
position < children.size() ? children.begin() + position : children.end(),
std::move(newFolderPtr))));
return newFolder;
};
void ObjectFolderOrObject::RemoveRecursivelyObjectNamed(
const gd::String& name) {
if (IsFolder()) {
children.erase(
std::remove_if(children.begin(),
children.end(),
[&name](std::unique_ptr<gd::ObjectFolderOrObject>&
objectFolderOrObject) {
return !objectFolderOrObject->IsFolder() &&
objectFolderOrObject->GetObject().GetName() ==
name;
}),
children.end());
for (auto& it : children) {
it->RemoveRecursivelyObjectNamed(name);
}
}
};
bool ObjectFolderOrObject::IsADescendantOf(
const ObjectFolderOrObject& otherObjectFolderOrObject) {
if (parent == nullptr) return false;
if (&(*parent) == &otherObjectFolderOrObject) return true;
return parent->IsADescendantOf(otherObjectFolderOrObject);
}
void ObjectFolderOrObject::MoveChild(std::size_t oldIndex,
std::size_t newIndex) {
if (!IsFolder()) return;
if (oldIndex >= children.size() || newIndex >= children.size()) return;
std::unique_ptr<gd::ObjectFolderOrObject> objectFolderOrObject =
std::move(children[oldIndex]);
children.erase(children.begin() + oldIndex);
children.insert(children.begin() + newIndex, std::move(objectFolderOrObject));
}
void ObjectFolderOrObject::RemoveFolderChild(
const ObjectFolderOrObject& childToRemove) {
if (!IsFolder() || !childToRemove.IsFolder() ||
childToRemove.GetChildrenCount() > 0) {
return;
}
std::vector<std::unique_ptr<gd::ObjectFolderOrObject>>::iterator it = find_if(
children.begin(),
children.end(),
[&childToRemove](std::unique_ptr<gd::ObjectFolderOrObject>& child) {
return child.get() == &childToRemove;
});
if (it == children.end()) return;
children.erase(it);
}
void ObjectFolderOrObject::MoveObjectFolderOrObjectToAnotherFolder(
gd::ObjectFolderOrObject& objectFolderOrObject,
gd::ObjectFolderOrObject& newParentFolder,
std::size_t newPosition) {
if (!newParentFolder.IsFolder()) return;
if (newParentFolder.IsADescendantOf(objectFolderOrObject)) return;
std::vector<std::unique_ptr<gd::ObjectFolderOrObject>>::iterator it =
find_if(children.begin(),
children.end(),
[&objectFolderOrObject](std::unique_ptr<gd::ObjectFolderOrObject>&
childObjectFolderOrObject) {
return childObjectFolderOrObject.get() == &objectFolderOrObject;
});
if (it == children.end()) return;
std::unique_ptr<gd::ObjectFolderOrObject> objectFolderOrObjectPtr =
std::move(*it);
children.erase(it);
objectFolderOrObjectPtr->parent = &newParentFolder;
newParentFolder.children.insert(
newPosition < newParentFolder.children.size()
? newParentFolder.children.begin() + newPosition
: newParentFolder.children.end(),
std::move(objectFolderOrObjectPtr));
}
void ObjectFolderOrObject::SerializeTo(SerializerElement& element) const {
if (IsFolder()) {
element.SetAttribute("folderName", GetFolderName());
if (children.size() > 0) {
SerializerElement& childrenElement = element.AddChild("children");
childrenElement.ConsiderAsArrayOf("objectFolderOrObject");
for (std::size_t j = 0; j < children.size(); j++) {
children[j]->SerializeTo(
childrenElement.AddChild("objectFolderOrObject"));
}
}
} else {
element.SetAttribute("objectName", GetObject().GetName());
}
}
void ObjectFolderOrObject::UnserializeFrom(
gd::Project& project,
const SerializerElement& element,
gd::ObjectsContainer& objectsContainer) {
children.clear();
gd::String potentialFolderName = element.GetStringAttribute("folderName", "");
if (!potentialFolderName.empty()) {
object = nullptr;
folderName = potentialFolderName;
if (element.HasChild("children")) {
const SerializerElement& childrenElements =
element.GetChild("children", 0);
childrenElements.ConsiderAsArrayOf("objectFolderOrObject");
for (std::size_t i = 0; i < childrenElements.GetChildrenCount(); ++i) {
std::unique_ptr<ObjectFolderOrObject> childObjectFolderOrObject =
make_unique<ObjectFolderOrObject>();
childObjectFolderOrObject->UnserializeFrom(
project, childrenElements.GetChild(i), objectsContainer);
childObjectFolderOrObject->parent = this;
children.push_back(std::move(childObjectFolderOrObject));
}
}
} else {
folderName = "";
gd::String objectName = element.GetStringAttribute("objectName");
if (objectsContainer.HasObjectNamed(objectName)) {
object = &objectsContainer.GetObject(objectName);
} else {
gd::LogError("Object with name " + objectName +
" not found in objects container.");
object = nullptr;
}
}
};
} // namespace gd

View File

@@ -0,0 +1,203 @@
/*
* GDevelop Core
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_OBJECTFOLDEROROBJECT_H
#define GDCORE_OBJECTFOLDEROROBJECT_H
#include <memory>
#include <vector>
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
namespace gd {
class Project;
class Object;
class SerializerElement;
class ObjectsContainer;
} // namespace gd
namespace gd {
/**
* \brief Class representing a folder structure in order to organize objects
* in folders (to be used with an ObjectsContainer.)
*
* \see gd::ObjectsContainer
*/
class GD_CORE_API ObjectFolderOrObject {
public:
/**
* \brief Default constructor creating an empty instance. Useful for the null
* object pattern.
*/
ObjectFolderOrObject();
virtual ~ObjectFolderOrObject();
/**
* \brief Constructor for creating an instance representing a folder.
*/
ObjectFolderOrObject(gd::String folderName_,
ObjectFolderOrObject* parent_ = nullptr);
/**
* \brief Constructor for creating an instance representing an object.
*/
ObjectFolderOrObject(gd::Object* object_,
ObjectFolderOrObject* parent_ = nullptr);
/**
* \brief Returns the object behind the instance.
*/
gd::Object& GetObject() const { return *object; }
/**
* \brief Returns true if the instance represents a folder.
*/
bool IsFolder() const { return !folderName.empty(); }
/**
* \brief Returns the name of the folder.
*/
const gd::String& GetFolderName() const { return folderName; }
/**
* \brief Set the folder name. Does nothing if called on an instance not
* representing a folder.
*/
void SetFolderName(const gd::String& name);
/**
* \brief Returns true if the instance represents the object with the given
* name or if any of the children does (recursive search).
*/
bool HasObjectNamed(const gd::String& name);
/**
* \brief Returns the child instance holding the object with the given name
* (recursive search).
*/
ObjectFolderOrObject& GetObjectNamed(const gd::String& name);
/**
* \brief Returns the number of children. Returns 0 if the instance represents
* an object.
*/
std::size_t GetChildrenCount() const {
if (IsFolder()) return children.size();
return 0;
}
/**
* \brief Returns the child ObjectFolderOrObject at the given index.
*/
ObjectFolderOrObject& GetChildAt(std::size_t index);
/**
* \brief Returns the child ObjectFolderOrObject at the given index.
*/
const ObjectFolderOrObject& GetChildAt(std::size_t index) const;
/**
* \brief Returns the child ObjectFolderOrObject that represents the object
* with the given name. To use only if sure that the instance holds the object
* in its direct children (no recursive search).
*
* \note The equivalent method to get a folder by its name cannot be
* implemented because there is no unicity enforced on the folder name.
*/
ObjectFolderOrObject& GetObjectChild(const gd::String& name);
/**
* \brief Returns the parent of the instance. If the instance has no parent
* (root folder), the null object is returned.
*/
ObjectFolderOrObject& GetParent() {
if (parent == nullptr) {
return badObjectFolderOrObject;
}
return *parent;
};
/**
* \brief Returns true if the instance is a root folder (that's to say it
* has no parent).
*/
bool IsRootFolder() { return !object && !parent; }
/**
* \brief Moves a child from a position to a new one.
*/
void MoveChild(std::size_t oldIndex, std::size_t newIndex);
/**
* \brief Removes the given child from the instance's children. If the given
* child contains children of its own, does nothing.
*/
void RemoveFolderChild(const ObjectFolderOrObject& childToRemove);
/**
* \brief Removes the child representing the object with the given name from
* the instance children and recursively does it for every folder children.
*/
void RemoveRecursivelyObjectNamed(const gd::String& name);
/**
* \brief Inserts an instance representing the given object at the given
* position.
*/
void InsertObject(gd::Object* insertedObject,
std::size_t position = (size_t)-1);
/**
* \brief Inserts an instance representing a folder with the given name at the
* given position.
*/
ObjectFolderOrObject& InsertNewFolder(const gd::String& newFolderName,
std::size_t position);
/**
* \brief Returns true if the instance is a descendant of the given instance
* of ObjectFolderOrObject.
*/
bool IsADescendantOf(const ObjectFolderOrObject& otherObjectFolderOrObject);
/**
* \brief Returns the position of the given instance of ObjectFolderOrObject
* in the instance's children.
*/
std::size_t GetChildPosition(const ObjectFolderOrObject& child) const;
/**
* \brief Moves the given child ObjectFolderOrObject to the given folder at
* the given position.
*/
void MoveObjectFolderOrObjectToAnotherFolder(
gd::ObjectFolderOrObject& objectFolderOrObject,
gd::ObjectFolderOrObject& newParentFolder,
std::size_t newPosition);
/** \name Saving and loading
* Members functions related to saving and loading the objects of the class.
*/
///@{
/**
* \brief Serialize the ObjectFolderOrObject instance.
*/
void SerializeTo(SerializerElement& element) const;
/**
* \brief Unserialize the ObjectFolderOrObject instance.
*/
void UnserializeFrom(gd::Project& project,
const SerializerElement& element,
ObjectsContainer& objectsContainer);
///@}
private:
static gd::ObjectFolderOrObject badObjectFolderOrObject;
gd::ObjectFolderOrObject*
parent; // nullptr if root folder, points to the parent folder otherwise.
// Representing an object:
gd::Object* object; // nullptr if folderName is set.
// or representing a folder:
gd::String folderName; // Empty if object is set.
std::vector<std::unique_ptr<ObjectFolderOrObject>>
children; // Folder children.
};
} // namespace gd
#endif // GDCORE_OBJECTFOLDEROROBJECT_H

View File

@@ -162,10 +162,12 @@ void ObjectGroupsContainer::Move(std::size_t oldIndex, std::size_t newIndex) {
objectGroups.insert(objectGroups.begin() + newIndex, std::move(objectGroup));
}
void ObjectGroupsContainer::ForEachNameWithPrefix(const gd::String& prefix,
std::function<void(const gd::String& name)> fn) const {
for (const auto& objectGroup: objectGroups) {
if (objectGroup->GetName().find(prefix) == 0) fn(objectGroup->GetName());
void ObjectGroupsContainer::ForEachNameMatchingSearch(
const gd::String& search,
std::function<void(const gd::String& name)> fn) const {
for (const auto& objectGroup : objectGroups) {
if (objectGroup->GetName().FindCaseInsensitive(search) != gd::String::npos)
fn(objectGroup->GetName());
}
}

View File

@@ -118,9 +118,9 @@ class GD_CORE_API ObjectGroupsContainer {
inline void Clear() { objectGroups.clear(); }
/**
* \brief Call the callback for each group name starting with the prefix passed in parameter.
* \brief Call the callback for each group name matching the specified search.
*/
void ForEachNameWithPrefix(const gd::String& prefix, std::function<void(const gd::String& name)> fn) const;
void ForEachNameMatchingSearch(const gd::String& search, std::function<void(const gd::String& name)> fn) const;
///@}
/** \name Saving and loading

View File

@@ -9,12 +9,15 @@
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectFolderOrObject.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Serialization/SerializerElement.h"
namespace gd {
ObjectsContainer::ObjectsContainer() {}
ObjectsContainer::ObjectsContainer() {
rootFolder = gd::make_unique<gd::ObjectFolderOrObject>("__ROOT");
}
ObjectsContainer::~ObjectsContainer() {}
@@ -24,6 +27,22 @@ void ObjectsContainer::SerializeObjectsTo(SerializerElement& element) const {
initialObjects[j]->SerializeTo(element.AddChild("object"));
}
}
void ObjectsContainer::SerializeFoldersTo(SerializerElement& element) const {
rootFolder->SerializeTo(element);
}
void ObjectsContainer::UnserializeFoldersFrom(
gd::Project& project, const SerializerElement& element) {
rootFolder->UnserializeFrom(project, element, *this);
}
void ObjectsContainer::AddMissingObjectsInRootFolder() {
for (std::size_t i = 0; i < initialObjects.size(); ++i) {
if (!rootFolder->HasObjectNamed(initialObjects[i]->GetName())) {
rootFolder->InsertObject(&(*initialObjects[i]));
}
}
}
void ObjectsContainer::UnserializeObjectsFrom(
gd::Project& project, const SerializerElement& element) {
@@ -48,17 +67,23 @@ void ObjectsContainer::UnserializeObjectsFrom(
bool ObjectsContainer::HasObjectNamed(const gd::String& name) const {
return (find_if(initialObjects.begin(),
initialObjects.end(),
bind2nd(gd::ObjectHasName(), name)) != initialObjects.end());
[&](const std::unique_ptr<gd::Object>& object) {
return object->GetName() == name;
}) != initialObjects.end());
}
gd::Object& ObjectsContainer::GetObject(const gd::String& name) {
return *(*find_if(initialObjects.begin(),
initialObjects.end(),
bind2nd(gd::ObjectHasName(), name)));
[&](const std::unique_ptr<gd::Object>& object) {
return object->GetName() == name;
}));
}
const gd::Object& ObjectsContainer::GetObject(const gd::String& name) const {
return *(*find_if(initialObjects.begin(),
initialObjects.end(),
bind2nd(gd::ObjectHasName(), name)));
[&](const std::unique_ptr<gd::Object>& object) {
return object->GetName() == name;
}));
}
gd::Object& ObjectsContainer::GetObject(std::size_t index) {
return *initialObjects[index];
@@ -84,6 +109,22 @@ gd::Object& ObjectsContainer::InsertNewObject(const gd::Project& project,
: initialObjects.end(),
project.CreateObject(objectType, name))));
rootFolder->InsertObject(&newlyCreatedObject);
return newlyCreatedObject;
}
gd::Object& ObjectsContainer::InsertNewObjectInFolder(
const gd::Project& project,
const gd::String& objectType,
const gd::String& name,
gd::ObjectFolderOrObject& objectFolderOrObject,
std::size_t position) {
gd::Object& newlyCreatedObject = *(*(initialObjects.insert(
initialObjects.end(), project.CreateObject(objectType, name))));
objectFolderOrObject.InsertObject(&newlyCreatedObject, position);
return newlyCreatedObject;
}
@@ -97,16 +138,6 @@ gd::Object& ObjectsContainer::InsertObject(const gd::Object& object,
return newlyCreatedObject;
}
void ObjectsContainer::SwapObjects(std::size_t firstObjectIndex,
std::size_t secondObjectIndex) {
if (firstObjectIndex >= initialObjects.size() ||
secondObjectIndex >= initialObjects.size())
return;
std::iter_swap(initialObjects.begin() + firstObjectIndex,
initialObjects.begin() + secondObjectIndex);
}
void ObjectsContainer::MoveObject(std::size_t oldIndex, std::size_t newIndex) {
if (oldIndex >= initialObjects.size() || newIndex >= initialObjects.size())
return;
@@ -120,30 +151,59 @@ void ObjectsContainer::RemoveObject(const gd::String& name) {
std::vector<std::unique_ptr<gd::Object>>::iterator objectIt =
find_if(initialObjects.begin(),
initialObjects.end(),
bind2nd(ObjectHasName(), name));
[&](const std::unique_ptr<gd::Object>& object) {
return object->GetName() == name;
});
if (objectIt == initialObjects.end()) return;
rootFolder->RemoveRecursivelyObjectNamed(name);
initialObjects.erase(objectIt);
}
void ObjectsContainer::MoveObjectToAnotherContainer(
const gd::String& name,
void ObjectsContainer::MoveObjectFolderOrObjectToAnotherContainerInFolder(
gd::ObjectFolderOrObject& objectFolderOrObject,
gd::ObjectsContainer& newContainer,
gd::ObjectFolderOrObject& newParentFolder,
std::size_t newPosition) {
std::vector<std::unique_ptr<gd::Object>>::iterator objectIt =
find_if(initialObjects.begin(),
initialObjects.end(),
bind2nd(ObjectHasName(), name));
if (objectFolderOrObject.IsFolder() || !newParentFolder.IsFolder()) return;
std::vector<std::unique_ptr<gd::Object>>::iterator objectIt = find_if(
initialObjects.begin(),
initialObjects.end(),
[&objectFolderOrObject](std::unique_ptr<gd::Object>& object) {
return object->GetName() == objectFolderOrObject.GetObject().GetName();
});
if (objectIt == initialObjects.end()) return;
std::unique_ptr<gd::Object> object = std::move(*objectIt);
initialObjects.erase(objectIt);
newContainer.initialObjects.insert(
newPosition < newContainer.initialObjects.size()
? newContainer.initialObjects.begin() + newPosition
: newContainer.initialObjects.end(),
std::move(object));
newContainer.initialObjects.push_back(std::move(object));
objectFolderOrObject.GetParent().MoveObjectFolderOrObjectToAnotherFolder(
objectFolderOrObject, newParentFolder, newPosition);
}
std::vector<const ObjectFolderOrObject*>
ObjectsContainer::GetAllObjectFolderOrObjects() const {
std::vector<const ObjectFolderOrObject*> results;
std::function<void(const ObjectFolderOrObject& folder)> addChildrenOfFolder =
[&](const ObjectFolderOrObject& folder) {
for (size_t i = 0; i < folder.GetChildrenCount(); ++i) {
const auto& child = folder.GetChildAt(i);
results.push_back(&child);
if (child.IsFolder()) {
addChildrenOfFolder(child);
}
}
};
addChildrenOfFolder(*rootFolder);
return results;
}
} // namespace gd

View File

@@ -9,11 +9,12 @@
#include <vector>
#include "GDCore/String.h"
#include "GDCore/Project/ObjectGroupsContainer.h"
#include "GDCore/Project/ObjectFolderOrObject.h"
namespace gd {
class Object;
class Project;
class SerializerElement;
}
} // namespace gd
#undef GetObject // Disable an annoying macro
namespace gd {
@@ -98,6 +99,19 @@ class GD_CORE_API ObjectsContainer {
const gd::String& objectType,
const gd::String& name,
std::size_t position);
/**
* \brief Add a new empty object of type \a objectType called \a name in the
* given folder at the specified position.<br>
*
* \note The object is created using the project's current platform.
* \return A reference to the object in the list.
*/
gd::Object& InsertNewObjectInFolder(
const gd::Project& project,
const gd::String& objectType,
const gd::String& name,
gd::ObjectFolderOrObject& objectFolderOrObject,
std::size_t position);
/**
* \brief Add a new object to the list
@@ -125,18 +139,18 @@ class GD_CORE_API ObjectsContainer {
void MoveObject(std::size_t oldIndex, std::size_t newIndex);
/**
* \brief Swap the position of the specified objects.
*/
void SwapObjects(std::size_t firstObjectIndex, std::size_t secondObjectIndex);
/**
* Move the specified object to another container, removing it from the current one
* and adding it to the new one at the specified position.
* Move the specified object to another container, removing it from the
* current one and adding it to the new one at the specified position in the
* given folder.
*
* \note This does not invalidate the references to the object (object is not moved in memory,
* as referenced by smart pointers internally).
* \note This does not invalidate the references to the object (object is not
* moved in memory, as referenced by smart pointers internally).
*/
void MoveObjectToAnotherContainer(const gd::String& name, gd::ObjectsContainer & newContainer, std::size_t newPosition);
void MoveObjectFolderOrObjectToAnotherContainerInFolder(
gd::ObjectFolderOrObject& objectFolderOrObject,
gd::ObjectsContainer& newContainer,
gd::ObjectFolderOrObject& newParentFolder,
std::size_t newPosition);
/**
* Provide a raw access to the vector containing the objects
@@ -153,20 +167,43 @@ class GD_CORE_API ObjectsContainer {
}
///@}
/**
* Returns a vector containing all object and folders in this container.
* Only use this for checking if you hold a valid `ObjectFolderOrObject` -
* don't use this for rendering or anything else.
*/
std::vector<const ObjectFolderOrObject*> GetAllObjectFolderOrObjects() const;
gd::ObjectFolderOrObject& GetRootFolder() {
return *rootFolder;
}
void AddMissingObjectsInRootFolder();
/** \name Saving and loading
* Members functions related to saving and loading the objects of the class.
*/
///@{
/**
* \brief Serialize instances container.
* \brief Serialize the objects container.
*/
void SerializeObjectsTo(SerializerElement& element) const;
/**
* \brief Unserialize the instances container.
* \brief Unserialize the objects container.
*/
void UnserializeObjectsFrom(gd::Project& project,
const SerializerElement& element);
/**
* \brief Serialize folder structure.
*/
void SerializeFoldersTo(SerializerElement& element) const;
/**
* \brief Unserialize folder structure.
*/
void UnserializeFoldersFrom(gd::Project& project,
const SerializerElement& element);
///@}
/** \name Objects groups management
@@ -190,6 +227,9 @@ class GD_CORE_API ObjectsContainer {
std::vector<std::unique_ptr<gd::Object> >
initialObjects; ///< Objects contained.
gd::ObjectGroupsContainer objectGroups;
private:
std::unique_ptr<gd::ObjectFolderOrObject> rootFolder;
};
} // namespace gd

View File

@@ -51,24 +51,69 @@ bool ObjectsContainersList::HasObjectNamed(const gd::String& name) const {
return false;
}
bool ObjectsContainersList::HasObjectOrGroupWithVariableNamed(
ObjectsContainersList::VariableExistence
ObjectsContainersList::HasObjectOrGroupWithVariableNamed(
const gd::String& objectOrGroupName, const gd::String& variableName) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
const auto& variables =
(*it)->GetObject(objectOrGroupName).GetVariables();
return variables.Has(variableName);
return variables.Has(variableName) ? VariableExistence::Exists
: VariableExistence::DoesNotExist;
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// Could be adapted if objects groups have variables in the future.
// This could be adapted if objects groups have variables in the future.
// Currently, a group is considered as the "intersection" of all of its
// objects. Search "groups is the intersection of its objects" in the
// codebase. Consider that a group has a variable if all objects of the
// group have it:
const auto& objectGroup = (*it)->GetObjectGroups().Get(objectOrGroupName);
const auto& objectNames = objectGroup.GetAllObjectsNames();
if (objectNames.empty()) return VariableExistence::GroupIsEmpty;
bool existsOnAtLeastOneObject = false;
bool missingOnAtLeastOneObject = false;
for (const auto& objectName : objectNames) {
if (!HasObjectWithVariableNamed(objectName, variableName)) {
missingOnAtLeastOneObject = true;
if (existsOnAtLeastOneObject) {
return VariableExistence::ExistsOnlyOnSomeObjectsOfTheGroup;
}
} else {
existsOnAtLeastOneObject = true;
if (missingOnAtLeastOneObject) {
return VariableExistence::ExistsOnlyOnSomeObjectsOfTheGroup;
}
}
}
if (missingOnAtLeastOneObject) {
return VariableExistence::DoesNotExist;
}
return VariableExistence::Exists;
}
}
return VariableExistence::DoesNotExist;
}
bool ObjectsContainersList::HasObjectWithVariableNamed(
const gd::String& objectName, const gd::String& variableName) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectName)) {
const auto& variables = (*it)->GetObject(objectName).GetVariables();
return variables.Has(variableName);
}
}
return false;
}
bool ObjectsContainersList::HasVariablesContainer(
bool ObjectsContainersList::HasObjectOrGroupVariablesContainer(
const gd::String& objectOrGroupName,
const gd::VariablesContainer& variablesContainer) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
@@ -78,7 +123,31 @@ bool ObjectsContainersList::HasVariablesContainer(
&(*it)->GetObject(objectOrGroupName).GetVariables();
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// Could be adapted if objects groups have variables in the future.
// For groups, we consider that the first object of the group defines the
// variables available for this group. Note that this is slightly
// different than other methods where a group is considered as the
// "intersection" of all of its objects.
const auto& objectNames =
(*it)->GetObjectGroups().Get(objectOrGroupName).GetAllObjectsNames();
if (!objectNames.empty()) {
return HasObjectVariablesContainer(objectNames[0], variablesContainer);
}
return false;
}
}
return false;
}
bool ObjectsContainersList::HasObjectVariablesContainer(
const gd::String& objectName,
const gd::VariablesContainer& variablesContainer) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectName)) {
return &variablesContainer ==
&(*it)->GetObject(objectName).GetVariables();
}
}
@@ -94,26 +163,154 @@ ObjectsContainersList::GetObjectOrGroupVariablesContainer(
return &(*it)->GetObject(objectOrGroupName).GetVariables();
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// Could be adapted if objects groups have variables in the future.
// For groups, we consider that the first object of the group defines the
// variables available for this group. Note that this is slightly
// different than other methods where a group is considered as the
// "intersection" of all of its objects.
const auto& objectNames =
(*it)->GetObjectGroups().Get(objectOrGroupName).GetAllObjectsNames();
if (!objectNames.empty()) {
return GetObjectVariablesContainer(objectNames[0]);
}
return nullptr;
}
}
return nullptr;
}
void ObjectsContainersList::ForEachNameWithPrefix(
const gd::String& prefix,
const gd::VariablesContainer*
ObjectsContainersList::GetObjectVariablesContainer(
const gd::String& objectName) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectName)) {
return &(*it)->GetObject(objectName).GetVariables();
}
}
return nullptr;
}
gd::Variable::Type ObjectsContainersList::GetTypeOfObjectOrGroupVariable(
const gd::String& objectOrGroupName, const gd::String& variableName) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
const auto& variables =
(*it)->GetObject(objectOrGroupName).GetVariables();
return variables.Get(variableName).GetType();
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// This could be adapted if objects groups have variables in the future.
// Currently, a group is considered as the "intersection" of all of its
// objects. Search "groups is the intersection of its objects" in the
// codebase. Consider that the first object having the variable will
// define its type.
const auto& objectGroup = (*it)->GetObjectGroups().Get(objectOrGroupName);
const auto& objectNames = objectGroup.GetAllObjectsNames();
for (const auto& objectName : objectNames) {
if (HasObjectWithVariableNamed(objectName, variableName)) {
return GetTypeOfObjectVariable(objectName, variableName);
}
}
return Variable::Type::Number;
}
}
return Variable::Type::Number;
}
gd::Variable::Type ObjectsContainersList::GetTypeOfObjectVariable(
const gd::String& objectName, const gd::String& variableName) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectName)) {
const auto& variables = (*it)->GetObject(objectName).GetVariables();
return variables.Get(variableName).GetType();
}
}
return Variable::Type::Number;
}
void ObjectsContainersList::ForEachObjectOrGroupVariableMatchingSearch(
const gd::String& objectOrGroupName,
const gd::String& search,
std::function<void(const gd::String& variableName,
const gd::Variable& variable)> fn) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
const auto& variables =
(*it)->GetObject(objectOrGroupName).GetVariables();
variables.ForEachVariableMatchingSearch(search, fn);
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// This could be adapted if objects groups have variables in the future.
// Currently, a group is considered as the "intersection" of all of its
// objects. Search "groups is the intersection of its objects" in the
// codebase. Consider that a group has a variable if all objects of the
// group have it:
const auto& objectGroup = (*it)->GetObjectGroups().Get(objectOrGroupName);
const auto& objectNames = objectGroup.GetAllObjectsNames();
if (objectNames.empty()) return;
const auto& firstObjectName = objectNames.front();
ForEachObjectVariableMatchingSearch(
firstObjectName,
search,
[&](const gd::String& variableName, const gd::Variable& variable) {
for (const auto& objectName : objectGroup.GetAllObjectsNames()) {
if (!HasObjectWithVariableNamed(objectName, variableName)) {
return; // This variable is not shared by all objects of the
// group.
}
}
// This variable is shared by all objects in the group. Note that
// other objects can have it with a different type - we allow this.
fn(variableName, variable);
});
}
}
}
void ObjectsContainersList::ForEachObjectVariableMatchingSearch(
const gd::String& objectOrGroupName,
const gd::String& search,
std::function<void(const gd::String& variableName,
const gd::Variable& variable)> fn) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
const auto& variables =
(*it)->GetObject(objectOrGroupName).GetVariables();
variables.ForEachVariableMatchingSearch(search, fn);
}
}
}
void ObjectsContainersList::ForEachNameMatchingSearch(
const gd::String& search,
std::function<void(const gd::String& name,
const gd::ObjectConfiguration* objectConfiguration)> fn)
const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
for (const auto& object : (*it)->GetObjects()) {
if (object->GetName().find(prefix) == 0)
if (object->GetName().FindCaseInsensitive(search) != gd::String::npos)
fn(object->GetName(), &object->GetConfiguration());
}
(*it)->GetObjectGroups().ForEachNameWithPrefix(
prefix, [&](const gd::String& name) { fn(name, nullptr); });
(*it)->GetObjectGroups().ForEachNameMatchingSearch(
search, [&](const gd::String& name) { fn(name, nullptr); });
}
}
@@ -229,8 +426,10 @@ gd::String ObjectsContainersList::GetTypeOfBehavior(
"ObjectsContainersList::GetTypeOfObject called with objectsContainers "
"not being exactly 2. This is a logical error and will crash.");
}
return gd::GetTypeOfBehavior(
*objectsContainers[0], *objectsContainers[1], behaviorName, searchInGroups);
return gd::GetTypeOfBehavior(*objectsContainers[0],
*objectsContainers[1],
behaviorName,
searchInGroups);
}
std::vector<gd::String> ObjectsContainersList::GetBehaviorsOfObject(

View File

@@ -42,18 +42,26 @@ class GD_CORE_API ObjectsContainersList {
*/
bool HasObjectOrGroupNamed(const gd::String& name) const;
enum VariableExistence {
DoesNotExist,
Exists,
GroupIsEmpty,
ExistsOnlyOnSomeObjectsOfTheGroup
};
/**
* \brief Check if the specified object or group has the specified variable in
* its declared variables.
*/
bool HasObjectOrGroupWithVariableNamed(const gd::String& objectOrGroupName,
const gd::String& variableName) const;
VariableExistence HasObjectOrGroupWithVariableNamed(
const gd::String& objectOrGroupName,
const gd::String& variableName) const;
/**
* \brief Check if the specified object or group has the specified variables
* container.
*/
bool HasVariablesContainer(
bool HasObjectOrGroupVariablesContainer(
const gd::String& objectOrGroupName,
const gd::VariablesContainer& variablesContainer) const;
@@ -64,6 +72,11 @@ class GD_CORE_API ObjectsContainersList {
const gd::VariablesContainer* GetObjectOrGroupVariablesContainer(
const gd::String& objectOrGroupName) const;
/**
* \brief Get a type from an object/group variable.
*/
gd::Variable::Type GetTypeOfObjectOrGroupVariable(const gd::String& objectOrGroupName, const gd::String& variableName) const;
/**
* \brief Get a type from an object/group name.
* \note If a group contains only objects of a same type, then the group has
@@ -81,18 +94,21 @@ class GD_CORE_API ObjectsContainersList {
const gd::String& behaviorName) const;
/**
* \brief Get the type of a behavior if an object or all objects of a group has it.
*/
gd::String GetTypeOfBehaviorInObjectOrGroup(const gd::String &objectOrGroupName,
const gd::String &behaviorName,
bool searchInGroups = true) const;
* \brief Get the type of a behavior if an object or all objects of a group
* has it.
*/
gd::String GetTypeOfBehaviorInObjectOrGroup(
const gd::String& objectOrGroupName,
const gd::String& behaviorName,
bool searchInGroups = true) const;
/**
* \brief Get a type from a behavior name
* @return Type of the behavior.
* @deprecated - Use GetTypeOfBehaviorInObjectOrGroup instead.
*/
gd::String GetTypeOfBehavior(const gd::String& behaviorName, bool searchInGroups = true) const;
gd::String GetTypeOfBehavior(const gd::String& behaviorName,
bool searchInGroups = true) const;
/**
* \brief Get behaviors of an object/group
@@ -101,9 +117,8 @@ class GD_CORE_API ObjectsContainersList {
*
* @return Vector containing names of behaviors
*/
std::vector<gd::String>
GetBehaviorsOfObject(const gd::String& objectName,
bool searchInGroups = true) const;
std::vector<gd::String> GetBehaviorsOfObject(
const gd::String& objectName, bool searchInGroups = true) const;
/**
* \brief Return a list containing all objects refered to by the group.
@@ -121,9 +136,24 @@ class GD_CORE_API ObjectsContainersList {
void ForEachObject(std::function<void(const gd::Object& object)> fn) const;
/**
* \brief Call the callback for each object or group name starting with the prefix passed in parameter.
* \brief Call the callback for each object or group name matching the
* search passed in parameter.
*/
void ForEachNameWithPrefix(const gd::String& prefix, std::function<void(const gd::String& name, const gd::ObjectConfiguration* objectConfiguration)> fn) const;
void ForEachNameMatchingSearch(
const gd::String& search,
std::function<void(const gd::String& name,
const gd::ObjectConfiguration* objectConfiguration)>
fn) const;
/**
* \brief Call the callback for each variable of the object (or group)
* matching the search passed in parameter.
*/
void ForEachObjectOrGroupVariableMatchingSearch(
const gd::String& objectOrGroupName,
const gd::String& search,
std::function<void(const gd::String& variableName,
const gd::Variable& variable)> fn) const;
/** Do not use - should be private but accessible to let Emscripten create a
* temporary. */
@@ -132,6 +162,24 @@ class GD_CORE_API ObjectsContainersList {
private:
bool HasObjectNamed(const gd::String& name) const;
bool HasObjectWithVariableNamed(const gd::String& objectName,
const gd::String& variableName) const;
bool HasObjectVariablesContainer(
const gd::String& objectName,
const gd::VariablesContainer& variablesContainer) const;
const gd::VariablesContainer* GetObjectVariablesContainer(
const gd::String& objectName) const;
gd::Variable::Type GetTypeOfObjectVariable(const gd::String& objectName, const gd::String& variableName) const;
void ForEachObjectVariableMatchingSearch(
const gd::String& objectOrGroupName,
const gd::String& search,
std::function<void(const gd::String& variableName,
const gd::Variable& variable)> fn) const;
void Add(const gd::ObjectsContainer& objectsContainer) {
objectsContainers.push_back(&objectsContainer);
};

View File

@@ -48,11 +48,6 @@ using namespace std;
namespace gd {
// By default, disallow unicode in identifiers, but this can be set to true
// by the IDE. In the future, this will be set to true by default, keeping backward compatibility.
// We keep it disabled by default to progressively ask users to test it in real projects.
bool Project::allowUsageOfUnicodeIdentifierNames = false;
Project::Project()
: name(_("Project")),
version("1.0.0"),
@@ -86,26 +81,24 @@ Project::~Project() {}
void Project::ResetProjectUuid() { projectUuid = UUID::MakeUuid4(); }
std::unique_ptr<gd::Object>
Project::CreateObject(const gd::String &objectType, const gd::String &name) const {
std::unique_ptr<gd::Object> object =
gd::make_unique<Object>(name, objectType, CreateObjectConfiguration(objectType));
std::unique_ptr<gd::Object> Project::CreateObject(
const gd::String& objectType, const gd::String& name) const {
std::unique_ptr<gd::Object> object = gd::make_unique<Object>(
name, objectType, CreateObjectConfiguration(objectType));
auto &platform = GetCurrentPlatform();
auto &project = *this;
auto addDefaultBehavior =
[&platform,
&project,
&object,
&objectType](const gd::String& behaviorType) {
auto &behaviorMetadata =
auto& platform = GetCurrentPlatform();
auto& project = *this;
auto addDefaultBehavior = [&platform, &project, &object, &objectType](
const gd::String& behaviorType) {
auto& behaviorMetadata =
gd::MetadataProvider::GetBehaviorMetadata(platform, behaviorType);
if (MetadataProvider::IsBadBehaviorMetadata(behaviorMetadata)) {
gd::LogWarning("Object: " + objectType + " has an unknown default behavior: " + behaviorType);
gd::LogWarning("Object: " + objectType +
" has an unknown default behavior: " + behaviorType);
return;
}
auto* behavior = object->AddNewBehavior(project, behaviorType,
behaviorMetadata.GetDefaultName());
auto* behavior = object->AddNewBehavior(
project, behaviorType, behaviorMetadata.GetDefaultName());
behavior->SetDefaultBehavior(true);
};
@@ -114,18 +107,17 @@ Project::CreateObject(const gd::String &objectType, const gd::String &name) cons
addDefaultBehavior("ResizableCapability::ResizableBehavior");
addDefaultBehavior("ScalableCapability::ScalableBehavior");
addDefaultBehavior("FlippableCapability::FlippableBehavior");
}
else {
auto &objectMetadata = gd::MetadataProvider::GetObjectMetadata(platform, objectType);
} else {
auto& objectMetadata =
gd::MetadataProvider::GetObjectMetadata(platform, objectType);
if (MetadataProvider::IsBadObjectMetadata(objectMetadata)) {
gd::LogWarning("Object: " + name + " has an unknown type: " + objectType);
}
for (auto &behaviorType : objectMetadata.GetDefaultBehaviors()) {
for (auto& behaviorType : objectMetadata.GetDefaultBehaviors()) {
addDefaultBehavior(behaviorType);
}
}
return std::move(object);
}
@@ -849,6 +841,11 @@ void Project::UnserializeFrom(const SerializerElement& element) {
resourcesManager.UnserializeFrom(
element.GetChild("resources", 0, "Resources"));
UnserializeObjectsFrom(*this, element.GetChild("objects", 0, "Objects"));
if (element.HasChild("objectsFolderStructure")) {
UnserializeFoldersFrom(*this, element.GetChild("objectsFolderStructure", 0));
}
AddMissingObjectsInRootFolder();
GetVariables().UnserializeFrom(element.GetChild("variables", 0, "Variables"));
scenes.clear();
@@ -1000,6 +997,7 @@ void Project::SerializeTo(SerializerElement& element) const {
resourcesManager.SerializeTo(element.AddChild("resources"));
SerializeObjectsTo(element.AddChild("objects"));
SerializeFoldersTo(element.AddChild("objectsFolderStructure"));
GetObjectGroups().SerializeTo(element.AddChild("objectsGroups"));
GetVariables().SerializeTo(element.AddChild("variables"));
@@ -1038,28 +1036,18 @@ void Project::SerializeTo(SerializerElement& element) const {
externalSourceFilesElement.AddChild("sourceFile"));
}
void Project::AllowUsageOfUnicodeIdentifierNames(bool enable) {
allowUsageOfUnicodeIdentifierNames = enable;
}
bool Project::IsNameSafe(const gd::String& name) {
if (name.empty()) return false;
if (isdigit(name[0])) return false;
if (!allowUsageOfUnicodeIdentifierNames) {
gd::String legacyAllowedCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
return !(name.find_first_not_of(legacyAllowedCharacters) != gd::String::npos);
} else {
for (auto character : name) {
if (!GrammarTerminals::IsAllowedInIdentifier(character)) {
return false;
}
for (auto character : name) {
if (!GrammarTerminals::IsAllowedInIdentifier(character)) {
return false;
}
return true;
}
return true;
}
gd::String Project::GetSafeName(const gd::String& name) {
@@ -1069,18 +1057,13 @@ gd::String Project::GetSafeName(const gd::String& name) {
if (isdigit(name[0])) newName = "_" + newName;
gd::String legacyAllowedCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
for (size_t i = 0;i < newName.size();++i) {
// Note that iterating on the characters is not super efficient (O(n^2), which
// could be avoided with an iterator), but this function is not critical for performance
// (only used to generate a name when a user creates a new entity or rename one).
for (size_t i = 0; i < newName.size(); ++i) {
// Note that iterating on the characters is not super efficient (O(n^2),
// which could be avoided with an iterator), but this function is not
// critical for performance (only used to generate a name when a user
// creates a new entity or rename one).
auto character = newName[i];
bool isAllowed =
allowUsageOfUnicodeIdentifierNames
? GrammarTerminals::IsAllowedInIdentifier(character)
: legacyAllowedCharacters.find(character) != gd::String::npos;
bool isAllowed = GrammarTerminals::IsAllowedInIdentifier(character);
// Replace all unallowed letters by an underscore.
if (!isAllowed) {

View File

@@ -11,12 +11,12 @@
#include "GDCore/Project/ExtensionProperties.h"
#include "GDCore/Project/LoadingScreen.h"
#include "GDCore/Project/Watermark.h"
#include "GDCore/Project/ObjectGroupsContainer.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/PlatformSpecificAssets.h"
#include "GDCore/Project/ResourcesManager.h"
#include "GDCore/Project/VariablesContainer.h"
#include "GDCore/Project/Watermark.h"
#include "GDCore/String.h"
namespace gd {
class Platform;
@@ -82,7 +82,9 @@ class GD_CORE_API Project : public ObjectsContainer {
/**
* \brief Change the project description
*/
void SetDescription(const gd::String& description_) { description = description_; };
void SetDescription(const gd::String& description_) {
description = description_;
};
/**
* \brief Get the project description
@@ -124,7 +126,9 @@ class GD_CORE_API Project : public ObjectsContainer {
/**
* \brief Get the author usernames of the project.
*/
const std::vector<gd::String>& GetAuthorUsernames() const { return authorUsernames; };
const std::vector<gd::String>& GetAuthorUsernames() const {
return authorUsernames;
};
/**
* \brief Get the author usernames of the project, to modify them (non-const).
@@ -135,7 +139,9 @@ class GD_CORE_API Project : public ObjectsContainer {
* Define the project as playable with a keyboard.
* \param enable True to define the project as playable with a keyboard.
*/
void SetPlayableWithKeyboard(bool playable = true) { isPlayableWithKeyboard = playable; }
void SetPlayableWithKeyboard(bool playable = true) {
isPlayableWithKeyboard = playable;
}
/**
* Check if the project is defined as playable with a keyboard.
@@ -146,7 +152,9 @@ class GD_CORE_API Project : public ObjectsContainer {
* Define the project as playable with a gamepad.
* \param enable True to define the project as playable with a gamepad.
*/
void SetPlayableWithGamepad(bool playable = true) { isPlayableWithGamepad = playable; }
void SetPlayableWithGamepad(bool playable = true) {
isPlayableWithGamepad = playable;
}
/**
* Check if the project is defined as playable with a gamepad.
@@ -157,7 +165,9 @@ class GD_CORE_API Project : public ObjectsContainer {
* Define the project as playable on a mobile.
* \param enable True to define the project as playable on a mobile.
*/
void SetPlayableWithMobile(bool playable = true) { isPlayableWithMobile = playable; }
void SetPlayableWithMobile(bool playable = true) {
isPlayableWithMobile = playable;
}
/**
* Check if the project is defined as playable on a mobile.
@@ -391,17 +401,23 @@ class GD_CORE_API Project : public ObjectsContainer {
/**
* Set the antialiasing mode used by the game ("none" or "MSAA").
*/
void SetAntialiasingMode(const gd::String& antialiasingMode_) { antialiasingMode = antialiasingMode_; }
void SetAntialiasingMode(const gd::String& antialiasingMode_) {
antialiasingMode = antialiasingMode_;
}
/**
* Return true if antialising is enabled on mobiles.
*/
bool IsAntialisingEnabledOnMobile() const { return isAntialisingEnabledOnMobile; }
bool IsAntialisingEnabledOnMobile() const {
return isAntialisingEnabledOnMobile;
}
/**
* Set whether antialising is enabled on mobiles or not.
*/
void SetAntialisingEnabledOnMobile(bool enable) { isAntialisingEnabledOnMobile = enable; }
void SetAntialisingEnabledOnMobile(bool enable) {
isAntialisingEnabledOnMobile = enable;
}
/**
* \brief Return if the project should set 0 as Z-order for objects created
@@ -905,7 +921,8 @@ class GD_CORE_API Project : public ObjectsContainer {
/**
* \brief Return the events based object with a given type.
*/
const gd::EventsBasedObject& GetEventsBasedObject(const gd::String& type) const;
const gd::EventsBasedObject& GetEventsBasedObject(
const gd::String& type) const;
/**
* \brief Check if events based behavior with a given type exists.
@@ -920,7 +937,8 @@ class GD_CORE_API Project : public ObjectsContainer {
/**
* \brief Return the events based behavior with a given type.
*/
const gd::EventsBasedBehavior& GetEventsBasedBehavior(const gd::String& type) const;
const gd::EventsBasedBehavior& GetEventsBasedBehavior(
const gd::String& type) const;
///@}
@@ -969,20 +987,6 @@ class GD_CORE_API Project : public ObjectsContainer {
*/
///@{
/**
* Check if unicode names are allowed in identifier names.
* \see IsNameSafe
* \see GetSafeName
*/
static bool IsUsageOfUnicodeIdentifierNamesAllowed() { return allowUsageOfUnicodeIdentifierNames; };
/**
* Set if unicode names are allowed in identifier names.
* \see IsNameSafe
* \see GetSafeName
*/
static void AllowUsageOfUnicodeIdentifierNames(bool enable);
/**
* Return true if \a name is valid (can be used safely for an object,
* behavior, events function name, etc...).
@@ -990,8 +994,8 @@ class GD_CORE_API Project : public ObjectsContainer {
static bool IsNameSafe(const gd::String& name);
/**
* Return a name, based on the one passed in parameter, that can be safely used
* for an object, behavior, events function name, etc...
* Return a name, based on the one passed in parameter, that can be safely
* used for an object, behavior, events function name, etc...
*/
static gd::String GetSafeName(const gd::String& name);
///@}
@@ -1068,8 +1072,8 @@ class GD_CORE_API Project : public ObjectsContainer {
bool adaptGameResolutionAtRuntime; ///< Should the game resolution be adapted
///< to the window size at runtime
gd::String
sizeOnStartupMode; ///< How to adapt the game size to the screen. Can be
///< "adaptWidth", "adaptHeight" or empty
sizeOnStartupMode; ///< How to adapt the game size to the screen. Can be
///< "adaptWidth", "adaptHeight" or empty
gd::String antialiasingMode;
bool isAntialisingEnabledOnMobile;
gd::String projectUuid; ///< UUID useful to identify the game in online
@@ -1095,19 +1099,18 @@ class GD_CORE_API Project : public ObjectsContainer {
externalSourceFiles; ///< List of external source files used.
gd::String author; ///< Game author name, for publishing purpose.
std::vector<gd::String>
authorIds; ///< Game author ids, from GDevelop users DB.
authorIds; ///< Game author ids, from GDevelop users DB.
std::vector<gd::String>
authorUsernames; ///< Game author usernames, from GDevelop users DB.
std::vector<gd::String>
categories; ///< Game categories
bool isPlayableWithKeyboard; ///< The project is playable with a keyboard.
bool isPlayableWithGamepad; ///< The project is playable with a gamepad.
bool isPlayableWithMobile; ///< The project is playable on a mobile.
gd::String packageName; ///< Game package name
gd::String templateSlug; ///< The slug of the template from which the game is
///< created.
gd::String orientation; ///< Lock game orientation (on mobile devices).
///< "default", "landscape" or "portrait".
authorUsernames; ///< Game author usernames, from GDevelop users DB.
std::vector<gd::String> categories; ///< Game categories
bool isPlayableWithKeyboard; ///< The project is playable with a keyboard.
bool isPlayableWithGamepad; ///< The project is playable with a gamepad.
bool isPlayableWithMobile; ///< The project is playable on a mobile.
gd::String packageName; ///< Game package name
gd::String templateSlug; ///< The slug of the template from which the game is
///< created.
gd::String orientation; ///< Lock game orientation (on mobile devices).
///< "default", "landscape" or "portrait".
bool
folderProject; ///< True if folder project, false if single file project.
gd::String
@@ -1128,8 +1131,6 @@ class GD_CORE_API Project : public ObjectsContainer {
///< time the project was saved.
mutable unsigned int gdBuildVersion; ///< The GD build version used the last
///< time the project was saved.
static bool allowUsageOfUnicodeIdentifierNames;
};
} // namespace gd

View File

@@ -97,8 +97,8 @@ class ProjectScopedContainers {
return notFoundCallback();
};
void ForEachIdentifierWithPrefix(
const gd::String &prefix,
void ForEachIdentifierMatchingSearch(
const gd::String &search,
std::function<void(const gd::String &name,
const ObjectConfiguration *objectConfiguration)>
objectCallback,
@@ -110,8 +110,8 @@ class ProjectScopedContainers {
parameterCallback) const {
std::set<gd::String> namesAlreadySeen;
objectsContainersList.ForEachNameWithPrefix(
prefix,
objectsContainersList.ForEachNameMatchingSearch(
search,
[&](const gd::String &name,
const ObjectConfiguration *objectConfiguration) {
if (namesAlreadySeen.count(name) == 0) {
@@ -119,24 +119,24 @@ class ProjectScopedContainers {
objectCallback(name, objectConfiguration);
}
});
variablesContainersList.ForEachVariableWithPrefix(
prefix, [&](const gd::String &name, const gd::Variable &variable) {
variablesContainersList.ForEachVariableMatchingSearch(
search, [&](const gd::String &name, const gd::Variable &variable) {
if (namesAlreadySeen.count(name) == 0) {
namesAlreadySeen.insert(name);
variableCallback(name, variable);
}
});
gd::ParameterMetadataTools::ForEachParameterWithPrefix(
gd::ParameterMetadataTools::ForEachParameterMatchingSearch(
parametersVectorsList,
prefix,
search,
[&](const gd::ParameterMetadata &parameter) {
if (namesAlreadySeen.count(parameter.GetName()) == 0) {
namesAlreadySeen.insert(parameter.GetName());
parameterCallback(parameter);
}
});
propertiesContainersList.ForEachPropertyWithPrefix(
prefix, [&](const gd::NamedPropertyDescriptor &property) {
propertiesContainersList.ForEachPropertyMatchingSearch(
search, [&](const gd::NamedPropertyDescriptor &property) {
if (namesAlreadySeen.count(property.GetName()) == 0) {
namesAlreadySeen.insert(property.GetName());
propertyCallback(property);

View File

@@ -31,9 +31,13 @@ class PropertiesContainer
return *this;
}
void ForEachPropertyWithPrefix(const gd::String& prefix, std::function<void(const gd::NamedPropertyDescriptor& property)> fn) const {
for (const auto& property: elements) {
if (property->GetName().find(prefix) == 0) fn(*property);
void ForEachPropertyMatchingSearch(
const gd::String& search,
std::function<void(const gd::NamedPropertyDescriptor& property)> fn)
const {
for (const auto& property : elements) {
if (property->GetName().FindCaseInsensitive(search) != gd::String::npos)
fn(*property);
}
}

View File

@@ -55,12 +55,12 @@ bool PropertiesContainersList::HasPropertiesContainer(const gd::PropertiesContai
return false;
}
void PropertiesContainersList::ForEachPropertyWithPrefix(
const gd::String& prefix,
void PropertiesContainersList::ForEachPropertyMatchingSearch(
const gd::String& search,
std::function<void(const gd::NamedPropertyDescriptor& property)> fn) const {
for (auto it = propertiesContainers.rbegin(); it != propertiesContainers.rend();
++it) {
(*it)->ForEachPropertyWithPrefix(prefix, fn);
(*it)->ForEachPropertyMatchingSearch(search, fn);
}
}

View File

@@ -67,9 +67,9 @@ class GD_CORE_API PropertiesContainersList {
}
/**
* \brief Call the callback for each property having a name starting with the specified prefix.
* \brief Call the callback for each property having a name matching the specified search.
*/
void ForEachPropertyWithPrefix(const gd::String& prefix, std::function<void(const gd::NamedPropertyDescriptor& property)> fn) const;
void ForEachPropertyMatchingSearch(const gd::String& search, std::function<void(const gd::NamedPropertyDescriptor& property)> fn) const;
/** Do not use - should be private but accessible to let Emscripten create a
* temporary. */

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