From 9977e395e2e63be1a098211c7d671c5ce6ac761c Mon Sep 17 00:00:00 2001 From: markdibarry Date: Thu, 10 Feb 2022 12:36:21 -0500 Subject: [PATCH] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 2c28729122c71e025f9491b3d9905752341530fe Merge: f91e5bad8c ba565bec1b Author: Rémi Verschelde Date: Wed Feb 9 19:53:31 2022 +0100 Merge pull request #57834 from Sauermann/fix-popup-control commit ba565bec1be8184ff02dea7b5d3655d912132ee0 Author: Markus Sauermann <6299227+Sauermann@users.noreply.github.com> Date: Wed Feb 9 03:34:30 2022 +0100 Update Popup and PopupMenu descriptions commit f91e5bad8ce2ae30834e0f46be644dbc1d9daf10 Merge: d368b88f5c a18ba63417 Author: Rémi Verschelde Date: Wed Feb 9 16:14:50 2022 +0100 Merge pull request #57855 from Faless/mp/4.x_rpc_strings commit d368b88f5c4a79fe28a8e098993bd7012a54fe4b Merge: 2cdbb7c9b8 346a4b4f50 Author: Rémi Verschelde Date: Wed Feb 9 16:14:34 2022 +0100 Merge pull request #57852 from bruvzg/hb_msdf_update commit a18ba63417fba0c73aa198566f3253b9b9a291fa Author: Fabio Alessandrelli Date: Wed Feb 9 14:27:16 2022 +0100 [Net] Allow to use strings as method name in RPC. Node::rpc and Node::rpc_id will now also accepts Strings instead of only accepting StringNames. commit 2cdbb7c9b858b412df4c1eb50eba921985054f69 Merge: 77c2bfb636 c283a0ece7 Author: Rémi Verschelde Date: Wed Feb 9 14:16:00 2022 +0100 Merge pull request #57847 from mbrlabs/region-select commit 77c2bfb6366add6aa0b4d1945c554b0a24584ab3 Merge: 85610588d1 d5ea8547d6 Author: Rémi Verschelde Date: Wed Feb 9 13:25:40 2022 +0100 Merge pull request #57838 from TechnicalSoup/ClassRefPatch2 commit 85610588d189e220799ca00237ebe4f00efcd445 Merge: f88a83f611 51cac0709e Author: Rémi Verschelde Date: Wed Feb 9 13:25:18 2022 +0100 Merge pull request #57843 from Pineapple/vector2-brackets-operator-master commit 346a4b4f5043dd99bc9e870dacb32964ee1994b7 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Wed Feb 9 14:20:15 2022 +0200 msdfgen: Update to version 1.9.2 commit c768189bd218937e5a754ba02fd41e936f278aba Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Wed Feb 9 14:18:08 2022 +0200 HarfBuzz: Update to version 3.3.2 commit c283a0ece7f8c508aa28d787fc103e6b6cdaffb6 Author: Marcus Brummer Date: Wed Feb 9 10:32:03 2022 +0100 Improved region-select in the 3D editor viewport commit f88a83f6113ac8c7788fda163b831f2f8dabb96b Merge: 5b866426fd 374299c6fb Author: Rémi Verschelde Date: Wed Feb 9 11:11:01 2022 +0100 Merge pull request #57810 from timothyqiu/tree-button-id commit 5b866426fd409bac3502238aaa3d3fd6b3a4ef90 Merge: 79077e6c10 eb24c91040 Author: Rémi Verschelde Date: Wed Feb 9 11:05:26 2022 +0100 Merge pull request #57605 from naithar/fix/godot-view-touch-4.0 commit d5ea8547d6528d838c0c44617f6dab0c4587709e Author: TechnicalSoup Date: Wed Feb 9 15:50:50 2022 +1100 Correct C# code example in Color class reference Correct C# example code for html method in the Color class reference. commit 79077e6c10db9e8e53a8134f72e326f3ffb9c51c Merge: 5e53d9d777 90162851a7 Author: Rémi Verschelde Date: Wed Feb 9 09:46:02 2022 +0100 Merge pull request #57817 from akien-mga/version-hash-cpp commit 5e53d9d777a4087204dc142d21b878bfab265d63 Merge: d6deada47c f94df6fa2b Author: Rémi Verschelde Date: Wed Feb 9 09:45:47 2022 +0100 Merge pull request #57820 from akien-mga/scons-compiledb-opt-in commit d6deada47caa9368a8a45ac3ae11473316d2cc50 Author: Rémi Verschelde Date: Wed Feb 9 09:39:28 2022 +0100 Revert "Show the 3D transform gizmo in the center when otherwise offscreen" This reverts commit 4234a72b256a95cc345dfa2289fe0a3b588631b6. There are some errors which are problematic to have in 4.0 alpha 2. The PR can be redone with fixes and merged again after the dev release. Fixes #57839. commit 196801fc64026ab7daee44994ee92bc6eb9faae4 Merge: 3cb9dc78d6 ef81dc1831 Author: Rémi Verschelde Date: Wed Feb 9 09:38:39 2022 +0100 Merge pull request #57837 from YeldhamDev/that_was_pointless commit f94df6fa2b59f688761988362607ee40455e8e3b Author: Rémi Verschelde Date: Tue Feb 8 21:46:47 2022 +0100 SCons: Make compilation database generation optional Saves around 3 s on incremental rebuilds to have it disabled by default. Can be enabled with `compiledb=yes`. commit 90162851a79f46a0e8887e9a403b9ee4db333eb9 Author: Rémi Verschelde Date: Tue Feb 8 20:50:37 2022 +0100 Core: Move generated `VERSION_HASH` to a `.cpp` file This lets us have its definition in `core/version.h` and avoid rebuilding a handful of files every time the commit hash changes. commit 51cac0709e9c053b0785fc11cd26cb9cc83c01ae Author: Bartłomiej T. Listwon Date: Wed Feb 9 09:17:17 2022 +0100 Fix Vector2 and Vector2i coord access via operator[] commit 3cb9dc78d6001af550f125bb6f8b7da27a291add Merge: d435c51cda d4553c5126 Author: Rémi Verschelde Date: Wed Feb 9 09:14:17 2022 +0100 Merge pull request #57806 from akien-mga/scons-gotta-go-fast commit d435c51cda529cc85a945fb909217dd6b0dc531b Merge: a4759e375a eb9d8ad44a Author: Rémi Verschelde Date: Wed Feb 9 08:16:49 2022 +0100 Merge pull request #57836 from BastiaanOlij/deprecated_vulkan_macros commit ef81dc1831db37c104091b5f7edf8102c8d72270 Author: Michael Alexsander Date: Wed Feb 9 01:36:20 2022 -0300 Remove code to update the layout direction of submenus from `PopupMenu` commit eb9d8ad44ae3cd6d793c472dd7c64110275eff8c Author: Bastiaan Olij Date: Wed Feb 9 15:04:47 2022 +1100 Nitpicking, VK_VERSION_* have been deprecated, replaced by VK_API_VERSION_*. commit a4759e375a5adcba6de7aaad11f3a5408592ea4c Merge: f111768ba9 35806c1511 Author: Rémi Verschelde Date: Wed Feb 9 01:22:12 2022 +0100 Merge pull request #57832 from Sauermann/fix-id-creation commit f111768ba9345e092d4e4878a32b71952ce1f960 Merge: b480140ce1 839b8cae1e Author: Rémi Verschelde Date: Wed Feb 9 01:20:58 2022 +0100 Merge pull request #57813 from MisoMosiSpy/disabled_icons commit 35806c1511a0ea4da8fcf3907f8a2011bebbdb7b Author: Markus Sauermann <6299227+Sauermann@users.noreply.github.com> Date: Wed Feb 9 00:53:57 2022 +0100 Adjust id creation in PopupMenu to avoid duplicate ids commit 839b8cae1e33b8fa656c5249b5cf4617148fef4e Author: MisoMosiSpy Date: Tue Feb 8 22:12:00 2022 +0530 Updated alpha value for disabled icons in default theme. commit b480140ce162414582aa185f4378a133684179b7 Merge: 64c4ce516a f7529d417d Author: Rémi Verschelde Date: Wed Feb 9 00:27:08 2022 +0100 Merge pull request #57825 from Calinou/ios-remove-obsolete-define commit 64c4ce516a9bc501a9e25421b722cd5245a0f5bb Merge: ba1024f42d 41a158af56 Author: Rémi Verschelde Date: Wed Feb 9 00:24:55 2022 +0100 Merge pull request #52592 from ellenhp/randomizer commit 41a158af56f8ebd06b7a09aad0949a86f12bfcc7 Author: Ellen Poe Date: Sat Sep 11 23:57:09 2021 -0700 Add AudioStreamRandomizer, replacing AudioStreamRandomPitch Add additional randomization options. commit ba1024f42d9ba279b13eee0cbe3286089dc39ffb Merge: 25d4c14fef 74fc4410f4 Author: Rémi Verschelde Date: Tue Feb 8 23:25:16 2022 +0100 Merge pull request #57822 from Calinou/ios-remove-armv7 commit 25d4c14fef0b76b8f4dcc09214b5b30f84998e62 Merge: 8907c566ed dd970482c5 Author: Rémi Verschelde Date: Tue Feb 8 23:23:50 2022 +0100 Merge pull request #57627 from JFonS/occluder_improvements commit 8907c566ed24b5c648c5b733b85e004299a51c27 Merge: c65a4fe9b6 2d82e076f4 Author: Rémi Verschelde Date: Tue Feb 8 23:12:05 2022 +0100 Merge pull request #57773 from pfertyk/issue_57710_tabbar_update_hover commit c65a4fe9b6d45357ed938744758b8e965746f0db Merge: d64b27e510 4234a72b25 Author: Rémi Verschelde Date: Tue Feb 8 22:49:14 2022 +0100 Merge pull request #48307 from aaronfranke/gizmo-offscreen commit f7529d417dfed775da3de9eeadf7a7a39a21c094 Author: Hugo Locurcio Date: Tue Feb 8 22:45:36 2022 +0100 Remove obsolete define in the iOS buildsystem code This define was used by the WebM/libvpx code, but it's now removed in `master`. commit d64b27e510d3bda72730717f1bce0f5a3470a689 Merge: a5963500b9 a6c77c7c5a Author: Rémi Verschelde Date: Tue Feb 8 22:34:23 2022 +0100 Merge pull request #57819 from Jojox/fix_padded_texture_format commit a5963500b9b1b4cc965c2ff14d1f50ca955f4964 Merge: f05f2dd80f 68b04a5a07 Author: Rémi Verschelde Date: Tue Feb 8 22:19:55 2022 +0100 Merge pull request #57818 from raulsntos/typed-navigation commit f05f2dd80fca926a5dc1a58249321b638209f425 Merge: 76ce5c16f3 6c3b6664b5 Author: Ignacio Roldán Etcheverry Date: Tue Feb 8 22:16:54 2022 +0100 Merge pull request #57076 from IgorKordiukiewicz/fix-mono-string-capitalize String.Capitalize() in C# now matches the behaviour of String::capitalize() in C++ commit 74fc4410f462e26ce9842eeb1cebb428532f9915 Author: Hugo Locurcio Date: Tue Feb 8 22:09:30 2022 +0100 Remove support for ARMv7 (32-bit) on iOS All iOS devices since the iPhone 5S support ARMv8 (64-bit). The last iOS version supported on ARMv7 devices is 10.x, which is too old to run Godot 4.0 projects since the minimum supported iOS version is 11.0. commit 2d82e076f49db6b4979da5e75926e1c0fa8c5636 Author: Paweł Fertyk Date: Mon Feb 7 21:40:16 2022 +0100 Fix `TabBar._update_hover` crash Fixes #57710. commit 4234a72b256a95cc345dfa2289fe0a3b588631b6 Author: Aaron Franke Date: Tue Feb 1 09:23:14 2022 -0600 Show the 3D transform gizmo in the center when otherwise offscreen commit a6c77c7c5a5a9f2f2d990679c7a4d2efa8749918 Author: JoJoX Date: Tue Feb 8 15:27:44 2022 -0500 Use source image format when creating padded texture commit 68b04a5a073e66f73b0df3daec89999960f04c84 Author: Raul Santos Date: Tue Feb 8 20:58:55 2022 +0100 Add array element type to `_get_polygons` and `_get_outlines` commit 76ce5c16f351632d40fea9e6ea2bb015cb827aca Merge: faeb71865e 4397109aab Author: Rémi Verschelde Date: Tue Feb 8 21:14:51 2022 +0100 Merge pull request #55584 from KoBeWi/twoids commit faeb71865eae069c1940d0e84c35ad82591a400a Merge: 39562294ff 8345aabaf4 Author: Rémi Verschelde Date: Tue Feb 8 21:13:00 2022 +0100 Merge pull request #56946 from JFonS/editor_transform_improvements commit 8345aabaf49fde32729fe168775d6a394115553b Author: jfons Date: Mon Jan 17 15:48:21 2022 +0100 Improve rotation in the 3D transform gizmo * Get rid of deadzones. * Make it easier to select rotation handles at very oblique angles. * Handle rotation for axes that are perpendicular to the camera. commit 39562294ff3e6a273f9a73f97bc54791a4e98f07 Merge: 592e92d938 e0c82913ff Author: Rémi Verschelde Date: Tue Feb 8 19:55:36 2022 +0100 Merge pull request #57816 from JFonS/fix_3d_viewport_navigation commit e0c82913ff39ab036f3c440f9e064f91ed797bc7 Author: jfons Date: Tue Feb 8 19:24:50 2022 +0100 Fix navigation in 3D viewport The incorrect initialization of EditData::instant to true was preventing the navigation code to run until the transform gizmo was used. commit 4397109aab412f1f9edcd949bb1e80728b6a8f67 Author: kobewi Date: Fri Dec 3 12:54:57 2021 +0100 Fix uid conflict when duplicating resource commit 592e92d938ce4eeb053e208dc673598d9aaa727a Merge: 72de251501 d3345ef1f8 Author: Rémi Verschelde Date: Tue Feb 8 17:32:49 2022 +0100 Merge pull request #57809 from akien-mga/osx-11.00-warning commit 72de2515019048dfdad5817ab069207061ba858c Merge: 6889085813 fb1fa2a3f7 Author: Rémi Verschelde Date: Tue Feb 8 17:32:33 2022 +0100 Merge pull request #57807 from KoBeWi/ninja_methods commit 68890858133ab2046b9418a349d613b5d2cfdffd Merge: 96e4de3511 0e8147d303 Author: Rémi Verschelde Date: Tue Feb 8 17:32:14 2022 +0100 Merge pull request #57808 from KoBeWi/named_inspector commit 374299c6fb7248c1bae08fcc0ba7f0467c980f7e Author: Haoyu Qiu Date: Tue Feb 8 23:56:13 2022 +0800 Improve TreeItem button API commit 96e4de35117e516b5c25303224bfd3269f067188 Merge: cc097cd22b 21b9f1ecfe Author: Rémi Verschelde Date: Tue Feb 8 16:42:01 2022 +0100 Merge pull request #57626 from Calinou/3d-editor-rename-debug-draw-modes commit cc097cd22b67416bb9b6db1fdcc96d8056b8c703 Merge: 2a39a1c221 58e8e5f219 Author: Rémi Verschelde Date: Tue Feb 8 16:39:19 2022 +0100 Merge pull request #56543 from rcorre/blendermotion-4.0 commit d3345ef1f84cd68a7d28912ba0063cd988b95cd5 Author: Rémi Verschelde Date: Tue Feb 8 16:20:07 2022 +0100 OSX: Pass `-mmacosx-version-min=11.0` instead of `11.00` Both are recognized by Xcode and equivalent, but osxcross issues a warning for the latter: ``` osxcross: warning: '-mmacosx-version-min=' (11.0.0 != 11.00) ``` commit 0e8147d3039e7e8ce3f61850c3ab8b004df8ce85 Author: kobewi Date: Tue Feb 8 16:15:27 2022 +0100 Display built-in script names in the inspector commit fb1fa2a3f74a0b5ff2562829a73c9b1b91a80706 Author: kobewi Date: Tue Feb 8 16:06:57 2022 +0100 Reload built-in scripts when picking methods commit d4553c51262e347b8c877d57ca251d94faf4dc30 Author: Rémi Verschelde Date: Tue Feb 8 15:35:13 2022 +0100 SCons: Add `fast_unsafe` option for faster rebuilds This reverts #53828 which had caused a significant drop in incremental rebuild time for debug builds (from 10s to 23s on my laptop). The "faster but unsafe" options are re-added, as well as adding `max_drift=60` which we didn't use previously. These options speed up SCons' own processing of the codebase to decide what to build/rebuild (i.e. the first step before actually calling the compiler). This will therefore not make much difference for scratch builds, and is mostly useful for incremental rebuilds (including "null" rebuilds with no change). These options are enabled automatically for `debug` builds, unless `fast_unsafe=no` is passed. They are disabled by default for `release` and `release_debug` builds, unless `fast_unsafe=yes` is passed. commit 2a39a1c2217a1d31826cd68f03cfdd8a0be769f0 Merge: e776580c30 b966ca6167 Author: Rémi Verschelde Date: Tue Feb 8 15:22:31 2022 +0100 Merge pull request #57612 from maiself/improve-gamepad-detection commit e776580c3047bf4c7418362b7b55de993e52194b Merge: cb7f21eac3 db43237c78 Author: Rémi Verschelde Date: Tue Feb 8 15:21:31 2022 +0100 Merge pull request #57801 from reduz/fix-blend-shape-mask-2 commit cb7f21eac33ce7e322dc51133045c332e7c14fa7 Merge: 4a2f22daf4 8e3245383a Author: Rémi Verschelde Date: Tue Feb 8 15:21:15 2022 +0100 Merge pull request #57774 from bruvzg/font_edit_fixes commit 4a2f22daf47c275a7839c0f85924366d1162c8f0 Merge: b6ddf4a629 3495288b03 Author: Rémi Verschelde Date: Tue Feb 8 14:36:31 2022 +0100 Merge pull request #57798 from akien-mga/scons-module-tests-simplify commit 8e3245383a266986605b098ed1ac0746258c3a61 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Mon Feb 7 22:40:28 2022 +0200 [Editor] Fix font style matching issues. Fix font selection when no style selected. Fix style matching when fonts have different style sets. Use SNAME for theme overrides. commit b6ddf4a62931e4154fac5b31bca60fe8f48cd7ff Merge: e26598b4a1 21bf23d966 Author: Rémi Verschelde Date: Tue Feb 8 14:04:28 2022 +0100 Merge pull request #57692 from YeldhamDev/popping_options commit db43237c787b0ab0a57e5366392aaa57af628309 Author: reduz Date: Tue Feb 8 13:38:19 2022 +0100 Fix BLEND_SHAPE_MASK * Should now be correct * Supersedes 53738 commit e26598b4a1b6bc4e5fa9eb4c9413b0d4aff6129d Merge: 561fbe2175 31824420e4 Author: Rémi Verschelde Date: Tue Feb 8 13:43:19 2022 +0100 Merge pull request #40140 from hinlopen/tree-scroll-center commit 561fbe21751806ddec84311e4fed7943e241cd25 Merge: f9e496168f f8dde5871c Author: Rémi Verschelde Date: Tue Feb 8 13:17:35 2022 +0100 Merge pull request #56923 from fire-forge/fix-create-root-node-scrolling commit 3495288b037a20c2baebad1985b3b00dda93e72d Author: Rémi Verschelde Date: Tue Feb 8 12:39:40 2022 +0100 SCons: Improve logic to generate `modules_tests.gen.h` This removes the need for `AlwaysBuild` by ensuring that the proper files are being tracked as `Depends`. commit f9e496168f336b750149ad95a9eab526a0be0fcc Merge: b0fd01b50c 8675ff0a74 Author: Rémi Verschelde Date: Tue Feb 8 12:56:43 2022 +0100 Merge pull request #57786 from 0And/vectorslerp commit b0fd01b50c70dd4724f753f3903932e3559ee0c7 Merge: f425d403fe 720fbe3101 Author: Rémi Verschelde Date: Tue Feb 8 12:55:49 2022 +0100 Merge pull request #57797 from bruvzg/mac_no_focus commit 720fbe310112521663d3d04bb3ebda7623a9d158 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Tue Feb 8 13:22:03 2022 +0200 [macOS] Fix NO_FOCUS macOS flag. commit f425d403fe8be16b1a5dafbdce4dce546bdd28a4 Merge: 13d4cbb87c a08fc442a0 Author: Rémi Verschelde Date: Tue Feb 8 11:15:01 2022 +0100 Merge pull request #57066 from KoBeWi/in_the_name_of_the_custom commit 13d4cbb87c027dde09fa11ed9d5db98e6600309d Merge: a66e55069e 38232c70db Author: Rémi Verschelde Date: Tue Feb 8 11:13:38 2022 +0100 Merge pull request #57788 from reduz/describe-sname-usage commit a66e55069e6794ff5bf7d7a39e797e5be2a7df4a Merge: dc17cce995 6eeeb9a63c Author: Rémi Verschelde Date: Tue Feb 8 11:13:24 2022 +0100 Merge pull request #57796 from akien-mga/revert-sname-theme-setters commit dc17cce995a5958fe608221aa089ff912853a744 Merge: f32c715fbc b801742b77 Author: Rémi Verschelde Date: Tue Feb 8 11:12:44 2022 +0100 Merge pull request #57795 from bruvzg/gde_missing_binds commit f32c715fbc7c74798df838254185ef33864db031 Merge: 7e308d5120 317cd0b19a Author: Rémi Verschelde Date: Tue Feb 8 11:09:43 2022 +0100 Merge pull request #57720 from akien-mga/prefer-cast-to-get_class-string-compare commit 7e308d5120f61bcb70eb0afdbac35511484324d8 Merge: 6b13056409 bbcd9c5b97 Author: Rémi Verschelde Date: Tue Feb 8 10:50:08 2022 +0100 Merge pull request #57791 from timothyqiu/raycast-clear-except commit 6b13056409dba9c9ac1bccce23fb9320d7c48725 Merge: deece9035a 4fcc35bdfa Author: Rémi Verschelde Date: Tue Feb 8 10:42:18 2022 +0100 Merge pull request #57794 from bruvzg/x11_fs_fix commit deece9035a4bf3a8db5b35dacff3e26285248c85 Merge: 0154ce2c8d 8bc837453b Author: Rémi Verschelde Date: Tue Feb 8 10:40:08 2022 +0100 Merge pull request #57790 from bruvzg/fix_fs_detect commit 6eeeb9a63ce6f9dc6134429ed9c6cf92b1a8f2ed Author: Rémi Verschelde Date: Tue Feb 8 10:30:18 2022 +0100 Re-add missing `SNAME` macros in `get_theme_*` calls They were removed in the previous commit reverting the addition of `SNAME` to `add_theme_*` and theme setter methods, which is not wanted. commit fc076ece3ddecd44a62dc0febed7baee47f2eede Author: Rémi Verschelde Date: Tue Feb 8 10:14:58 2022 +0100 Revert "Add missing SNAME macro optimization to all theme methods call" This reverts commit a988fad9a092053434545c32afae91ccbdfbe792. As discussed in #57725 and clarified in #57788, `SNAME` is not meant to be used everywhere but only in critical code paths. For theme methods specifically, it was by design that only getters use `SNAME` and not setters. commit 317cd0b19a284f9a7296fcb4e06d9b2362da8c85 Author: Rémi Verschelde Date: Sun Feb 6 14:12:19 2022 +0100 Refactor some object type checking code with `cast_to` Less stringly typed logic, and less String allocations and comparisons. commit 4fcc35bdfa86fa8b39efe1030ed6d40ef36f76a7 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Tue Feb 8 11:01:24 2022 +0200 [X11] Fix decoration reset when returning from fullscreen mode. commit 8bc837453b32df943dbf866954bbc66f1c023bfd Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Tue Feb 8 10:36:57 2022 +0200 [Windows] Fix fullscreen mode detection on window move/resize. commit bbcd9c5b9781fdbebd2e03a6bf054d387d3df24c Author: Haoyu Qiu Date: Tue Feb 8 16:31:07 2022 +0800 Fix RayCast{2,3}D.clear_exceptions clears parent commit 38232c70db31eaca3ae9ac770b763141d6daa68c Author: reduz Date: Tue Feb 8 08:55:20 2022 +0100 Clarify SNAME usage * Explain where it should be used, with examples. * Clarify that it should _not_ be used everywhere, only where needed. * Supersedes #57720 This PR is the result of the discussion that happened in a contractor meeting, and it attempts to clarify the intended use for this macro for other contributors. As a personal note, It is my view that other approaches to using SNAME (like having a global or per class table of string names) are mere overengineering without any real benefit (performance remains the same, and usage of stringnames becomes more cumbersome. Additionally, there was not any significant amount of errors in name mismatching as a result of using strings since Godot was open sourced). commit 0154ce2c8d66528d617e10b139640fd4c4405c6b Merge: d6ba4a223f 7a8b11ee14 Author: Rémi Verschelde Date: Tue Feb 8 08:57:29 2022 +0100 Merge pull request #43015 from Xrayez/refactor-auto-instaprop Refactor auto-instantiation of `Object` properties in editor commit b801742b773a8002dc6bddb4fb157565179654d3 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Tue Feb 8 09:49:14 2022 +0200 [GDExtension] Add binds for missing methods, operators, and constants required for GDExtension TextServer implementation. commit d6ba4a223fc394fa2fd9321c19468e05b5de1de0 Merge: 26facc0543 acd562be5d Author: Rémi Verschelde Date: Tue Feb 8 08:14:21 2022 +0100 Merge pull request #57785 from TechnicalSoup/ClassRefPatch2 Add method descriptions to Color class reference commit 8675ff0a745836263d09565e5ab729fc02abf760 Author: Andrew Jacob Date: Mon Feb 7 22:58:43 2022 -0700 Allow C# Vector2/3 slerp values to have any length commit acd562be5d3295d9f5e6741e36149ca0849c9b3d Author: TechnicalSoup Date: Tue Feb 8 16:13:48 2022 +1100 Add method descriptions to Color Class Reference Add definitions and code examples for the html and html_is_valid methods commit 26facc054331f8810dba0c040f730111d2b27899 Merge: c842402ced 8a1d924896 Author: Rémi Verschelde Date: Mon Feb 7 22:49:15 2022 +0100 Merge pull request #57775 from TechnicalSoup/ClassRefPatch Add method description to Vector3i Class Reference commit 8a1d924896a338cbe10118168cf3b57b221965c9 Author: TechnicalSoup Date: Tue Feb 8 07:52:11 2022 +1100 Add method description to Vector3i Class Reference Add description for the abs method in the Vector3i class. Description added is identical to the abs method for the other vector classes commit c842402ced8422b3904909a15bd15ca76b029a26 Merge: d35269ab21 81b6da9d3d Author: Rémi Verschelde Date: Mon Feb 7 19:21:12 2022 +0100 Merge pull request #57766 from winterpixelgames/master-faster-script-class-get-parent commit 81b6da9d3db46b0043052330ea97dffe15902dae Author: Jason Knight Date: Mon Feb 7 11:37:48 2022 -0600 Use ScriptServer::get_global_class_base instead of script_class_get_base in script_class_is_parent. commit d35269ab21451a88331844b2098d4e9e20d43fc5 Merge: 05bad19a9a 5d4a141c97 Author: Rémi Verschelde Date: Mon Feb 7 18:28:27 2022 +0100 Merge pull request #57764 from timothyqiu/octant-delete commit 05bad19a9ab6b173c5e3287283eaa8f72c0c16d2 Merge: 650e218b96 5268786ac0 Author: Rémi Verschelde Date: Mon Feb 7 18:27:58 2022 +0100 Merge pull request #57752 from Calinou/doc-csg-nodes-performance commit 5d4a141c978dbac6cd89cf3afc6f75f0e5ac431e Author: Haoyu Qiu Date: Tue Feb 8 00:27:01 2022 +0800 Fix GridMap memory leak commit 5268786ac0213d4881f8a214b4d81f0c33340802 Author: Hugo Locurcio Date: Mon Feb 7 13:58:08 2022 +0100 Document performance limitations with CSG nodes, link to tutorial commit 650e218b9606e4640ac10ecbff1b7f08b6be0daf Merge: 8910d0bcb4 59e9a8c275 Author: Rémi Verschelde Date: Mon Feb 7 15:54:32 2022 +0100 Merge pull request #56768 from YeldhamDev/dock_float_theme commit 8910d0bcb4a6c03acfbcb7ec4c585ed0f2e1f4d9 Merge: be1adf491a 086256431a Author: Fabio Alessandrelli Date: Mon Feb 7 15:24:32 2022 +0100 Merge pull request #53704 from Faless/mp/4.x_gdscript_custom_callable [GDScript] Implement RPC custom callable (`my_func.rpc()`) commit a08fc442a07be2c2c668a6b6a92a501522115cd4 Author: kobewi Date: Sun Feb 6 19:02:53 2022 +0100 Fix script editor errors with CustomCallables commit be1adf491a6b40642cba186167e6635efca37f99 Merge: 1694626e03 20fb34927d Author: Rémi Verschelde Date: Mon Feb 7 13:58:35 2022 +0100 Merge pull request #57736 from TechnicalSoup/Patch-3 commit 59e9a8c275ac56bdc23883d8902920e93bcb6c69 Author: Michael Alexsander Date: Thu Jan 13 18:13:45 2022 -0300 Fix theming for floating window docks commit 086256431a958cb843e5d009749edb943dc8666f Author: Fabio Alessandrelli Date: Fri Oct 8 12:36:06 2021 +0200 [Net] Add type check to GDScriptRPCCallable. It will print an error when using an RPC defined on an object which does not extend Node. commit 994638da4f78ad09a1ead707874654ae0d2a36db Author: Fabio Alessandrelli Date: Thu Oct 7 14:39:52 2021 +0200 [Net] Implement GDScript custom RPC callable. commit 1694626e03639cdf6879117e00772bdcc6bad594 Merge: bc1a3d791d b84ef16aa7 Author: Rémi Verschelde Date: Mon Feb 7 13:36:09 2022 +0100 Merge pull request #57305 from bruvzg/macos_cleanup commit bc1a3d791d8797071844ca5ac3d15aee59b361f9 Merge: 6a56314eab a6e8cdae66 Author: Rémi Verschelde Date: Mon Feb 7 13:27:12 2022 +0100 Merge pull request #57490 from Calinou/test-add-animation commit 6a56314eaba261b73cb297ebd8754a9746de2e45 Merge: 6a33d8b93f e81ccaf270 Author: Rémi Verschelde Date: Mon Feb 7 13:22:23 2022 +0100 Merge pull request #57748 from fabriceci/rename-script-template-variable commit 6a33d8b93fc7175865a9c064c6ba4bf400c9650d Merge: 88aea70a09 948e66c3d6 Author: Rémi Verschelde Date: Mon Feb 7 13:21:53 2022 +0100 Merge pull request #57718 from Faless/js/4.x_pwa_prefer_cache_pr commit 88aea70a098d91f1887e7c4e3218ce23c42e3b4f Merge: bfb75d107c d9d12cd352 Author: Rémi Verschelde Date: Mon Feb 7 13:20:15 2022 +0100 Merge pull request #57749 from timothyqiu/feature-class-props commit dd970482c5961278034fde5fb2961e31c543e9ae Author: jfons Date: Fri Feb 4 16:28:18 2022 +0100 Improvements and fixes to occluders Improvements: * Occluder3D is now an abstract type inherited by: ArrayOccluder3D, QuadOccluder3D, BoxOccluder3D, SphereOccluder3D and PolygonOccluder3D. ArrayOccluder3D serves the same purpose as the old Occluder3D (triangle mesh occluder) while the rest are primitives that can be used to manually place simple occluders. * Occluder baking can now apply simplification. The "bake_simplification_distance" property can be used to set a world-space distance as the desired maximum error, set to 0.1 by default. * Occluders can now be generated on import. Using the "occ" and "occonly" keywords (similar to "col" and "colonly" for colliders) or by enabling on MeshInstance3Ds in the scene's import window. Fixes: * Fixed saving of occluder files after bake. * Fixed a small error where occluders didn't correctly update in the rendering server. Bonus content: * Generalized "CollisionPolygon3DEditor" so it can also be used to edit Resources. Renamed it to "Polygon3DEditor" since it was already being used by other things, not just colliders. * Fixed a small bug in "EditorPropertyArray" where a call to "remove" was left after the "remove_at" rename. commit 20fb34927d8a03c60c8fa9a3c58d0d582fd6aee9 Author: TechnicalSoup Date: Mon Feb 7 12:28:42 2022 +1100 Fix icons for sub windows Modify the create_sub_window method to set an icon for all sub windows, setting the icon to the same icon as the main window. Co-Authored-By: Rémi Verschelde commit bfb75d107c91fc36c07b917a12de715b6b107ef9 Merge: b024602660 a6e280c5de Author: Rémi Verschelde Date: Mon Feb 7 12:50:44 2022 +0100 Merge pull request #57741 from Chaosus/vs_fixes commit d9d12cd352b0244cc95b5d4d928753a26a0515ea Author: Haoyu Qiu Date: Mon Feb 7 18:55:17 2022 +0800 Don't display empty Class Properties in feature profile commit e81ccaf270fa06a7ecc6cbf9aed2a35dee29984c Author: fabriceci Date: Mon Feb 7 11:46:30 2022 +0100 rename jump force to jump velocity commit b02460266065413957eae3de34f9aef49c5a72f1 Merge: 6f425242dc a988fad9a0 Author: Rémi Verschelde Date: Mon Feb 7 11:46:25 2022 +0100 Merge pull request #57725 from jmb462/missing-sname-theme-setters commit 6f425242dcce95ae9993dca017f91e7bab2060d3 Merge: a6abeb6b20 ec00283f91 Author: Rémi Verschelde Date: Mon Feb 7 11:16:27 2022 +0100 Merge pull request #57743 from akien-mga/resource-importer-insert-opt-in commit a6abeb6b20fe542f4cf2582819ec6f3c99ab1fed Merge: 404364d4b4 60d8df3fee Author: Rémi Verschelde Date: Mon Feb 7 10:16:50 2022 +0100 Merge pull request #57682 from clayjohn/VULKAN-canvas-blur commit 404364d4b4eb3949ea85f628fd1cb85fdaa59399 Merge: 863f2840a5 ab5b5e1577 Author: Rémi Verschelde Date: Mon Feb 7 09:52:11 2022 +0100 Merge pull request #57684 from KoBeWi/todo_optimize commit ec00283f9100c0456f09fd70edd2c6d3eb763c92 Author: Rémi Verschelde Date: Mon Feb 7 09:38:42 2022 +0100 ResourceImporter: Restore default append logic for new importers This was changed in #56943 to allow adding new importers from plugins that take precedence over built-in ones, but this should be opt-in, not the default behavior. Fixes #57730. commit a6e280c5de51fd8c3b5d1ab1161fd5ee58f1e6be Author: Yuri Roubinsky Date: Mon Feb 7 08:46:51 2022 +0300 Add some more fixes to visual shader commit 863f2840a582e289612147be43d8cbededed668c Merge: c2a540de51 8bde86da10 Author: Rémi Verschelde Date: Mon Feb 7 08:19:58 2022 +0100 Merge pull request #57735 from YeldhamDev/popups_and_mirrors Make popups from `MenuButton`, `OptionButton`, and submenus obey the layout direction commit c2a540de51e41e1dbfc3ae8d6cfc438061e3c28d Merge: 602cacae21 ee3b7bc747 Author: Rémi Verschelde Date: Mon Feb 7 08:14:30 2022 +0100 Merge pull request #57732 from KoBeWi/leftplication Move Replication tab to a fixed position commit 602cacae2190e2f8714b19158878d7a65c04e98d Merge: 481b05fef1 803ac608a6 Author: Rémi Verschelde Date: Mon Feb 7 08:00:24 2022 +0100 Merge pull request #57727 from kleonc/sprite_frames_editor_fix_loading_non_texture_crash `SpriteFramesEditor` Fix crash when selecting non-`Texture2D` file for splitting commit 481b05fef1918aed9cee47680cb55b997acc5daa Merge: 8aa4ed8b5b 303f0c8626 Author: Ignacio Roldán Etcheverry Date: Mon Feb 7 04:58:16 2022 +0100 Merge pull request #57738 from raulsntos/fix-57503 Attach mono thread before getting `nativeName` field commit 303f0c8626aea2aef21a70a1598f689ee4e16b0d Author: Raul Santos Date: Mon Feb 7 03:41:44 2022 +0100 Attach mono thread before getting nativeName field In order to access the `nativeName` constant field from a C# class, the mono scope thread must be attached or the mono domain will be null. commit 8bde86da10eacff9221a19a2806eb2ece789de03 Author: Michael Alexsander Date: Sun Feb 6 23:07:08 2022 -0300 Make popups from `MenuButton`, `OptionButton`, and submenus obey the layout direction commit ee3b7bc747f2dd244d216e392535bccf5be23ca2 Author: kobewi Date: Mon Feb 7 01:06:55 2022 +0100 Move Replication tab to a fixed position commit 8aa4ed8b5b22881661f1355cf90871c4e25c68e2 Merge: 3ae38edc8e 5108af42ad Author: Rémi Verschelde Date: Mon Feb 7 00:35:42 2022 +0100 Merge pull request #57729 from TechnoPorg/astar-fix-invalid-include Remove a cross include from a_star.cpp commit 803ac608a62d738a5bec9c8dbed4b62c8bcd8b5a Author: kleonc <9283098+kleonc@users.noreply.github.com> Date: Sun Feb 6 23:39:04 2022 +0100 SpriteFramesEditor Fix crash when selecting non-Texture2D file for splitting commit 3ae38edc8e09a97578fc93e11399e635cd82b15c Merge: 61dd3136fc d6c7d4ab5d Author: Rémi Verschelde Date: Sun Feb 6 23:22:15 2022 +0100 Merge pull request #56844 from Calinou/ssr-fix-background-line-master Fix visible background line in intersections in screen-space reflections commit a988fad9a092053434545c32afae91ccbdfbe792 Author: jmb462 Date: Sun Feb 6 20:17:35 2022 +0100 Add missing SNAME macro optimization to all theme methods call commit 5108af42ad9b84ca8cf9e4a499f67481ca09b9da Author: TechnoPorg Date: Sun Feb 6 15:05:17 2022 -0700 Remove a cross include from a_star.cpp commit a6e8cdae662b8a84e37edd41d46de8b23a1c76a6 Author: Hugo Locurcio Date: Fri Jan 21 14:12:55 2022 +0100 Add a unit test suite for Animation commit 61dd3136fc19fb752f140c1942f32b1517373a42 Merge: 9cf6e5113b 01c1667836 Author: Rémi Verschelde Date: Sun Feb 6 21:25:42 2022 +0100 Merge pull request #57701 from Calinou/contributing-prefer-attachments Recommend using GitHub attachments for minimal reproduction projects commit 9cf6e5113b05e4442b9a6a3f617f00902ca1d80e Merge: 784b74ef56 db18faf660 Author: Rémi Verschelde Date: Sun Feb 6 21:24:47 2022 +0100 Merge pull request #57716 from Chaosus/vs_vector_3d Rename `PORT_TYPE_VECTOR` to `PORT_TYPE_VECTOR_3D` in visual shaders commit 784b74ef563bf3a33c4b0ae438447ed7ce99e6e1 Merge: 95719930a8 989caab0ad Author: Rémi Verschelde Date: Sun Feb 6 21:24:06 2022 +0100 Merge pull request #57721 from YeldhamDev/separate_from_separators Better handle icons and checkboxes with separators in `PopupMenu` commit 989caab0ad80316120e6eb2c248c3e793e61c7eb Author: Michael Alexsander Date: Sun Feb 6 15:19:04 2022 -0300 Better handle icons and checkboxes with separators in `PopupMenu` commit 21bf23d9660337473c98fdb1952def6d5f9847c6 Author: Michael Alexsander Date: Sat Feb 5 23:47:00 2022 -0300 Enhancements and fixes for `OptionButton` and `PopupMenu` commit 948e66c3d6930924b54ec48a0dfc929040befb0a Author: Fabio Alessandrelli Date: Mon Jan 31 15:28:12 2022 +0100 [HTML5] Implement JavaScript PWA update callbacks. Allows detecting when a new version of the progressive web app service worker is waiting (i.e. an update is pending), along a function to force the update and reload all clients. commit 3cc72ac03f6d7254220b17e23384196112c60192 Author: Fabio Alessandrelli Date: Mon Jan 31 15:19:25 2022 +0100 [HTML5] Improve editor progressive web app behavior. Ensures early claim for aggressive caching. Adds a button to update when it detects a new version asking confirmation due to the necessary reload. commit cc4612277be252ca7b0b3b718a6142d4e01a6093 Author: Fabio Alessandrelli Date: Tue Dec 21 14:41:26 2021 +0100 [HTML5] PWA service worker prefers cached version. Use an offline first approach, where we prefer the cached version over the network one. This forces games using PWA to always re-export the project and not just the PCK, so that the service worker version gets updated correctly, and the end-user cache is correctly cleared on update. commit db18faf660c9901f7d8c43526a056b6536d731a4 Author: Yuri Roubinsky Date: Sun Feb 6 20:15:28 2022 +0300 Rename `PORT_TYPE_VECTOR` to `PORT_TYPE_VECTOR_3D` commit 95719930a8940d4737d512d75004e8b5cd50e825 Merge: 79a4d782a5 871b9fc352 Author: Rémi Verschelde Date: Sun Feb 6 18:11:12 2022 +0100 Merge pull request #57672 from fire-forge/fix-image-drop-nodes commit 79a4d782a5ef0801fb65ee8c8a488942044c8e10 Merge: e38df41de8 1ce81dc5f2 Author: Rémi Verschelde Date: Sun Feb 6 17:03:11 2022 +0100 Merge pull request #57709 from jmb462/missing-sname-optimization commit e38df41de8f20516ab8b2c91bedb4388cd9e41aa Merge: 762a31169d 8c7268664d Author: Rémi Verschelde Date: Sun Feb 6 16:40:59 2022 +0100 Merge pull request #57607 from reduz/fix-variant-vec-integer-float-mul-div commit 762a31169d5e0939fda5bdaab537cec5ab39f870 Merge: fb6cf1e3b7 635da44ef8 Author: Rémi Verschelde Date: Sun Feb 6 16:40:48 2022 +0100 Merge pull request #57633 from jordigcs/x11-snap-refresh-rate commit fb6cf1e3b7a4734dac36cd40ddaaaedf5010d766 Merge: fd0d2dcabf 729c1f056b Author: Rémi Verschelde Date: Sun Feb 6 16:39:42 2022 +0100 Merge pull request #57689 from NeilKleistGao/master commit 1ce81dc5f2bd5db8bff0e60af846b994492810f9 Author: jmb462 Date: Sun Feb 6 15:53:53 2022 +0100 Add missing SNAME macro optimization in some function calls commit 8c7268664da7ef98f802ec90fa2ba17b4d695847 Author: reduz Date: Thu Feb 3 22:16:58 2022 +0100 Fix integer vector mul/div operators and bindings. * Vector2i and Vector3i mul/div by a float results in Vector2 and Vector3 respectively. * Create specializations to allow proper bindings. This fixes #44408 and supersedes #44441 and keeps the same rule of int float returnig float, like with scalars. commit 635da44ef8d1875e2e8b8b7fbfbc574456e92226 Author: jordi Date: Fri Feb 4 10:21:07 2022 -0600 Snap refresh rate to hundreths place on X11 commit 01c1667836dde9338a1fd4fa9425aca815a277a3 Author: Hugo Locurcio Date: Sun Feb 6 12:33:00 2022 +0100 Recommend using GitHub attachments for minimal reproduction projects Third-party file hosts can have their files expire or be removed by the owner. In comparison, GitHub attachments are more resilient. This also fixes the link to create a bug report. commit 729c1f056b2bb0b2101452172596d556f75f73fe Author: NeilKleistGao Date: Sun Feb 6 18:32:55 2022 +0800 Edit font properties on multiple objects at once commit fd0d2dcabf5b7418691b693cd01baecbb69fdeb9 Merge: 9d1626b4d7 a2484c3293 Author: Rémi Verschelde Date: Sun Feb 6 11:18:20 2022 +0100 Merge pull request #57694 from Chaosus/shader_fix-for_loop Fix unknown identifier error in for loop commit a2484c329313d7b1ade0cb8f84c7dc4c57e37a4d Author: Yuri Roubinsky Date: Sun Feb 6 08:17:25 2022 +0300 Fix unknown identifier error in for loop commit 871b9fc352b89f0471c9c549c0421beaa8540056 Author: FireForge Date: Sat Feb 5 12:28:16 2022 -0600 Fix node types in image drag-and-drop and add node icons commit ab5b5e15771f8167985f6ae05a85278802d5a55f Author: kobewi Date: Sun Feb 6 00:26:28 2022 +0100 Rework CanvasItem visibility propagation commit 60d8df3feefd07b955c4b083ad7ab27d90b49fa4 Author: clayjohn Date: Sat Feb 5 15:03:39 2022 -0800 Optimize and fix backbuffer gaussian blur commit 9d1626b4d7a867b4778845dd12519c603f0cf88f Merge: bd32dd4a48 0e659b4230 Author: Rémi Verschelde Date: Sat Feb 5 20:26:34 2022 +0100 Merge pull request #57017 from godotengine/string-name-static-false-unclaimed commit bd32dd4a48ba0008dc72c8dcd75f8d41bc510aba Merge: 5f42e0d0ab a6f34ea2d0 Author: Rémi Verschelde Date: Sat Feb 5 19:50:37 2022 +0100 Merge pull request #56943 from V-Sekai/override-import Make add_importer and add_post_importer_plugin override existing importers. commit 5f42e0d0abad7b2c6d90e56cbc2064b8444e2a07 Merge: eb4b4a317b 347d2dfc42 Author: Rémi Verschelde Date: Sat Feb 5 18:26:04 2022 +0100 Merge pull request #57646 from Faless/mp/4.x_interfaces [Net] Move RPC, Node cache out of MultiplayerAPI. commit eb4b4a317b4aa4e35dcf0749cdcacaa958e0498a Merge: feb34dfb2e 1ec96bc206 Author: Rémi Verschelde Date: Sat Feb 5 18:23:11 2022 +0100 Merge pull request #57649 from Faless/net/4.x_ws_queue_hostres [Net] Non-blocking WebSocket hostname resolution. commit feb34dfb2e4ed748f9ea672e24cff69d1bfe1cd1 Merge: eac1883791 012809d8ae Author: Rémi Verschelde Date: Sat Feb 5 16:09:52 2022 +0100 Merge pull request #57385 from madmiraal/update-mouse-pointer-definitions Update definitions of get_mouse_position methods commit eac1883791020930c2f5a3e1ca67ea5cae0cd17d Merge: aecff478b7 9963c4f0d3 Author: Rémi Verschelde Date: Sat Feb 5 16:08:41 2022 +0100 Merge pull request #57657 from lawnjelly/err_macros_flush Add fflush to error macros commit aecff478b71ccc4b8a6017ea896563fb0a95c002 Merge: 69d7d1ec52 189dab2d76 Author: Rémi Verschelde Date: Sat Feb 5 16:05:53 2022 +0100 Merge pull request #48006 from KoBeWi/incognito_layer Add visibility to CanvasLayer commit 69d7d1ec52c610a618265e48f26bd3d4bb1a1083 Merge: dd9426d14b 74adf0bf2e Author: Rémi Verschelde Date: Sat Feb 5 14:38:56 2022 +0100 Merge pull request #57655 from reduz/remove-get-rid-by-index Remove RID_Owner.get_rid_by_index commit dd9426d14b5d37616b54f3e94fc96745f80b3fae Merge: df1724470d 8acc8838c4 Author: Rémi Verschelde Date: Sat Feb 5 14:36:33 2022 +0100 Merge pull request #56503 from gerhean/Add-shortcut_cell-double-click-functionality Add shortcut_cell double click functionality commit 189dab2d765dcc9ad74e7392fa0b239ea70a9513 Author: kobewi Date: Sun Apr 18 20:49:21 2021 +0200 Add visibility to CanvasLayer commit 9963c4f0d328600a39646479e7b8809694f3108a Author: lawnjelly Date: Sat Feb 5 12:31:54 2022 +0000 Add fflush to error macros CRASH_NOW macros would previously crash before outputting any error messages. This PR ensures calling fflush for stdout before terminating. commit 8acc8838c4b95e645a61b417e8a89fd14990a555 Author: Ger Hean Date: Wed Jan 5 12:03:52 2022 +0800 Add shortcut_cell double click functionality commit 74adf0bf2e171353c33cd47ed67ec5ee040a284a Author: reduz Date: Sat Feb 5 11:59:34 2022 +0100 Remove RID_Owner.get_rid_by_index * Implementing this function efficiently is not really possible. * Replaced by an option to get all RIDs into a buffer for performance. commit 31824420e4cf3a30eb35334c43cb8393af585346 Author: Stijn Hinlopen Date: Sun Jul 5 20:06:51 2020 +0200 Center when scrolling to tree item. commit df1724470d1cff4f67aeb4c0d039114373aeb001 Merge: 2e44778cd2 419b342a9a Author: Rémi Verschelde Date: Sat Feb 5 10:28:07 2022 +0100 Merge pull request #49775 from fire/faster-cvtt Faster CVTT by lowering default quality commit 2e44778cd2f3d004bac80c3cd3935a8835936e2c Merge: eb371dee01 f86ab4031a Author: Rémi Verschelde Date: Sat Feb 5 10:18:21 2022 +0100 Merge pull request #57635 from jmb462/fix-template-optionbutton Fix OptionButton in create script dialog doesn't select the correct template commit eb371dee01674c3cf5acc61daeb1f817ae7dc00b Merge: 2511d63215 0c0ff5da50 Author: Rémi Verschelde Date: Sat Feb 5 10:15:38 2022 +0100 Merge pull request #57651 from theoniko/theoniko-effects_rc.cpp Fix copy paste bug in renderer_rd/effects_rd.cpp commit 2511d6321517cc6a50e89fa2b2b1e53ea57139fc Merge: a0c87d4c11 e01d08159c Author: Rémi Verschelde Date: Sat Feb 5 10:15:16 2022 +0100 Merge pull request #57614 from Chaosus/shader_for_fixes Few more fixes to for loop in shaders commit a0c87d4c114002ead8362ef1fa3a647282a725de Merge: 9fd095011e 1305ff92f7 Author: Rémi Verschelde Date: Sat Feb 5 10:14:10 2022 +0100 Merge pull request #57620 from Haydoggo/expression-exp-fix Fix Expression's parsing of positive exponent literals commit 9fd095011ed27c7f6bd7f20cf6f18025cb8b1971 Merge: c5a832e087 70da14db68 Author: Rémi Verschelde Date: Sat Feb 5 10:11:13 2022 +0100 Merge pull request #57639 from Sauermann/fix-onready-docs Add @ to onready annotated variables in docs commit c5a832e0873e5d6c722f7fddb3fc206d00d9c7f1 Merge: 8b5d6d4cb2 e714f5e56e Author: Rémi Verschelde Date: Sat Feb 5 10:10:38 2022 +0100 Merge pull request #57648 from KoBeWi/shrunken_tb Rework TextureButton stretch commit f86ab4031a8d5044ecf2ba79dc40bf6906156a48 Author: Jean-Michel Bernard Date: Fri Feb 4 19:08:10 2022 +0100 Fix template OptionButton in create script dialog doesn't select the correct template commit e01d08159cc21ef1b7fde5990804444cbbba1417 Author: Yuri Roubinsky Date: Fri Feb 4 08:40:42 2022 +0300 Few more fixes to for loop in shaders commit 0c0ff5da50645bb625421e49dae5496fec16fa98 Author: theoniko Date: Sat Feb 5 06:41:30 2022 +0100 Fix copy paste bug in renderer_rd/effects_rd.cpp commit 1305ff92f79f3ae4d84e8a92f5caedda94e076cb Author: Hayden Date: Sat Feb 5 00:14:49 2022 +1300 Make parser treat all exponent literals as float commit 8b5d6d4cb28789e7ebacdf5adee9ec77bd34f3e8 Merge: d091995e05 3acc39095e Author: Fabio Alessandrelli Date: Sat Feb 5 04:32:47 2022 +0100 Merge pull request #48329 from Faless/net/4.x_file_access_network [Net] Fix bogus FileAccessNetwork deconstructor. commit 3acc39095ea668dc4fe2b2f907a8cdec4be3a6f9 Author: Fabio Alessandrelli Date: Fri Apr 30 15:52:29 2021 +0200 [Net] Fix bogus FileAccessNetwork deconstructor. Now correctly erases old instances. The code will likely need overhaul anyway to be usable. It doesn't apply to editor runs, there's a bunch of inconsistencies on how to clients are handled, and I don't really understand why multiple instances are created for a single client/server. commit 1ec96bc2063579f8e2f73944974cadcb15e348b0 Author: Jordan Schidlowsky Date: Sat Jan 29 00:29:27 2022 +0100 [Net] Non-blocking WebSocket hostname resolution. Hostname is now resolved during poll in WebSocketClient (wslay) to avoid blocking during connect. An attempt is still made to find the hostname in the resolver cache. commit e714f5e56e1c581e448ee683b2f1a48e9edd0e05 Author: kobewi Date: Sat Feb 5 02:11:32 2022 +0100 Rework TextureButton stretch commit 347d2dfc42967c14daced64706ad6db0b3ebf9b5 Author: Fabio Alessandrelli Date: Fri Feb 4 16:24:16 2022 +0100 [Net] Move RPC, Node cache out of MultiplayerAPI. Now uses two interfaces so it can be overridden in the future, and core no longer depends on Node. The interfaces are implements in scene/multiplayer. Replaces root_node with root_path. Remove all Node references from MultiplayerAPI. commit 70da14db688cc750fdb12ea0f262dcf39bc8e42d Author: Markus Sauermann <6299227+Sauermann@users.noreply.github.com> Date: Fri Feb 4 19:52:43 2022 +0100 Add @ to onready annotated variables in docs commit d091995e058e3b143ed44f484a05058c856922cc Merge: 992794e44a 54b47b8fa6 Author: Rémi Verschelde Date: Sat Feb 5 00:58:43 2022 +0100 Merge pull request #57518 from pycbouh/docs-theme-refinement commit 419b342a9a716426159f7a51ae17390386ecc884 Author: K. S. Ernest (iFire) Lee Date: Tue Jan 18 04:39:55 2022 -0800 Faster CVTT by reducing quality. Make BC6 and BC7 CVTT faster while still having better quality than DXT5. commit 992794e44a47a7adddce589bd68ad71402d5ba66 Author: Rémi Verschelde Date: Fri Feb 4 22:26:16 2022 +0100 CI: Force invalidate macOS cache I *hate* having to commit this kind of noise to our commit history. Especially on a Friday at 10 pm. commit 3db1d689ce11507d9597bb434891da95168f2c69 Merge: 77d08b68b0 64e53cdc55 Author: Rémi Verschelde Date: Fri Feb 4 19:03:44 2022 +0100 Merge pull request #57631 from groud/terrain_fix_with_empty_cells commit 77d08b68b06b5b821d90ac561c261c93b4bc5928 Merge: 2885befbe6 de45534fed Author: Rémi Verschelde Date: Fri Feb 4 19:03:28 2022 +0100 Merge pull request #49445 from Calinou/gdscript-highlight-namespace-reserved-keyword commit 2885befbe6891ab20606dabdfb6f62184156f477 Merge: 84290fe4b1 c971316d88 Author: Rémi Verschelde Date: Fri Feb 4 19:03:10 2022 +0100 Merge pull request #55950 from Faless/mp/4.x_replication_nodes commit 84290fe4b1a51cae21bcfb83b6c6d4ecc4392186 Merge: 61c57491bc 5ddb518496 Author: Rémi Verschelde Date: Fri Feb 4 19:02:57 2022 +0100 Merge pull request #57623 from akien-mga/core-math-struct-em-all commit 61c57491bc346aef567990c293deaa1706f6afca Merge: 8495be9cec e223bad86d Author: Rémi Verschelde Date: Fri Feb 4 19:00:39 2022 +0100 Merge pull request #57625 from akien-mga/core-split-vector2i-own-header commit 54b47b8fa6c8d26351b531d63131ea41e3772164 Author: Yuri Sizov Date: Tue Feb 1 18:43:53 2022 +0300 Refine Theme documentation to make it more factual commit 64e53cdc552bada4c93e2e3879a6eff1fd5da8d6 Author: Gilles Roudière Date: Fri Feb 4 18:10:12 2022 +0100 Fixes terrain painting on TileMaps when using empty terrain bits commit de45534fed49523211ee234185f101f4e5ec80ff Author: Hugo Locurcio Date: Wed Jun 9 01:19:16 2021 +0200 Highlight "namespace" as a GDScript keyword in the syntax highlighter Like "trait" and "yield", "namespace" is currently not implemented but is still reserved for future use. commit 5ddb51849666759f1ad172eb999791663501aaee Author: Rémi Verschelde Date: Fri Feb 4 14:30:17 2022 +0100 Core: Make all Variant math types structs Some were declared as structs (public by default) and others as classes (private by default) but in practice all these math types exposed as Variants are all 100% public. commit 21b9f1ecfeeefeeed51cc83a102ee149d8e93c06 Author: Hugo Locurcio Date: Fri Feb 4 16:36:19 2022 +0100 Rename 3D editor debug draw modes to be more explicit - Rename "Directional Shadow" to "Directional Shadow Map" to distinguish it from the "Directional Shadow Splits" option. - Rename "Disable LOD" to "Disable Mesh LOD" as it only affects automatic mesh LOD, not visibility ranges. - Rename "GI Buffer" to "VoxelGI/SDFGI Buffer" as it doesn't cover LightmapGI or SSIL. - Rename the cluster options to match the respective node names. commit e223bad86d6e5225aa205394d047714a92610967 Author: Rémi Verschelde Date: Fri Feb 4 15:35:14 2022 +0100 Core: Move Vector2i to its own `vector2i.h` header Also reduce interdependencies and clean up a bit. commit 8495be9cece924b22a8148ce335d04836027bc40 Merge: 721c32ee2b 5f56d385b0 Author: Rémi Verschelde Date: Fri Feb 4 16:05:30 2022 +0100 Merge pull request #57621 from akien-mga/core-split-rect2i-own-header commit 012809d8ae75df43629bbf7e1233a2c82ed5edb8 Author: Marcel Admiraal Date: Fri Jan 28 15:19:07 2022 +0000 Update definitions of get_mouse_position methods commit 721c32ee2bb15ca16692dee848fc3190ed0b6a49 Merge: 89eb6d372d 93e2d0446f Author: Ignacio Roldán Etcheverry Date: Fri Feb 4 15:05:51 2022 +0100 Merge pull request #57618 from Densorius/master Fixed opening new instances of VS 2022 while a instance is already open commit c971316d887eb2406ef86106c158845a9a6b76ba Author: Fabio Alessandrelli Date: Wed Dec 1 08:24:46 2021 +0100 [Editor] Replication plugin to configure MultiplayerSynchronizers. Allows configuring the MultiplayerSynchornizer in a way similar to AnimationPlayer. Properties are added manually, edither as plain properties, or via the NodePath format for child nodes' properties "path/to/node:property" relative to the MultiplayerSynchronizer root path. Nice things to add would be: - Moving properties up/down in the list. - Some form of keying, autmatic filling of the replication properity line edit. commit d219547c96ce66a6f54d9d9d7ae431e9b115221f Author: Fabio Alessandrelli Date: Fri Oct 8 14:13:06 2021 +0200 [Net] New replication interface, spawner and synchronizer nodes. Initial implementation of the MultiplayerReplicationInterface and its default implementation (SceneReplicationInterface). New MultiplayerSpawner node helps dealing with instantiation of scenes on remote peers (e.g. clients). It supports both custom spawns via a `_spawn_custom` virtual function, and optional auto-spawn of known scenes via a TypedArray property. New MultiplayerSynchornizer helps synchronizing states between the local and remote peers, supports both sync and spawn properties and is configured via a `SceneReplicationConfig` resource. It can also sync via path (i.e. without being spawned by a MultiplayerSpawner if both peers has it in tree, but will not send the spawn state in that case, only the sync one. commit 5f56d385b04f4054ec86605fcda56ffeed4ca5f4 Author: Rémi Verschelde Date: Fri Feb 4 13:28:02 2022 +0100 Core: Move Rect2i to its own `rect2i.h` header And take the opportunity to improve interdependencies a bit with forward declares where possible. commit 89eb6d372d83f0e79ff7bcc78c304a37bed72abe Merge: 225a3b2545 ceafdf347e Author: Rémi Verschelde Date: Fri Feb 4 13:49:15 2022 +0100 Merge pull request #57591 from vnen/gdscript-enum-fixes commit 225a3b2545691148dbcb2acf94e4fada024e809d Merge: 2e320dcf87 f4ea9cd9f3 Author: Rémi Verschelde Date: Fri Feb 4 13:28:56 2022 +0100 Merge pull request #57341 from bruvzg/win_multiwin_fs commit 2e320dcf8796d77b6196ef93d4ea304bf5bcb3d4 Merge: d235c1bb19 244db37508 Author: Rémi Verschelde Date: Fri Feb 4 13:06:38 2022 +0100 Merge pull request #57617 from bruvzg/char_cleanup commit d235c1bb1964b80d776a64aa2a1b198a8e52bf72 Merge: 85f6151e9d 54dec44dba Author: Rémi Verschelde Date: Fri Feb 4 11:51:07 2022 +0100 Merge pull request #57335 from jordigcs/display-refresh-rate commit 85f6151e9d4ce9ecc4fe587cc6e76356b49e8a43 Merge: 29c4644890 d14165dae9 Author: Rémi Verschelde Date: Fri Feb 4 11:48:57 2022 +0100 Merge pull request #54645 from rxlecky/editor-window-offset-bug-45740 commit f4ea9cd9f301d3af3a707e330221a4f1e242e7be Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Fri Jan 28 11:19:53 2022 +0200 [Windows] Add WS_BORDER flag to windows in WINDOW_MODE_FULLSCREEN mode to allow multi-window interface in full-screen. [Windows] Add WINDOW_MODE_EXCLUSIVE_FULLSCREEN without WS_BORDER flag enabled (no multi-window support). commit 29c4644890c82f38f54553fa127a9661e7a0c75a Merge: 2a3c4f00c8 45a32f21ee Author: Rémi Verschelde Date: Fri Feb 4 11:01:41 2022 +0100 Merge pull request #57086 from YeldhamDev/scene_tabs_fix commit 93e2d0446fb7b3eb4dbf875a488b8506fb1e73ff Author: Densorius Date: Fri Feb 4 10:38:28 2022 +0100 Fixed opening new instances of VS 2022 while a instance is already open commit 244db375087440888ca5b86fd0d114a54f41489a Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Fri Feb 4 10:32:20 2022 +0200 Cleanup and move char functions to the `char_utils.h` header. commit 2a3c4f00c892dbee388cda69239285df3e0a41b5 Merge: b68db2f98a fbd9599b04 Author: Rémi Verschelde Date: Fri Feb 4 10:13:29 2022 +0100 Merge pull request #57541 from reduz/node-add-remove-hook commit b68db2f98aafa9550d683c20a675650613fbc6a6 Merge: c24fc415dc 018de19eba Author: Rémi Verschelde Date: Fri Feb 4 09:30:50 2022 +0100 Merge pull request #57571 from Haydoggo/improved-expression commit b966ca6167beefc470c3469665200b542b1ea58a Author: Mai Lavelle Date: Thu Feb 3 22:56:43 2022 -0500 Improve detection of gamepads on Linux Some devices (Nintendo Switch Right Joy-Con) report only a right stick. commit 54dec44dbae4859842c6d99aafaafd186b33fee4 Author: jordi Date: Thu Jan 27 13:46:57 2022 -0600 Add screen_get_refresh_rate to DisplayServer commit c24fc415dc703c65cf6b556dca90adb50915e7dc Merge: cfa2bfca4e 9ea0508d35 Author: Ignacio Roldán Etcheverry Date: Fri Feb 4 03:47:11 2022 +0100 Merge pull request #57609 from Densorius/master Add Visual Studio 2022 support with fallback to 2019 commit 9ea0508d35fa747b81b327e41969f926633d768f Author: Densorius Date: Fri Feb 4 00:06:53 2022 +0100 Add Visual Studio 2022 support with fallback to 2019 commit cfa2bfca4eb03dbbdf8bd107db01aeac8cf646a1 Merge: f8f19b313d 6d3d17651a Author: Rémi Verschelde Date: Fri Feb 4 00:15:18 2022 +0100 Merge pull request #57598 from Faless/js/4.x_fix_config_regression commit 018de19ebade24d72ae7ba39fdcfeffbff99fa88 Author: Hayden Leete Date: Thu Feb 3 14:52:53 2022 +1300 Added hex and bin literal support to Expression parser fixed formatting commit f8f19b313d62b707467c54d245b9c3e0ad53f34f Merge: 025e778020 adbe948bda Author: Rémi Verschelde Date: Thu Feb 3 22:21:24 2022 +0100 Merge pull request #57562 from AnilBK/string-add-contains String: Add contains(). commit 025e778020dde6dcee89f5ae1e2a63ccddc24340 Merge: c47f059776 d5d05386a6 Author: Rémi Verschelde Date: Thu Feb 3 22:17:25 2022 +0100 Merge pull request #57175 from fire-forge/add-type-icons Add type icons to Project Settings, Array, and Dictionary editors commit eb24c9104014f7d26676e712453dbc5fbea5f7ff Author: Sergey Minakov Date: Tue Jan 11 13:56:45 2022 +0300 [iOS] Fix touch handling for overlay views Workaround for GodotView touches being called from UIWindow on different UIView input commit adbe948bda202209b55249198e1837324e703ddb Author: Anilforextra Date: Thu Feb 3 21:48:38 2022 +0545 String: Add contains(). commit c47f0597763c13793821adf1eb7352acde704520 Merge: 7191605324 466661c78f Author: Rémi Verschelde Date: Thu Feb 3 20:41:29 2022 +0100 Merge pull request #57467 from webbuf/modules-initialize Initialized Member Variables in /modules commit 7191605324fe04d16ffaf30c31ec808624cc17a2 Merge: bf0253bab9 ddd96b3059 Author: Rémi Verschelde Date: Thu Feb 3 20:36:59 2022 +0100 Merge pull request #57587 from bruvzg/gde_fix_ptr_and_enum_returns [GDExtension] Fix registration of functions with enum or native pointer return type. commit d5d05386a658875bf3fff908ceb879158d1a1c2f Author: fire540 Date: Mon Jan 24 18:16:05 2022 -0600 Add type icons to Project Settings, Array, and Dictionary editors commit 6d3d17651a6e264d47126b2dd5e641b3fa6ba3f7 Author: Fabio Alessandrelli Date: Thu Feb 3 19:10:08 2022 +0100 Revert "[HTML5] Better engine config parsing." This reverts commit 2f509f1b12c33234a0d8f0e254c727fd92e57720. Breaks closure compiler builds. And adds a warning for future readers. commit bf0253bab936f053bb5ae56e6fcf67defaf9afbc Merge: ffc828ac50 5c3600b29f Author: Rémi Verschelde Date: Thu Feb 3 18:10:30 2022 +0100 Merge pull request #56764 from madmiraal/fix-45592-2 commit ffc828ac50a54b177caf99ff44c385b459827335 Merge: 5e39a8eded ebe9495b7d Author: Rémi Verschelde Date: Thu Feb 3 17:51:16 2022 +0100 Merge pull request #57582 from akien-mga/editorproperty-range-fix-step commit ceafdf347e5ecc050629fd4eac93030dabb0d0e9 Author: George Marques Date: Wed Feb 2 13:57:24 2022 -0300 GDScript: Treat enum values as int and enum types as dictionary Since enums resolve to a dictionary at runtime, calling dictionary methods on an enum type is a valid use case. This ensures this is true by adding test cases. This also makes enum values be treated as ints when used in operations. commit b013c0d544e784392020aa693fd248fa52f450c2 Author: George Marques Date: Tue Feb 1 21:31:12 2022 -0300 GDScript: Allow tests to run on release builds - Fix compilation issues by disabling warnings on release builds. This also strips warnings from expected result before the comparison to avoid false mismatches. - Add a `#debug-only` flag to tests. Must be the first line of the test script. Those won't run with release builds. Can be used for test cases that rely on checks only available on debug builds. commit ad6e2e82a9e2f7e6f6db99a7be474a1f2f2739bf Author: George Marques Date: Thu Jan 27 11:34:33 2022 -0300 GDScript: Consolidate behavior for assigning enum types This makes sure that assigning values to enum-typed variables are consistent. Same enum is always valid, different enum is always invalid (without casting) and assigning `int` creates a warning if there is no casting. There are new test cases to ensure this behavior doesn't break in the future. commit 5e39a8eded8e3eba84ab9c74d31ba4aca6a235d2 Merge: 3004415bfc 73c225838f Author: Rémi Verschelde Date: Thu Feb 3 17:15:06 2022 +0100 Merge pull request #56992 from YeldhamDev/smarter_popmenu_focus commit 466661c78fe254ba8ff2fe0d90183c66874aabf2 Author: zwebb Date: Mon Jan 31 12:42:32 2022 -0500 initialized member variables in header commit 3004415bfc1cf53879729a3c5dd4185c51496973 Merge: 45d5aa5d47 71fb89390f Author: Rémi Verschelde Date: Thu Feb 3 17:04:49 2022 +0100 Merge pull request #57565 from jmb462/split_offset commit 45d5aa5d47b2a54c8a2bd50ef95b6a3151bfa24e Merge: c0daec389d 8cfd264148 Author: Rémi Verschelde Date: Thu Feb 3 16:59:07 2022 +0100 Merge pull request #56365 from aaronfranke/default-shape-size commit c0daec389d654637885b7c1f1a650fa0f8cd4858 Merge: 6acbd5f774 ead6f67670 Author: Rémi Verschelde Date: Thu Feb 3 16:09:04 2022 +0100 Merge pull request #57589 from bruvzg/rtl_autowrap commit 6acbd5f77449dacbbe4989c99e63fbf67e6e29ea Merge: c4f38813b4 339dcd80ae Author: Rémi Verschelde Date: Thu Feb 3 15:16:57 2022 +0100 Merge pull request #57102 from akien-mga/libwebp-1.2.2 commit c4f38813b4e9d14f59fc43f66f24307962eb4fc1 Merge: 17d33c0530 2eeff4caec Author: Rémi Verschelde Date: Thu Feb 3 15:16:46 2022 +0100 Merge pull request #57577 from bruvzg/mac_export_tr commit ead6f67670961f1760e543803e4971b5ea214a0c Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Thu Feb 3 15:56:44 2022 +0200 Add auto-wrap mode property to the RichTextLabel, set default auto-wrap mode to AUTOWRAP_WORD_SMART to match 3.x behavior. commit ddd96b30594eb06de7d2f9a6fe3fcb10709e1c81 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Thu Feb 3 15:03:51 2022 +0200 [GDExtension] Fix registration of functions with enum or native pointer return type. commit 17d33c0530fbd01404dced47a51be97cd73c87aa Merge: 7f93eb34cf 58696fd774 Author: Rémi Verschelde Date: Thu Feb 3 14:54:59 2022 +0100 Merge pull request #57579 from Chaosus/shader_better_for_loop commit 7f93eb34cf4b4e900aa9ba185b009fcb409d5d1c Merge: e165f18ae5 73e784de1e Author: Rémi Verschelde Date: Thu Feb 3 13:35:19 2022 +0100 Merge pull request #57581 from groud/remove_get_fowus_owner commit ebe9495b7d4ac0095cdb703816d7743c684d4c3d Author: Rémi Verschelde Date: Thu Feb 3 11:57:32 2022 +0100 EditorProperty: Fix range hint parsing with optional step This could lead to have a step of 0 when parsing e.g. "1,10,is_greater". commit e165f18ae532ddf118b19bdd439f4ac6c226903a Merge: 36880714e4 1cf2b9a44b Author: Rémi Verschelde Date: Thu Feb 3 12:36:23 2022 +0100 Merge pull request #57350 from NeilKleistGao/master commit 73e784de1e95cfdf775e00e608114f5295813060 Author: Gilles Roudière Date: Thu Feb 3 11:59:32 2022 +0100 Remove get_focus_owner() from Control, replaced by get_viewport()->gui_get_focus_owner() commit 36880714e49f0f3662f7da9d1e2e4719f03fab53 Merge: 309b9d3301 3521eecb4c Author: Rémi Verschelde Date: Thu Feb 3 11:20:20 2022 +0100 Merge pull request #57517 from groud/viewport_expose_gui_focus commit 309b9d33019dbb2fdf7111c3b81ebabab7aa6914 Merge: bab1ac6dcb b30c566c19 Author: Rémi Verschelde Date: Thu Feb 3 10:24:12 2022 +0100 Merge pull request #57575 from timothyqiu/doc-stream-peer-buffer commit bab1ac6dcb43a7ecb169cd74f38b4d998e900871 Merge: 98d8c9acd7 ac4fb2996b Author: Rémi Verschelde Date: Thu Feb 3 10:06:13 2022 +0100 Merge pull request #57570 from Faless/net/4.x_http_client_req_noblock commit 98d8c9acd7ff234c23cb0902e8f7fb7278abddd5 Merge: 6de5bafd2f 3dc1fad262 Author: Max Hilbrunner Date: Thu Feb 3 09:51:09 2022 +0100 Merge pull request #57568 from TechnicalSoup/TechnicalSoup-patch-1 Expand description for warp_mouse_position method commit 58696fd77458bfbbe42d36ce8ff9455de298dc64 Author: Yuri Roubinsky Date: Thu Feb 3 11:13:20 2022 +0300 Allow multiple declarations in for loop in a shader commit 1cf2b9a44bd778bcbb1517e2abde4517ef5d4a73 Author: NeilKleistGao Date: Thu Feb 3 16:25:00 2022 +0800 Add warning for Windows export when rcedit is not configured commit b30c566c190162da565ccd7dc58e175301043944 Author: Haoyu Qiu Date: Thu Feb 3 13:45:17 2022 +0800 Add documentation for StreamPeerBuffer commit 2eeff4caec00a9eec06919d35c3ea8d87322cd31 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Thu Feb 3 09:43:11 2022 +0200 [macOS] Add empty translation files to the exported app bundle, to allow translation detection by the OS. commit ac4fb2996bc54302b8e1cf9b9cc47ae443808b31 Author: Fabio Alessandrelli Date: Thu Feb 3 01:33:15 2022 +0100 [Net] Non-blocking request in HTTPClientTCP. HTTPClientJavaScript already supports non-blocking requests. commit 3dc1fad2623b01bd8c8e5a83dfe198a8a2b0394e Author: TechnicalSoup Date: Thu Feb 3 10:11:25 2022 +1100 Expand description for warp_mouse_position method Add more detail to the description for the warp_mouse_position method, clarifying that the vector is in screen coordinates and relative to an origin at the top of the game window. commit 339dcd80aed70d025b98f943e2a57767701a84f1 Author: Rémi Verschelde Date: Sun Jan 23 23:01:49 2022 +0100 libwebp: Sync with upstream 1.2.2 Changes: https://chromium.googlesource.com/webm/libwebp/+/1.2.2/NEWS commit 6de5bafd2fa7513cf78377b61c2606327b293d44 Merge: 969780cd2d 9f0a693b50 Author: Rémi Verschelde Date: Wed Feb 2 23:19:30 2022 +0100 Merge pull request #57547 from akien-mga/editorhelp-tooltip-set-fit_content_height commit 969780cd2dc9c6d35a5f300281d142b835a7a8af Merge: 5f3f0b5e00 57db989a97 Author: Rémi Verschelde Date: Wed Feb 2 23:14:30 2022 +0100 Merge pull request #57203 from bruvzg/ios_export_fix commit 71fb89390f0011a6c758b47c419a0ec7a27aa240 Author: jmb462 Date: Wed Feb 2 22:35:40 2022 +0100 Save script editor's function list split offset with the editor layout commit 5f3f0b5e00a2e692a282d087321b647f1a274315 Merge: 7be7623d69 5676b3c022 Author: Rémi Verschelde Date: Wed Feb 2 22:17:43 2022 +0100 Merge pull request #57563 from bruvzg/hb331 commit 9f0a693b50de9f9b28f870bf09b60eb269500336 Author: Rémi Verschelde Date: Wed Feb 2 14:10:15 2022 +0100 EditorHelpBit: Fix content height fit and RTL theme propagation This reverts #51619 and fixes the issue properly, as well as enabling `fit_content_height` which is necessary following #57304. Fixes #57174. Also adds a placeholder for property and signal tooltips with no description, factoring the code while at it. Co-authored-by: bruvzg <7645683+bruvzg@users.noreply.github.com> commit 7be7623d693a4328d8ef1947e16ff3d1d70daa65 Merge: ca42bfb2a5 2f1e7c28a4 Author: Rémi Verschelde Date: Wed Feb 2 21:55:08 2022 +0100 Merge pull request #57494 from Geometror/project-and-editor-settings-fixes commit 5676b3c022874c6a636073792e4be5ee3abd170d Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Wed Feb 2 16:04:28 2022 +0200 HarfBuzz: Update to version 3.3.1 commit ca42bfb2a5c91a52ea14302aa3bbf7292e387b9b Merge: 6ff753675a 59af063636 Author: Yuri Roubinsky Date: Wed Feb 2 21:12:45 2022 +0300 Merge pull request #57504 from Chaosus/vs_vector2 commit 6ff753675a1c6f86ece68905bdedbf5e81712800 Merge: 68e62fb5cd 3ef5a97505 Author: Fabio Alessandrelli Date: Wed Feb 2 18:28:30 2022 +0100 Merge pull request #56771 from mhilbrunner/unacceptable Verify custom HTTP headers, fix off by one error commit 59af063636b3e17df488ad0d9e059919aa03fcf1 Author: Yuri Roubinsky Date: Tue Feb 1 11:32:01 2022 +0300 Add support for 2D vector type to visual shaders commit 68e62fb5cde0891416e0eed5f4e3f9e9d5f63d82 Merge: c8ee8082f4 bb7d003881 Author: Max Hilbrunner Date: Wed Feb 2 16:31:26 2022 +0100 Merge pull request #57540 from mhilbrunner/docs-object-set DOCS: Object.set() does nothing on type mismatch commit c8ee8082f49f4051d7143e24961c45c56910997a Merge: bf12719cca 2ea08134c3 Author: Max Hilbrunner Date: Wed Feb 2 15:22:38 2022 +0100 Merge pull request #57531 from Calinou/doc-area-overlaps Clarify Area2D/Area3D `overlaps_area()`/`overlaps_body()` documentation commit bf12719ccabcea9bf9b274f77511e02581678774 Merge: 2daa3ae1fd 51b5b51653 Author: Rémi Verschelde Date: Wed Feb 2 11:51:07 2022 +0100 Merge pull request #57524 from Sauermann/fix-display-grid-fadeout commit 2daa3ae1fd2e235eb5437ec168efa42d9335ea45 Merge: 050908626f 215bede6ff Author: Rémi Verschelde Date: Wed Feb 2 11:39:54 2022 +0100 Merge pull request #57511 from bruvzg/ts_font_change Improve performance of the font change. commit fbd9599b04086faefbf9796c41869dddbb6abf37 Author: reduz Date: Wed Feb 2 11:22:11 2022 +0100 Add a signal to notify when children nodes enter or exit tree -Allows more fine grained notifications (hence better performance) than using the global scene tree signals (node added and removed). -Required for #55950 commit bb7d00388106920393e6140e9d26f3c047e12433 Author: Max Hilbrunner Date: Wed Feb 2 11:24:47 2022 +0100 DOCS: Object.set() does nothing on type mismatch commit 215bede6ff494bb371fa610b6d003fe1cb7d1c7d Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Thu Jan 20 09:30:42 2022 +0200 [TextServer] Add function to change font, font size, and OpenType features without invalidating line break points, justification points, or recreating shaped text buffer. commit 050908626f64c0c984e078055215c8b5f6231ce3 Merge: 232bf54a68 c37bd41c79 Author: Rémi Verschelde Date: Wed Feb 2 10:16:24 2022 +0100 Merge pull request #57526 from tavurth/bugfix/high-macos-cpu-usage commit 232bf54a6875d16653e6913355a72d55cb9daf08 Merge: 7ed7bf1fa7 69e30d91ee Author: Rémi Verschelde Date: Wed Feb 2 09:07:59 2022 +0100 Merge pull request #57537 from noidexe/fix-theora-video-playback commit 7ed7bf1fa7454655776b71f4c78605024111cc0a Merge: b5707400eb eaa70fd3f8 Author: Rémi Verschelde Date: Wed Feb 2 07:58:49 2022 +0100 Merge pull request #48156 from madmiraal/fix-46438 Fix `mouse_over` not dropped when mouse leaves window commit 69e30d91eed2310d2bf25041e84ec3a0277b7971 Author: Lisandro Lorea Date: Tue Feb 1 23:54:36 2022 -0300 Fix "texture not initialized" error preventing video from playing Closes #57153 commit 2ea08134c3e3f73e506442fc0ac3897f5943c909 Author: Hugo Locurcio Date: Tue Feb 1 22:19:58 2022 +0100 Clarify Area2D/Area3D `overlaps_area()`/`overlaps_body()` documentation commit c37bd41c794819e0b6bc3ad4b162548057098e1c Author: Will Whitty Date: Tue Feb 1 21:53:32 2022 +0300 Increase RemoteDebuggerPeerTCP poll to 6.9ms Fix high CPU usage on MacOS by reverting the polling for Network debugging to match 144hz refresh rate. commit b5707400ebb3daa346fece54c119a5da70852647 Merge: dc4483a3a7 fc27636999 Author: Rémi Verschelde Date: Tue Feb 1 21:29:33 2022 +0100 Merge pull request #57525 from AnilBK/vector-use-clear-has commit dc4483a3a74a4cac83fc0387ea4d09066a0a0e1c Merge: ea12094f19 10e7977be3 Author: Rémi Verschelde Date: Tue Feb 1 19:59:06 2022 +0100 Merge pull request #57519 from Calinou/doc-rect2-has-no-area Clarify documentation for Rect2/Rect2i's `has_no_area()` commit fc27636999a15f88b1f3b1d7101d84a67968ba06 Author: Anilforextra Date: Wed Feb 2 00:04:13 2022 +0545 Vectors: Use clear() and has(). Use clear() instead of resize(0). Use has() instead of "find(p_val) != -1". commit 51b5b5165323093749d38a246bc515c578311b99 Author: Markus Sauermann <6299227+Sauermann@users.noreply.github.com> Date: Tue Feb 1 18:53:42 2022 +0100 Fix TileMap Display Grid fadeout commit 10e7977be38945da229754301767de039868dec4 Author: Hugo Locurcio Date: Tue Feb 1 17:58:24 2022 +0100 Clarify documentation for Rect2/Rect2i's `has_no_area()` commit eaa70fd3f836a995b54cd90a568ce9a5db8f94af Author: Marcel Admiraal Date: Sat Apr 24 13:03:38 2021 +0100 Fix mouse_over not dropped when mouse leaves window commit 45a32f21eeb2393fa5c3f68739ce504050305493 Author: Michael Alexsander Date: Sun Jan 23 12:42:52 2022 -0300 Fix buggy behavior of the "Add tab" button in the scene tabs commit 3521eecb4cf4a9bcf33a86da5c1e4e88075e1699 Author: Gilles Roudière Date: Tue Feb 1 15:07:22 2022 +0100 Exposes gui_release_focus and gui_get_focus_owner to Viewport commit b84ef16aa7ef7a871ccc3a06aca52fe820b47967 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Wed Jan 26 11:01:27 2022 +0200 [macOS] Cleanup and split Objective-C objects to the separate files commit 33d6d4bdf718c9d71e9e97339a5181ae53706880 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Tue Jan 25 10:07:01 2022 +0200 [macOS] Enable Objective-C ARC commit ea12094f19b028c1dcf6d402b8cbb3296b2065a8 Merge: d4e21b7d62 c058361a23 Author: Rémi Verschelde Date: Tue Feb 1 14:46:30 2022 +0100 Merge pull request #57497 from Geometror/fix-mouse-mode commit d4e21b7d62318e1552803e1e76d6013cd7a38075 Merge: f1bff648f2 7c3003fcbe Author: Rémi Verschelde Date: Tue Feb 1 14:24:47 2022 +0100 Merge pull request #57358 from akien-mga/signal-bindings-object commit f1bff648f25746dc6d41d7101fd56f4464985805 Merge: 6914a58f99 7072b359b4 Author: Rémi Verschelde Date: Tue Feb 1 14:24:35 2022 +0100 Merge pull request #57355 from akien-mga/method-bindings-clearer-types commit 6914a58f99ae0f7aaaa785706d7184c8d6cea373 Merge: dafadd73ac 34d382eab6 Author: Rémi Verschelde Date: Tue Feb 1 14:22:01 2022 +0100 Merge pull request #57376 from Calinou/gradienttexture2d-clamp-size commit 8cfd2641481652b2588596f863217022b129817a Author: Aaron Franke Date: Thu Dec 30 15:20:56 2021 -0800 Improve the default size for 3D shapes (Box, Capsule, and Cylinder) commit dafadd73ac133308a2ea1cfcd3a85e22a1a1a0aa Merge: 473f681651 b4f0d4c7db Author: Rémi Verschelde Date: Tue Feb 1 13:39:16 2022 +0100 Merge pull request #57375 from Calinou/gradienttexture-curvetexture-decrease-default-size commit 473f681651e5a4859d6e4c4a3cef2d5aa3d736e6 Merge: a1469bff19 5a1f42b322 Author: Rémi Verschelde Date: Tue Feb 1 12:19:53 2022 +0100 Merge pull request #57509 from akien-mga/windows-pck-embed-fpermissive commit 5a1f42b32220d750410ccc5f5f7c6a3408426294 Author: Rémi Verschelde Date: Tue Feb 1 11:33:32 2022 +0100 Windows: Fix GCC -fpermissive error with 'pck' section workaround Follow-up to #57450. commit a1469bff19294c2cfd5c103d1e80540a9d883dda Merge: 2aee84c755 56549a0195 Author: Rémi Verschelde Date: Tue Feb 1 10:42:44 2022 +0100 Merge pull request #57505 from akien-mga/ci-scripts-fix-exclude-pattern commit 56549a019594d68121ac17781f7450aa82a06db2 Author: Rémi Verschelde Date: Tue Feb 1 09:55:07 2022 +0100 CI: Fix exclude patterns with `git ls-files` Follow-up to #55785. In `black_format.sh`, the `--exclude` switch was wrongly used. It's a misnomer that only excludes _untracked_ files, arcane pathspec patterns should instead be used to exclude _tracked_ files. Using this newfound knowledge, we can also simplify the other scripts. commit 2aee84c755ccb5674b257d7b281bbfb7a3769f44 Merge: 8c7cd904f5 7b4635d9cd Author: Rémi Verschelde Date: Tue Feb 1 08:54:17 2022 +0100 Merge pull request #57495 from Sauermann/fix-remove-layer-doc Fix TileMap remove_layer Description commit c058361a238892b0384cd648f2a8de3a5d9622a6 Author: Hendrik Brucker Date: Tue Feb 1 03:49:51 2022 +0100 Fix captured mouse mode commit 7b4635d9cd8d3ea09877f40983466f7bc65b4e1d Author: Markus Sauermann <6299227+Sauermann@users.noreply.github.com> Date: Tue Feb 1 00:27:24 2022 +0100 Fix TileMap remove_layer description commit 2f1e7c28a48b8a36c0131cc0320e07859f9da8e8 Author: Hendrik Brucker Date: Tue Feb 1 00:19:01 2022 +0100 Minor fixes/refactoring of project and editor setting dialogs commit 8c7cd904f599b949972564ad13c3d4c1b016e977 Merge: ee6b4b5800 23a4fe5b27 Author: Rémi Verschelde Date: Tue Feb 1 00:05:02 2022 +0100 Merge pull request #57469 from Sauermann/fix-rect2i-intersect commit ee6b4b5800686e86bdb522ffa5df74028bf49dca Merge: 45553fd586 a30dd094d3 Author: Rémi Verschelde Date: Mon Jan 31 23:57:11 2022 +0100 Merge pull request #57492 from Scony/fix-navigation-transforms commit a30dd094d345fc7bf65bc10486b2797e19054d56 Author: Pawel Lampe Date: Sun Jan 30 11:30:17 2022 +0100 Fix transforms involved into navmesh baking Within the context of parsing navigation geometry, this commit: - added missing transform of `MultiMeshInstance` - changed all transforms to global ones so that they don't need to be calculated by hand commit 45553fd5868dd8733d7cab021f4f047360cace8e Merge: cc7d8817eb 64c4a5b283 Author: Rémi Verschelde Date: Mon Jan 31 21:46:07 2022 +0100 Merge pull request #56970 from YeldhamDev/rise_tabbar_rise commit cc7d8817eb70c7a66914bbae4168fbbf2b9f29be Merge: d9eeced580 65b6b140b6 Author: Rémi Verschelde Date: Mon Jan 31 19:58:49 2022 +0100 Merge pull request #57413 from fazil47/master commit 64c4a5b2833368a8ddafec69b62fe3742635a5e5 Author: Michael Alexsander Date: Wed Jan 19 13:11:44 2022 -0300 Bring `TabBar` to full parity with the `TabContainer` implementation. commit d9eeced58077e34f79e3b2b222f1c3427d84ee06 Merge: 7da9e31f66 3382e0304d Author: Rémi Verschelde Date: Mon Jan 31 19:34:44 2022 +0100 Merge pull request #57435 from AnilBK/thorvg-0.7.1 commit 23a4fe5b27c191eae1dde05aa522463e67b0766f Author: Markus Sauermann <6299227+Sauermann@users.noreply.github.com> Date: Mon Jan 31 17:55:48 2022 +0100 Fix incorrect Rect2i calculations: intersects and encloses Clarify expand documentation commit 3382e0304d87fd0b3c6c4da681a14a7cf68a05ea Author: Anilforextra Date: Sat Jan 29 21:16:13 2022 +0545 ThorVG: Sync with upstream 0.7.1 Changes: https://github.com/Samsung/thorvg/releases/tag/v0.7.1 commit d14165dae98df44a44335bcbe16695274aeaa057 Author: SeleckyErik <35656626+SeleckyErik@users.noreply.github.com> Date: Fri Nov 5 21:36:13 2021 +0100 Simplify DisplayServerWindows pos/size message handling Replace WM_MOVE and WM_SIZE message handling with WM_POSCHANGED instead. This is for multiple reasons: 1) Microsoft suggest using WM_POSCHANGED is more efficient 2) RectChanged callback is only called once for most window operations 3) Simplifies message handling code commit 7da9e31f66c49e8b705b8a1713a2cf3526ed48c6 Merge: 74b1e77938 078b8c25ce Author: Rémi Verschelde Date: Mon Jan 31 18:22:58 2022 +0100 Merge pull request #57405 from kleonc/texture_button_focus_only_logic_fix commit 74b1e779383371a020d818330ef1d6beaf0ca22b Merge: 7d97f04da8 f170d6a171 Author: Rémi Verschelde Date: Mon Jan 31 18:14:01 2022 +0100 Merge pull request #57276 from IgorKordiukiewicz/fix-auto-brace-complete-wrap-on-selection commit 7d97f04da837f833e12ff4e7356663abd17f0a3e Merge: 243fbebb89 c9cce53983 Author: Rémi Verschelde Date: Mon Jan 31 18:13:32 2022 +0100 Merge pull request #57454 from rcorre/undo_skel commit 243fbebb895713cb846e19538969112d3695eded Merge: f14b1441e7 8e79c5fb8d Author: Rémi Verschelde Date: Mon Jan 31 18:11:55 2022 +0100 Merge pull request #57447 from bruvzg/unicode_escape commit f14b1441e79de31564f6b7926755682947348e7b Merge: b0604622a8 ac1e7e10eb Author: Rémi Verschelde Date: Mon Jan 31 18:08:48 2022 +0100 Merge pull request #56548 from madmiraal/fix-53894 commit b0604622a871fd87e39a645d943165f83391433a Merge: d7822cbf21 b0202c3a7d Author: Rémi Verschelde Date: Mon Jan 31 18:06:44 2022 +0100 Merge pull request #57419 from orosmatthew/fix_ortho_lod commit d7822cbf2136b5dcff899b498c80c139b7701c9e Merge: 6bc1383b18 88b2afa28f Author: Rémi Verschelde Date: Mon Jan 31 16:51:30 2022 +0100 Merge pull request #57367 from Chaosus/vs_derivative commit 6bc1383b182d2d46dab9d454f5b851886db213e7 Merge: 777c821748 93968e1451 Author: Fabio Alessandrelli Date: Mon Jan 31 16:19:41 2022 +0100 Merge pull request #57482 from Faless/js/4.x_misc_fixes [HTML5] Fix Gamepad sampling, cleanup config code. commit 93968e1451595ad4f2dc290497cebcc0bc7314a6 Author: Fabio Alessandrelli Date: Sun Jan 30 19:52:56 2022 +0100 [HTML5] Fix gamepad samples not being properly reset. commit 2f509f1b12c33234a0d8f0e254c727fd92e57720 Author: Fabio Alessandrelli Date: Sun Jan 30 17:41:40 2022 +0100 [HTML5] Better engine config parsing. commit 777c8217485b7ead6534966c511b2388c55bca04 Merge: 7cb25c2870 c317a97359 Author: Rémi Verschelde Date: Mon Jan 31 14:53:22 2022 +0100 Merge pull request #57462 from mashumafi/master-fix-button-icon-alpha3 Fix button icon_disabled_color alpha channel commit 7cb25c2870247e6c5aeed986c88204752a7faaf5 Merge: be60e04639 1cd1df5dc3 Author: Rémi Verschelde Date: Mon Jan 31 14:52:34 2022 +0100 Merge pull request #57456 from Paulb23/placeholder-color Move placeholder colour to theme item commit be60e04639dbf906d5b38947b670ac5d8da16182 Merge: 2c85f2a8f6 e1148cc452 Author: Rémi Verschelde Date: Mon Jan 31 13:45:52 2022 +0100 Merge pull request #57481 from bruvzg/restore_snap_controls_to_pixels commit e1148cc452c1a627974d5d63e95cfc12bf6fa319 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Mon Jan 31 13:30:35 2022 +0200 Revert "Fix control node transform animation jitter with pivot offset" This reverts commit dfb7d46a2aaf9bef2c7dd067e699e70291062fb6. commit 2c85f2a8f60d691c126f87f9cd831d50d272e499 Merge: 5b1b545a58 b6c543179c Author: Rémi Verschelde Date: Mon Jan 31 09:49:09 2022 +0100 Merge pull request #57432 from Ev01/audiostreamgenerator-doc-link-fix Fixed invalid link to tutorial in AudioStreamGenerator class reference commit 5b1b545a5821c1be465bd6c3af897804590b51d9 Merge: 78e3e65e7c e4bde938a1 Author: Rémi Verschelde Date: Mon Jan 31 09:47:42 2022 +0100 Merge pull request #57450 from Pineapple/master-ltcg-embed-pck Prevent LTCG (MSVC LTO) from removing "pck" section commit b6c543179c26636a1d78d55b78cb23f42e24917e Author: Ev01 Date: Sun Jan 30 12:33:16 2022 +1100 Fixes invalid links to tutorials in AudioStreamGenerator, AudioStreamGeneratorPlayback, and AudioEffectSpectrumAnalyzer class references commit c317a9735981e03a8c230916617503ca006f0a08 Author: mashumafi Date: Sun Jan 30 14:25:42 2022 -0500 Fix button icon_color_disabled alpha channel commit 8e79c5fb8dd986aa6903a6419ac8c45444fff6f0 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Sun Jan 30 15:44:07 2022 +0200 Add support for the escaped UTF-16 and UTF-32 Unicode characters in the scripts and expressions. commit 1cd1df5dc3f7977a78bad20d2cecd79874e4dae3 Author: Paulb23 Date: Sun Jan 30 17:56:23 2022 +0000 Move placeholder color to theme item commit c9cce53983fcf5f00d780d1ec08d0f82f34bf49e Author: Ryan Roden-Corrent Date: Sun Jan 30 12:28:44 2022 -0500 Merge create_physical_skeleton undo entries. Pressing `ctrl+z` after clicking "Create Physical Skeleton" will now undo the creation of all physical bones by that operation. Previously undo would remove one bone at a time. Fixes https://github.com/godotengine/godot/issues/55351. commit b0202c3a7d1921329859d3b4494184597d2de6b5 Author: orosmatthew Date: Sat Jan 29 14:38:39 2022 -0500 Fix orthogonal camera auto LOD calculation - Do not take orthogonal camera's distance into account when calculating LOD. - Only take into account screen size taken up. commit f170d6a17170228c28899ad8686788eaf1101de5 Author: Igor Kordiukiewicz Date: Wed Jan 26 22:32:59 2022 +0100 With auto_brace_complete enabled, selected text now gets wrapped by braces commit e4bde938a17ee4b0f643240d71a174c63c2e263b Author: Bartłomiej T. Listwon Date: Sun Jan 30 16:26:24 2022 +0100 Prevent LTCG (MSVC LTO) from removing "pck" section commit 78e3e65e7c38d18128524e0822a106ac76e38800 Merge: e4265e86ce 6e2c9f398c Author: Yuri Roubinsky Date: Sun Jan 30 11:54:30 2022 +0300 Merge pull request #57412 from Chaosus/fix_doc_panel_theming commit 6e2c9f398c74b8d73d02814e5e9e6d09ad0ef51c Author: Yuri Roubinsky Date: Sat Jan 29 20:48:30 2022 +0300 Fix theming of doc background commit 65b6b140b6d8da2dea67ef53c192339a705e58bf Author: Fazil Babu Date: Sun Jan 30 00:02:36 2022 +0530 Inspector retains content when detached and when docked commit e4265e86ceee1bf08bd92b85b51e969310a08bd6 Merge: 2f57a11ed9 736ac25306 Author: Rémi Verschelde Date: Sat Jan 29 22:00:58 2022 +0100 Merge pull request #57409 from Calinou/physics-run-on-thread-rename Rename the physics server `run_on_thread` project settings commit 736ac253061cad240acf390c2aedf619ad7f3a32 Author: Hugo Locurcio Date: Sat Jan 29 17:35:50 2022 +0100 Rename the physics server `run_on_thread` project settings `run_on_separate_thread` is more explicit. commit 078b8c25ce286558a7cfc09034c4ec4e763165eb Author: kleonc <9283098+kleonc@users.noreply.github.com> Date: Sat Jan 29 16:00:30 2022 +0100 TextureButton Fix logic for drawing only the focus texture commit 2f57a11ed9b383706b72783c0d9b1e869774e08a Merge: 9467350f37 8a0a3accee Author: Rémi Verschelde Date: Sat Jan 29 13:46:38 2022 +0100 Merge pull request #55785 from nathanfranke/clang-tidy commit 9467350f37089eb3f4ef7de683daba28b0c69dec Merge: 01f5d7c616 51834a4589 Author: Ignacio Roldán Etcheverry Date: Sat Jan 29 12:45:14 2022 +0100 Merge pull request #57384 from madmiraal/vswhere-errors Be more verbose about why msbuild tools could not be found commit 8a0a3acceee804b91afe31022cf0310c01162f73 Author: Nathan Franke Date: Thu Jan 27 10:34:33 2022 -0600 simplify formatting scripts, add a clang-tidy script, and run clang-tidy commit 01f5d7c616920373ff7d140673bc6f8301213713 Merge: cb3d308f96 49297d937c Author: Rémi Verschelde Date: Sat Jan 29 08:21:05 2022 +0100 Merge pull request #57379 from Faless/net/4.x_ip_cache_fixes [Net] Simplify IP resolution code, fix caching. commit 51834a4589339beae3b9d7a313f70b546392a0ef Author: Marcel Admiraal Date: Sat Jan 29 07:16:34 2022 +0000 Be more verbose about why msbuild tools could not be found commit cb3d308f9695a2099fa109c6a29cc8a9e7e58422 Merge: 252ec22ff9 038977a985 Author: Rémi Verschelde Date: Sat Jan 29 08:08:34 2022 +0100 Merge pull request #57372 from KoBeWi/tween_freeze() Better handle infinite Tween loops commit 49297d937ce6128b2c9f1fb09199dd7d4f2404b7 Author: Fabio Alessandrelli Date: Sat Jan 29 01:33:29 2022 +0100 [Net] Simplify IP resolution code, fix caching. First, we should not insert into cache if the hostname resolution has failed (as it might be a temporary internet issue), second, the async resolver should also properly insert into cache. Took the chance to remove some duplicate code with critical section in it at the cost of little performance when calling the blocking resolve_hostname function. commit 252ec22ff9fc2befd6b120fd40b0e008137a0392 Merge: e84c552b08 9f01c887b1 Author: Rémi Verschelde Date: Sat Jan 29 00:36:42 2022 +0100 Merge pull request #57296 from emcguirk/bug-57253-fix-hint-label commit 038977a985722036d4e2e7f90e0b477225955d80 Author: kobewi Date: Fri Jan 28 22:58:54 2022 +0100 Better handle infinite Tween loops commit e84c552b08531224ad807aad4c7f5b4e7d17eec4 Merge: ffa566c770 b619a47416 Author: Rémi Verschelde Date: Sat Jan 29 00:36:03 2022 +0100 Merge pull request #52557 from jmb462/rename_layers commit 34d382eab643af9c5282267e447449e1c2b1c198 Author: Hugo Locurcio Date: Sat Jan 29 00:31:22 2022 +0100 Clamp GradientTexture2D dimensions to 2048×2048 in the inspector Larger sizes take up a lot of memory for little visual benefit. They also take a while to initialize, which makes the inspector slow to refresh when the texture needs to be regenerated. commit b4f0d4c7db295158ddf35bad770a2c9d976013a6 Author: Hugo Locurcio Date: Sat Jan 29 00:28:10 2022 +0100 Decrease the default GradientTexture and CurveTexture size This provides better usability when a GradientTexture or CurveTexture is added to a Control node. Visual appearance of most GradientTextures and CurveTextures will be unaffected. commit ffa566c770a859628900fe85f41aec065d116b9f Merge: e22a162003 3b146c5eaa Author: Rémi Verschelde Date: Fri Jan 28 23:59:18 2022 +0100 Merge pull request #57330 from eikobear/master commit e22a16200368881a5cf7f060efe92c506c80a4d5 Merge: 1c6f0aa3a0 876345191f Author: Rémi Verschelde Date: Fri Jan 28 23:53:23 2022 +0100 Merge pull request #57365 from pycbouh/editor-icons-uniformity commit b619a47416ef062c757fdd92addcff18be7a684e Author: jmb462 Date: Mon Dec 20 21:30:05 2021 +0100 Renaming layers from the inspector via a popup menu. commit 1c6f0aa3a0952a38dd6d4ef416b3007b888fff17 Merge: 2a5b136de2 9bda2d5859 Author: Rémi Verschelde Date: Fri Jan 28 23:25:55 2022 +0100 Merge pull request #56601 from Scony/fix-navigation-obstacle-errors commit 2a5b136de20c9c7f51abb8a90b9e9d8dc57152ac Merge: 2279edeaf0 450e29a569 Author: Rémi Verschelde Date: Fri Jan 28 23:24:38 2022 +0100 Merge pull request #57371 from Scony/fix-navigation-2d-defaults commit 2279edeaf02d44612d09d332bd1dbaf7924ec7c1 Merge: 27e4c84edd 0650846248 Author: Rémi Verschelde Date: Fri Jan 28 23:16:29 2022 +0100 Merge pull request #57368 from TokageItLab/fix-delta-for-animation-tree commit 27e4c84edd250857db4770bb732e8b12d72e1f52 Merge: b9a2569be6 a2d176a53d Author: Rémi Verschelde Date: Fri Jan 28 23:06:59 2022 +0100 Merge pull request #57326 from Josephblt/material-preview-sphere-icon-has-wrong-inactive-modulation commit 3b146c5eaa06d9f6827c651802b9fb2a9a1e013d Author: eikobear Date: Fri Jan 28 17:03:45 2022 -0500 Make various improvements to OptionButton - Allow OptionButton selection to be set to -1 to signify no selection, both via API and in the editor. - Reset OptionButton selection to -1 when the selected item has been removed. - Fully convert PopupMenu to a zero-based ID system, which improves an inconsistency in generated IDs when making new items in the editor. commit 876345191f871d3e7e5f36934be4d07e8b69f805 Author: Yuri Sizov Date: Fri Jan 28 20:21:50 2022 +0300 Fix theme application in various curve editors commit 36ff66c62f156a6c9f5c046f039023517e95f72e Author: Yuri Sizov Date: Fri Jan 28 19:55:16 2022 +0300 Fix the breakpoint icon in CodeEdit commit 49eddd22e0780e6458bc43b6356c462237681f80 Author: Yuri Sizov Date: Fri Jan 28 19:38:48 2022 +0300 Update icons and color conversion rules to simplify the palette commit 450e29a569b31539cbabd3221f998d2d51bf51ec Author: Pawel Lampe Date: Fri Jan 28 22:35:05 2022 +0100 Improve Navigation2D default settings, see #56852 This commit reduces `cell_size` and `edge_connection_margin` default values so that `Navigation2D` behaves more like in Godot <= `3.4` by default. commit 065084624848999e7559be065e5d8233d028ee3c Author: Silc 'Tokage' Renew Date: Sat Jan 29 05:07:30 2022 +0900 Make AnimationTree delta argument force double in core commit 88b2afa28fe7ab91596d3d2517e0eb24545846fb Author: Yuri Roubinsky Date: Fri Jan 28 21:36:10 2022 +0300 [VisualShader] Merge scalar and vector derivative functions into one commit b9a2569be6fcd1127454127d989b504334d2dac8 Merge: 02d48f88ef fd8c0f4a6a Author: Rémi Verschelde Date: Fri Jan 28 17:45:54 2022 +0100 Merge pull request #57347 from Chaosus/vs_refactor_addop commit 02d48f88ef1fe83ae3f748a90a584117cc1882c1 Merge: 82d412a961 050f746e19 Author: Rémi Verschelde Date: Fri Jan 28 17:45:28 2022 +0100 Merge pull request #56933 from Chaosus/fix_shader_editor_theming commit 82d412a96193e4c3142eb8036b19a38035455135 Merge: e3a644ee37 872ce9adb7 Author: Rémi Verschelde Date: Fri Jan 28 16:27:01 2022 +0100 Merge pull request #57359 from madmiraal/fix-gles3_builder-typo commit e3a644ee3718629bf5b3733d6136ceec7f437a82 Merge: b9de47f4ce 4f5c3d5a60 Author: Rémi Verschelde Date: Fri Jan 28 15:51:58 2022 +0100 Merge pull request #57353 from KoBeWi/tween_stop() commit 872ce9adb7deaeb1732aa8707287eae3c7953d4c Author: Marcel Admiraal Date: Fri Jan 28 14:42:23 2022 +0000 Fix typo in gles3_builders.py commit b9de47f4ce13bf795f0768ae7424d7a73d157282 Merge: 83c7bf6d94 b8b33df178 Author: Rémi Verschelde Date: Fri Jan 28 15:40:53 2022 +0100 Merge pull request #57351 from akien-mga/tileset-get_tile_data-better-return commit 7c3003fcbedf1eed3dac3ffe8ee740b5cf0efe3a Author: Rémi Verschelde Date: Fri Jan 28 15:35:25 2022 +0100 Improve some signal bindings to use specific `Object` subtypes commit 7072b359b40f57e178e87b386acef5a6928e61fe Author: Rémi Verschelde Date: Fri Jan 28 15:06:54 2022 +0100 Improve some method bindings to use specific `Object` subtypes This was made possible by changes to `VariantCaster` which now make it possible to pass any `Object`-derived type as pointer. commit 83c7bf6d941cc67a671ca420c50363540e601b7b Merge: 38c6611b91 99a1e552ac Author: Rémi Verschelde Date: Fri Jan 28 15:04:09 2022 +0100 Merge pull request #57336 from bruvzg/win_con_redir commit 4f5c3d5a60b867e9d9e239a9f5db8c897d9f2a4d Author: kobewi Date: Fri Jan 28 14:54:14 2022 +0100 Fix not being able to stop() empty Tweens commit b8b33df1785e5558dd03227c4df3bdeae2012975 Author: Rémi Verschelde Date: Fri Jan 28 14:26:35 2022 +0100 TileSetAtlasSource: Make `get_tile_data` return `TileData *` This is now possible thanks to `Variant` changes. Also unbind some `_` prefixed methods which don't need to be exposed. commit af045e568d6c5eecb10563d2aa3df7e3ea258488 Author: Yuri Sizov Date: Fri Jan 28 16:16:28 2022 +0300 Remove unnecessary transformations from editor icons commit 38c6611b91f5ee7ded0c2c1d279ab7dcdc4f2f1c Merge: bb1f55c387 a2f5f7cccf Author: Rémi Verschelde Date: Fri Jan 28 14:04:33 2022 +0100 Merge pull request #57344 from bruvzg/rtl_fix_tab_offset commit fd8c0f4a6af36caeec9459306921c73b6c6b250d Author: Yuri Roubinsky Date: Fri Jan 28 14:36:35 2022 +0300 Refactor AddOption in visual shader editor commit a2f5f7cccf5aa9fce3130c3ab536bb111448b5ee Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Fri Jan 28 12:27:47 2022 +0200 Fix RTL table offset, if indent is set. commit bb1f55c38741e9b820fc0d3ef9e005cbf8c4c184 Merge: 9686d680b7 2c8511579f Author: Rémi Verschelde Date: Fri Jan 28 11:15:19 2022 +0100 Merge pull request #57248 from bruvzg/win_confined commit 9686d680b754337a4f399449a7e95fa2d2eb2324 Merge: 26672df72e cba8280515 Author: Rémi Verschelde Date: Fri Jan 28 11:03:23 2022 +0100 Merge pull request #57116 from bruvzg/win_net_share commit 26672df72e8aa09a9a7ec85cb442c50ebf87e251 Merge: 6ff6ec612d 978f2edeea Author: Rémi Verschelde Date: Fri Jan 28 10:45:29 2022 +0100 Merge pull request #57338 from bruvzg/rtl_fix_missing_line_sep2 commit 6ff6ec612dfec8591045ba6f67645cca72889ac5 Merge: e6caaf4c80 02c002ff28 Author: Rémi Verschelde Date: Fri Jan 28 10:28:19 2022 +0100 Merge pull request #57318 from TechnoPorg/fix-face-area-calculation commit 2c8511579fdf1a8352291c340a7355315261c411 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Wed Jan 26 13:58:54 2022 +0200 Fix MOUSE_MODE_CONFINED not updating area when full-screen is toggled or current screen is changed. commit 978f2edeeaf1b4079b297f19f8812882980a21c2 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Fri Jan 28 10:13:23 2022 +0200 [RTL] Fix calculation of the last line height. commit 99a1e552acebef5db9794fbc17e7658acc660e64 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Fri Jan 28 08:30:01 2022 +0200 [Windows] Disable console I/O redirection, if it's already redirected to the pipe or file. commit 9f01c887b11959f2bc7447ed3051b357c20c0c7f Author: Eric McGuirk Date: Thu Jan 27 00:07:54 2022 -0500 Fixes Hint label in 2D editor appearing at wrong position and pushes zoom controls commit e6caaf4c800912517af783e90519cc2a70001e85 Merge: 46053a1be9 051ef479c9 Author: Rémi Verschelde Date: Thu Jan 27 23:46:37 2022 +0100 Merge pull request #57205 from TechnoPorg/variant-template-cast Allow method binds to take Object subclasses as arguments commit a2d176a53db6fa8fec329d37da3ec5060ec7b10f Author: Wagner Scholl Lemos Date: Thu Jan 27 19:29:42 2022 -0300 Fix to prevent icons from disappearing. commit 02c002ff28cfe203b44cc45393ade6ce7569d8c8 Author: TechnoPorg Date: Thu Jan 27 09:57:15 2022 -0700 Fix triangular area calculation It's a triangle, so the area should be halved. Co-authored-by: Jeffrey Cochran commit 46053a1be9cc1bc4957280cc76602aead421884c Merge: b6d3f44095 c35968e276 Author: Rémi Verschelde Date: Thu Jan 27 17:16:28 2022 +0100 Merge pull request #57307 from Calinou/doc-reflectionprobe-max-distance commit b6d3f440950e6efbe723cc14827d17744cf52c8f Merge: a743bcef4d 6ecb92a1fc Author: Rémi Verschelde Date: Thu Jan 27 16:54:41 2022 +0100 Merge pull request #57310 from raulsntos/rename-subsequence Rename C# `IsSubsequenceOfI` to `IsSubsequenceOfN` commit a743bcef4dfaf97c128a245121709f5f23de7a25 Merge: 4b36b6e92a 0014f0233a Author: Rémi Verschelde Date: Thu Jan 27 12:49:01 2022 +0100 Merge pull request #57304 from bruvzg/fix_fit_content_height commit c35968e27672669a9cbf7ccf983ab2c309d08126 Author: Hugo Locurcio Date: Thu Jan 27 12:38:38 2022 +0100 Improve documentation for `ReflectionProbe.max_distance` property commit 4b36b6e92abea51baf8420e6c7dccd89a440bc59 Merge: 203e261526 89f37d4105 Author: Rémi Verschelde Date: Thu Jan 27 12:12:34 2022 +0100 Merge pull request #56785 from bruvzg/nat_handles_4 commit 203e261526ebe123523d7e6ea6cfc6c628216e9d Merge: aa94d5d61a 9590eeebb5 Author: Rémi Verschelde Date: Thu Jan 27 11:48:53 2022 +0100 Merge pull request #57293 from mhilbrunner/windows-console-fixups commit aa94d5d61a793708e631e1533a04db63e5fad16f Merge: 899cd34426 a6b20c1816 Author: Rémi Verschelde Date: Thu Jan 27 11:44:13 2022 +0100 Merge pull request #57290 from IgorKordiukiewicz/fix-tile-map-editor-not-disappearing commit 0014f0233a8932dda1c47bcad7d3e79112332e38 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Thu Jan 27 12:05:21 2022 +0200 [RTL] Fix min. height calculation when fit_content_height is enabled. commit 899cd3442659f5208a08545b520ae6e6fe2552e7 Merge: a4f999b7dc 3eb5e0ac50 Author: Rémi Verschelde Date: Thu Jan 27 11:03:13 2022 +0100 Merge pull request #57281 from Rubonnek/rename-subsequence commit a4f999b7dc1314da752692a19231d0c1a8e70601 Merge: 6604d88407 1c0b163df5 Author: Rémi Verschelde Date: Thu Jan 27 10:17:04 2022 +0100 Merge pull request #57295 from TokageItLab/fix-blendspace2d-discrete commit 6604d88407b0ffe46b37d839e13d31f82086a517 Merge: b7127a17bf 6a4614ef48 Author: Fredia Huya-Kouadio Date: Wed Jan 26 23:40:40 2022 -0800 Merge pull request #57277 from m4gr3d/fix_android_plugin_crash_master Include the `godot_plugin_jni.cpp` file into the `platform/android/SCsub` file commit b7127a17bf2d946d3735db650f7e22cdcd0167d9 Merge: 74653062d5 2cd0c3f8be Author: Rémi Verschelde Date: Thu Jan 27 08:39:17 2022 +0100 Merge pull request #56881 from KoBeWi/get_over_here Improve 2D editor's right-click menu commit 74653062d5fa436ce2c716567a7fffd21e08377c Merge: 2c7ff931df 5deb5ebf23 Author: Rémi Verschelde Date: Thu Jan 27 08:37:39 2022 +0100 Merge pull request #57291 from mhilbrunner/expose-transform3d-slerp Expose Transform3D::sphere_interpolate_with() commit 1c0b163df5b49352391b9f797093d71e3da1dc23 Author: Silc 'Tokage' Renew Date: Thu Jan 27 11:52:39 2022 +0900 More time parameters change type float to double commit 3ef5a975054834466107ed8598352e5315a3a191 Author: Max Hilbrunner Date: Fri Jan 14 03:22:23 2022 +0100 Verify custom HTTP headers, fix off by one error commit 3a83872d261790ed20fdc626eb0de9f515f04f88 Author: Max Hilbrunner Date: Fri Jan 14 00:20:58 2022 +0100 HTTP comment cleanup commit 9590eeebb53f084a3d61d8dfbd21dc2fa89705e7 Author: Max Hilbrunner Date: Thu Jan 27 03:11:00 2022 +0100 Minor typo fixups to Windows console changes commit 6ecb92a1fc26e763b35d83a826fae3198c050a91 Author: Raul Santos Date: Thu Jan 27 03:04:16 2022 +0100 Rename C# `IsSubsequenceOfI` to `IsSubsequenceOfN` commit 2cd0c3f8bea164a7be401f2e36685489dec2a39e Author: kobewi Date: Tue Jan 18 16:35:12 2022 +0100 Improve 2D editor's right-click menu commit 5deb5ebf2365d23c8228df252a30f444d5c3f20b Author: Max Hilbrunner Date: Thu Jan 27 02:53:20 2022 +0100 Expose Transform3D::sphere_interpolate_with() commit a6b20c181606012a1d74c8fba74378b1b9a90e4c Author: Igor Kordiukiewicz Date: Thu Jan 27 02:49:29 2022 +0100 Fixes TileMap editor not disappearing commit 3eb5e0ac509def467d53df7a729e76743e235e90 Author: Wilson E. Alvarez Date: Wed Jan 26 18:03:56 2022 -0500 Rename String::is_subsequence_ofi to String::is_subsequence_ofn commit 2c7ff931dfacb72df446473b3e74fb61c472bf36 Merge: 48db38c5e7 68580ecedd Author: Rémi Verschelde Date: Wed Jan 26 23:55:26 2022 +0100 Merge pull request #57278 from Calinou/editor-voxelgi-rename-bake-actions commit 48db38c5e753ca6f246b6375a7bb4a32456be5c3 Merge: 9f5c18cb12 cc3c4d6323 Author: Rémi Verschelde Date: Wed Jan 26 23:55:09 2022 +0100 Merge pull request #57275 from fabriceci/revert-applying-delta-move-and-collide commit 6a4614ef48aba0cdee4ed2aedf15b21170ecb4ed Author: Fredia Huya-Kouadio Date: Wed Jan 26 13:50:58 2022 -0800 Include the `godot_plugin_jni.cpp` file into the `platform/android/SCsub` file This should resolve https://github.com/godotengine/godot/issues/57209 commit 68580ecedda65ec3898a6cdc68193e32a2a1751a Author: Hugo Locurcio Date: Wed Jan 26 23:12:26 2022 +0100 Rename VoxelGI editor bake actions from "GI Probe" to "VoxelGI" commit cc3c4d6323112abc65cf034a3be3289bad19e22e Author: fabriceci Date: Wed Jan 26 22:21:16 2022 +0100 Revert #53174 (applying the delta in move and collide), rename rec_vel to distance and improve the doc description commit 9f5c18cb1275419abeca69a44b3093de0027aca5 Merge: 9df9dc77a3 b284dd92d3 Author: Rémi Verschelde Date: Wed Jan 26 22:14:19 2022 +0100 Merge pull request #57262 from m4gr3d/update_android_xr_manifest_master commit b284dd92d3a9e1cfc03432f6d6b5fecf8caf66d5 Author: Fredia Huya-Kouadio Date: Wed Jan 26 08:58:47 2022 -0800 Fix XR Android manifest metadata - Adds the parameters for supported Meta devices, which is required to access some device specific capabilities - Remove the 'com.samsung.android.vr.application.mode' metadata commit 9df9dc77a396bcdfe362365e9511de80c1e94fc0 Merge: 1894f3f165 e793331cd7 Author: Rémi Verschelde Date: Wed Jan 26 15:46:48 2022 +0100 Merge pull request #54822 from KoBeWi/sortuces commit e793331cd71f0fd147d22f588a6afa170e17f4a9 Author: kobewi Date: Wed Nov 10 02:27:37 2021 +0100 Allow sorting tileset sources commit 1894f3f16567ff7475dd65ac61d6b8939fdccad4 Merge: 58324f4df8 cf3d3a6ffa Author: Rémi Verschelde Date: Wed Jan 26 13:43:43 2022 +0100 Merge pull request #57247 from bruvzg/rtl_ol_type1 commit 58324f4df822c7416278cda5bf41662e979f9e31 Merge: d74d4cbdff 90652b1755 Author: Rémi Verschelde Date: Wed Jan 26 13:39:51 2022 +0100 Merge pull request #54574 from Ansraer/glow_map commit d74d4cbdffb7c4c15911e8c16e8b7f23d0363bab Merge: 1ce6f6b309 dc1c4cfbfa Author: Rémi Verschelde Date: Wed Jan 26 13:35:36 2022 +0100 Merge pull request #54173 from nathanfranke/fix-exact-match commit 1ce6f6b30920d664c01a4dc911e506b116cac827 Merge: 5eaa93e6b0 98e5cd24db Author: Rémi Verschelde Date: Wed Jan 26 13:30:07 2022 +0100 Merge pull request #57240 from BastiaanOlij/xrinterface_render_hooks commit 5eaa93e6b06c6476d2e87c7c681e4abac750181a Merge: d289448346 a775744742 Author: Rémi Verschelde Date: Wed Jan 26 13:26:44 2022 +0100 Merge pull request #57182 from timothyqiu/shape-owner commit cf3d3a6ffabf8a57ae1fb0f0e2cb047c6b0b1c93 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Wed Jan 26 13:52:24 2022 +0200 Fix RichTextLabel [ol type=1] tag closing. commit d289448346a161a55e97efd09fd9b55d33dc346a Merge: b25c7fef04 78c946f554 Author: Rémi Verschelde Date: Wed Jan 26 08:03:08 2022 +0100 Merge pull request #57235 from Calinou/3d-import-fix-lightmap-size-hint-option Fix lightmap size hint option not displaying for 3D scenes commit 98e5cd24dbe1151e129883731b26ec8f521ee57f Author: Bastiaan Olij Date: Wed Jan 26 12:25:20 2022 +1100 Improve XRInterface hooks into rendering commit 78c946f554514e09a42f709decd2b43a95e59930 Author: Hugo Locurcio Date: Wed Jan 26 00:33:47 2022 +0100 Fix lightmap size hint option not displaying for 3D scenes This also renames the Static Lightmaps option hint to be more explicit about which GI techniques are supported (as VoxelGI/SDFGI can still be used with Static Lightmaps). commit b25c7fef04499a7bee2333184bd7e5879d304eee Merge: d9fd16c8e4 28ac4d8b7d Author: Rémi Verschelde Date: Tue Jan 25 22:36:16 2022 +0100 Merge pull request #57229 from cdemirer/match-dictionary-non-const-key-crash-fix commit 28ac4d8b7d4139dee4f2baf8e091fdb754f03713 Author: cdemirer <41021322+cdemirer@users.noreply.github.com> Date: Wed Jan 26 04:10:07 2022 +0800 Fix crash with non-constant keys in match statement Dictionary pattern commit d9fd16c8e4de693400f7cfbaa20f198535eb13ca Merge: a6fce87d20 26a26d6657 Author: Rémi Verschelde Date: Tue Jan 25 20:09:52 2022 +0100 Merge pull request #53954 from Chaosus/fix_quit_errors commit a6fce87d20e83491b10d3fc58ff1141d59c20407 Merge: b82cb79c4e 93a95ae84a Author: Rémi Verschelde Date: Tue Jan 25 19:47:54 2022 +0100 Merge pull request #57214 from kleonc/sprite_frames_editor_texture_type_fix commit b82cb79c4e92718c7dca6a501ee2fd876ad1c999 Merge: ee7c555665 b01065b9a4 Author: Yuri Roubinsky Date: Tue Jan 25 21:36:02 2022 +0300 Merge pull request #57207 from Chaosus/fix_global_uniforms commit ee7c555665b70fc32b56455fa7876ee3987cde9a Merge: 894e2fddda e911eee21b Author: Rémi Verschelde Date: Tue Jan 25 19:13:16 2022 +0100 Merge pull request #57215 from Paulb23/placeholder-invis commit e911eee21bb262f42d1a140037c2fb9f3964d191 Author: Paulb23 Date: Tue Jan 25 17:41:05 2022 +0000 Fix TextEdit placeholder not checking line count commit 93a95ae84abf91513f40ef64f8fdd7e707e43f30 Author: kleonc <9283098+kleonc@users.noreply.github.com> Date: Tue Jan 25 18:39:26 2022 +0100 SpriteFramesEditor Incorrect texture type fix commit 73c225838fa2e6f3ca3adf328375d1c659dc261b Author: Michael Alexsander Date: Thu Jan 20 08:44:27 2022 -0300 Make popup menus focus items automatically when not using the mouse commit b01065b9a425e5a1306b111f6d8a581227192db4 Author: Yuri Roubinsky Date: Tue Jan 25 19:22:37 2022 +0300 Prevent checking of global uniform type outside the editor commit 051ef479c93c0c830b60059e3dabed6fc381cdd6 Author: TechnoPorg Date: Tue Jan 25 08:37:41 2022 -0700 Allow method binds to take Object subclasses as arguments This commit adds a condition to VariantCaster that casts Variants of type OBJECT to any type T, if T is derived from Object. This change enables a fair bit of code cleanup. First, the Variant implicit cast operators for Node and Control can be removed, which allows for some invalid includes to be removed. Second, helper methods in Tree whose sole purpose was to cast arguments to TreeItem * are no longer necessary. A few small changes also had to be made to other files, due to the changes cascading down all the includes. commit 894e2fddda029247e6e614f4a4e11d4b7fd8176d Merge: fba4c6606d 453912d48d Author: Rémi Verschelde Date: Tue Jan 25 16:54:26 2022 +0100 Merge pull request #55841 from OverloadedOrama/expose-bitmap-methods commit fba4c6606d648188e54308413c51ea2b2205d908 Merge: 1a64485b97 5f8b292ad3 Author: Rémi Verschelde Date: Tue Jan 25 16:53:41 2022 +0100 Merge pull request #56891 from rafallus/meshlib_shapes_array commit 1a64485b97b2308e88e5ad65f0f24ca1cf30fcb0 Merge: 203e07aa39 8be49838b3 Author: Rémi Verschelde Date: Tue Jan 25 16:44:55 2022 +0100 Merge pull request #57187 from timothyqiu/leak-from-trash commit 57db989a97a5bdd7ab989032d1e7570fe2750895 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Tue Jan 25 17:12:50 2022 +0200 [iOS] Fix iOS export with manually specified signing/provisioning data. commit 203e07aa39d5b55c8c16842d3e2e628209a063c4 Merge: 7cbe1835f9 5ea4a8b421 Author: Rémi Verschelde Date: Tue Jan 25 15:13:24 2022 +0100 Merge pull request #57190 from timothyqiu/ani-node-rename commit 7cbe1835f97ba8753ce3230537428aa42606ec88 Merge: 3102660512 306b98638e Author: Rémi Verschelde Date: Tue Jan 25 14:44:30 2022 +0100 Merge pull request #57189 from bruvzg/fix_shortcut_context_unset commit 5ea4a8b4214571087a42a29b2caf9053bbb2e548 Author: Haoyu Qiu Date: Tue Jan 25 20:18:40 2022 +0800 Fix crash after renaming an animation node commit 306b98638e3d8f52ff7bdf639fbd4a478dfc8913 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Tue Jan 25 14:13:00 2022 +0200 Allow unsetting `shortcut_context`. commit 8be49838b3ecf6a4dd76914f324e103255f38fa3 Author: Haoyu Qiu Date: Tue Jan 25 18:38:13 2022 +0800 Fix memory leak when move to trash fails on Linux commit a77574474211957a050d7cd48b44eb32a254350e Author: Haoyu Qiu Date: Tue Jan 25 17:16:06 2022 +0800 Store ObjectID instead of raw pointer for Shape Owners commit 050f746e1919ec0a7ee11b5afe39791ba1c55419 Author: Yuri Roubinsky Date: Wed Jan 19 08:31:39 2022 +0300 Fix theming update of shader editor commit 3102660512e2808e2f133a6edc1c3d74202e76c4 Merge: 855368547f f16c483c9d Author: Rémi Verschelde Date: Tue Jan 25 07:58:46 2022 +0100 Merge pull request #37945 from IllusiveS/master Expose AnimationNodeOneShot::mix_mode as a property commit 855368547fa70208479fcc1953bc07af43887876 Merge: 1991eae992 81f2ce4e46 Author: Rémi Verschelde Date: Tue Jan 25 07:50:54 2022 +0100 Merge pull request #57169 from akien-mga/tree-treeitem-control doc: Clarify expected type of `Object *` parameters in Tree methods commit 1991eae992a3154308e0ae4456b84bf411a68815 Merge: 30701e3966 12d0ff1a4d Author: Rémi Verschelde Date: Tue Jan 25 07:49:52 2022 +0100 Merge pull request #57168 from pfertyk/issue-57130-fix-generate-scene-if-state-is-null Fix `GLTFDocument.generate_scene` crash with invalid state commit f16c483c9dcc4a9ed2c77d9d1ddbf358e8f3805e Author: Wysocki Patryk Date: Thu Apr 16 23:21:11 2020 +0200 Expose AnimationNodeOneShot::mix_mode as a property Fixes #23458. commit 81f2ce4e46ac2c542bb7527efddfa30e377234ef Author: Rémi Verschelde Date: Tue Jan 25 00:54:21 2022 +0100 doc: Clarify expected type of `Object *` parameters in Tree methods They're meant to be `TreeItem *` but this can't be bound in Variant. Fixes #20538. commit 12d0ff1a4dbf244f35dc688122e649b2cb916497 Author: Paweł Fertyk Date: Tue Jan 25 00:48:12 2022 +0100 Issue 57130 Fix GLTFDocument.generate_scene if state is null commit 30701e3966fe0869868d09d57249ff140e55849e Merge: 672363f295 342a31e326 Author: Rémi Verschelde Date: Mon Jan 24 23:16:31 2022 +0100 Merge pull request #57155 from KoBeWi/drag_by_force commit 342a31e3265508a3fc79b08053bf63be9ff2da51 Author: kobewi Date: Mon Jan 24 22:19:50 2022 +0100 Fix 2D Pan Tool commit 672363f295495478ef7d03ea00878b8ebfbdb430 Merge: 33960b3b87 6f88294528 Author: Rémi Verschelde Date: Mon Jan 24 22:19:55 2022 +0100 Merge pull request #56888 from FreegleBarr/implement_gpuparticle_subemitters2d commit 33960b3b87f663923bc7d29a8cfaed77060e1056 Merge: 2255777fb9 6f1089af86 Author: Rémi Verschelde Date: Mon Jan 24 21:33:14 2022 +0100 Merge pull request #55884 from preslavnpetrov/ctrl-enter-deleting-selection-fix-master commit 2255777fb9db0f51ea7f5bbb9da98d80f6a3b992 Merge: 37472fa2c0 80187b77a9 Author: Rémi Verschelde Date: Mon Jan 24 21:32:38 2022 +0100 Merge pull request #57144 from AnilBK/fix-pos-dragging commit 6f882945280ddc90a2de4000c1917375af6376e7 Author: freeglebarr Date: Mon Jan 24 17:28:59 2022 -0300 ported particle sub-emission to 2D commit 37472fa2c0c4c03a62fa6a57b25655c91bd8263f Merge: 5f8f6ae7f6 9456454109 Author: Rémi Verschelde Date: Mon Jan 24 21:21:51 2022 +0100 Merge pull request #57133 from bruvzg/fix_multi_file_tr commit 5f8f6ae7f6e7d598629993fcdd4b3828f1bf2d04 Merge: fc09d783f4 d7f51dd2ec Author: Rémi Verschelde Date: Mon Jan 24 21:03:48 2022 +0100 Merge pull request #57120 from KoBeWi/grandpa_issue commit 80187b77a9fc5f21758904bcb667a812c3230c07 Author: Anilforextra Date: Tue Jan 25 00:52:11 2022 +0545 Node2D/Node3D: Fix Undraggable Position Property. commit 945645410905e8b23228965e29c2be274cb12645 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Mon Jan 24 18:58:16 2022 +0200 Fix translation with multiple sources for the same language. Remove unnecessary locale length checks. Add "C" -> "en" locale remap. commit fc09d783f4e8a6b8978c6971450f26e812268e67 Merge: 61b7962327 17d4d3839e Author: Rémi Verschelde Date: Mon Jan 24 17:01:08 2022 +0100 Merge pull request #57122 from Faless/net/4.x_http_request_leak commit 61b79623274db1ed97ec6474084fbe803d947ada Merge: 233699f9e7 238862bddb Author: Rémi Verschelde Date: Mon Jan 24 17:00:56 2022 +0100 Merge pull request #57123 from JFonS/shadow_atlas_fixes commit 233699f9e7810311d46159ea1e279f5e78d615d1 Merge: 6a3ff8fa1f 856142a97d Author: Rémi Verschelde Date: Mon Jan 24 16:25:28 2022 +0100 Merge pull request #57103 from fabriceci/rename-free-mode-floating commit 238862bddb647a832b4be38791b86f882b20af6a Author: jfons Date: Mon Jan 24 15:55:32 2022 +0100 Minor fixes to shadow atlases: * Erase shadow owner *before* setting it to RID(). * Add default texture in shadow atlas debug view to avoid error spam when no atlas is present. * Fix typo. commit cba82805156ada29993d18b4ceb0721636a3db80 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Mon Jan 24 13:12:46 2022 +0200 [Windows] Add support for handling network share paths. commit 856142a97d7df66da6d5a05ab7d3ba94b4d49e69 Author: fabriceci Date: Sun Jan 23 23:36:07 2022 +0100 rename free mode to floating mode commit 17d4d3839ec03a25642b19f70b85d396c0d8a0ad Author: Fabio Alessandrelli Date: Mon Jan 24 14:24:45 2022 +0100 [Net] Fix HTTPRequest memory leak in compressed responses. When accept_gzip = true. commit d7f51dd2ec60f5c237e471b65e890c11f172b24b Author: kobewi Date: Mon Jan 24 13:59:24 2022 +0100 Better clarify map_to_world() description commit dc1c4cfbfa7a5da8e26f21b2a180c68d3e241c50 Author: Nathan Franke Date: Sat Oct 23 22:38:32 2021 -0500 Fix action exact match commit 6f1089af861d74f116e1163f8ead9e3a42abddaa Author: Preslavb Date: Sun Dec 12 22:07:46 2021 +0000 Fix selection being deleted and indentation not being accounted for commit 6c3b6664b55677e4d86c1728b5e483d861a6fdfe Author: Igor Kordiukiewicz Date: Sun Jan 23 04:37:34 2022 +0100 String.Capitalize() in C# now matches the behaviour of String::capitalize() in C++ commit 58e8e5f219d821056750d446da8e349049e9174b Author: Ryan Roden-Corrent Date: Sat Jul 18 15:03:08 2020 -0400 Implement blender-style 3D transform tools. See godotengine/godot-proposals#1215. This adds shortcuts for blender-inspired transforms, where you can press the key and immediately be transforming an object without holding the mouse. Clicking commits the transformation, ESC aborts it. This is inspired by Blender's G(rab)/R(otate)/S(cale) shortcuts, but I decided not to add default bindings as `S` is already bound to the regular scale tool, and it might be confusing to only bind some of them. While actively using a transform tool, you can press X/Y/Z to lock the transform to an axis or (shift)+X/Y/Z to constrain the transform to a plane. These keys are only processed if you have a transform tool (translate/rotate/scale) active _and_ the mouse button is held. Pressing XX/YY/ZZ will lock the transform to a local (rather than global) axis. This is achieved by temporarily toggling the local transform button. I did this (vs handling it in the transform functions) for 3 reasons: - Transform logic for translate/rotate (but not scale) appears to be tightly coupled to the gizmo - This ensures the gizmo changes to indicate we're transforming locally/globally - Toggling the button state in the UI also gives the user feedback about the nature of the transform. The original state of the button is reset when the transform completes. Pressing the `spatial_editor/cancel_transform` shortcut key during a transform operation will cancel the transform and reset the objects back to their original transforms. This functionality was already accessible by pressing RMB during a transform, however: - ESC is more familiar to blender users, and a more common "cancel" key in general. - Given you must hold LMB during a transform, pressing RMB as well is clumsy if not impossible (on a laptop trackpad). commit 0e659b423095b494cca2eb9fb9b91cc246ac4380 Author: Ignacio Roldán Etcheverry Date: Thu Jan 20 22:09:03 2022 +0100 Fix false reporting unclaimed StringName at exit due to static refs commit 90652b17551b6abd6537e124fd548ae78c39fbea Author: Ansraer Date: Thu Jan 20 16:47:25 2022 +0100 add support for glow maps commit f8dde5871c02bad4cb5c14b187a087cf8a82c534 Author: fire540 Date: Tue Jan 18 17:05:43 2022 -0600 Fix Create Root Node dialog expanding in favorites tab commit a6f34ea2d00d6cef252501a580fbaf08ec5ad423 Author: K. S. Ernest (iFire) Lee Date: Wed Jan 19 03:03:47 2022 -0800 Make add_importer and add_post_importer_plugin override existing importers. commit 5f8b292ad39e0ba51d7d16be4717df26c87bd8a7 Author: rafallus Date: Tue Jan 18 00:05:31 2022 -0600 Make sure `MeshLibrary` shape array has correct number of elements commit d6c7d4ab5dd27311adbc340a857b1a6930324749 Author: Hugo Locurcio Date: Sun Jan 16 18:08:07 2022 +0100 Fix visible background line in intersections in screen-space reflections Adjusting the step grading by one resolves the issue without affecting performance or introducing adverse artifacts. commit 89f37d410557c9062b45f090f08d006453bc4fc9 Author: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Fri Jan 14 12:41:04 2022 +0200 Add support for getting native display, window, and view handles. commit 5c3600b29fbc02e92a3ccbd86a9da46efd03bac2 Author: Marcel Admiraal Date: Thu Jan 13 18:13:50 2022 +0000 Fix mouse velocity not changing fast enough - Uses all accumulated movements when calculating velocity - Discards old accumulated movements - Sets last mouse velocity to zero when there is no movement commit 9bda2d5859fd0fa62e837c26fc465e0a5ffbc60a Author: Pawel Lampe Date: Fri Jan 7 19:55:22 2022 +0100 Fix NavigationObstacle errors * `NavigationObstacle2D` premature radius estimation (before entering the tree) * `NavigationObstacle3D` premature radius estimation (before entering the tree) commit ac1e7e10eb1a060ec64eb8317c763b3e06653c28 Author: Marcel Admiraal Date: Thu Jan 6 08:04:59 2022 +0000 Set window to focused when created commit 7a8b11ee14f34ca97e6d5023ef15b8abf4d59cda Author: Andrii Doroshenko (Xrayez) Date: Thu Oct 22 22:02:57 2020 +0300 Refactor auto-instantiation of `Object` properties in editor Auto-instantiation is used by the create dialog, but should also be used by the editor inspector. This refactors object properties auto-instantiation into a dedicated method to be reused throughout editor (and possibly scripting). commit 453912d48d320e358480d768414e8373964c51b1 Author: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sun Dec 12 01:05:25 2021 +0200 Expose BitMap's `convert_to_image` and `resize` methods to GDScript commit 26a26d6657a18eb4a13d3e810885a65bd5b040ef Author: Yuri Roubinsky Date: Mon Oct 18 14:23:10 2021 +0300 Fix errors on quitting from an empty project --- .clang-tidy | 7 +- .github/workflows/ios_builds.yml | 2 +- .github/workflows/macos_builds.yml | 2 +- CONTRIBUTING.md | 8 +- SConstruct | 29 +- core/SCsub | 1 + core/config/engine.cpp | 5 +- core/config/project_settings.cpp | 4 +- core/core_constants.cpp | 3 + core/debugger/remote_debugger_peer.cpp | 9 +- core/debugger/script_debugger.cpp | 2 +- core/error/error_macros.cpp | 4 + core/error/error_macros.h | 3 + core/input/input.cpp | 30 +- core/input/input.h | 2 +- core/input/input_event.cpp | 109 +- core/input/input_event.h | 12 +- core/input/input_map.cpp | 42 +- core/input/input_map.h | 4 +- core/io/compression.cpp | 6 +- core/io/dir_access.cpp | 10 +- core/io/file_access.cpp | 2 +- core/io/file_access_network.cpp | 1 - core/io/file_access_pack.cpp | 2 +- core/io/http_client.cpp | 11 + core/io/http_client.h | 1 + core/io/http_client_tcp.cpp | 92 +- core/io/http_client_tcp.h | 11 +- core/io/image.cpp | 132 +- core/io/image.h | 2 + core/io/ip.cpp | 42 +- core/io/ip_address.cpp | 4 +- core/io/json.cpp | 14 +- core/io/packet_peer.cpp | 2 +- core/io/pck_packer.cpp | 4 +- core/io/resource.h | 4 +- core/io/resource_format_binary.cpp | 4 +- core/io/resource_importer.cpp | 9 + core/io/resource_importer.h | 5 +- core/io/resource_loader.cpp | 2 +- core/io/resource_uid.cpp | 4 +- core/io/stream_peer.cpp | 4 +- core/io/translation_loader_po.cpp | 2 +- core/math/a_star.cpp | 1 - core/math/aabb.h | 8 +- core/math/audio_frame.h | 12 + core/math/basis.h | 11 +- core/math/camera_matrix.cpp | 4 + core/math/camera_matrix.h | 10 +- core/math/convex_hull.cpp | 8 +- core/math/delaunay_2d.h | 1 + core/math/dynamic_bvh.h | 6 +- core/math/expression.cpp | 111 +- core/math/face3.h | 6 +- core/math/geometry_2d.h | 4 + core/math/plane.h | 5 +- core/math/quaternion.h | 3 +- core/math/rect2.cpp | 10 +- core/math/rect2.h | 218 +- core/math/rect2i.cpp | 42 + core/math/rect2i.h | 245 + core/math/transform_2d.cpp | 2 + core/math/transform_2d.h | 7 +- core/math/transform_3d.h | 9 +- core/math/triangulate.h | 1 + core/math/vector2.cpp | 92 +- core/math/vector2.h | 137 +- core/math/vector2i.cpp | 125 + core/math/vector2i.h | 149 + core/math/vector3.h | 6 +- core/math/vector3i.h | 10 +- core/multiplayer/multiplayer_api.cpp | 364 +- core/multiplayer/multiplayer_api.h | 116 +- core/multiplayer/multiplayer_replicator.cpp | 791 -- core/multiplayer/multiplayer_replicator.h | 138 - core/object/callable_method_pointer.h | 8 + core/object/class_db.cpp | 5 +- core/object/make_virtuals.py | 4 +- core/object/object.h | 3 +- core/register_core_types.cpp | 2 - core/string/char_utils.h | 92 + core/string/locales.h | 1 + core/string/string_name.cpp | 15 +- core/string/string_name.h | 12 + core/string/translation.cpp | 39 +- core/string/ustring.cpp | 78 +- core/string/ustring.h | 7 +- core/templates/rid_owner.h | 56 +- core/templates/vector.h | 4 +- core/variant/binder_common.h | 50 +- core/variant/callable.cpp | 9 +- core/variant/callable.h | 1 + core/variant/callable_bind.cpp | 14 + core/variant/callable_bind.h | 2 + core/variant/method_ptrcall.h | 1 - core/variant/native_ptr.h | 46 +- core/variant/variant.cpp | 20 +- core/variant/variant.h | 8 +- core/variant/variant_call.cpp | 7 +- core/variant/variant_op.cpp | 137 +- core/variant/variant_parser.cpp | 62 +- core/version.h | 3 + doc/classes/@GlobalScope.xml | 6 + doc/classes/AnimationNodeOneShot.xml | 15 +- doc/classes/Area2D.xml | 6 +- doc/classes/Area3D.xml | 6 +- doc/classes/ArrayOccluder3D.xml | 24 + doc/classes/AudioEffectSpectrumAnalyzer.xml | 4 +- doc/classes/AudioStreamGenerator.xml | 2 +- doc/classes/AudioStreamGeneratorPlayback.xml | 2 +- doc/classes/AudioStreamRandomPitch.xml | 19 - doc/classes/AudioStreamRandomizer.xml | 90 + doc/classes/BitMap.xml | 13 + doc/classes/BoxMesh.xml | 2 +- doc/classes/BoxOccluder3D.xml | 13 + doc/classes/BoxShape3D.xml | 2 +- doc/classes/Button.xml | 2 +- doc/classes/ButtonGroup.xml | 2 +- doc/classes/CanvasItem.xml | 4 +- doc/classes/CanvasLayer.xml | 11 + doc/classes/CapsuleMesh.xml | 4 +- doc/classes/CapsuleShape3D.xml | 4 +- doc/classes/CharacterBody2D.xml | 8 +- doc/classes/CharacterBody3D.xml | 4 +- doc/classes/CodeEdit.xml | 3 + doc/classes/Color.xml | 34 + doc/classes/Control.xml | 6 - doc/classes/CurveTexture.xml | 2 +- doc/classes/CurveXYZTexture.xml | 2 +- doc/classes/CylinderMesh.xml | 4 +- doc/classes/CylinderShape3D.xml | 2 +- doc/classes/DisplayServer.xml | 39 + doc/classes/EditorInspector.xml | 4 +- doc/classes/EditorPlugin.xml | 10 +- doc/classes/Environment.xml | 7 + doc/classes/GPUParticles2D.xml | 29 + doc/classes/GPUParticles3D.xml | 6 + doc/classes/GradientTexture1D.xml | 2 +- doc/classes/Image.xml | 9 + doc/classes/Input.xml | 7 +- doc/classes/InputEventMouse.xml | 6 +- doc/classes/JavaScript.xml | 22 + doc/classes/LineEdit.xml | 6 +- doc/classes/Mesh.xml | 2 +- doc/classes/MultiplayerAPI.xml | 6 +- doc/classes/MultiplayerReplicator.xml | 191 - doc/classes/MultiplayerSpawner.xml | 47 + doc/classes/MultiplayerSynchronizer.xml | 17 + doc/classes/Node.xml | 30 +- doc/classes/OS.xml | 6 +- doc/classes/Object.xml | 2 +- doc/classes/Occluder3D.xml | 18 +- doc/classes/OccluderInstance3D.xml | 2 + doc/classes/OptionButton.xml | 3 +- doc/classes/Panel.xml | 10 - doc/classes/PhysicsBody2D.xml | 12 +- doc/classes/PhysicsBody3D.xml | 10 +- doc/classes/PolygonOccluder3D.xml | 13 + doc/classes/Popup.xml | 8 +- doc/classes/PopupMenu.xml | 16 +- doc/classes/ProjectSettings.xml | 14 +- doc/classes/QuadOccluder3D.xml | 13 + doc/classes/RID.xml | 6 + doc/classes/RayCast2D.xml | 8 +- doc/classes/RayCast3D.xml | 8 +- doc/classes/Rect2.xml | 5 +- doc/classes/Rect2i.xml | 7 +- doc/classes/ReflectionProbe.xml | 1 + doc/classes/RenderingServer.xml | 4 +- doc/classes/RichTextLabel.xml | 15 + doc/classes/SceneReplicationConfig.xml | 61 + doc/classes/ShapeCast2D.xml | 8 +- doc/classes/SphereOccluder3D.xml | 13 + doc/classes/StreamPeerBuffer.xml | 10 + doc/classes/String.xml | 25 +- doc/classes/StringName.xml | 24 + doc/classes/TabBar.xml | 62 +- doc/classes/TextEdit.xml | 6 +- doc/classes/TextLine.xml | 1 + doc/classes/TextParagraph.xml | 1 + doc/classes/TextServer.xml | 27 + doc/classes/TextServerExtension.xml | 44 +- doc/classes/TextureButton.xml | 10 +- doc/classes/Theme.xml | 200 +- doc/classes/TileMap.xml | 5 +- doc/classes/TileSetAtlasSource.xml | 2 +- doc/classes/Transform3D.xml | 8 + doc/classes/TranslationServer.xml | 7 + doc/classes/Tree.xml | 24 +- doc/classes/TreeItem.xml | 26 +- doc/classes/Tween.xml | 2 +- doc/classes/Vector2i.xml | 10 +- doc/classes/Vector3i.xml | 11 +- doc/classes/Viewport.xml | 16 +- doc/classes/VisualShaderNode.xml | 14 +- doc/classes/VisualShaderNodeClamp.xml | 9 +- doc/classes/VisualShaderNodeCompare.xml | 13 +- .../VisualShaderNodeDerivativeFunc.xml | 45 + doc/classes/VisualShaderNodeFaceForward.xml | 2 +- doc/classes/VisualShaderNodeMix.xml | 18 +- doc/classes/VisualShaderNodeMultiplyAdd.xml | 11 +- .../VisualShaderNodeParticleRandomness.xml | 10 +- .../VisualShaderNodeScalarDerivativeFunc.xml | 30 - doc/classes/VisualShaderNodeSmoothStep.xml | 18 +- doc/classes/VisualShaderNodeStep.xml | 18 +- doc/classes/VisualShaderNodeSwitch.xml | 13 +- doc/classes/VisualShaderNodeVec2Constant.xml | 16 + doc/classes/VisualShaderNodeVec2Uniform.xml | 19 + doc/classes/VisualShaderNodeVectorBase.xml | 26 + doc/classes/VisualShaderNodeVectorCompose.xml | 2 +- .../VisualShaderNodeVectorDecompose.xml | 2 +- .../VisualShaderNodeVectorDerivativeFunc.xml | 30 - .../VisualShaderNodeVectorDistance.xml | 2 +- doc/classes/VisualShaderNodeVectorFunc.xml | 2 +- doc/classes/VisualShaderNodeVectorLen.xml | 2 +- doc/classes/VisualShaderNodeVectorOp.xml | 2 +- doc/classes/Window.xml | 7 +- doc/classes/XRInterfaceExtension.xml | 50 +- doc/classes/XRServer.xml | 18 - doc/classes/bool.xml | 2 +- doc/classes/float.xml | 12 +- doc/classes/int.xml | 7 + drivers/gles3/rasterizer_canvas_gles3.cpp | 19 +- drivers/gles3/rasterizer_gles3.cpp | 56 +- drivers/gles3/rasterizer_scene_gles3.cpp | 2 +- drivers/gles3/rasterizer_scene_gles3.h | 2 +- drivers/gles3/rasterizer_storage_gles3.cpp | 64 +- drivers/gles3/rasterizer_storage_gles3.h | 118 +- drivers/gles3/texture_loader_gles3.cpp | 3 +- drivers/unix/ip_unix.cpp | 2 +- drivers/vulkan/vulkan_context.cpp | 10 +- drivers/windows/dir_access_windows.cpp | 11 +- editor/animation_track_editor.cpp | 18 +- editor/array_property_edit.cpp | 4 +- editor/code_editor.cpp | 41 +- editor/connections_dialog.cpp | 34 +- editor/create_dialog.cpp | 30 +- editor/create_dialog.h | 2 +- editor/debugger/editor_debugger_inspector.cpp | 2 +- editor/debugger/editor_debugger_node.cpp | 4 +- editor/debugger/editor_debugger_tree.cpp | 2 +- editor/debugger/editor_profiler.cpp | 3 +- editor/debugger/script_editor_debugger.cpp | 24 +- editor/dependency_editor.cpp | 2 +- editor/doc_tools.cpp | 4 +- editor/editor_about.cpp | 3 +- editor/editor_atlas_packer.cpp | 3 + editor/editor_atlas_packer.h | 5 +- editor/editor_autoload_settings.cpp | 16 +- editor/editor_command_palette.cpp | 2 +- editor/editor_data.cpp | 30 +- editor/editor_data.h | 2 + editor/editor_export.cpp | 14 +- editor/editor_feature_profile.cpp | 12 +- editor/editor_file_dialog.cpp | 16 +- editor/editor_file_dialog.h | 2 +- editor/editor_help.cpp | 70 +- editor/editor_help.h | 2 +- editor/editor_help_search.cpp | 9 +- editor/editor_inspector.cpp | 115 +- editor/editor_locale_dialog.cpp | 14 +- editor/editor_node.cpp | 165 +- editor/editor_node.h | 7 +- editor/editor_plugin.cpp | 25 +- editor/editor_plugin.h | 9 +- editor/editor_properties.cpp | 476 +- editor/editor_properties.h | 44 +- editor/editor_properties_array_dict.cpp | 49 +- editor/editor_properties_array_dict.h | 1 + editor/editor_resource_picker.cpp | 18 +- editor/editor_sectioned_inspector.cpp | 4 +- editor/editor_settings.cpp | 4 +- ..._dialog.cpp => editor_settings_dialog.cpp} | 57 +- ...nfig_dialog.h => editor_settings_dialog.h} | 9 +- editor/editor_themes.cpp | 326 +- editor/editor_toaster.cpp | 30 +- editor/filesystem_dock.cpp | 99 +- editor/find_in_files.cpp | 9 +- editor/groups_editor.cpp | 4 +- editor/icons/AudioBusLayout.svg | 2 +- editor/icons/AudioListener2D.svg | 2 +- editor/icons/AudioStreamMP3.svg | 2 +- editor/icons/AudioStreamOGGVorbis.svg | 2 +- editor/icons/AudioStreamPlayer.svg | 2 +- editor/icons/AudioStreamPlayer2D.svg | 2 +- editor/icons/AudioStreamPlayer3D.svg | 2 +- editor/icons/AudioStreamSample.svg | 2 +- editor/icons/BoxMesh.svg | 2 +- editor/icons/BoxShape3D.svg | 2 +- editor/icons/Breakpoint.svg | 2 +- editor/icons/BusVuEmpty.svg | 2 +- editor/icons/BusVuFull.svg | 2 +- editor/icons/CPUParticles2D.svg | 2 +- editor/icons/Callable.svg | 2 +- editor/icons/CanvasGroup.svg | 2 +- editor/icons/CanvasModulate.svg | 2 +- editor/icons/CapsuleShape2D.svg | 2 +- editor/icons/CapsuleShape3D.svg | 2 +- editor/icons/CircleShape2D.svg | 2 +- editor/icons/CodeEdit.svg | 2 +- editor/icons/ColorRect.svg | 2 +- editor/icons/ConcavePolygonShape2D.svg | 2 +- editor/icons/ConcavePolygonShape3D.svg | 2 +- editor/icons/ConvexPolygonShape2D.svg | 2 +- editor/icons/ConvexPolygonShape3D.svg | 2 +- editor/icons/CurveClose.svg | 2 +- editor/icons/CurveCreate.svg | 2 +- editor/icons/CurveCurve.svg | 2 +- editor/icons/CurveDelete.svg | 2 +- editor/icons/CurveEdit.svg | 2 +- editor/icons/CylinderShape3D.svg | 2 +- editor/icons/DebugSkipBreakpointsOff.svg | 2 +- editor/icons/DebugSkipBreakpointsOn.svg | 2 +- editor/icons/Decal.svg | 2 +- editor/icons/DirectionalLight2D.svg | 2 +- editor/icons/EditorBoneHandle.svg | 2 +- editor/icons/EditorControlAnchor.svg | 2 +- editor/icons/EditorCurveHandle.svg | 2 +- editor/icons/EditorPathSharpHandle.svg | 2 +- editor/icons/EditorPathSmoothHandle.svg | 2 +- editor/icons/EditorPositionPrevious.svg | 2 +- editor/icons/EditorPositionUnselected.svg | 2 +- editor/icons/Error.svg | 2 +- editor/icons/ErrorWarning.svg | 2 +- editor/icons/FileBroken.svg | 2 +- editor/icons/FileBrokenBigThumb.svg | 2 +- editor/icons/FileDead.svg | 2 +- editor/icons/FileDeadBigThumb.svg | 2 +- editor/icons/FileDeadMediumThumb.svg | 2 +- editor/icons/GizmoCPUParticles3D.svg | 2 +- editor/icons/GizmoDirectionalLight.svg | 2 +- editor/icons/GizmoLight.svg | 2 +- editor/icons/GizmoSpotLight.svg | 2 +- editor/icons/GraphEdit.svg | 2 +- editor/icons/GraphNode.svg | 2 +- editor/icons/GuiDropdown.svg | 2 +- editor/icons/GuiScrollGrabber.svg | 2 +- editor/icons/GuiScrollGrabberHl.svg | 2 +- editor/icons/GuiScrollGrabberPressed.svg | 2 +- editor/icons/GuiSliderGrabber.svg | 2 +- editor/icons/GuiSliderGrabberHl.svg | 2 +- editor/icons/GuiToggleOn.svg | 2 +- editor/icons/GuiToggleOnMirrored.svg | 2 +- editor/icons/GuiTreeArrowDown.svg | 2 +- editor/icons/GuiTreeArrowLeft.svg | 2 +- editor/icons/GuiTreeArrowRight.svg | 2 +- editor/icons/GuiTreeUpdown.svg | 2 +- editor/icons/HFlowContainer.svg | 2 +- editor/icons/Heart.svg | 2 +- editor/icons/HeightMapShape3D.svg | 2 +- editor/icons/Help.svg | 2 +- editor/icons/ImmediateMesh.svg | 2 +- editor/icons/ImportCheck.svg | 2 +- editor/icons/ImportFail.svg | 2 +- editor/icons/KeyBlendShape.svg | 45 +- editor/icons/KeyInvalid.svg | 2 +- editor/icons/KeyTrackBlendShape.svg | 46 +- editor/icons/KeyTrackPosition.svg | 48 +- editor/icons/KeyTrackRotation.svg | 48 +- editor/icons/KeyTrackScale.svg | 48 +- editor/icons/KeyXPosition.svg | 44 +- editor/icons/KeyXRotation.svg | 45 +- editor/icons/KeyXScale.svg | 45 +- editor/icons/MaterialPreviewCube.svg | 2 +- editor/icons/MaterialPreviewCubeOff.svg | 2 +- editor/icons/MaterialPreviewLight1Off.svg | 2 +- editor/icons/MaterialPreviewSphereOff.svg | 2 +- editor/icons/NavigationAgent2D.svg | 2 +- editor/icons/NavigationAgent3D.svg | 2 +- editor/icons/NavigationObstacle2D.svg | 2 +- editor/icons/NavigationObstacle3D.svg | 2 +- editor/icons/NodeDisabled.svg | 2 +- editor/icons/OccluderPolygon2D.svg | 2 +- editor/icons/OverbrightIndicator.svg | 2 +- editor/icons/PackedByteArray.svg | 2 +- editor/icons/PackedColorArray.svg | 2 +- editor/icons/PageFirst.svg | 48 +- editor/icons/PageLast.svg | 48 +- editor/icons/PageNext.svg | 43 +- editor/icons/PagePrevious.svg | 43 +- editor/icons/ParallaxBackground.svg | 2 +- editor/icons/ParallaxLayer.svg | 2 +- editor/icons/PlayOverlay.svg | 2 +- editor/icons/RectangleShape2D.svg | 2 +- editor/icons/ReverseGradient.svg | 2 +- editor/icons/Ruler.svg | 2 +- editor/icons/Script.svg | 2 +- editor/icons/ScriptCreate.svg | 2 +- editor/icons/ScriptExtend.svg | 2 +- editor/icons/ScriptRemove.svg | 2 +- editor/icons/SegmentShape2D.svg | 2 +- editor/icons/SeparationRayShape2D.svg | 2 +- editor/icons/ShapeCast2D.svg | 2 +- editor/icons/SphereShape3D.svg | 2 +- editor/icons/StaticBody2D.svg | 2 +- editor/icons/StatusError.svg | 2 +- editor/icons/StatusSuccess.svg | 2 +- editor/icons/TerrainMatchCorners.svg | 2 +- editor/icons/TerrainMatchCornersAndSides.svg | 2 +- editor/icons/TerrainMatchSides.svg | 2 +- editor/icons/TextEdit.svg | 2 +- editor/icons/Texture3D.svg | 2 +- editor/icons/TimelineIndicator.svg | 2 +- editor/icons/ToolTriangle.svg | 2 +- editor/icons/TransitionEndAutoBig.svg | 2 +- editor/icons/TransitionEndBig.svg | 2 +- editor/icons/TransitionImmediateAutoBig.svg | 2 +- editor/icons/TransitionImmediateBig.svg | 2 +- editor/icons/TransitionSyncAutoBig.svg | 2 +- editor/icons/TransitionSyncBig.svg | 2 +- editor/icons/VFlowContainer.svg | 2 +- .../icons/VisualShaderGraphTextureUniform.svg | 2 +- .../icons/VisualShaderNodeColorConstant.svg | 2 +- editor/icons/VisualShaderNodeColorOp.svg | 2 +- editor/icons/VisualShaderNodeColorUniform.svg | 2 +- editor/icons/VisualShaderNodeCurveTexture.svg | 2 +- .../icons/VisualShaderNodeCurveXYZTexture.svg | 2 +- editor/icons/VisualShaderNodeExpression.svg | 2 +- editor/icons/VisualShaderNodeInput.svg | 2 +- .../VisualShaderNodeTexture2DArrayUniform.svg | 2 +- .../VisualShaderNodeTexture3DUniform.svg | 2 +- .../icons/VisualShaderNodeTextureUniform.svg | 2 +- ...isualShaderNodeTextureUniformTriplanar.svg | 2 +- .../VisualShaderNodeTransformCompose.svg | 2 +- .../VisualShaderNodeTransformDecompose.svg | 2 +- .../VisualShaderNodeTransformVecMult.svg | 2 +- editor/icons/VisualShaderNodeVec3Uniform.svg | 2 +- .../icons/VisualShaderNodeVectorCompose.svg | 2 +- .../icons/VisualShaderNodeVectorDecompose.svg | 2 +- .../icons/VisualShaderNodeVectorDistance.svg | 2 +- editor/icons/VisualShaderNodeVectorFunc.svg | 2 +- editor/icons/VisualShaderNodeVectorLen.svg | 2 +- editor/icons/WorldBoundaryShape2D.svg | 2 +- editor/import/collada.cpp | 11 +- editor/import/dynamicfont_import_settings.cpp | 68 +- .../resource_importer_layered_texture.cpp | 178 +- .../resource_importer_layered_texture.h | 23 + editor/import/resource_importer_scene.cpp | 171 +- editor/import/resource_importer_scene.h | 22 +- editor/import/resource_importer_texture.cpp | 36 +- editor/import_dock.cpp | 2 +- editor/plugins/abstract_polygon_2d_editor.cpp | 9 +- .../animation_blend_space_2d_editor.cpp | 2 +- .../animation_blend_tree_editor_plugin.cpp | 7 +- .../animation_blend_tree_editor_plugin.h | 2 +- .../animation_player_editor_plugin.cpp | 4 +- .../animation_state_machine_editor.cpp | 4 +- .../audio_stream_randomizer_editor_plugin.cpp | 119 + .../audio_stream_randomizer_editor_plugin.h | 57 + editor/plugins/canvas_item_editor_plugin.cpp | 141 +- editor/plugins/canvas_item_editor_plugin.h | 2 + editor/plugins/editor_preview_plugins.cpp | 20 +- editor/plugins/node_3d_editor_gizmos.cpp | 215 +- editor/plugins/node_3d_editor_gizmos.h | 5 + editor/plugins/node_3d_editor_plugin.cpp | 1006 ++- editor/plugins/node_3d_editor_plugin.h | 14 +- .../occluder_instance_3d_editor_plugin.cpp | 8 +- ...packed_scene_translation_parser_plugin.cpp | 9 +- .../packed_scene_translation_parser_plugin.h | 4 +- editor/plugins/path_2d_editor_plugin.cpp | 25 +- editor/plugins/path_3d_editor_plugin.cpp | 35 +- editor/plugins/path_3d_editor_plugin.h | 2 + editor/plugins/polygon_2d_editor_plugin.cpp | 4 +- ...lugin.cpp => polygon_3d_editor_plugin.cpp} | 114 +- ...or_plugin.h => polygon_3d_editor_plugin.h} | 29 +- editor/plugins/replication_editor_plugin.cpp | 390 + editor/plugins/replication_editor_plugin.h | 108 + .../resource_preloader_editor_plugin.cpp | 2 +- editor/plugins/script_editor_plugin.cpp | 36 +- editor/plugins/script_editor_plugin.h | 2 +- editor/plugins/script_text_editor.cpp | 56 +- editor/plugins/script_text_editor.h | 5 + editor/plugins/shader_editor_plugin.cpp | 22 +- editor/plugins/shader_editor_plugin.h | 2 + editor/plugins/skeleton_3d_editor_plugin.cpp | 4 +- .../plugins/sprite_frames_editor_plugin.cpp | 4 +- editor/plugins/text_control_editor_plugin.cpp | 449 +- editor/plugins/text_control_editor_plugin.h | 5 +- editor/plugins/theme_editor_plugin.cpp | 8 +- editor/plugins/tiles/atlas_merging_dialog.cpp | 2 +- editor/plugins/tiles/tile_atlas_view.cpp | 6 +- editor/plugins/tiles/tile_data_editors.cpp | 77 +- editor/plugins/tiles/tile_map_editor.cpp | 102 +- editor/plugins/tiles/tile_map_editor.h | 2 + .../tiles/tile_set_atlas_source_editor.cpp | 14 +- editor/plugins/tiles/tile_set_editor.cpp | 56 +- editor/plugins/tiles/tile_set_editor.h | 2 + editor/plugins/tiles/tiles_editor_plugin.cpp | 101 +- editor/plugins/tiles/tiles_editor_plugin.h | 23 +- .../plugins/visual_shader_editor_plugin.cpp | 1205 +-- editor/plugins/visual_shader_editor_plugin.h | 29 +- editor/plugins/voxel_gi_editor_plugin.cpp | 4 +- editor/project_export.cpp | 2 - editor/project_export.h | 2 - editor/project_manager.cpp | 5 +- editor/project_settings_editor.cpp | 118 +- editor/project_settings_editor.h | 6 +- editor/property_editor.cpp | 8 +- editor/property_selector.cpp | 15 +- editor/quick_open.cpp | 2 +- editor/rename_dialog.cpp | 6 +- editor/scene_tree_dock.cpp | 22 +- editor/scene_tree_dock.h | 1 + editor/scene_tree_editor.cpp | 18 +- editor/script_create_dialog.cpp | 18 +- editor/shader_create_dialog.cpp | 2 +- gles3_builders.py | 2 +- main/main.cpp | 42 +- methods.py | 8 +- misc/dist/html/editor.html | 36 +- misc/dist/html/service-worker.js | 112 +- .../godot_ios.xcodeproj/project.pbxproj | 6 +- misc/scripts/black_format.sh | 16 +- misc/scripts/clang_format.sh | 51 +- misc/scripts/clang_tidy.sh | 31 + misc/scripts/file_format.sh | 7 +- modules/SCsub | 45 +- modules/bullet/shape_bullet.h | 10 +- modules/camera/camera_osx.h | 1 - modules/camera/camera_osx.mm | 27 - modules/csg/csg_shape.cpp | 8 +- modules/csg/csg_shape.h | 2 +- modules/csg/doc_classes/CSGBox3D.xml | 4 +- modules/csg/doc_classes/CSGCombiner3D.xml | 2 + modules/csg/doc_classes/CSGCylinder3D.xml | 6 +- modules/csg/doc_classes/CSGMesh3D.xml | 2 + modules/csg/doc_classes/CSGPolygon3D.xml | 2 + modules/csg/doc_classes/CSGPrimitive3D.xml | 2 + modules/csg/doc_classes/CSGShape3D.xml | 2 + modules/csg/doc_classes/CSGSphere3D.xml | 2 + modules/csg/doc_classes/CSGTorus3D.xml | 6 +- modules/cvtt/SCsub | 11 +- modules/cvtt/image_compress_cvtt.cpp | 34 +- modules/fbx/editor_scene_importer_fbx.cpp | 2 +- modules/fbx/fbx_parser/FBXAnimation.cpp | 2 +- modules/fbx/fbx_parser/FBXDeformer.cpp | 4 +- modules/fbx/fbx_parser/FBXParser.cpp | 2 +- modules/fbx/fbx_parser/FBXProperties.cpp | 3 +- modules/gdnative/gdnative/packed_arrays.cpp | 2 +- modules/gdnative/gdnative/rect2.cpp | 2 + modules/gdnative/gdnative/transform2d.cpp | 1 + modules/gdnative/gdnative/vector2.cpp | 2 + .../gdnative_library_editor_plugin.cpp | 1 - .../gdnative/nativescript/api_generator.cpp | 8 +- .../gdnative/nativescript/nativescript.cpp | 2 +- .../pluginscript/pluginscript_script.cpp | 2 +- .../gdscript/editor/gdscript_highlighter.cpp | 22 +- .../CharacterBody2D/basic_movement.gd | 4 +- .../CharacterBody3D/basic_movement.gd | 4 +- .../VisualShaderNodeCustom/basic.gd | 3 - modules/gdscript/gdscript.cpp | 18 +- modules/gdscript/gdscript_analyzer.cpp | 149 +- modules/gdscript/gdscript_analyzer.h | 2 +- modules/gdscript/gdscript_compiler.cpp | 16 +- modules/gdscript/gdscript_editor.cpp | 30 +- modules/gdscript/gdscript_parser.cpp | 10 +- modules/gdscript/gdscript_parser.h | 5 +- modules/gdscript/gdscript_rpc_callable.cpp | 86 + modules/gdscript/gdscript_rpc_callable.h | 61 + modules/gdscript/gdscript_tokenizer.cpp | 116 +- modules/gdscript/gdscript_warning.cpp | 4 + modules/gdscript/gdscript_warning.h | 1 + .../gdscript_extend_parser.cpp | 4 +- .../language_server/gdscript_workspace.cpp | 8 +- modules/gdscript/language_server/lsp.hpp | 2 +- .../gdscript/tests/gdscript_test_runner.cpp | 60 +- ...m_class_var_assign_with_wrong_enum_type.gd | 10 + ..._class_var_assign_with_wrong_enum_type.out | 2 + ...num_class_var_init_with_wrong_enum_type.gd | 8 + ...um_class_var_init_with_wrong_enum_type.out | 2 + ...m_local_var_assign_with_wrong_enum_type.gd | 8 + ..._local_var_assign_with_wrong_enum_type.out | 2 + ...num_local_var_init_with_wrong_enum_type.gd | 6 + ...um_local_var_init_with_wrong_enum_type.out | 2 + .../enum_assign_enum_to_int_typed_var.gd | 13 + .../enum_assign_enum_to_int_typed_var.out | 5 + .../enum_assign_int_cast_to_same_enum.gd | 13 + .../enum_assign_int_cast_to_same_enum.out | 5 + ...num_assign_other_enum_cast_to_same_enum.gd | 14 + ...um_assign_other_enum_cast_to_same_enum.out | 5 + .../features/enum_assign_same_enum.gd | 13 + .../features/enum_assign_same_enum.out | 5 + .../features/enum_is_treated_as_int.gd | 21 + .../features/enum_is_treated_as_int.out | 7 + .../enum_type_is_treated_as_dictionary.gd | 13 + .../enum_type_is_treated_as_dictionary.out | 7 + .../enum_assign_int_without_casting.gd | 15 + .../enum_assign_int_without_casting.out | 21 + .../errors/callable_call_after_free_object.gd | 1 + .../callable_call_after_free_object.out | 2 +- modules/glslang/glslang_resource_limits.h | 18 +- modules/gltf/gltf_document.cpp | 43 +- modules/gridmap/grid_map.cpp | 1 + modules/gridmap/grid_map_editor_plugin.cpp | 2 +- modules/meshoptimizer/register_types.cpp | 6 + modules/mobile_vr/mobile_vr_interface.cpp | 2 +- modules/mobile_vr/mobile_vr_interface.h | 2 +- modules/modules_builders.py | 7 +- modules/mono/build_scripts/mono_reg_utils.py | 8 +- modules/mono/csharp_script.cpp | 4 +- modules/mono/csharp_script.h | 2 - .../GodotTools.OpenVisualStudio/Program.cs | 25 +- .../GodotTools/Export/AotBuilder.cs | 1 - modules/mono/editor/bindings_generator.cpp | 4 +- .../CharacterBody2D/basic_movement.cs | 4 +- .../CharacterBody3D/basic_movement.cs | 4 +- .../VisualShaderNodeCustom/basic.cs | 5 - .../GodotSharp/Core/StringExtensions.cs | 51 +- .../GodotSharp/GodotSharp/Core/Vector2.cs | 24 +- .../GodotSharp/GodotSharp/Core/Vector3.cs | 25 +- .../navigation/navigation_mesh_generator.cpp | 23 +- .../navigation/navigation_mesh_generator.h | 2 +- modules/ogg/ogg_packet_sequence.h | 2 +- modules/raycast/raycast_occlusion_cull.cpp | 5 +- modules/text_server_adv/SCsub | 1 - modules/text_server_adv/text_server_adv.cpp | 508 +- modules/text_server_adv/text_server_adv.h | 29 +- modules/text_server_fb/text_server_fb.cpp | 318 +- modules/text_server_fb/text_server_fb.h | 33 +- modules/theora/video_stream_theora.cpp | 1 + .../editor/visual_script_editor.cpp | 3 +- .../editor/visual_script_editor.h | 18 +- .../visual_script_property_selector.cpp | 6 +- .../editor/visual_script_property_selector.h | 8 +- modules/visual_script/icons/VisualScript.svg | 2 +- .../icons/VisualScriptInternal.svg | 2 +- modules/visual_script/visual_script.cpp | 3 - .../visual_script_expression.cpp | 59 +- .../visual_script_func_nodes.cpp | 2 +- modules/visual_script/visual_script_nodes.cpp | 10 +- .../doc_classes/WebRTCPeerConnection.xml | 2 +- modules/webrtc/webrtc_peer_connection.cpp | 2 +- modules/websocket/wsl_client.cpp | 72 +- modules/websocket/wsl_client.h | 5 +- modules/webxr/webxr_interface_js.cpp | 2 +- modules/webxr/webxr_interface_js.h | 2 +- platform/android/SCsub | 1 + platform/android/display_server_android.cpp | 28 + platform/android/display_server_android.h | 4 + platform/android/export/export_plugin.cpp | 24 +- .../android/export/gradle_export_util.cpp | 5 +- platform/android/java/app/AndroidManifest.xml | 11 +- .../src/org/godotengine/godot/GodotIO.java | 8 + platform/android/java_godot_io_wrapper.cpp | 14 + platform/android/java_godot_io_wrapper.h | 2 + platform/android/plugin/godot_plugin_jni.cpp | 2 +- platform/iphone/detect.py | 1 - platform/iphone/display_server_iphone.h | 3 + platform/iphone/display_server_iphone.mm | 22 + platform/iphone/export/export_plugin.cpp | 34 +- platform/iphone/export/export_plugin.h | 2 +- platform/iphone/godot_view.h | 5 + platform/iphone/godot_view.mm | 8 +- .../iphone/godot_view_gesture_recognizer.mm | 31 +- platform/javascript/api/api.cpp | 9 + .../javascript/api/javascript_singleton.h | 2 + .../javascript/display_server_javascript.cpp | 7 +- .../javascript/display_server_javascript.h | 1 + platform/javascript/export/export_plugin.cpp | 54 +- platform/javascript/export/export_plugin.h | 2 +- platform/javascript/godot_js.h | 2 + .../javascript/http_client_javascript.cpp | 5 + platform/javascript/javascript_singleton.cpp | 8 + platform/javascript/js/engine/config.js | 10 + platform/javascript/js/engine/engine.js | 3 + .../javascript/js/libs/library_godot_input.js | 2 +- .../javascript/js/libs/library_godot_os.js | 55 + platform/javascript/os_javascript.cpp | 16 + platform/javascript/os_javascript.h | 5 + platform/linuxbsd/crash_handler_linuxbsd.cpp | 7 +- platform/linuxbsd/display_server_x11.cpp | 114 +- platform/linuxbsd/display_server_x11.h | 7 +- platform/linuxbsd/gl_manager_x11.cpp | 44 +- platform/linuxbsd/joypad_linux.cpp | 5 +- platform/linuxbsd/os_linuxbsd.cpp | 8 +- platform/osx/SCsub | 11 +- platform/osx/crash_handler_osx.mm | 7 +- platform/osx/detect.py | 10 +- platform/osx/display_server_osx.h | 146 +- platform/osx/display_server_osx.mm | 2880 ++----- platform/osx/export/export_plugin.cpp | 18 + platform/osx/export/export_plugin.h | 2 +- ..._manager_osx.h => gl_manager_osx_legacy.h} | 39 +- ...anager_osx.mm => gl_manager_osx_legacy.mm} | 121 +- platform/osx/godot_application.h | 42 + platform/osx/godot_application.mm | 53 + platform/osx/godot_application_delegate.h | 45 + platform/osx/godot_application_delegate.mm | 132 + platform/osx/godot_content_view.h | 65 + platform/osx/godot_content_view.mm | 760 ++ platform/osx/godot_main_osx.mm | 23 +- platform/osx/godot_menu_item.h | 52 + platform/osx/godot_window.h | 47 + platform/osx/godot_window.mm | 69 + platform/osx/godot_window_delegate.h | 47 + platform/osx/godot_window_delegate.mm | 254 + platform/osx/joypad_osx.cpp | 12 +- platform/osx/joypad_osx.h | 2 +- platform/osx/key_mapping_osx.h | 52 + platform/osx/key_mapping_osx.mm | 477 ++ platform/osx/os_osx.h | 27 +- platform/osx/os_osx.mm | 438 +- platform/osx/osx_terminal_logger.h | 44 + platform/osx/osx_terminal_logger.mm | 81 + platform/osx/vulkan_context_osx.mm | 2 +- platform/windows/crash_handler_windows.cpp | 7 +- platform/windows/display_server_windows.cpp | 259 +- platform/windows/display_server_windows.h | 7 +- platform/windows/export/export_plugin.cpp | 48 +- platform/windows/export/export_plugin.h | 1 + platform/windows/godot_windows.cpp | 7 +- platform/windows/os_windows.cpp | 38 +- scene/2d/animated_sprite_2d.cpp | 2 +- scene/2d/collision_object_2d.cpp | 4 +- scene/2d/collision_object_2d.h | 2 +- scene/2d/gpu_particles_2d.cpp | 58 + scene/2d/gpu_particles_2d.h | 16 + scene/2d/line_2d.cpp | 2 +- scene/2d/navigation_obstacle_2d.cpp | 7 +- scene/2d/navigation_region_2d.cpp | 10 +- scene/2d/navigation_region_2d.h | 4 +- scene/2d/node_2d.cpp | 2 +- scene/2d/physics_body_2d.cpp | 50 +- scene/2d/physics_body_2d.h | 14 +- scene/2d/ray_cast_2d.cpp | 27 +- scene/2d/ray_cast_2d.h | 6 +- scene/2d/shape_cast_2d.cpp | 20 +- scene/2d/shape_cast_2d.h | 6 +- scene/2d/tile_map.cpp | 19 +- scene/3d/collision_object_3d.cpp | 4 +- scene/3d/collision_object_3d.h | 2 +- scene/3d/navigation_obstacle_3d.cpp | 7 +- scene/3d/node_3d.cpp | 5 +- scene/3d/occluder_instance_3d.cpp | 548 +- scene/3d/occluder_instance_3d.h | 128 +- scene/3d/physics_body_3d.cpp | 31 +- scene/3d/physics_body_3d.h | 8 +- scene/3d/ray_cast_3d.cpp | 27 +- scene/3d/ray_cast_3d.h | 6 +- scene/3d/skeleton_3d.cpp | 2 +- scene/3d/sprite_3d.cpp | 3 +- scene/3d/xr_nodes.cpp | 8 +- scene/SCsub | 1 + scene/animation/animation_blend_space_1d.cpp | 4 +- scene/animation/animation_blend_space_2d.cpp | 6 +- scene/animation/animation_blend_tree.cpp | 6 +- .../animation_node_state_machine.cpp | 6 +- scene/animation/animation_player.cpp | 4 +- scene/animation/animation_tree.cpp | 36 +- scene/animation/animation_tree.h | 16 +- scene/animation/tween.cpp | 13 +- scene/debugger/scene_debugger.h | 1 + scene/gui/base_button.cpp | 13 +- scene/gui/button.cpp | 5 +- scene/gui/code_edit.cpp | 75 +- scene/gui/control.cpp | 14 +- scene/gui/control.h | 2 - scene/gui/file_dialog.cpp | 15 +- scene/gui/file_dialog.h | 2 +- scene/gui/graph_edit.cpp | 6 +- scene/gui/label.cpp | 40 +- scene/gui/label.h | 1 + scene/gui/line_edit.cpp | 17 +- scene/gui/line_edit.h | 4 - scene/gui/menu_button.cpp | 11 +- scene/gui/option_button.cpp | 96 +- scene/gui/panel.cpp | 23 +- scene/gui/panel.h | 15 - scene/gui/popup_menu.cpp | 168 +- scene/gui/popup_menu.h | 3 +- scene/gui/rich_text_label.cpp | 141 +- scene/gui/rich_text_label.h | 15 + scene/gui/tab_bar.cpp | 555 +- scene/gui/tab_bar.h | 16 +- scene/gui/tab_container.cpp | 2 +- scene/gui/text_edit.cpp | 128 +- scene/gui/text_edit.h | 9 +- scene/gui/texture_button.cpp | 105 +- scene/gui/texture_button.h | 8 +- scene/gui/tree.cpp | 65 +- scene/gui/tree.h | 30 +- scene/gui/view_panner.cpp | 11 +- scene/gui/view_panner.h | 3 + scene/main/canvas_item.cpp | 74 +- scene/main/canvas_item.h | 5 +- scene/main/canvas_layer.cpp | 33 + scene/main/canvas_layer.h | 4 + scene/main/http_request.cpp | 71 +- scene/main/node.cpp | 36 +- scene/main/node.h | 3 +- scene/main/scene_tree.cpp | 5 +- scene/main/scene_tree.h | 2 +- scene/main/viewport.cpp | 83 +- scene/main/viewport.h | 8 +- scene/main/window.cpp | 1 + scene/main/window.h | 1 + scene/multiplayer/SCsub | 5 + scene/multiplayer/multiplayer_spawner.cpp | 227 + scene/multiplayer/multiplayer_spawner.h | 101 + .../multiplayer/multiplayer_synchronizer.cpp | 158 + scene/multiplayer/multiplayer_synchronizer.h | 72 + scene/multiplayer/scene_cache_interface.cpp | 249 + scene/multiplayer/scene_cache_interface.h | 82 + .../scene_replication_interface.cpp | 411 + .../multiplayer/scene_replication_interface.h | 84 + scene/multiplayer/scene_replication_state.cpp | 258 + scene/multiplayer/scene_replication_state.h | 121 + .../multiplayer/scene_rpc_interface.cpp | 123 +- .../multiplayer/scene_rpc_interface.h | 26 +- scene/property_utils.cpp | 2 +- scene/register_scene_types.cpp | 27 +- scene/resources/animation.cpp | 24 +- scene/resources/bit_map.cpp | 2 + scene/resources/box_shape_3d.cpp | 2 +- scene/resources/capsule_shape_3d.h | 4 +- scene/resources/curve.cpp | 14 +- scene/resources/cylinder_shape_3d.h | 2 +- .../resources/default_theme/default_theme.cpp | 10 +- scene/resources/environment.cpp | 37 +- scene/resources/environment.h | 6 + scene/resources/font.cpp | 3 +- scene/resources/mesh.cpp | 4 +- scene/resources/mesh_library.cpp | 33 +- scene/resources/primitive_meshes.h | 10 +- scene/resources/resource_format_text.cpp | 14 +- scene/resources/scene_replication_config.cpp | 187 + scene/resources/scene_replication_config.h | 90 + scene/resources/skeleton_modification_3d.cpp | 3 +- scene/resources/sky_material.cpp | 2 - scene/resources/surface_tool.cpp | 79 + scene/resources/surface_tool.h | 12 + scene/resources/syntax_highlighter.cpp | 14 +- scene/resources/text_line.cpp | 6 +- scene/resources/text_line.h | 2 +- scene/resources/text_paragraph.cpp | 6 +- scene/resources/text_paragraph.h | 2 +- scene/resources/texture.cpp | 6 +- scene/resources/texture.h | 6 +- scene/resources/theme.cpp | 4 +- scene/resources/tile_set.cpp | 25 +- scene/resources/tile_set.h | 2 +- scene/resources/visual_shader.cpp | 833 +- scene/resources/visual_shader.h | 14 +- scene/resources/visual_shader_nodes.cpp | 1488 +++- scene/resources/visual_shader_nodes.h | 259 +- .../visual_shader_particle_nodes.cpp | 205 +- .../resources/visual_shader_particle_nodes.h | 14 +- scene/resources/visual_shader_sdf_nodes.cpp | 32 +- scene/resources/world_2d.cpp | 4 +- servers/audio/audio_stream.cpp | 370 +- servers/audio/audio_stream.h | 77 +- servers/audio/effects/audio_effect_record.cpp | 2 +- servers/audio_server.cpp | 6 +- servers/display_server.cpp | 13 + servers/display_server.h | 15 +- servers/display_server_headless.h | 1 + servers/physics_3d/godot_soft_body_3d.cpp | 2 +- servers/register_server_types.cpp | 6 +- servers/rendering/rasterizer_dummy.h | 2 +- servers/rendering/renderer_rd/effects_rd.cpp | 63 +- servers/rendering/renderer_rd/effects_rd.h | 8 +- .../render_forward_clustered.cpp | 4 + .../forward_mobile/render_forward_mobile.cpp | 6 +- .../renderer_scene_environment_rd.cpp | 4 +- .../renderer_scene_environment_rd.h | 4 +- .../renderer_rd/renderer_scene_render_rd.cpp | 45 +- .../renderer_rd/renderer_scene_render_rd.h | 4 +- .../renderer_rd/renderer_scene_sky_rd.cpp | 1 + .../renderer_rd/renderer_storage_rd.cpp | 45 +- .../renderer_rd/renderer_storage_rd.h | 7 +- .../rendering/renderer_rd/shaders/copy.glsl | 91 +- .../shaders/screen_space_reflection.glsl | 3 +- .../renderer_rd/shaders/tonemap.glsl | 10 +- servers/rendering/renderer_scene.h | 2 +- servers/rendering/renderer_scene_cull.cpp | 8 +- servers/rendering/renderer_scene_cull.h | 2 +- servers/rendering/renderer_scene_render.h | 2 +- servers/rendering/renderer_viewport.cpp | 49 +- servers/rendering/rendering_device_binds.cpp | 4 +- .../rendering/rendering_server_default.cpp | 6 + servers/rendering/rendering_server_default.h | 2 +- servers/rendering/shader_compiler.cpp | 11 +- servers/rendering/shader_language.cpp | 191 +- servers/rendering/shader_language.h | 5 +- servers/rendering_server.cpp | 4 +- servers/rendering_server.h | 4 +- servers/text/text_server_extension.cpp | 60 +- servers/text/text_server_extension.h | 25 +- servers/text_server.cpp | 6 +- servers/text_server.h | 21 +- servers/xr/xr_interface.cpp | 3 - servers/xr/xr_interface.h | 9 +- servers/xr/xr_interface_extension.cpp | 36 +- servers/xr/xr_interface_extension.h | 11 +- servers/xr/xr_positional_tracker.cpp | 10 +- servers/xr_server.cpp | 50 +- servers/xr_server.h | 18 +- tests/core/math/test_expression.h | 2 +- tests/core/math/test_math.cpp | 6 +- tests/core/math/test_rect2.h | 272 +- tests/core/math/test_rect2i.h | 311 + tests/core/math/test_vector2.h | 1 + tests/core/math/test_vector2i.h | 1 + tests/core/object/test_method_bind.h | 16 + tests/core/string/test_string.h | 18 +- tests/scene/test_animation.h | 314 + tests/scene/test_code_edit.h | 95 + tests/test_main.cpp | 2 + thirdparty/README.md | 20 +- thirdparty/cvtt/ConvectionKernels.cpp | 7586 ----------------- thirdparty/cvtt/ConvectionKernels.h | 169 +- thirdparty/cvtt/ConvectionKernels_API.cpp | 346 + .../cvtt/ConvectionKernels_AggregatedError.h | 55 + thirdparty/cvtt/ConvectionKernels_BC67.cpp | 3485 ++++++++ thirdparty/cvtt/ConvectionKernels_BC67.h | 99 + thirdparty/cvtt/ConvectionKernels_BC6H_IO.cpp | 881 ++ thirdparty/cvtt/ConvectionKernels_BC6H_IO.h | 16 + thirdparty/cvtt/ConvectionKernels_BC7_Prio.h | 17 + .../cvtt/ConvectionKernels_BC7_PrioData.cpp | 1301 +++ .../cvtt/ConvectionKernels_BC7_SingleColor.h | 2 + .../cvtt/ConvectionKernels_BCCommon.cpp | 46 + thirdparty/cvtt/ConvectionKernels_BCCommon.h | 104 + thirdparty/cvtt/ConvectionKernels_Config.h | 12 + thirdparty/cvtt/ConvectionKernels_ETC.cpp | 3147 +++++++ thirdparty/cvtt/ConvectionKernels_ETC.h | 126 + thirdparty/cvtt/ConvectionKernels_ETC1.h | 29 + thirdparty/cvtt/ConvectionKernels_ETC2.h | 35 + .../cvtt/ConvectionKernels_ETC2_Rounding.h | 27 + .../cvtt/ConvectionKernels_EndpointRefiner.h | 181 + .../cvtt/ConvectionKernels_EndpointSelector.h | 153 + .../ConvectionKernels_FakeBT709_Rounding.h | 282 + .../cvtt/ConvectionKernels_IndexSelector.cpp | 66 + .../cvtt/ConvectionKernels_IndexSelector.h | 147 + .../cvtt/ConvectionKernels_IndexSelectorHDR.h | 155 + ...ConvectionKernels_PackedCovarianceMatrix.h | 68 + .../cvtt/ConvectionKernels_ParallelMath.h | 1816 ++++ thirdparty/cvtt/ConvectionKernels_S3TC.cpp | 1054 +++ thirdparty/cvtt/ConvectionKernels_S3TC.h | 51 + .../cvtt/ConvectionKernels_S3TC_SingleColor.h | 304 + .../cvtt/ConvectionKernels_SingleFile.cpp | 48 + .../ConvectionKernels_UnfinishedEndpoints.h | 121 + thirdparty/cvtt/ConvectionKernels_Util.cpp | 88 + thirdparty/cvtt/ConvectionKernels_Util.h | 21 + thirdparty/cvtt/etc_notes.txt | 27 + .../harfbuzz/src/hb-aat-layout-common.hh | 2 +- .../harfbuzz/src/hb-aat-layout-just-table.hh | 2 +- thirdparty/harfbuzz/src/hb-algs.hh | 32 +- thirdparty/harfbuzz/src/hb-array.hh | 2 +- thirdparty/harfbuzz/src/hb-bimap.hh | 14 - thirdparty/harfbuzz/src/hb-buffer.cc | 185 +- thirdparty/harfbuzz/src/hb-buffer.h | 92 +- thirdparty/harfbuzz/src/hb-buffer.hh | 168 +- .../harfbuzz/src/hb-cff-interp-common.hh | 14 +- .../harfbuzz/src/hb-cff-interp-cs-common.hh | 9 +- thirdparty/harfbuzz/src/hb-cff2-interp-cs.hh | 14 +- thirdparty/harfbuzz/src/hb-common.cc | 78 +- thirdparty/harfbuzz/src/hb-coretext.cc | 9 +- thirdparty/harfbuzz/src/hb-directwrite.cc | 3 +- thirdparty/harfbuzz/src/hb-draw.h | 2 +- thirdparty/harfbuzz/src/hb-face.cc | 23 +- thirdparty/harfbuzz/src/hb-font.cc | 114 +- thirdparty/harfbuzz/src/hb-font.h | 8 +- thirdparty/harfbuzz/src/hb-font.hh | 3 + thirdparty/harfbuzz/src/hb-graphite2.cc | 3 +- thirdparty/harfbuzz/src/hb-iter.hh | 6 +- thirdparty/harfbuzz/src/hb-kern.hh | 9 +- thirdparty/harfbuzz/src/hb-machinery.hh | 6 +- thirdparty/harfbuzz/src/hb-map.hh | 71 +- thirdparty/harfbuzz/src/hb-meta.hh | 39 +- .../harfbuzz/src/hb-ms-feature-ranges.cc | 177 - .../harfbuzz/src/hb-ms-feature-ranges.hh | 145 +- thirdparty/harfbuzz/src/hb-object.hh | 6 +- thirdparty/harfbuzz/src/hb-ot-cff-common.hh | 2 - thirdparty/harfbuzz/src/hb-ot-cff1-table.hh | 38 +- thirdparty/harfbuzz/src/hb-ot-cff2-table.hh | 63 +- thirdparty/harfbuzz/src/hb-ot-cmap-table.hh | 13 +- .../harfbuzz/src/hb-ot-color-cbdt-table.hh | 24 +- .../harfbuzz/src/hb-ot-color-colr-table.hh | 14 +- .../src/hb-ot-color-colrv1-closure.hh | 2 +- .../harfbuzz/src/hb-ot-color-sbix-table.hh | 9 +- .../harfbuzz/src/hb-ot-color-svg-table.hh | 8 +- thirdparty/harfbuzz/src/hb-ot-color.cc | 12 +- thirdparty/harfbuzz/src/hb-ot-glyf-table.hh | 19 +- thirdparty/harfbuzz/src/hb-ot-hmtx-table.hh | 20 +- .../harfbuzz/src/hb-ot-layout-common.hh | 21 +- .../harfbuzz/src/hb-ot-layout-gdef-table.hh | 17 +- .../harfbuzz/src/hb-ot-layout-gpos-table.hh | 180 +- .../harfbuzz/src/hb-ot-layout-gsub-table.hh | 22 +- .../harfbuzz/src/hb-ot-layout-gsubgpos.hh | 304 +- thirdparty/harfbuzz/src/hb-ot-layout.cc | 8 +- thirdparty/harfbuzz/src/hb-ot-layout.hh | 5 +- thirdparty/harfbuzz/src/hb-ot-meta-table.hh | 8 +- thirdparty/harfbuzz/src/hb-ot-metrics.cc | 45 +- thirdparty/harfbuzz/src/hb-ot-name-table.hh | 13 +- .../harfbuzz/src/hb-ot-post-table-v2subset.hh | 7 +- thirdparty/harfbuzz/src/hb-ot-post-table.hh | 17 +- .../src/hb-ot-shape-complex-arabic-win1256.hh | 32 +- .../src/hb-ot-shape-complex-arabic.cc | 21 + .../src/hb-ot-shape-complex-hangul.cc | 4 +- .../src/hb-ot-shape-complex-syllabic.cc | 2 +- .../harfbuzz/src/hb-ot-shape-complex-thai.cc | 2 +- .../hb-ot-shape-complex-vowel-constraints.cc | 2 +- .../harfbuzz/src/hb-ot-shape-fallback.cc | 19 + .../harfbuzz/src/hb-ot-shape-normalize.cc | 7 +- thirdparty/harfbuzz/src/hb-ot-shape.cc | 29 +- thirdparty/harfbuzz/src/hb-ot-tag-table.hh | 97 +- .../harfbuzz/src/hb-ot-var-fvar-table.hh | 2 +- .../harfbuzz/src/hb-ot-var-gvar-table.hh | 10 +- .../harfbuzz/src/hb-ot-var-hvar-table.hh | 7 +- thirdparty/harfbuzz/src/hb-ot-var.cc | 3 + thirdparty/harfbuzz/src/hb-ot-var.h | 2 +- thirdparty/harfbuzz/src/hb-repacker.hh | 38 +- thirdparty/harfbuzz/src/hb-serialize.hh | 14 +- thirdparty/harfbuzz/src/hb-style.cc | 15 +- .../harfbuzz/src/hb-subset-cff-common.hh | 102 +- thirdparty/harfbuzz/src/hb-subset-cff1.cc | 42 +- thirdparty/harfbuzz/src/hb-subset-cff2.cc | 43 +- thirdparty/harfbuzz/src/hb-subset-plan.cc | 26 +- thirdparty/harfbuzz/src/hb-uniscribe.cc | 3 +- thirdparty/harfbuzz/src/hb-vector.hh | 200 +- thirdparty/harfbuzz/src/hb-version.h | 6 +- thirdparty/harfbuzz/src/hb.hh | 2 + thirdparty/libwebp/AUTHORS | 1 + thirdparty/libwebp/src/dec/vp8_dec.c | 2 +- thirdparty/libwebp/src/dec/vp8i_dec.h | 2 +- thirdparty/libwebp/src/dec/vp8l_dec.c | 2 +- thirdparty/libwebp/src/demux/anim_decode.c | 40 +- thirdparty/libwebp/src/demux/demux.c | 2 +- thirdparty/libwebp/src/dsp/dsp.h | 7 +- thirdparty/libwebp/src/dsp/enc_neon.c | 2 +- thirdparty/libwebp/src/dsp/lossless.c | 58 +- thirdparty/libwebp/src/dsp/lossless.h | 45 +- thirdparty/libwebp/src/dsp/lossless_common.h | 2 +- thirdparty/libwebp/src/dsp/lossless_enc.c | 2 +- .../libwebp/src/dsp/lossless_mips_dsp_r2.c | 37 +- thirdparty/libwebp/src/dsp/lossless_neon.c | 20 +- thirdparty/libwebp/src/dsp/lossless_sse2.c | 41 +- thirdparty/libwebp/src/dsp/msa_macro.h | 5 + thirdparty/libwebp/src/dsp/neon.h | 7 +- thirdparty/libwebp/src/dsp/yuv.h | 2 +- thirdparty/libwebp/src/enc/frame_enc.c | 9 +- thirdparty/libwebp/src/enc/predictor_enc.c | 2 +- thirdparty/libwebp/src/enc/quant_enc.c | 58 +- thirdparty/libwebp/src/enc/vp8i_enc.h | 2 +- thirdparty/libwebp/src/mux/muxi.h | 2 +- .../libwebp/src/utils/huffman_encode_utils.c | 2 +- .../src/utils/quant_levels_dec_utils.c | 2 +- thirdparty/libwebp/src/utils/utils.c | 2 +- thirdparty/libwebp/src/webp/decode.h | 2 +- .../patches/polypartition-godot-types.patch | 85 +- thirdparty/misc/polypartition.cpp | 2 + thirdparty/msdfgen/core/edge-coloring.cpp | 4 +- thirdparty/msdfgen/core/equation-solver.cpp | 47 +- thirdparty/thorvg/AUTHORS | 2 + thirdparty/thorvg/inc/config.h | 2 +- .../patches/thorvg-pr1159-mingw-fix.patch | 73 - .../patches/thorvg-pr1166-vs2017-minmax.patch | 49 - .../thorvg/src/lib/sw_engine/tvgSwImage.cpp | 4 +- .../thorvg/src/lib/sw_engine/tvgSwRaster.cpp | 36 +- .../lib/sw_engine/tvgSwRasterTexmapInternal.h | 6 +- thirdparty/thorvg/src/lib/tvgMath.h | 2 +- .../thorvg/src/loaders/jpg/tvgJpgLoader.cpp | 5 + thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp | 5 +- thirdparty/thorvg/src/loaders/jpg/tvgJpgd.h | 2 +- .../thorvg/src/loaders/png/tvgPngLoader.cpp | 9 +- .../thorvg/src/loaders/svg/tvgSvgLoader.cpp | 224 +- .../thorvg/src/loaders/svg/tvgXmlParser.cpp | 6 +- thirdparty/thorvg/update-thorvg.sh | 2 +- 1068 files changed, 38554 insertions(+), 21815 deletions(-) create mode 100644 core/math/rect2i.cpp create mode 100644 core/math/rect2i.h create mode 100644 core/math/vector2i.cpp create mode 100644 core/math/vector2i.h delete mode 100644 core/multiplayer/multiplayer_replicator.cpp delete mode 100644 core/multiplayer/multiplayer_replicator.h create mode 100644 core/string/char_utils.h create mode 100644 doc/classes/ArrayOccluder3D.xml delete mode 100644 doc/classes/AudioStreamRandomPitch.xml create mode 100644 doc/classes/AudioStreamRandomizer.xml create mode 100644 doc/classes/BoxOccluder3D.xml delete mode 100644 doc/classes/MultiplayerReplicator.xml create mode 100644 doc/classes/MultiplayerSpawner.xml create mode 100644 doc/classes/MultiplayerSynchronizer.xml create mode 100644 doc/classes/PolygonOccluder3D.xml create mode 100644 doc/classes/QuadOccluder3D.xml create mode 100644 doc/classes/SceneReplicationConfig.xml create mode 100644 doc/classes/SphereOccluder3D.xml create mode 100644 doc/classes/VisualShaderNodeDerivativeFunc.xml delete mode 100644 doc/classes/VisualShaderNodeScalarDerivativeFunc.xml create mode 100644 doc/classes/VisualShaderNodeVec2Constant.xml create mode 100644 doc/classes/VisualShaderNodeVec2Uniform.xml create mode 100644 doc/classes/VisualShaderNodeVectorBase.xml delete mode 100644 doc/classes/VisualShaderNodeVectorDerivativeFunc.xml rename editor/{settings_config_dialog.cpp => editor_settings_dialog.cpp} (91%) rename editor/{settings_config_dialog.h => editor_settings_dialog.h} (95%) create mode 100644 editor/plugins/audio_stream_randomizer_editor_plugin.cpp create mode 100644 editor/plugins/audio_stream_randomizer_editor_plugin.h rename editor/plugins/{collision_polygon_3d_editor_plugin.cpp => polygon_3d_editor_plugin.cpp} (82%) rename editor/plugins/{collision_polygon_3d_editor_plugin.h => polygon_3d_editor_plugin.h} (85%) create mode 100644 editor/plugins/replication_editor_plugin.cpp create mode 100644 editor/plugins/replication_editor_plugin.h create mode 100755 misc/scripts/clang_tidy.sh create mode 100644 modules/gdscript/gdscript_rpc_callable.cpp create mode 100644 modules/gdscript/gdscript_rpc_callable.h create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.out create mode 100644 modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.gd create mode 100644 modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out rename platform/osx/{gl_manager_osx.h => gl_manager_osx_legacy.h} (81%) rename platform/osx/{gl_manager_osx.mm => gl_manager_osx_legacy.mm} (73%) create mode 100644 platform/osx/godot_application.h create mode 100644 platform/osx/godot_application.mm create mode 100644 platform/osx/godot_application_delegate.h create mode 100644 platform/osx/godot_application_delegate.mm create mode 100644 platform/osx/godot_content_view.h create mode 100644 platform/osx/godot_content_view.mm create mode 100644 platform/osx/godot_menu_item.h create mode 100644 platform/osx/godot_window.h create mode 100644 platform/osx/godot_window.mm create mode 100644 platform/osx/godot_window_delegate.h create mode 100644 platform/osx/godot_window_delegate.mm create mode 100644 platform/osx/key_mapping_osx.h create mode 100644 platform/osx/key_mapping_osx.mm create mode 100644 platform/osx/osx_terminal_logger.h create mode 100644 platform/osx/osx_terminal_logger.mm create mode 100644 scene/multiplayer/SCsub create mode 100644 scene/multiplayer/multiplayer_spawner.cpp create mode 100644 scene/multiplayer/multiplayer_spawner.h create mode 100644 scene/multiplayer/multiplayer_synchronizer.cpp create mode 100644 scene/multiplayer/multiplayer_synchronizer.h create mode 100644 scene/multiplayer/scene_cache_interface.cpp create mode 100644 scene/multiplayer/scene_cache_interface.h create mode 100644 scene/multiplayer/scene_replication_interface.cpp create mode 100644 scene/multiplayer/scene_replication_interface.h create mode 100644 scene/multiplayer/scene_replication_state.cpp create mode 100644 scene/multiplayer/scene_replication_state.h rename core/multiplayer/rpc_manager.cpp => scene/multiplayer/scene_rpc_interface.cpp (80%) rename core/multiplayer/rpc_manager.h => scene/multiplayer/scene_rpc_interface.h (83%) create mode 100644 scene/resources/scene_replication_config.cpp create mode 100644 scene/resources/scene_replication_config.h create mode 100644 tests/core/math/test_rect2i.h create mode 100644 tests/scene/test_animation.h delete mode 100644 thirdparty/cvtt/ConvectionKernels.cpp create mode 100644 thirdparty/cvtt/ConvectionKernels_API.cpp create mode 100644 thirdparty/cvtt/ConvectionKernels_AggregatedError.h create mode 100644 thirdparty/cvtt/ConvectionKernels_BC67.cpp create mode 100644 thirdparty/cvtt/ConvectionKernels_BC67.h create mode 100644 thirdparty/cvtt/ConvectionKernels_BC6H_IO.cpp create mode 100644 thirdparty/cvtt/ConvectionKernels_BC6H_IO.h create mode 100644 thirdparty/cvtt/ConvectionKernels_BC7_Prio.h create mode 100644 thirdparty/cvtt/ConvectionKernels_BC7_PrioData.cpp create mode 100644 thirdparty/cvtt/ConvectionKernels_BCCommon.cpp create mode 100644 thirdparty/cvtt/ConvectionKernels_BCCommon.h create mode 100644 thirdparty/cvtt/ConvectionKernels_Config.h create mode 100644 thirdparty/cvtt/ConvectionKernels_ETC.cpp create mode 100644 thirdparty/cvtt/ConvectionKernels_ETC.h create mode 100644 thirdparty/cvtt/ConvectionKernels_ETC1.h create mode 100644 thirdparty/cvtt/ConvectionKernels_ETC2.h create mode 100644 thirdparty/cvtt/ConvectionKernels_ETC2_Rounding.h create mode 100644 thirdparty/cvtt/ConvectionKernels_EndpointRefiner.h create mode 100644 thirdparty/cvtt/ConvectionKernels_EndpointSelector.h create mode 100644 thirdparty/cvtt/ConvectionKernels_FakeBT709_Rounding.h create mode 100644 thirdparty/cvtt/ConvectionKernels_IndexSelector.cpp create mode 100644 thirdparty/cvtt/ConvectionKernels_IndexSelector.h create mode 100644 thirdparty/cvtt/ConvectionKernels_IndexSelectorHDR.h create mode 100644 thirdparty/cvtt/ConvectionKernels_PackedCovarianceMatrix.h create mode 100644 thirdparty/cvtt/ConvectionKernels_ParallelMath.h create mode 100644 thirdparty/cvtt/ConvectionKernels_S3TC.cpp create mode 100644 thirdparty/cvtt/ConvectionKernels_S3TC.h create mode 100644 thirdparty/cvtt/ConvectionKernels_S3TC_SingleColor.h create mode 100644 thirdparty/cvtt/ConvectionKernels_SingleFile.cpp create mode 100644 thirdparty/cvtt/ConvectionKernels_UnfinishedEndpoints.h create mode 100644 thirdparty/cvtt/ConvectionKernels_Util.cpp create mode 100644 thirdparty/cvtt/ConvectionKernels_Util.h create mode 100644 thirdparty/cvtt/etc_notes.txt delete mode 100644 thirdparty/harfbuzz/src/hb-ms-feature-ranges.cc delete mode 100644 thirdparty/thorvg/patches/thorvg-pr1159-mingw-fix.patch delete mode 100644 thirdparty/thorvg/patches/thorvg-pr1166-vs2017-minmax.patch diff --git a/.clang-tidy b/.clang-tidy index 59d0facb9677..b623aef13367 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,10 +1,9 @@ --- -Checks: 'clang-diagnostic-*,clang-analyzer-*,-*,modernize-redundant-void-arg,modernize-use-bool-literals,modernize-use-default-member-init,modernize-use-nullptr,readability-braces-around-statements' +Checks: 'clang-diagnostic-*,clang-analyzer-*,-*,modernize-redundant-void-arg,modernize-use-bool-literals,modernize-use-default-member-init,modernize-use-nullptr,readability-braces-around-statements' WarningsAsErrors: '' -HeaderFilterRegex: '.*' +HeaderFilterRegex: '' AnalyzeTemporaryDtors: false -FormatStyle: none -CheckOptions: +FormatStyle: none CheckOptions: - key: cert-dcl16-c.NewSuffixes value: 'L;LL;LU;LLU' diff --git a/.github/workflows/ios_builds.yml b/.github/workflows/ios_builds.yml index 721d574dbe28..cd9c7ec117b1 100644 --- a/.github/workflows/ios_builds.yml +++ b/.github/workflows/ios_builds.yml @@ -25,7 +25,7 @@ jobs: - name: Setup python and scons uses: ./.github/actions/godot-deps - - name: Compilation (armv7) + - name: Compilation (arm64v8) uses: ./.github/actions/godot-build with: sconsflags: ${{ env.SCONSFLAGS }} diff --git a/.github/workflows/macos_builds.yml b/.github/workflows/macos_builds.yml index aede3f8d49ef..68623f27708a 100644 --- a/.github/workflows/macos_builds.yml +++ b/.github/workflows/macos_builds.yml @@ -4,7 +4,7 @@ on: [push, pull_request] # Global Settings env: # Only used for the cache key. Increment version to force clean build. - GODOT_BASE_BRANCH: master-v2 + GODOT_BASE_BRANCH: master-v3 SCONSFLAGS: verbose=yes warnings=extra werror=yes module_text_server_fb_enabled=yes concurrency: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d7a4f976bf03..49355d278268 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,8 +66,10 @@ understand that: To speed up our work, **please upload a minimal project** that isolates and reproduces the issue. This is always the **best way for us to fix it**. -You can attach a ZIP file with the minimal project directly to the bug report, -by drag and dropping the file in the GitHub edition field. +We recommend attaching a ZIP file with the minimal project directly to the bug report, +by drag and dropping the file in the GitHub edition field. This ensures the file +can remain available for a long period of time. Only use third-party file hosts +if your ZIP file isn't accepted by GitHub because it's too large. We recommend always attaching a minimal reproduction project, even if the issue may seem simple to reproduce manually. @@ -84,7 +86,7 @@ it'll be considered too difficult to diagnose. Now that you've read the guidelines, click the link below to create a bug report: -- **[Report a bug](https://github.com/godotengine/godot/issues/new?assignees=&labels=&template=bug_report.md&title=)** +- **[Report a bug](https://github.com/godotengine/godot/issues/new?assignees=&labels=&template=bug_report.yml)** ## Proposing features or improvements diff --git a/SConstruct b/SConstruct index b8063589c688..a27e5490a5d7 100644 --- a/SConstruct +++ b/SConstruct @@ -179,9 +179,11 @@ opts.Add(BoolVariable("use_volk", "Use the volk library to load the Vulkan loade # Advanced options opts.Add(BoolVariable("dev", "If yes, alias for verbose=yes warnings=extra werror=yes", False)) -opts.Add(BoolVariable("progress", "Show a progress indicator during compilation", True)) opts.Add(BoolVariable("tests", "Build the unit tests", False)) +opts.Add(BoolVariable("fast_unsafe", "Enable unsafe options for faster rebuilds", False)) +opts.Add(BoolVariable("compiledb", "Generate compilation DB (`compile_commands.json`) for external tools", False)) opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False)) +opts.Add(BoolVariable("progress", "Show a progress indicator during compilation", True)) opts.Add(EnumVariable("warnings", "Level of compilation warnings", "all", ("extra", "all", "moderate", "no"))) opts.Add(BoolVariable("werror", "Treat compiler warnings as errors", False)) opts.Add("extra_suffix", "Custom extra suffix added to the base filename of all generated binary files", "") @@ -360,6 +362,17 @@ if env_base["target"] == "debug": # working on the engine itself. env_base.Append(CPPDEFINES=["DEV_ENABLED"]) +# SCons speed optimization controlled by the `fast_unsafe` option, which provide +# more than 10 s speed up for incremental rebuilds. +# Unsafe as they reduce the certainty of rebuilding all changed files, so it's +# enabled by default for `debug` builds, and can be overridden from command line. +# Ref: https://github.com/SCons/scons/wiki/GoFastButton +if methods.get_cmdline_bool("fast_unsafe", env_base["target"] == "debug"): + # Renamed to `content-timestamp` in SCons >= 4.2, keeping MD5 for compat. + env_base.Decider("MD5-timestamp") + env_base.SetOption("implicit_cache", 1) + env_base.SetOption("max_drift", 60) + if env_base["use_precise_math_checks"]: env_base.Append(CPPDEFINES=["PRECISE_MATH_CHECKS"]) @@ -385,14 +398,15 @@ if selected_platform in platform_list: else: env = env_base.Clone() - # Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later. - from SCons import __version__ as scons_raw_version + if env["compiledb"]: + # Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later. + from SCons import __version__ as scons_raw_version - scons_ver = env._get_major_minor_revision(scons_raw_version) + scons_ver = env._get_major_minor_revision(scons_raw_version) - if scons_ver >= (4, 0, 0): - env.Tool("compilation_db") - env.Alias("compiledb", env.CompilationDatabase()) + if scons_ver >= (4, 0, 0): + env.Tool("compilation_db") + env.Alias("compiledb", env.CompilationDatabase()) # 'dev' and 'production' are aliases to set default options if they haven't been set # manually by the user. @@ -817,6 +831,7 @@ elif selected_platform != "": # The following only makes sense when the 'env' is defined, and assumes it is. if "env" in locals(): + # FIXME: This method mixes both cosmetic progress stuff and cache handling... methods.show_progress(env) # TODO: replace this with `env.Dump(format="json")` # once we start requiring SCons 4.0 as min version. diff --git a/core/SCsub b/core/SCsub index c12dd4e60e24..1379e9df9b9f 100644 --- a/core/SCsub +++ b/core/SCsub @@ -147,6 +147,7 @@ env.core_sources += thirdparty_obj env.add_source_files(env.core_sources, "*.cpp") env.add_source_files(env.core_sources, "script_encryption_key.gen.cpp") +env.add_source_files(env.core_sources, "version_hash.gen.cpp") # Certificates env.Depends( diff --git a/core/config/engine.cpp b/core/config/engine.cpp index d9abf5e5e998..ff8a8d283fa2 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -35,7 +35,6 @@ #include "core/donors.gen.h" #include "core/license.gen.h" #include "core/version.h" -#include "core/version_hash.gen.h" void Engine::set_physics_ticks_per_second(int p_ips) { ERR_FAIL_COND_MSG(p_ips <= 0, "Engine iterations per second must be greater than 0."); @@ -95,8 +94,8 @@ Dictionary Engine::get_version_info() const { dict["build"] = VERSION_BUILD; dict["year"] = VERSION_YEAR; - String hash = VERSION_HASH; - dict["hash"] = hash.length() == 0 ? String("unknown") : hash; + String hash = String(VERSION_HASH); + dict["hash"] = hash.is_empty() ? String("unknown") : hash; String stringver = String(dict["major"]) + "." + String(dict["minor"]); if ((int)dict["patch"] != 0) { diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 45776c03e4f6..b5f1015ff537 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1235,8 +1235,8 @@ ProjectSettings::ProjectSettings() { // Keep the enum values in sync with the `DisplayServer::VSyncMode` enum. custom_prop_info["display/window/vsync/vsync_mode"] = PropertyInfo(Variant::INT, "display/window/vsync/vsync_mode", PROPERTY_HINT_ENUM, "Disabled,Enabled,Adaptive,Mailbox"); custom_prop_info["rendering/driver/threads/thread_model"] = PropertyInfo(Variant::INT, "rendering/driver/threads/thread_model", PROPERTY_HINT_ENUM, "Single-Unsafe,Single-Safe,Multi-Threaded"); - GLOBAL_DEF("physics/2d/run_on_thread", false); - GLOBAL_DEF("physics/3d/run_on_thread", false); + GLOBAL_DEF("physics/2d/run_on_separate_thread", false); + GLOBAL_DEF("physics/3d/run_on_separate_thread", false); GLOBAL_DEF("debug/settings/profiler/max_functions", 16384); custom_prop_info["debug/settings/profiler/max_functions"] = PropertyInfo(Variant::INT, "debug/settings/profiler/max_functions", PROPERTY_HINT_RANGE, "128,65535,1"); diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 2f5fd05e6acc..63e7323f7a32 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -165,6 +165,9 @@ void register_global_constants() { BIND_CORE_ENUM_CONSTANT(INLINE_ALIGNMENT_CENTER); BIND_CORE_ENUM_CONSTANT(INLINE_ALIGNMENT_BOTTOM); + BIND_CORE_ENUM_CONSTANT(INLINE_ALIGNMENT_IMAGE_MASK); + BIND_CORE_ENUM_CONSTANT(INLINE_ALIGNMENT_TEXT_MASK); + BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, SPECIAL); BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, ESCAPE); BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, TAB); diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp index a45430465f35..7c7d38ab0a8a 100644 --- a/core/debugger/remote_debugger_peer.cpp +++ b/core/debugger/remote_debugger_peer.cpp @@ -68,8 +68,8 @@ void RemoteDebuggerPeerTCP::close() { running = false; thread.wait_to_finish(); tcp_client->disconnect_from_host(); - out_buf.resize(0); - in_buf.resize(0); + out_buf.clear(); + in_buf.clear(); } RemoteDebuggerPeerTCP::RemoteDebuggerPeerTCP(Ref p_tcp) { @@ -190,7 +190,8 @@ Error RemoteDebuggerPeerTCP::connect_to_host(const String &p_host, uint16_t p_po } void RemoteDebuggerPeerTCP::_thread_func(void *p_ud) { - const uint64_t min_tick = 100; + // Update in time for 144hz monitors + const uint64_t min_tick = 6900; RemoteDebuggerPeerTCP *peer = (RemoteDebuggerPeerTCP *)p_ud; while (peer->running && peer->is_peer_connected()) { uint64_t ticks_usec = OS::get_singleton()->get_ticks_usec(); @@ -225,7 +226,7 @@ RemoteDebuggerPeer *RemoteDebuggerPeerTCP::create(const String &p_uri) { String debug_host = p_uri.replace("tcp://", ""); uint16_t debug_port = 6007; - if (debug_host.find(":") != -1) { + if (debug_host.contains(":")) { int sep_pos = debug_host.rfind(":"); debug_port = debug_host.substr(sep_pos + 1).to_int(); debug_host = debug_host.substr(0, sep_pos); diff --git a/core/debugger/script_debugger.cpp b/core/debugger/script_debugger.cpp index 36723e55684a..4dd93249ef7d 100644 --- a/core/debugger/script_debugger.cpp +++ b/core/debugger/script_debugger.cpp @@ -104,7 +104,7 @@ void ScriptDebugger::send_error(const String &p_func, const String &p_file, int // Store stack info, this is ugly, but allows us to separate EngineDebugger and ScriptDebugger. There might be a better way. error_stack_info.append_array(p_stack_info); EngineDebugger::get_singleton()->send_error(p_func, p_file, p_line, p_err, p_descr, p_editor_notify, p_type); - error_stack_info.resize(0); + error_stack_info.clear(); } Vector ScriptDebugger::get_error_stack_info() const { diff --git a/core/error/error_macros.cpp b/core/error/error_macros.cpp index 928ddd339747..ceccd43259b0 100644 --- a/core/error/error_macros.cpp +++ b/core/error/error_macros.cpp @@ -118,3 +118,7 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool p_editor_notify, bool p_fatal) { _err_print_index_error(p_function, p_file, p_line, p_index, p_size, p_index_str, p_size_str, p_message.utf8().get_data(), p_fatal); } + +void _err_flush_stdout() { + fflush(stdout); +} diff --git a/core/error/error_macros.h b/core/error/error_macros.h index 802d7f9ef458..7b032fb4cdf4 100644 --- a/core/error/error_macros.h +++ b/core/error/error_macros.h @@ -69,6 +69,7 @@ void _err_print_error(const char *p_function, const char *p_file, int p_line, co void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const String &p_message, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR); void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const char *p_message = "", bool p_editor_notify = false, bool fatal = false); void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool p_editor_notify = false, bool fatal = false); +void _err_flush_stdout(); #ifdef __GNUC__ //#define FUNCTION_STR __PRETTY_FUNCTION__ - too annoying @@ -789,6 +790,7 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li #define CRASH_NOW() \ if (true) { \ _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "FATAL: Method/function failed."); \ + _err_flush_stdout(); \ GENERATE_TRAP(); \ } else \ ((void)0) @@ -801,6 +803,7 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li #define CRASH_NOW_MSG(m_msg) \ if (true) { \ _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "FATAL: Method/function failed.", m_msg); \ + _err_flush_stdout(); \ GENERATE_TRAP(); \ } else \ ((void)0) diff --git a/core/input/input.cpp b/core/input/input.cpp index 7106bd0745d3..d36d0f4da0c6 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -190,32 +190,37 @@ void Input::VelocityTrack::update(const Vector2 &p_delta_p) { float delta_t = tdiff / 1000000.0; last_tick = tick; + if (delta_t > max_ref_frame) { + // First movement in a long time, reset and start again. + velocity = Vector2(); + accum = p_delta_p; + accum_t = 0; + return; + } + accum += p_delta_p; accum_t += delta_t; - if (accum_t > max_ref_frame * 10) { - accum_t = max_ref_frame * 10; + if (accum_t < min_ref_frame) { + // Not enough time has passed to calculate speed precisely. + return; } - while (accum_t >= min_ref_frame) { - float slice_t = min_ref_frame / accum_t; - Vector2 slice = accum * slice_t; - accum = accum - slice; - accum_t -= min_ref_frame; - - velocity = (slice / min_ref_frame).lerp(velocity, min_ref_frame / max_ref_frame); - } + velocity = accum / accum_t; + accum = Vector2(); + accum_t = 0; } void Input::VelocityTrack::reset() { last_tick = OS::get_singleton()->get_ticks_usec(); velocity = Vector2(); + accum = Vector2(); accum_t = 0; } Input::VelocityTrack::VelocityTrack() { min_ref_frame = 0.1; - max_ref_frame = 0.3; + max_ref_frame = 3.0; reset(); } @@ -719,7 +724,8 @@ Point2 Input::get_mouse_position() const { return mouse_pos; } -Point2 Input::get_last_mouse_velocity() const { +Point2 Input::get_last_mouse_velocity() { + mouse_velocity_track.update(Vector2()); return mouse_velocity_track.velocity; } diff --git a/core/input/input.h b/core/input/input.h index 80f260f30ef1..ab2cd377f40c 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -270,7 +270,7 @@ class Input : public Object { Vector3 get_gyroscope() const; Point2 get_mouse_position() const; - Vector2 get_last_mouse_velocity() const; + Vector2 get_last_mouse_velocity(); MouseButton get_mouse_button_mask() const; void warp_mouse_position(const Vector2 &p_to); diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index c608076a21bf..ab0f36132f14 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -86,7 +86,7 @@ Ref InputEvent::xformed_by(const Transform2D &p_xform, const Vector2 return Ref((InputEvent *)this); } -bool InputEvent::action_match(const Ref &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const { +bool InputEvent::action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const { return false; } @@ -412,35 +412,32 @@ Ref InputEventKey::create_reference(Key p_keycode) { return ie; } -bool InputEventKey::action_match(const Ref &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const { +bool InputEventKey::action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const { Ref key = p_event; if (key.is_null()) { return false; } - bool match = false; - if (get_keycode() == Key::NONE) { - Key code = get_physical_keycode_with_modifiers(); - Key event_code = key->get_physical_keycode_with_modifiers(); - - match = get_physical_keycode() == key->get_physical_keycode() && (!key->is_pressed() || (code & event_code) == code); + bool match; + if (keycode != Key::NONE) { + match = keycode == key->keycode; } else { - Key code = get_keycode_with_modifiers(); - Key event_code = key->get_keycode_with_modifiers(); - - match = get_keycode() == key->get_keycode() && (!key->is_pressed() || (code & event_code) == code); + match = get_physical_keycode() == key->get_physical_keycode(); + } + if (p_exact_match) { + match &= get_modifiers_mask() == key->get_modifiers_mask(); } if (match) { bool pressed = key->is_pressed(); - if (p_pressed != nullptr) { - *p_pressed = pressed; + if (r_pressed != nullptr) { + *r_pressed = pressed; } float strength = pressed ? 1.0f : 0.0f; - if (p_strength != nullptr) { - *p_strength = strength; + if (r_strength != nullptr) { + *r_strength = strength; } - if (p_raw_strength != nullptr) { - *p_raw_strength = strength; + if (r_raw_strength != nullptr) { + *r_raw_strength = strength; } } return match; @@ -585,24 +582,27 @@ Ref InputEventMouseButton::xformed_by(const Transform2D &p_xform, co return mb; } -bool InputEventMouseButton::action_match(const Ref &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const { +bool InputEventMouseButton::action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const { Ref mb = p_event; if (mb.is_null()) { return false; } - bool match = mb->button_index == button_index; + bool match = button_index == mb->button_index; + if (p_exact_match) { + match &= get_modifiers_mask() == mb->get_modifiers_mask(); + } if (match) { bool pressed = mb->is_pressed(); - if (p_pressed != nullptr) { - *p_pressed = pressed; + if (r_pressed != nullptr) { + *r_pressed = pressed; } float strength = pressed ? 1.0f : 0.0f; - if (p_strength != nullptr) { - *p_strength = strength; + if (r_strength != nullptr) { + *r_strength = strength; } - if (p_raw_strength != nullptr) { - *p_raw_strength = strength; + if (r_raw_strength != nullptr) { + *r_raw_strength = strength; } } @@ -887,36 +887,40 @@ bool InputEventJoypadMotion::is_pressed() const { return Math::abs(axis_value) >= 0.5f; } -bool InputEventJoypadMotion::action_match(const Ref &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const { +bool InputEventJoypadMotion::action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const { Ref jm = p_event; if (jm.is_null()) { return false; } - bool match = (axis == jm->axis); // Matches even if not in the same direction, but returns a "not pressed" event. + // Matches even if not in the same direction, but returns a "not pressed" event. + bool match = axis == jm->axis; + if (p_exact_match) { + match &= (axis_value < 0) == (jm->axis_value < 0); + } if (match) { float jm_abs_axis_value = Math::abs(jm->get_axis_value()); bool same_direction = (((axis_value < 0) == (jm->axis_value < 0)) || jm->axis_value == 0); bool pressed = same_direction && jm_abs_axis_value >= p_deadzone; - if (p_pressed != nullptr) { - *p_pressed = pressed; + if (r_pressed != nullptr) { + *r_pressed = pressed; } - if (p_strength != nullptr) { + if (r_strength != nullptr) { if (pressed) { if (p_deadzone == 1.0f) { - *p_strength = 1.0f; + *r_strength = 1.0f; } else { - *p_strength = CLAMP(Math::inverse_lerp(p_deadzone, 1.0f, jm_abs_axis_value), 0.0f, 1.0f); + *r_strength = CLAMP(Math::inverse_lerp(p_deadzone, 1.0f, jm_abs_axis_value), 0.0f, 1.0f); } } else { - *p_strength = 0.0f; + *r_strength = 0.0f; } } - if (p_raw_strength != nullptr) { + if (r_raw_strength != nullptr) { if (same_direction) { // NOT pressed, because we want to ignore the deadzone. - *p_raw_strength = jm_abs_axis_value; + *r_raw_strength = jm_abs_axis_value; } else { - *p_raw_strength = 0.0f; + *r_raw_strength = 0.0f; } } } @@ -994,7 +998,7 @@ float InputEventJoypadButton::get_pressure() const { return pressure; } -bool InputEventJoypadButton::action_match(const Ref &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const { +bool InputEventJoypadButton::action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const { Ref jb = p_event; if (jb.is_null()) { return false; @@ -1003,15 +1007,15 @@ bool InputEventJoypadButton::action_match(const Ref &p_event, bool * bool match = button_index == jb->button_index; if (match) { bool pressed = jb->is_pressed(); - if (p_pressed != nullptr) { - *p_pressed = pressed; + if (r_pressed != nullptr) { + *r_pressed = pressed; } float strength = pressed ? 1.0f : 0.0f; - if (p_strength != nullptr) { - *p_strength = strength; + if (r_strength != nullptr) { + *r_strength = strength; } - if (p_raw_strength != nullptr) { - *p_raw_strength = strength; + if (r_raw_strength != nullptr) { + *r_raw_strength = strength; } } @@ -1217,8 +1221,9 @@ String InputEventScreenDrag::to_string() { bool InputEventScreenDrag::accumulate(const Ref &p_event) { Ref drag = p_event; - if (drag.is_null()) + if (drag.is_null()) { return false; + } if (get_index() != drag->get_index()) { return false; @@ -1288,7 +1293,7 @@ bool InputEventAction::is_action(const StringName &p_action) const { return action == p_action; } -bool InputEventAction::action_match(const Ref &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const { +bool InputEventAction::action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const { Ref act = p_event; if (act.is_null()) { return false; @@ -1297,15 +1302,15 @@ bool InputEventAction::action_match(const Ref &p_event, bool *p_pres bool match = action == act->action; if (match) { bool pressed = act->pressed; - if (p_pressed != nullptr) { - *p_pressed = pressed; + if (r_pressed != nullptr) { + *r_pressed = pressed; } float strength = pressed ? 1.0f : 0.0f; - if (p_strength != nullptr) { - *p_strength = strength; + if (r_strength != nullptr) { + *r_strength = strength; } - if (p_raw_strength != nullptr) { - *p_raw_strength = strength; + if (r_raw_strength != nullptr) { + *r_raw_strength = strength; } } return match; diff --git a/core/input/input_event.h b/core/input/input_event.h index 29450dfc52cd..114db4662322 100644 --- a/core/input/input_event.h +++ b/core/input/input_event.h @@ -79,7 +79,7 @@ class InputEvent : public Resource { virtual Ref xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const; - virtual bool action_match(const Ref &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const; + virtual bool action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const; virtual bool is_match(const Ref &p_event, bool p_exact_match = true) const; virtual bool is_action_type() const; @@ -192,7 +192,7 @@ class InputEventKey : public InputEventWithModifiers { Key get_keycode_with_modifiers() const; Key get_physical_keycode_with_modifiers() const; - virtual bool action_match(const Ref &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const override; + virtual bool action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override; virtual bool is_match(const Ref &p_event, bool p_exact_match = true) const override; virtual bool is_action_type() const override { return true; } @@ -255,7 +255,7 @@ class InputEventMouseButton : public InputEventMouse { virtual Ref xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override; - virtual bool action_match(const Ref &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const override; + virtual bool action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override; virtual bool is_match(const Ref &p_event, bool p_exact_match = true) const override; virtual bool is_action_type() const override { return true; } @@ -315,7 +315,7 @@ class InputEventJoypadMotion : public InputEvent { virtual bool is_pressed() const override; - virtual bool action_match(const Ref &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const override; + virtual bool action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override; virtual bool is_match(const Ref &p_event, bool p_exact_match = true) const override; virtual bool is_action_type() const override { return true; } @@ -344,7 +344,7 @@ class InputEventJoypadButton : public InputEvent { void set_pressure(float p_pressure); float get_pressure() const; - virtual bool action_match(const Ref &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const override; + virtual bool action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override; virtual bool is_match(const Ref &p_event, bool p_exact_match = true) const override; virtual bool is_action_type() const override { return true; } @@ -437,7 +437,7 @@ class InputEventAction : public InputEvent { virtual bool is_action(const StringName &p_action) const; - virtual bool action_match(const Ref &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const override; + virtual bool action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override; virtual bool is_match(const Ref &p_event, bool p_exact_match = true) const override; virtual bool is_action_type() const override { return true; } diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 753ac72ab6c3..41083b4c4734 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -126,15 +126,13 @@ List InputMap::get_actions() const { return actions; } -List>::Element *InputMap::_find_event(Action &p_action, const Ref &p_event, bool p_exact_match, bool *p_pressed, float *p_strength, float *p_raw_strength) const { +List>::Element *InputMap::_find_event(Action &p_action, const Ref &p_event, bool p_exact_match, bool *r_pressed, float *r_strength, float *r_raw_strength) const { ERR_FAIL_COND_V(!p_event.is_valid(), nullptr); for (List>::Element *E = p_action.inputs.front(); E; E = E->next()) { int device = E->get()->get_device(); if (device == ALL_DEVICES || device == p_event->get_device()) { - if (p_exact_match && E->get()->is_match(p_event, true)) { - return E; - } else if (!p_exact_match && E->get()->action_match(p_event, p_pressed, p_strength, p_raw_strength, p_action.deadzone)) { + if (E->get()->action_match(p_event, p_exact_match, p_action.deadzone, r_pressed, r_strength, r_raw_strength)) { return E; } } @@ -217,40 +215,28 @@ bool InputMap::event_is_action(const Ref &p_event, const StringName return event_get_action_status(p_event, p_action, p_exact_match); } -bool InputMap::event_get_action_status(const Ref &p_event, const StringName &p_action, bool p_exact_match, bool *p_pressed, float *p_strength, float *p_raw_strength) const { +bool InputMap::event_get_action_status(const Ref &p_event, const StringName &p_action, bool p_exact_match, bool *r_pressed, float *r_strength, float *r_raw_strength) const { OrderedHashMap::Element E = input_map.find(p_action); ERR_FAIL_COND_V_MSG(!E, false, suggest_actions(p_action)); Ref input_event_action = p_event; if (input_event_action.is_valid()) { - bool pressed = input_event_action->is_pressed(); - if (p_pressed != nullptr) { - *p_pressed = pressed; + const bool pressed = input_event_action->is_pressed(); + if (r_pressed != nullptr) { + *r_pressed = pressed; + } + const float strength = pressed ? input_event_action->get_strength() : 0.0f; + if (r_strength != nullptr) { + *r_strength = strength; } - if (p_strength != nullptr) { - *p_strength = pressed ? input_event_action->get_strength() : 0.0f; + if (r_raw_strength != nullptr) { + *r_raw_strength = strength; } return input_event_action->get_action() == p_action; } - bool pressed; - float strength; - float raw_strength; - List>::Element *event = _find_event(E.get(), p_event, p_exact_match, &pressed, &strength, &raw_strength); - if (event != nullptr) { - if (p_pressed != nullptr) { - *p_pressed = pressed; - } - if (p_strength != nullptr) { - *p_strength = strength; - } - if (p_raw_strength != nullptr) { - *p_raw_strength = raw_strength; - } - return true; - } else { - return false; - } + List>::Element *event = _find_event(E.get(), p_event, p_exact_match, r_pressed, r_strength, r_raw_strength); + return event != nullptr; } const OrderedHashMap &InputMap::get_action_map() const { diff --git a/core/input/input_map.h b/core/input/input_map.h index e896d1f679f0..79b4d1038fc3 100644 --- a/core/input/input_map.h +++ b/core/input/input_map.h @@ -58,7 +58,7 @@ class InputMap : public Object { OrderedHashMap>> default_builtin_cache; OrderedHashMap>> default_builtin_with_overrides_cache; - List>::Element *_find_event(Action &p_action, const Ref &p_event, bool p_exact_match = false, bool *p_pressed = nullptr, float *p_strength = nullptr, float *p_raw_strength = nullptr) const; + List>::Element *_find_event(Action &p_action, const Ref &p_event, bool p_exact_match = false, bool *r_pressed = nullptr, float *r_strength = nullptr, float *r_raw_strength = nullptr) const; Array _action_get_events(const StringName &p_action); Array _get_actions(); @@ -83,7 +83,7 @@ class InputMap : public Object { const List> *action_get_events(const StringName &p_action); bool event_is_action(const Ref &p_event, const StringName &p_action, bool p_exact_match = false) const; - bool event_get_action_status(const Ref &p_event, const StringName &p_action, bool p_exact_match = false, bool *p_pressed = nullptr, float *p_strength = nullptr, float *p_raw_strength = nullptr) const; + bool event_get_action_status(const Ref &p_event, const StringName &p_action, bool p_exact_match = false, bool *r_pressed = nullptr, float *r_strength = nullptr, float *r_raw_strength = nullptr) const; const OrderedHashMap &get_action_map() const; void load_from_project_settings(); diff --git a/core/io/compression.cpp b/core/io/compression.cpp index d1f915f064d0..ae5ccf8354b4 100644 --- a/core/io/compression.cpp +++ b/core/io/compression.cpp @@ -212,7 +212,7 @@ int Compression::decompress_dynamic(Vector *p_dst_vect, int p_max_dst_s strm.avail_in = p_src_size; // Ensure the destination buffer is empty - p_dst_vect->resize(0); + p_dst_vect->clear(); // decompress until deflate stream ends or end of file do { @@ -244,7 +244,7 @@ int Compression::decompress_dynamic(Vector *p_dst_vect, int p_max_dst_s WARN_PRINT(strm.msg); } (void)inflateEnd(&strm); - p_dst_vect->resize(0); + p_dst_vect->clear(); return ret; } } while (strm.avail_out > 0 && strm.avail_in > 0); @@ -254,7 +254,7 @@ int Compression::decompress_dynamic(Vector *p_dst_vect, int p_max_dst_s // Enforce max output size if (p_max_dst_size > -1 && strm.total_out > (uint64_t)p_max_dst_size) { (void)inflateEnd(&strm); - p_dst_vect->resize(0); + p_dst_vect->clear(); return Z_BUF_ERROR; } } while (ret != Z_STREAM_END); diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp index 3da9288ffd96..86d8dea3d986 100644 --- a/core/io/dir_access.cpp +++ b/core/io/dir_access.cpp @@ -145,17 +145,21 @@ Error DirAccess::make_dir_recursive(String p_dir) { full_dir = full_dir.replace("\\", "/"); - //int slices = full_dir.get_slice_count("/"); - String base; if (full_dir.begins_with("res://")) { base = "res://"; } else if (full_dir.begins_with("user://")) { base = "user://"; + } else if (full_dir.is_network_share_path()) { + int pos = full_dir.find("/", 2); + ERR_FAIL_COND_V(pos < 0, ERR_INVALID_PARAMETER); + pos = full_dir.find("/", pos + 1); + ERR_FAIL_COND_V(pos < 0, ERR_INVALID_PARAMETER); + base = full_dir.substr(0, pos + 1); } else if (full_dir.begins_with("/")) { base = "/"; - } else if (full_dir.find(":/") != -1) { + } else if (full_dir.contains(":/")) { base = full_dir.substr(0, full_dir.find(":/") + 2); } else { ERR_FAIL_V(ERR_INVALID_PARAMETER); diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp index bb92648484a1..86836454c5bc 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -538,7 +538,7 @@ void FileAccess::store_csv_line(const Vector &p_values, const String &p_ for (int i = 0; i < size; ++i) { String value = p_values[i]; - if (value.find("\"") != -1 || value.find(p_delim) != -1 || value.find("\n") != -1) { + if (value.contains("\"") || value.contains(p_delim) || value.contains("\n")) { value = "\"" + value.replace("\"", "\"\"") + "\""; } if (i < size - 1) { diff --git a/core/io/file_access_network.cpp b/core/io/file_access_network.cpp index 307004b1c278..cb38ac09284c 100644 --- a/core/io/file_access_network.cpp +++ b/core/io/file_access_network.cpp @@ -487,7 +487,6 @@ FileAccessNetwork::~FileAccessNetwork() { FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton; nc->lock_mutex(); - id = nc->last_id++; nc->accesses.erase(id); nc->unlock_mutex(); } diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index 388262710351..7dbea96c3d6a 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -70,7 +70,7 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64 String p = p_path.replace_first("res://", ""); PackedDir *cd = root; - if (p.find("/") != -1) { //in a subdir + if (p.contains("/")) { //in a subdir Vector ds = p.get_base_dir().split("/"); diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp index 4d0747c5910f..52b1120b2ae4 100644 --- a/core/io/http_client.cpp +++ b/core/io/http_client.cpp @@ -96,6 +96,17 @@ String HTTPClient::query_string_from_dict(const Dictionary &p_dict) { return query.substr(1); } +Error HTTPClient::verify_headers(const Vector &p_headers) { + for (int i = 0; i < p_headers.size(); i++) { + String sanitized = p_headers[i].strip_edges(); + ERR_FAIL_COND_V_MSG(sanitized.is_empty(), ERR_INVALID_PARAMETER, "Invalid HTTP header at index " + itos(i) + ": empty."); + ERR_FAIL_COND_V_MSG(sanitized.find(":") < 1, ERR_INVALID_PARAMETER, + "Invalid HTTP header at index " + itos(i) + ": String must contain header-value pair, delimited by ':', but was: " + p_headers[i]); + } + + return OK; +} + Dictionary HTTPClient::_get_response_headers_as_dictionary() { List rh; get_response_headers(&rh); diff --git a/core/io/http_client.h b/core/io/http_client.h index 90c859d685f6..de6045f647c9 100644 --- a/core/io/http_client.h +++ b/core/io/http_client.h @@ -165,6 +165,7 @@ class HTTPClient : public RefCounted { static HTTPClient *create(); String query_string_from_dict(const Dictionary &p_dict); + Error verify_headers(const Vector &p_headers); virtual Error request(Method p_method, const String &p_url, const Vector &p_headers, const uint8_t *p_body, int p_body_size) = 0; virtual Error connect_to_host(const String &p_host, int p_port = -1, bool p_ssl = false, bool p_verify_host = true) = 0; diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp index 6e4417e1fff4..f9207996774c 100644 --- a/core/io/http_client_tcp.cpp +++ b/core/io/http_client_tcp.cpp @@ -71,7 +71,7 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_ss connection = tcp_connection; if (ssl && https_proxy_port != -1) { - proxy_client.instantiate(); // Needs proxy negotiation + proxy_client.instantiate(); // Needs proxy negotiation. server_host = https_proxy_host; server_port = https_proxy_port; } else if (!ssl && http_proxy_port != -1) { @@ -83,7 +83,7 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_ss } if (server_host.is_valid_ip_address()) { - // Host contains valid IP + // Host contains valid IP. Error err = tcp_connection->connect_to_host(IPAddress(server_host), server_port); if (err) { status = STATUS_CANT_CONNECT; @@ -92,7 +92,7 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_ss status = STATUS_CONNECTING; } else { - // Host contains hostname and needs to be resolved to IP + // Host contains hostname and needs to be resolved to IP. resolving = IP::get_singleton()->resolve_hostname_queue_item(server_host); status = STATUS_RESOLVING; } @@ -124,7 +124,7 @@ Ref HTTPClientTCP::get_connection() const { static bool _check_request_url(HTTPClientTCP::Method p_method, const String &p_url) { switch (p_method) { case HTTPClientTCP::METHOD_CONNECT: { - // Authority in host:port format, as in RFC7231 + // Authority in host:port format, as in RFC7231. int pos = p_url.find_char(':'); return 0 < pos && pos < p_url.length() - 1; } @@ -135,7 +135,7 @@ static bool _check_request_url(HTTPClientTCP::Method p_method, const String &p_u [[fallthrough]]; } default: - // Absolute path or absolute URL + // Absolute path or absolute URL. return p_url.begins_with("/") || p_url.begins_with("http://") || p_url.begins_with("https://"); } } @@ -146,6 +146,11 @@ Error HTTPClientTCP::request(Method p_method, const String &p_url, const Vector< ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(connection.is_null(), ERR_INVALID_DATA); + Error err = verify_headers(p_headers); + if (err) { + return err; + } + String uri = p_url; if (!ssl && http_proxy_port != -1) { uri = vformat("http://%s:%d%s", conn_host, conn_port, p_url); @@ -173,7 +178,7 @@ Error HTTPClientTCP::request(Method p_method, const String &p_url, const Vector< } if (add_host) { if ((ssl && conn_port == PORT_HTTPS) || (!ssl && conn_port == PORT_HTTP)) { - // Don't append the standard ports + // Don't append the standard ports. request += "Host: " + conn_host + "\r\n"; } else { request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n"; @@ -192,21 +197,12 @@ Error HTTPClientTCP::request(Method p_method, const String &p_url, const Vector< request += "\r\n"; CharString cs = request.utf8(); - Vector data; - data.resize(cs.length() + p_body_size); - memcpy(data.ptrw(), cs.get_data(), cs.length()); + request_buffer->clear(); + request_buffer->put_data((const uint8_t *)cs.get_data(), cs.length()); if (p_body_size > 0) { - memcpy(data.ptrw() + cs.length(), p_body, p_body_size); - } - - // TODO Implement non-blocking requests. - Error err = connection->put_data(data.ptr(), data.size()); - - if (err) { - close(); - status = STATUS_CONNECTION_ERROR; - return err; + request_buffer->put_data(p_body, p_body_size); } + request_buffer->seek(0); status = STATUS_REQUESTING; head_request = p_method == METHOD_HEAD; @@ -257,6 +253,7 @@ void HTTPClientTCP::close() { ip_candidates.clear(); response_headers.clear(); response_str.clear(); + request_buffer->clear(); body_size = -1; body_left = 0; chunk_left = 0; @@ -274,7 +271,7 @@ Error HTTPClientTCP::poll() { IP::ResolverStatus rstatus = IP::get_singleton()->get_resolve_item_status(resolving); switch (rstatus) { case IP::RESOLVER_STATUS_WAITING: - return OK; // Still resolving + return OK; // Still resolving. case IP::RESOLVER_STATUS_DONE: { ip_candidates = IP::get_singleton()->get_resolve_item_addresses(resolving); @@ -356,7 +353,7 @@ Error HTTPClientTCP::poll() { } else if (ssl) { Ref ssl; if (!handshaking) { - // Connect the StreamPeerSSL and start handshaking + // Connect the StreamPeerSSL and start handshaking. ssl = Ref(StreamPeerSSL::create()); ssl->set_blocking_handshake_enabled(false); Error err = ssl->connect_to_stream(tcp_connection, ssl_verify_host, conn_host); @@ -368,7 +365,7 @@ Error HTTPClientTCP::poll() { connection = ssl; handshaking = true; } else { - // We are already handshaking, which means we can use your already active SSL connection + // We are already handshaking, which means we can use your already active SSL connection. ssl = static_cast>(connection); if (ssl.is_null()) { close(); @@ -376,22 +373,22 @@ Error HTTPClientTCP::poll() { return ERR_CANT_CONNECT; } - ssl->poll(); // Try to finish the handshake + ssl->poll(); // Try to finish the handshake. } if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) { - // Handshake has been successful + // Handshake has been successful. handshaking = false; ip_candidates.clear(); status = STATUS_CONNECTED; return OK; } else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) { - // Handshake has failed + // Handshake has failed. close(); status = STATUS_SSL_HANDSHAKE_ERROR; return ERR_CANT_CONNECT; } - // ... we will need to poll more for handshake to finish + // ... we will need to poll more for handshake to finish. } else { ip_candidates.clear(); status = STATUS_CONNECTED; @@ -416,7 +413,7 @@ Error HTTPClientTCP::poll() { } break; case STATUS_BODY: case STATUS_CONNECTED: { - // Check if we are still connected + // Check if we are still connected. if (ssl) { Ref tmp = connection; tmp->poll(); @@ -428,10 +425,34 @@ Error HTTPClientTCP::poll() { status = STATUS_CONNECTION_ERROR; return ERR_CONNECTION_ERROR; } - // Connection established, requests can now be made + // Connection established, requests can now be made. return OK; } break; case STATUS_REQUESTING: { + if (request_buffer->get_available_bytes()) { + int avail = request_buffer->get_available_bytes(); + int pos = request_buffer->get_position(); + const Vector data = request_buffer->get_data_array(); + int wrote = 0; + Error err; + if (blocking) { + err = connection->put_data(data.ptr() + pos, avail); + wrote += avail; + } else { + err = connection->put_partial_data(data.ptr() + pos, avail, wrote); + } + if (err != OK) { + close(); + status = STATUS_CONNECTION_ERROR; + return ERR_CONNECTION_ERROR; + } + pos += wrote; + request_buffer->seek(pos); + if (avail - wrote > 0) { + return OK; + } + request_buffer->clear(); + } while (true) { uint8_t byte; int rec = 0; @@ -547,7 +568,7 @@ PackedByteArray HTTPClientTCP::read_response_body_chunk() { if (chunked) { while (true) { if (chunk_trailer_part) { - // We need to consume the trailer part too or keep-alive will break + // We need to consume the trailer part too or keep-alive will break. uint8_t b; int rec = 0; err = _get_http_data(&b, 1, rec); @@ -560,18 +581,18 @@ PackedByteArray HTTPClientTCP::read_response_body_chunk() { int cs = chunk.size(); if ((cs >= 2 && chunk[cs - 2] == '\r' && chunk[cs - 1] == '\n')) { if (cs == 2) { - // Finally over + // Finally over. chunk_trailer_part = false; status = STATUS_CONNECTED; chunk.clear(); break; } else { - // We do not process nor return the trailer data + // We do not process nor return the trailer data. chunk.clear(); } } } else if (chunk_left == 0) { - // Reading length + // Reading length. uint8_t b; int rec = 0; err = _get_http_data(&b, 1, rec); @@ -593,7 +614,7 @@ PackedByteArray HTTPClientTCP::read_response_body_chunk() { for (int i = 0; i < chunk.size() - 2; i++) { char c = chunk[i]; int v = 0; - if (c >= '0' && c <= '9') { + if (is_digit(c)) { v = c - '0'; } else if (c >= 'a' && c <= 'f') { v = c - 'a' + 10; @@ -658,7 +679,7 @@ PackedByteArray HTTPClientTCP::read_response_body_chunk() { uint8_t *w = ret.ptrw(); err = _get_http_data(w + _offset, to_read, rec); } - if (rec <= 0) { // Ended up reading less + if (rec <= 0) { // Ended up reading less. ret.resize(_offset); break; } else { @@ -679,7 +700,7 @@ PackedByteArray HTTPClientTCP::read_response_body_chunk() { close(); if (err == ERR_FILE_EOF) { - status = STATUS_DISCONNECTED; // Server disconnected + status = STATUS_DISCONNECTED; // Server disconnected. } else { status = STATUS_CONNECTION_ERROR; } @@ -759,6 +780,7 @@ void HTTPClientTCP::set_https_proxy(const String &p_host, int p_port) { HTTPClientTCP::HTTPClientTCP() { tcp_connection.instantiate(); + request_buffer.instantiate(); } HTTPClient *(*HTTPClient::_create)() = HTTPClientTCP::_create_func; diff --git a/core/io/http_client_tcp.h b/core/io/http_client_tcp.h index 3fe8e2c0df84..c10e0b1ecae0 100644 --- a/core/io/http_client_tcp.h +++ b/core/io/http_client_tcp.h @@ -38,13 +38,13 @@ class HTTPClientTCP : public HTTPClient { Status status = STATUS_DISCONNECTED; IP::ResolverID resolving = IP::RESOLVER_INVALID_ID; Array ip_candidates; - int conn_port = -1; // Server to make requests to + int conn_port = -1; // Server to make requests to. String conn_host; - int server_port = -1; // Server to connect to (might be a proxy server) + int server_port = -1; // Server to connect to (might be a proxy server). String server_host; - int http_proxy_port = -1; // Proxy server for http requests + int http_proxy_port = -1; // Proxy server for http requests. String http_proxy_host; - int https_proxy_port = -1; // Proxy server for https requests + int https_proxy_port = -1; // Proxy server for https requests. String https_proxy_host; bool ssl = false; bool ssl_verify_host = false; @@ -62,9 +62,10 @@ class HTTPClientTCP : public HTTPClient { int64_t body_left = 0; bool read_until_eof = false; + Ref request_buffer; Ref tcp_connection; Ref connection; - Ref proxy_client; // Negotiate with proxy server + Ref proxy_client; // Negotiate with proxy server. int response_num = 0; Vector response_headers; diff --git a/core/io/image.cpp b/core/io/image.cpp index 9df2b6835cab..577fc598075c 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -30,14 +30,17 @@ #include "image.h" +#include "core/error/error_list.h" #include "core/error/error_macros.h" #include "core/io/image_loader.h" #include "core/io/resource_loader.h" #include "core/math/math_funcs.h" #include "core/string/print_string.h" #include "core/templates/hash_map.h" +#include "core/variant/dictionary.h" #include +#include const char *Image::format_names[Image::FORMAT_MAX] = { "Lum8", //luminance @@ -2056,7 +2059,7 @@ void Image::create(const char **p_xpm) { for (int i = 0; i < 6; i++) { char v = line_ptr[i]; - if (v >= '0' && v <= '9') { + if (is_digit(v)) { v -= '0'; } else if (v >= 'A' && v <= 'F') { v = (v - 'A') + 10; @@ -3135,6 +3138,8 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("rgbe_to_srgb"), &Image::rgbe_to_srgb); ClassDB::bind_method(D_METHOD("bump_map_to_normal_map", "bump_scale"), &Image::bump_map_to_normal_map, DEFVAL(1.0)); + ClassDB::bind_method(D_METHOD("compute_image_metrics", "compared_image", "use_luma"), &Image::compute_image_metrics); + ClassDB::bind_method(D_METHOD("blit_rect", "src", "src_rect", "dst"), &Image::blit_rect); ClassDB::bind_method(D_METHOD("blit_rect_mask", "src", "mask", "src_rect", "dst"), &Image::blit_rect_mask); ClassDB::bind_method(D_METHOD("blend_rect", "src", "src_rect", "dst"), &Image::blend_rect); @@ -3620,3 +3625,128 @@ Ref Image::duplicate(bool p_subresources) const { void Image::set_as_black() { memset(data.ptrw(), 0, data.size()); } + +Dictionary Image::compute_image_metrics(const Ref p_compared_image, bool p_luma_metric) { + // https://github.com/richgel999/bc7enc_rdo/blob/master/LICENSE + // + // This is free and unencumbered software released into the public domain. + // Anyone is free to copy, modify, publish, use, compile, sell, or distribute this + // software, either in source code form or as a compiled binary, for any purpose, + // commercial or non - commercial, and by any means. + // In jurisdictions that recognize copyright laws, the author or authors of this + // software dedicate any and all copyright interest in the software to the public + // domain. We make this dedication for the benefit of the public at large and to + // the detriment of our heirs and successors. We intend this dedication to be an + // overt act of relinquishment in perpetuity of all present and future rights to + // this software under copyright law. + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE + // AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Dictionary result; + result["max"] = INFINITY; + result["mean"] = INFINITY; + result["mean_squared"] = INFINITY; + result["root_mean_squared"] = INFINITY; + result["peak_snr"] = 0.0f; + + ERR_FAIL_NULL_V(p_compared_image, result); + Error err = OK; + Ref compared_image = duplicate(true); + if (compared_image->is_compressed()) { + err = compared_image->decompress(); + } + ERR_FAIL_COND_V(err != OK, result); + Ref source_image = p_compared_image->duplicate(true); + if (source_image->is_compressed()) { + err = source_image->decompress(); + } + ERR_FAIL_COND_V(err != OK, result); + + ERR_FAIL_COND_V(err != OK, result); + + ERR_FAIL_COND_V_MSG((compared_image->get_format() >= Image::FORMAT_RH) && (compared_image->get_format() <= Image::FORMAT_RGBE9995), result, "Metrics on HDR images are not supported."); + ERR_FAIL_COND_V_MSG((source_image->get_format() >= Image::FORMAT_RH) && (source_image->get_format() <= Image::FORMAT_RGBE9995), result, "Metrics on HDR images are not supported."); + + double image_metric_max, image_metric_mean, image_metric_mean_squared, image_metric_root_mean_squared, image_metric_peak_snr = 0.0; + const bool average_component_error = true; + + const uint32_t width = MIN(compared_image->get_width(), source_image->get_width()); + const uint32_t height = MIN(compared_image->get_height(), source_image->get_height()); + + // Histogram approach originally due to Charles Bloom. + double hist[256]; + memset(hist, 0, sizeof(hist)); + + for (uint32_t y = 0; y < height; y++) { + for (uint32_t x = 0; x < width; x++) { + const Color color_a = compared_image->get_pixel(x, y); + + const Color color_b = source_image->get_pixel(x, y); + + if (!p_luma_metric) { + ERR_FAIL_COND_V_MSG(color_a.r > 1.0f, Dictionary(), "Can't compare HDR colors."); + ERR_FAIL_COND_V_MSG(color_b.r > 1.0f, Dictionary(), "Can't compare HDR colors."); + hist[Math::abs(color_a.get_r8() - color_b.get_r8())]++; + ERR_FAIL_COND_V_MSG(color_a.g > 1.0f, Dictionary(), "Can't compare HDR colors."); + ERR_FAIL_COND_V_MSG(color_b.g > 1.0f, Dictionary(), "Can't compare HDR colors."); + hist[Math::abs(color_a.get_g8() - color_b.get_g8())]++; + ERR_FAIL_COND_V_MSG(color_a.b > 1.0f, Dictionary(), "Can't compare HDR colors."); + ERR_FAIL_COND_V_MSG(color_b.b > 1.0f, Dictionary(), "Can't compare HDR colors."); + hist[Math::abs(color_a.get_b8() - color_b.get_b8())]++; + ERR_FAIL_COND_V_MSG(color_a.a > 1.0f, Dictionary(), "Can't compare HDR colors."); + ERR_FAIL_COND_V_MSG(color_b.a > 1.0f, Dictionary(), "Can't compare HDR colors."); + hist[Math::abs(color_a.get_a8() - color_b.get_a8())]++; + } else { + ERR_FAIL_COND_V_MSG(color_a.r > 1.0f, Dictionary(), "Can't compare HDR colors."); + ERR_FAIL_COND_V_MSG(color_b.r > 1.0f, Dictionary(), "Can't compare HDR colors."); + // REC709 weightings + int luma_a = (13938U * color_a.get_r8() + 46869U * color_a.get_g8() + 4729U * color_a.get_b8() + 32768U) >> 16U; + int luma_b = (13938U * color_b.get_r8() + 46869U * color_b.get_g8() + 4729U * color_b.get_b8() + 32768U) >> 16U; + hist[Math::abs(luma_a - luma_b)]++; + } + } + } + + image_metric_max = 0; + double sum = 0.0f, sum2 = 0.0f; + for (uint32_t i = 0; i < 256; i++) { + if (!hist[i]) { + continue; + } + + image_metric_max = MAX(image_metric_max, i); + + double x = i * hist[i]; + + sum += x; + sum2 += i * x; + } + + // See http://richg42.blogspot.com/2016/09/how-to-compute-psnr-from-old-berkeley.html + double total_values = width * height; + + if (average_component_error) { + total_values *= 4; + } + + image_metric_mean = CLAMP(sum / total_values, 0.0f, 255.0f); + image_metric_mean_squared = CLAMP(sum2 / total_values, 0.0f, 255.0f * 255.0f); + + image_metric_root_mean_squared = sqrt(image_metric_mean_squared); + + if (!image_metric_root_mean_squared) { + image_metric_peak_snr = 1e+10f; + } else { + image_metric_peak_snr = CLAMP(log10(255.0f / image_metric_root_mean_squared) * 20.0f, 0.0f, 500.0f); + } + result["max"] = image_metric_max; + result["mean"] = image_metric_mean; + result["mean_squared"] = image_metric_mean_squared; + result["root_mean_squared"] = image_metric_root_mean_squared; + result["peak_snr"] = image_metric_peak_snr; + return result; +} diff --git a/core/io/image.h b/core/io/image.h index ddfb2bb01dc8..53bfa0881f77 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -399,6 +399,8 @@ class Image : public Resource { mipmaps = p_image->mipmaps; data = p_image->data; } + + Dictionary compute_image_metrics(const Ref p_compared_image, bool p_luma_metric = true); }; VARIANT_ENUM_CAST(Image::Format) diff --git a/core/io/ip.cpp b/core/io/ip.cpp index 4970afc1d3e3..8e0d47e7624e 100644 --- a/core/io/ip.cpp +++ b/core/io/ip.cpp @@ -98,6 +98,11 @@ struct _IP_ResolverPrivate { if (queue[i].status.get() != IP::RESOLVER_STATUS_WAITING) { continue; } + // We might be overriding another result, but we don't care as long as the result is valid. + if (response.size()) { + String key = get_cache_key(hostname, type); + cache[key] = response; + } queue[i].response = response; queue[i].status.set(response.is_empty() ? IP::RESOLVER_STATUS_ERROR : IP::RESOLVER_STATUS_DONE); } @@ -120,30 +125,8 @@ struct _IP_ResolverPrivate { }; IPAddress IP::resolve_hostname(const String &p_hostname, IP::Type p_type) { - List res; - String key = _IP_ResolverPrivate::get_cache_key(p_hostname, p_type); - - resolver->mutex.lock(); - if (resolver->cache.has(key)) { - res = resolver->cache[key]; - } else { - // This should be run unlocked so the resolver thread can keep - // resolving other requests. - resolver->mutex.unlock(); - _resolve_hostname(res, p_hostname, p_type); - resolver->mutex.lock(); - // We might be overriding another result, but we don't care (they are the - // same hostname). - resolver->cache[key] = res; - } - resolver->mutex.unlock(); - - for (int i = 0; i < res.size(); ++i) { - if (res[i].is_valid()) { - return res[i]; - } - } - return IPAddress(); + const Array addresses = resolve_hostname_addresses(p_hostname, p_type); + return addresses.size() ? addresses[0].operator IPAddress() : IPAddress(); } Array IP::resolve_hostname_addresses(const String &p_hostname, Type p_type) { @@ -159,17 +142,16 @@ Array IP::resolve_hostname_addresses(const String &p_hostname, Type p_type) { resolver->mutex.unlock(); _resolve_hostname(res, p_hostname, p_type); resolver->mutex.lock(); - // We might be overriding another result, but we don't care (they are the - // same hostname). - resolver->cache[key] = res; + // We might be overriding another result, but we don't care as long as the result is valid. + if (res.size()) { + resolver->cache[key] = res; + } } resolver->mutex.unlock(); Array result; for (int i = 0; i < res.size(); ++i) { - if (res[i].is_valid()) { - result.push_back(String(res[i])); - } + result.push_back(String(res[i])); } return result; } diff --git a/core/io/ip_address.cpp b/core/io/ip_address.cpp index 38f99a08a41d..d183c6079832 100644 --- a/core/io/ip_address.cpp +++ b/core/io/ip_address.cpp @@ -71,7 +71,7 @@ static void _parse_hex(const String &p_string, int p_start, uint8_t *p_dst) { int n = 0; char32_t c = p_string[i]; - if (c >= '0' && c <= '9') { + if (is_digit(c)) { n = c - '0'; } else if (c >= 'a' && c <= 'f') { n = 10 + (c - 'a'); @@ -113,7 +113,7 @@ void IPAddress::_parse_ipv6(const String &p_string) { } else if (c == '.') { part_ipv4 = true; - } else if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { + } else if (is_hex_digit(c)) { if (!part_found) { parts[parts_idx++] = i; part_found = true; diff --git a/core/io/json.cpp b/core/io/json.cpp index 7b642f6a5904..4b745dff449a 100644 --- a/core/io/json.cpp +++ b/core/io/json.cpp @@ -229,12 +229,12 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to r_err_str = "Unterminated String"; return ERR_PARSE_ERROR; } - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { + if (!is_hex_digit(c)) { r_err_str = "Malformed hex constant in string"; return ERR_PARSE_ERROR; } char32_t v; - if (c >= '0' && c <= '9') { + if (is_digit(c)) { v = c - '0'; } else if (c >= 'a' && c <= 'f') { v = c - 'a'; @@ -265,12 +265,12 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to r_err_str = "Unterminated String"; return ERR_PARSE_ERROR; } - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { + if (!is_hex_digit(c)) { r_err_str = "Malformed hex constant in string"; return ERR_PARSE_ERROR; } char32_t v; - if (c >= '0' && c <= '9') { + if (is_digit(c)) { v = c - '0'; } else if (c >= 'a' && c <= 'f') { v = c - 'a'; @@ -326,7 +326,7 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to break; } - if (p_str[index] == '-' || (p_str[index] >= '0' && p_str[index] <= '9')) { + if (p_str[index] == '-' || is_digit(p_str[index])) { //a number const char32_t *rptr; double number = String::to_float(&p_str[index], &rptr); @@ -335,10 +335,10 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to r_token.value = number; return OK; - } else if ((p_str[index] >= 'A' && p_str[index] <= 'Z') || (p_str[index] >= 'a' && p_str[index] <= 'z')) { + } else if (is_ascii_char(p_str[index])) { String id; - while ((p_str[index] >= 'A' && p_str[index] <= 'Z') || (p_str[index] >= 'a' && p_str[index] <= 'z')) { + while (is_ascii_char(p_str[index])) { id += p_str[index]; index++; } diff --git a/core/io/packet_peer.cpp b/core/io/packet_peer.cpp index e90d1695e545..0af236f76665 100644 --- a/core/io/packet_peer.cpp +++ b/core/io/packet_peer.cpp @@ -39,7 +39,7 @@ void PacketPeer::set_encode_buffer_max_size(int p_max_size) { ERR_FAIL_COND_MSG(p_max_size < 1024, "Max encode buffer must be at least 1024 bytes"); ERR_FAIL_COND_MSG(p_max_size > 256 * 1024 * 1024, "Max encode buffer cannot exceed 256 MiB"); encode_buffer_max_size = next_power_of_2(p_max_size); - encode_buffer.resize(0); + encode_buffer.clear(); } int PacketPeer::get_encode_buffer_max_size() const { diff --git a/core/io/pck_packer.cpp b/core/io/pck_packer.cpp index 221a6801304b..272ace34383b 100644 --- a/core/io/pck_packer.cpp +++ b/core/io/pck_packer.cpp @@ -62,7 +62,7 @@ Error PCKPacker::pck_start(const String &p_file, int p_alignment, const String & int v = 0; if (i * 2 < _key.length()) { char32_t ct = _key[i * 2]; - if (ct >= '0' && ct <= '9') { + if (is_digit(ct)) { ct = ct - '0'; } else if (ct >= 'a' && ct <= 'f') { ct = 10 + ct - 'a'; @@ -72,7 +72,7 @@ Error PCKPacker::pck_start(const String &p_file, int p_alignment, const String & if (i * 2 + 1 < _key.length()) { char32_t ct = _key[i * 2 + 1]; - if (ct >= '0' && ct <= '9') { + if (is_digit(ct)) { ct = ct - '0'; } else if (ct >= 'a' && ct <= 'f') { ct = 10 + ct - 'a'; diff --git a/core/io/resource.h b/core/io/resource.h index a0800c57cba7..b1e1c155412b 100644 --- a/core/io/resource.h +++ b/core/io/resource.h @@ -37,6 +37,8 @@ #include "core/templates/safe_refcount.h" #include "core/templates/self_list.h" +class Node; + #define RES_BASE_EXTENSION(m_ext) \ public: \ static void register_custom_data_to_otdb() { ClassDB::add_resource_base_extension(m_ext, get_class_static()); } \ @@ -103,7 +105,7 @@ class Resource : public RefCounted { virtual void set_path(const String &p_path, bool p_take_over = false); String get_path() const; - _FORCE_INLINE_ bool is_built_in() const { return path_cache.is_empty() || path_cache.find("::") != -1 || path_cache.begins_with("local://"); } + _FORCE_INLINE_ bool is_built_in() const { return path_cache.is_empty() || path_cache.contains("::") || path_cache.begins_with("local://"); } static String generate_scene_unique_id(); void set_scene_unique_id(const String &p_id); diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 200d5fafde30..8588bab0be86 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -335,7 +335,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { String exttype = get_unicode_string(); String path = get_unicode_string(); - if (path.find("://") == -1 && path.is_relative_path()) { + if (!path.contains("://") && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path path = ProjectSettings::get_singleton()->localize_path(res_path.get_base_dir().plus_file(path)); } @@ -626,7 +626,7 @@ Error ResourceLoaderBinary::load() { path = remaps[path]; } - if (path.find("://") == -1 && path.is_relative_path()) { + if (!path.contains("://") && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path path = ProjectSettings::get_singleton()->localize_path(path.get_base_dir().plus_file(external_resources[i].path)); } diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp index 470fb2d42d60..9b6440e2a2e5 100644 --- a/core/io/resource_importer.cpp +++ b/core/io/resource_importer.cpp @@ -463,3 +463,12 @@ void ResourceImporter::_bind_methods() { BIND_ENUM_CONSTANT(IMPORT_ORDER_DEFAULT); BIND_ENUM_CONSTANT(IMPORT_ORDER_SCENE); } + +void ResourceFormatImporter::add_importer(const Ref &p_importer, bool p_first_priority) { + ERR_FAIL_COND(p_importer.is_null()); + if (p_first_priority) { + importers.insert(0, p_importer); + } else { + importers.push_back(p_importer); + } +} diff --git a/core/io/resource_importer.h b/core/io/resource_importer.h index 261afbab69b0..2fffc16ad8dc 100644 --- a/core/io/resource_importer.h +++ b/core/io/resource_importer.h @@ -80,9 +80,8 @@ class ResourceFormatImporter : public ResourceFormatLoader { String get_internal_resource_path(const String &p_path) const; void get_internal_resource_path_list(const String &p_path, List *r_paths); - void add_importer(const Ref &p_importer) { - importers.push_back(p_importer); - } + void add_importer(const Ref &p_importer, bool p_first_priority = false); + void remove_importer(const Ref &p_importer) { importers.erase(p_importer); } Ref get_importer_by_name(const String &p_name) const; Ref get_importer_by_extension(const String &p_extension) const; diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 3e1c9d2e4a51..21bf566b1b3b 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -820,7 +820,7 @@ String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_rem } String l = res_remaps[i].substr(split + 1).strip_edges(); int score = TranslationServer::get_singleton()->compare_locales(locale, l); - if (score > best_score) { + if (score > 0 && score >= best_score) { new_path = res_remaps[i].left(split); best_score = score; if (score == 10) { diff --git a/core/io/resource_uid.cpp b/core/io/resource_uid.cpp index 1a16d5b47a3e..776756e64e54 100644 --- a/core/io/resource_uid.cpp +++ b/core/io/resource_uid.cpp @@ -71,9 +71,9 @@ ResourceUID::ID ResourceUID::text_to_id(const String &p_text) const { for (uint32_t i = 6; i < l; i++) { uid *= base; uint32_t c = p_text[i]; - if (c >= 'a' && c <= 'z') { + if (is_ascii_lower_case(c)) { uid += c - 'a'; - } else if (c >= '0' && c <= '9') { + } else if (is_digit(c)) { uid += c - '0' + char_count; } else { return INVALID_ID; diff --git a/core/io/stream_peer.cpp b/core/io/stream_peer.cpp index 28ebe811c936..c65968ef0338 100644 --- a/core/io/stream_peer.cpp +++ b/core/io/stream_peer.cpp @@ -98,7 +98,7 @@ Array StreamPeer::_get_partial_data(int p_bytes) { Error err = get_partial_data(&w[0], p_bytes, received); if (err != OK) { - data.resize(0); + data.clear(); } else if (received != data.size()) { data.resize(received); } @@ -563,7 +563,7 @@ Vector StreamPeerBuffer::get_data_array() const { } void StreamPeerBuffer::clear() { - data.resize(0); + data.clear(); pointer = 0; } diff --git a/core/io/translation_loader_po.cpp b/core/io/translation_loader_po.cpp index 2f6742bd7a13..8d3e58cad1b7 100644 --- a/core/io/translation_loader_po.cpp +++ b/core/io/translation_loader_po.cpp @@ -179,7 +179,7 @@ RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error) { } if (l.is_empty() || l.begins_with("#")) { - if (l.find("fuzzy") != -1) { + if (l.contains("fuzzy")) { skip_next = true; } line++; diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp index ce2435216bc6..14057b96be9e 100644 --- a/core/math/a_star.cpp +++ b/core/math/a_star.cpp @@ -32,7 +32,6 @@ #include "core/math/geometry_3d.h" #include "core/object/script_language.h" -#include "scene/scene_string_names.h" int AStar::get_available_point_id() const { if (points.has(last_free_id)) { diff --git a/core/math/aabb.h b/core/math/aabb.h index 3d19410ddfa7..cb6f05e9ea99 100644 --- a/core/math/aabb.h +++ b/core/math/aabb.h @@ -36,13 +36,13 @@ #include "core/math/vector3.h" /** - * AABB / AABB (Axis Aligned Bounding Box) - * This is implemented by a point (position) and the box size + * AABB (Axis Aligned Bounding Box) + * This is implemented by a point (position) and the box size. */ + class Variant; -class _NO_DISCARD_ AABB { -public: +struct _NO_DISCARD_ AABB { Vector3 position; Vector3 size; diff --git a/core/math/audio_frame.h b/core/math/audio_frame.h index 94fc3de2b136..8b244e9fe4bd 100644 --- a/core/math/audio_frame.h +++ b/core/math/audio_frame.h @@ -140,4 +140,16 @@ struct AudioFrame { _ALWAYS_INLINE_ AudioFrame() {} }; +_ALWAYS_INLINE_ AudioFrame operator*(float p_scalar, const AudioFrame &p_frame) { + return AudioFrame(p_frame.l * p_scalar, p_frame.r * p_scalar); +} + +_ALWAYS_INLINE_ AudioFrame operator*(int32_t p_scalar, const AudioFrame &p_frame) { + return AudioFrame(p_frame.l * p_scalar, p_frame.r * p_scalar); +} + +_ALWAYS_INLINE_ AudioFrame operator*(int64_t p_scalar, const AudioFrame &p_frame) { + return AudioFrame(p_frame.l * p_scalar, p_frame.r * p_scalar); +} + #endif // AUDIO_FRAME_H diff --git a/core/math/basis.h b/core/math/basis.h index 802da820895a..683f05150cc4 100644 --- a/core/math/basis.h +++ b/core/math/basis.h @@ -34,11 +34,7 @@ #include "core/math/quaternion.h" #include "core/math/vector3.h" -class _NO_DISCARD_ Basis { -private: - void _set_diagonal(const Vector3 &p_diag); - -public: +struct _NO_DISCARD_ Basis { Vector3 elements[3] = { Vector3(1, 0, 0), Vector3(0, 1, 0), @@ -263,6 +259,10 @@ class _NO_DISCARD_ Basis { } _FORCE_INLINE_ Basis() {} + +private: + // Helper method. + void _set_diagonal(const Vector3 &p_diag); }; _FORCE_INLINE_ void Basis::operator*=(const Basis &p_matrix) { @@ -334,4 +334,5 @@ real_t Basis::determinant() const { elements[1][0] * (elements[0][1] * elements[2][2] - elements[2][1] * elements[0][2]) + elements[2][0] * (elements[0][1] * elements[1][2] - elements[1][1] * elements[0][2]); } + #endif // BASIS_H diff --git a/core/math/camera_matrix.cpp b/core/math/camera_matrix.cpp index 2902ca59b90d..f5d746ef0f7f 100644 --- a/core/math/camera_matrix.cpp +++ b/core/math/camera_matrix.cpp @@ -30,7 +30,11 @@ #include "camera_matrix.h" +#include "core/math/aabb.h" #include "core/math/math_funcs.h" +#include "core/math/plane.h" +#include "core/math/rect2.h" +#include "core/math/transform_3d.h" #include "core/string/print_string.h" float CameraMatrix::determinant() const { diff --git a/core/math/camera_matrix.h b/core/math/camera_matrix.h index da1aba756298..285d2ae384e4 100644 --- a/core/math/camera_matrix.h +++ b/core/math/camera_matrix.h @@ -31,8 +31,14 @@ #ifndef CAMERA_MATRIX_H #define CAMERA_MATRIX_H -#include "core/math/rect2.h" -#include "core/math/transform_3d.h" +#include "core/math/math_defs.h" +#include "core/math/vector3.h" + +struct AABB; +struct Plane; +struct Rect2; +struct Transform3D; +struct Vector2; struct CameraMatrix { enum Planes { diff --git a/core/math/convex_hull.cpp b/core/math/convex_hull.cpp index 912ffb8b163f..bd292f4c2adf 100644 --- a/core/math/convex_hull.cpp +++ b/core/math/convex_hull.cpp @@ -2129,7 +2129,7 @@ bool ConvexHullInternal::shift_face(Face *p_face, real_t p_amount, LocalVectororigin = shifted_origin; return true; @@ -2167,9 +2167,9 @@ real_t ConvexHullComputer::compute(const Vector3 *p_coords, int32_t p_count, rea return shift; } - vertices.resize(0); - edges.resize(0); - faces.resize(0); + vertices.clear(); + edges.clear(); + faces.clear(); LocalVector old_vertices; get_vertex_copy(hull.vertex_list, old_vertices); diff --git a/core/math/delaunay_2d.h b/core/math/delaunay_2d.h index 08f5df84724b..c39997d6a93f 100644 --- a/core/math/delaunay_2d.h +++ b/core/math/delaunay_2d.h @@ -32,6 +32,7 @@ #define DELAUNAY_2D_H #include "core/math/rect2.h" +#include "core/templates/vector.h" class Delaunay2D { public: diff --git a/core/math/dynamic_bvh.h b/core/math/dynamic_bvh.h index 3041cdf26815..50ec2c2b304e 100644 --- a/core/math/dynamic_bvh.h +++ b/core/math/dynamic_bvh.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef DYNAMICBVH_H -#define DYNAMICBVH_H +#ifndef DYNAMIC_BVH_H +#define DYNAMIC_BVH_H #include "core/math/aabb.h" #include "core/templates/list.h" @@ -474,4 +474,4 @@ void DynamicBVH::ray_query(const Vector3 &p_from, const Vector3 &p_to, QueryResu } while (depth > 0); } -#endif // DYNAMICBVH_H +#endif // DYNAMIC_BVH_H diff --git a/core/math/expression.cpp b/core/math/expression.cpp index 4f8e79038f39..0ddac9744ef0 100644 --- a/core/math/expression.cpp +++ b/core/math/expression.cpp @@ -37,10 +37,6 @@ #include "core/os/os.h" #include "core/variant/variant_parser.h" -static bool _is_number(char32_t c) { - return (c >= '0' && c <= '9'); -} - Error Expression::_get_token(Token &r_token) { while (true) { #define GET_CHAR() (str_ofs >= expression.length() ? 0 : expression[str_ofs++]) @@ -88,7 +84,7 @@ Error Expression::_get_token(Token &r_token) { r_token.type = TK_INPUT; int index = 0; do { - if (!_is_number(expression[str_ofs])) { + if (!is_digit(expression[str_ofs])) { _set_error("Expected number after '$'"); r_token.type = TK_ERROR; return ERR_PARSE_ERROR; @@ -97,7 +93,7 @@ Error Expression::_get_token(Token &r_token) { index += expression[str_ofs] - '0'; str_ofs++; - } while (_is_number(expression[str_ofs])); + } while (is_digit(expression[str_ofs])); r_token.value = index; return OK; @@ -197,6 +193,7 @@ Error Expression::_get_token(Token &r_token) { case '\'': case '"': { String str; + char32_t prev = 0; while (true) { char32_t ch = GET_CHAR(); @@ -234,9 +231,11 @@ Error Expression::_get_token(Token &r_token) { case 'r': res = 13; break; + case 'U': case 'u': { - // hex number - for (int j = 0; j < 4; j++) { + // Hexadecimal sequence. + int hex_len = (next == 'U') ? 6 : 4; + for (int j = 0; j < hex_len; j++) { char32_t c = GET_CHAR(); if (c == 0) { @@ -244,13 +243,13 @@ Error Expression::_get_token(Token &r_token) { r_token.type = TK_ERROR; return ERR_PARSE_ERROR; } - if (!(_is_number(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { + if (!is_hex_digit(c)) { _set_error("Malformed hex constant in string"); r_token.type = TK_ERROR; return ERR_PARSE_ERROR; } char32_t v; - if (_is_number(c)) { + if (is_digit(c)) { v = c - '0'; } else if (c >= 'a' && c <= 'f') { v = c - 'a'; @@ -273,12 +272,46 @@ Error Expression::_get_token(Token &r_token) { } break; } + // Parse UTF-16 pair. + if ((res & 0xfffffc00) == 0xd800) { + if (prev == 0) { + prev = res; + continue; + } else { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } + } else if ((res & 0xfffffc00) == 0xdc00) { + if (prev == 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired trail surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } else { + res = (prev << 10UL) + res - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev = 0; + } + } + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } str += res; - } else { + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } str += ch; } } + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } r_token.type = TK_CONSTANT; r_token.value = str; @@ -291,39 +324,67 @@ Error Expression::_get_token(Token &r_token) { } char32_t next_char = (str_ofs >= expression.length()) ? 0 : expression[str_ofs]; - if (_is_number(cchar) || (cchar == '.' && _is_number(next_char))) { + if (is_digit(cchar) || (cchar == '.' && is_digit(next_char))) { //a number String num; #define READING_SIGN 0 #define READING_INT 1 -#define READING_DEC 2 -#define READING_EXP 3 -#define READING_DONE 4 +#define READING_HEX 2 +#define READING_BIN 3 +#define READING_DEC 4 +#define READING_EXP 5 +#define READING_DONE 6 int reading = READING_INT; char32_t c = cchar; bool exp_sign = false; bool exp_beg = false; + bool bin_beg = false; + bool hex_beg = false; bool is_float = false; + bool is_first_char = true; while (true) { switch (reading) { case READING_INT: { - if (_is_number(c)) { - //pass + if (is_digit(c)) { + if (is_first_char && c == '0') { + if (next_char == 'b') { + reading = READING_BIN; + } else if (next_char == 'x') { + reading = READING_HEX; + } + } } else if (c == '.') { reading = READING_DEC; is_float = true; } else if (c == 'e') { reading = READING_EXP; + is_float = true; } else { reading = READING_DONE; } + } break; + case READING_BIN: { + if (bin_beg && !is_binary_digit(c)) { + reading = READING_DONE; + } else if (c == 'b') { + bin_beg = true; + } + + } break; + case READING_HEX: { + if (hex_beg && !is_hex_digit(c)) { + reading = READING_DONE; + } else if (c == 'x') { + hex_beg = true; + } + } break; case READING_DEC: { - if (_is_number(c)) { + if (is_digit(c)) { } else if (c == 'e') { reading = READING_EXP; @@ -333,13 +394,10 @@ Error Expression::_get_token(Token &r_token) { } break; case READING_EXP: { - if (_is_number(c)) { + if (is_digit(c)) { exp_beg = true; } else if ((c == '-' || c == '+') && !exp_sign && !exp_beg) { - if (c == '-') { - is_float = true; - } exp_sign = true; } else { @@ -353,6 +411,7 @@ Error Expression::_get_token(Token &r_token) { } num += String::chr(c); c = GET_CHAR(); + is_first_char = false; } str_ofs--; @@ -361,16 +420,20 @@ Error Expression::_get_token(Token &r_token) { if (is_float) { r_token.value = num.to_float(); + } else if (bin_beg) { + r_token.value = num.bin_to_int(); + } else if (hex_beg) { + r_token.value = num.hex_to_int(); } else { r_token.value = num.to_int(); } return OK; - } else if ((cchar >= 'A' && cchar <= 'Z') || (cchar >= 'a' && cchar <= 'z') || cchar == '_') { + } else if (is_ascii_char(cchar) || is_underscore(cchar)) { String id; bool first = true; - while ((cchar >= 'A' && cchar <= 'Z') || (cchar >= 'a' && cchar <= 'z') || cchar == '_' || (!first && _is_number(cchar))) { + while (is_ascii_char(cchar) || is_underscore(cchar) || (!first && is_digit(cchar))) { id += String::chr(cchar); cchar = GET_CHAR(); first = false; diff --git a/core/math/face3.h b/core/math/face3.h index 3dbbca09e0a9..8b123f078cef 100644 --- a/core/math/face3.h +++ b/core/math/face3.h @@ -36,8 +36,7 @@ #include "core/math/transform_3d.h" #include "core/math/vector3.h" -class _NO_DISCARD_ Face3 { -public: +struct _NO_DISCARD_ Face3 { enum Side { SIDE_OVER, SIDE_UNDER, @@ -48,14 +47,11 @@ class _NO_DISCARD_ Face3 { Vector3 vertex[3]; /** - * * @param p_plane plane used to split the face * @param p_res array of at least 3 faces, amount used in function return * @param p_is_point_over array of at least 3 booleans, determining which face is over the plane, amount used in function return - * @param _epsilon constant used for numerical error rounding, to add "thickness" to the plane (so coplanar points can happen) * @return amount of faces generated by the split, either 0 (means no split possible), 2 or 3 */ - int split_by_plane(const Plane &p_plane, Face3 *p_res, bool *p_is_point_over) const; Plane get_plane(ClockDirection p_dir = CLOCKWISE) const; diff --git a/core/math/geometry_2d.h b/core/math/geometry_2d.h index 7385dba43865..a2881d5f607b 100644 --- a/core/math/geometry_2d.h +++ b/core/math/geometry_2d.h @@ -32,7 +32,11 @@ #define GEOMETRY_2D_H #include "core/math/delaunay_2d.h" +#include "core/math/math_funcs.h" #include "core/math/triangulate.h" +#include "core/math/vector2.h" +#include "core/math/vector2i.h" +#include "core/math/vector3.h" #include "core/math/vector3i.h" #include "core/templates/vector.h" diff --git a/core/math/plane.h b/core/math/plane.h index 8cb6f62b3b9d..66c17416624f 100644 --- a/core/math/plane.h +++ b/core/math/plane.h @@ -35,13 +35,12 @@ class Variant; -class _NO_DISCARD_ Plane { -public: +struct _NO_DISCARD_ Plane { Vector3 normal; real_t d = 0; void set_normal(const Vector3 &p_normal); - _FORCE_INLINE_ Vector3 get_normal() const { return normal; }; ///Point is coplanar, CMP_EPSILON for precision + _FORCE_INLINE_ Vector3 get_normal() const { return normal; }; void normalize(); Plane normalized() const; diff --git a/core/math/quaternion.h b/core/math/quaternion.h index 2575d7d229a3..7874e4f428c3 100644 --- a/core/math/quaternion.h +++ b/core/math/quaternion.h @@ -36,8 +36,7 @@ #include "core/math/vector3.h" #include "core/string/ustring.h" -class _NO_DISCARD_ Quaternion { -public: +struct _NO_DISCARD_ Quaternion { union { struct { real_t x; diff --git a/core/math/rect2.cpp b/core/math/rect2.cpp index 9047c19434fe..d6e20bdc3c94 100644 --- a/core/math/rect2.cpp +++ b/core/math/rect2.cpp @@ -28,7 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "core/math/transform_2d.h" // Includes rect2.h but Rect2 needs Transform2D +#include "rect2.h" + +#include "core/math/rect2i.h" +#include "core/math/transform_2d.h" +#include "core/string/ustring.h" bool Rect2::is_equal_approx(const Rect2 &p_rect) const { return position.is_equal_approx(p_rect.position) && size.is_equal_approx(p_rect.size); @@ -278,6 +282,6 @@ Rect2::operator String() const { return "[P: " + position.operator String() + ", S: " + size + "]"; } -Rect2i::operator String() const { - return "[P: " + position.operator String() + ", S: " + size + "]"; +Rect2::operator Rect2i() const { + return Rect2i(position, size); } diff --git a/core/math/rect2.h b/core/math/rect2.h index 4ea24e8f8858..6ecc02336c22 100644 --- a/core/math/rect2.h +++ b/core/math/rect2.h @@ -31,8 +31,11 @@ #ifndef RECT2_H #define RECT2_H -#include "core/math/vector2.h" // also includes math_funcs and ustring +#include "core/error/error_macros.h" +#include "core/math/vector2.h" +class String; +struct Rect2i; struct Transform2D; struct _NO_DISCARD_ Rect2 { @@ -179,6 +182,7 @@ struct _NO_DISCARD_ Rect2 { return new_rect; } + inline bool has_point(const Point2 &p_point) const { #ifdef MATH_CHECKS if (unlikely(size.x < 0 || size.y < 0)) { @@ -201,6 +205,7 @@ struct _NO_DISCARD_ Rect2 { return true; } + bool is_equal_approx(const Rect2 &p_rect) const; bool operator==(const Rect2 &p_rect) const { return position == p_rect.position && size == p_rect.size; } @@ -351,6 +356,7 @@ struct _NO_DISCARD_ Rect2 { } operator String() const; + operator Rect2i() const; Rect2() {} Rect2(real_t p_x, real_t p_y, real_t p_width, real_t p_height) : @@ -363,214 +369,4 @@ struct _NO_DISCARD_ Rect2 { } }; -struct _NO_DISCARD_ Rect2i { - Point2i position; - Size2i size; - - const Point2i &get_position() const { return position; } - void set_position(const Point2i &p_position) { position = p_position; } - const Size2i &get_size() const { return size; } - void set_size(const Size2i &p_size) { size = p_size; } - - int get_area() const { return size.width * size.height; } - - _FORCE_INLINE_ Vector2i get_center() const { return position + (size / 2); } - - inline bool intersects(const Rect2i &p_rect) const { -#ifdef MATH_CHECKS - if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) { - ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size."); - } -#endif - if (position.x > (p_rect.position.x + p_rect.size.width)) { - return false; - } - if ((position.x + size.width) < p_rect.position.x) { - return false; - } - if (position.y > (p_rect.position.y + p_rect.size.height)) { - return false; - } - if ((position.y + size.height) < p_rect.position.y) { - return false; - } - - return true; - } - - inline bool encloses(const Rect2i &p_rect) const { -#ifdef MATH_CHECKS - if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) { - ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size."); - } -#endif - return (p_rect.position.x >= position.x) && (p_rect.position.y >= position.y) && - ((p_rect.position.x + p_rect.size.x) < (position.x + size.x)) && - ((p_rect.position.y + p_rect.size.y) < (position.y + size.y)); - } - - _FORCE_INLINE_ bool has_no_area() const { - return (size.x <= 0 || size.y <= 0); - } - - // Returns the instersection between two Rect2is or an empty Rect2i if there is no intersection - inline Rect2i intersection(const Rect2i &p_rect) const { - Rect2i new_rect = p_rect; - - if (!intersects(new_rect)) { - return Rect2i(); - } - - new_rect.position.x = MAX(p_rect.position.x, position.x); - new_rect.position.y = MAX(p_rect.position.y, position.y); - - Point2i p_rect_end = p_rect.position + p_rect.size; - Point2i end = position + size; - - new_rect.size.x = MIN(p_rect_end.x, end.x) - new_rect.position.x; - new_rect.size.y = MIN(p_rect_end.y, end.y) - new_rect.position.y; - - return new_rect; - } - - inline Rect2i merge(const Rect2i &p_rect) const { ///< return a merged rect -#ifdef MATH_CHECKS - if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) { - ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size."); - } -#endif - Rect2i new_rect; - - new_rect.position.x = MIN(p_rect.position.x, position.x); - new_rect.position.y = MIN(p_rect.position.y, position.y); - - new_rect.size.x = MAX(p_rect.position.x + p_rect.size.x, position.x + size.x); - new_rect.size.y = MAX(p_rect.position.y + p_rect.size.y, position.y + size.y); - - new_rect.size = new_rect.size - new_rect.position; //make relative again - - return new_rect; - } - bool has_point(const Point2i &p_point) const { -#ifdef MATH_CHECKS - if (unlikely(size.x < 0 || size.y < 0)) { - ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size."); - } -#endif - if (p_point.x < position.x) { - return false; - } - if (p_point.y < position.y) { - return false; - } - - if (p_point.x >= (position.x + size.x)) { - return false; - } - if (p_point.y >= (position.y + size.y)) { - return false; - } - - return true; - } - - bool operator==(const Rect2i &p_rect) const { return position == p_rect.position && size == p_rect.size; } - bool operator!=(const Rect2i &p_rect) const { return position != p_rect.position || size != p_rect.size; } - - Rect2i grow(int p_amount) const { - Rect2i g = *this; - g.position.x -= p_amount; - g.position.y -= p_amount; - g.size.width += p_amount * 2; - g.size.height += p_amount * 2; - return g; - } - - inline Rect2i grow_side(Side p_side, int p_amount) const { - Rect2i g = *this; - g = g.grow_individual((SIDE_LEFT == p_side) ? p_amount : 0, - (SIDE_TOP == p_side) ? p_amount : 0, - (SIDE_RIGHT == p_side) ? p_amount : 0, - (SIDE_BOTTOM == p_side) ? p_amount : 0); - return g; - } - - inline Rect2i grow_side_bind(uint32_t p_side, int p_amount) const { - return grow_side(Side(p_side), p_amount); - } - - inline Rect2i grow_individual(int p_left, int p_top, int p_right, int p_bottom) const { - Rect2i g = *this; - g.position.x -= p_left; - g.position.y -= p_top; - g.size.width += p_left + p_right; - g.size.height += p_top + p_bottom; - - return g; - } - - _FORCE_INLINE_ Rect2i expand(const Vector2i &p_vector) const { - Rect2i r = *this; - r.expand_to(p_vector); - return r; - } - - inline void expand_to(const Point2i &p_vector) { -#ifdef MATH_CHECKS - if (unlikely(size.x < 0 || size.y < 0)) { - ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size."); - } -#endif - Point2i begin = position; - Point2i end = position + size; - - if (p_vector.x < begin.x) { - begin.x = p_vector.x; - } - if (p_vector.y < begin.y) { - begin.y = p_vector.y; - } - - if (p_vector.x > end.x) { - end.x = p_vector.x; - } - if (p_vector.y > end.y) { - end.y = p_vector.y; - } - - position = begin; - size = end - begin; - } - - _FORCE_INLINE_ Rect2i abs() const { - return Rect2i(Point2i(position.x + MIN(size.x, 0), position.y + MIN(size.y, 0)), size.abs()); - } - - _FORCE_INLINE_ void set_end(const Vector2i &p_end) { - size = p_end - position; - } - - _FORCE_INLINE_ Vector2i get_end() const { - return position + size; - } - - operator String() const; - - operator Rect2() const { return Rect2(position, size); } - - Rect2i() {} - Rect2i(const Rect2 &p_r2) : - position(p_r2.position), - size(p_r2.size) { - } - Rect2i(int p_x, int p_y, int p_width, int p_height) : - position(Point2i(p_x, p_y)), - size(Size2i(p_width, p_height)) { - } - Rect2i(const Point2i &p_pos, const Size2i &p_size) : - position(p_pos), - size(p_size) { - } -}; - #endif // RECT2_H diff --git a/core/math/rect2i.cpp b/core/math/rect2i.cpp new file mode 100644 index 000000000000..0782c450d03e --- /dev/null +++ b/core/math/rect2i.cpp @@ -0,0 +1,42 @@ +/*************************************************************************/ +/* rect2i.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "rect2i.h" + +#include "core/math/rect2.h" +#include "core/string/ustring.h" + +Rect2i::operator String() const { + return "[P: " + position.operator String() + ", S: " + size + "]"; +} + +Rect2i::operator Rect2() const { + return Rect2(position, size); +} diff --git a/core/math/rect2i.h b/core/math/rect2i.h new file mode 100644 index 000000000000..db1459a3e6bb --- /dev/null +++ b/core/math/rect2i.h @@ -0,0 +1,245 @@ +/*************************************************************************/ +/* rect2i.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef RECT2I_H +#define RECT2I_H + +#include "core/error/error_macros.h" +#include "core/math/vector2i.h" + +class String; +struct Rect2; + +struct _NO_DISCARD_ Rect2i { + Point2i position; + Size2i size; + + const Point2i &get_position() const { return position; } + void set_position(const Point2i &p_position) { position = p_position; } + const Size2i &get_size() const { return size; } + void set_size(const Size2i &p_size) { size = p_size; } + + int get_area() const { return size.width * size.height; } + + _FORCE_INLINE_ Vector2i get_center() const { return position + (size / 2); } + + inline bool intersects(const Rect2i &p_rect) const { +#ifdef MATH_CHECKS + if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) { + ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size."); + } +#endif + if (position.x >= (p_rect.position.x + p_rect.size.width)) { + return false; + } + if ((position.x + size.width) <= p_rect.position.x) { + return false; + } + if (position.y >= (p_rect.position.y + p_rect.size.height)) { + return false; + } + if ((position.y + size.height) <= p_rect.position.y) { + return false; + } + + return true; + } + + inline bool encloses(const Rect2i &p_rect) const { +#ifdef MATH_CHECKS + if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) { + ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size."); + } +#endif + return (p_rect.position.x >= position.x) && (p_rect.position.y >= position.y) && + ((p_rect.position.x + p_rect.size.x) <= (position.x + size.x)) && + ((p_rect.position.y + p_rect.size.y) <= (position.y + size.y)); + } + + _FORCE_INLINE_ bool has_no_area() const { + return (size.x <= 0 || size.y <= 0); + } + + // Returns the instersection between two Rect2is or an empty Rect2i if there is no intersection + inline Rect2i intersection(const Rect2i &p_rect) const { + Rect2i new_rect = p_rect; + + if (!intersects(new_rect)) { + return Rect2i(); + } + + new_rect.position.x = MAX(p_rect.position.x, position.x); + new_rect.position.y = MAX(p_rect.position.y, position.y); + + Point2i p_rect_end = p_rect.position + p_rect.size; + Point2i end = position + size; + + new_rect.size.x = MIN(p_rect_end.x, end.x) - new_rect.position.x; + new_rect.size.y = MIN(p_rect_end.y, end.y) - new_rect.position.y; + + return new_rect; + } + + inline Rect2i merge(const Rect2i &p_rect) const { ///< return a merged rect +#ifdef MATH_CHECKS + if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) { + ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size."); + } +#endif + Rect2i new_rect; + + new_rect.position.x = MIN(p_rect.position.x, position.x); + new_rect.position.y = MIN(p_rect.position.y, position.y); + + new_rect.size.x = MAX(p_rect.position.x + p_rect.size.x, position.x + size.x); + new_rect.size.y = MAX(p_rect.position.y + p_rect.size.y, position.y + size.y); + + new_rect.size = new_rect.size - new_rect.position; //make relative again + + return new_rect; + } + bool has_point(const Point2i &p_point) const { +#ifdef MATH_CHECKS + if (unlikely(size.x < 0 || size.y < 0)) { + ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size."); + } +#endif + if (p_point.x < position.x) { + return false; + } + if (p_point.y < position.y) { + return false; + } + + if (p_point.x >= (position.x + size.x)) { + return false; + } + if (p_point.y >= (position.y + size.y)) { + return false; + } + + return true; + } + + bool operator==(const Rect2i &p_rect) const { return position == p_rect.position && size == p_rect.size; } + bool operator!=(const Rect2i &p_rect) const { return position != p_rect.position || size != p_rect.size; } + + Rect2i grow(int p_amount) const { + Rect2i g = *this; + g.position.x -= p_amount; + g.position.y -= p_amount; + g.size.width += p_amount * 2; + g.size.height += p_amount * 2; + return g; + } + + inline Rect2i grow_side(Side p_side, int p_amount) const { + Rect2i g = *this; + g = g.grow_individual((SIDE_LEFT == p_side) ? p_amount : 0, + (SIDE_TOP == p_side) ? p_amount : 0, + (SIDE_RIGHT == p_side) ? p_amount : 0, + (SIDE_BOTTOM == p_side) ? p_amount : 0); + return g; + } + + inline Rect2i grow_side_bind(uint32_t p_side, int p_amount) const { + return grow_side(Side(p_side), p_amount); + } + + inline Rect2i grow_individual(int p_left, int p_top, int p_right, int p_bottom) const { + Rect2i g = *this; + g.position.x -= p_left; + g.position.y -= p_top; + g.size.width += p_left + p_right; + g.size.height += p_top + p_bottom; + + return g; + } + + _FORCE_INLINE_ Rect2i expand(const Vector2i &p_vector) const { + Rect2i r = *this; + r.expand_to(p_vector); + return r; + } + + inline void expand_to(const Point2i &p_vector) { +#ifdef MATH_CHECKS + if (unlikely(size.x < 0 || size.y < 0)) { + ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size."); + } +#endif + Point2i begin = position; + Point2i end = position + size; + + if (p_vector.x < begin.x) { + begin.x = p_vector.x; + } + if (p_vector.y < begin.y) { + begin.y = p_vector.y; + } + + if (p_vector.x > end.x) { + end.x = p_vector.x; + } + if (p_vector.y > end.y) { + end.y = p_vector.y; + } + + position = begin; + size = end - begin; + } + + _FORCE_INLINE_ Rect2i abs() const { + return Rect2i(Point2i(position.x + MIN(size.x, 0), position.y + MIN(size.y, 0)), size.abs()); + } + + _FORCE_INLINE_ void set_end(const Vector2i &p_end) { + size = p_end - position; + } + + _FORCE_INLINE_ Vector2i get_end() const { + return position + size; + } + + operator String() const; + operator Rect2() const; + + Rect2i() {} + Rect2i(int p_x, int p_y, int p_width, int p_height) : + position(Point2i(p_x, p_y)), + size(Size2i(p_width, p_height)) { + } + Rect2i(const Point2i &p_pos, const Size2i &p_size) : + position(p_pos), + size(p_size) { + } +}; + +#endif // RECT2I_H diff --git a/core/math/transform_2d.cpp b/core/math/transform_2d.cpp index 0201cf575ca2..e6e24e9b32b3 100644 --- a/core/math/transform_2d.cpp +++ b/core/math/transform_2d.cpp @@ -30,6 +30,8 @@ #include "transform_2d.h" +#include "core/string/ustring.h" + void Transform2D::invert() { // FIXME: this function assumes the basis is a rotation matrix, with no scaling. // Transform2D::affine_inverse can handle matrices with scaling, so GDScript should eventually use that. diff --git a/core/math/transform_2d.h b/core/math/transform_2d.h index 6c2d51bd9ba4..f4546c13c8c1 100644 --- a/core/math/transform_2d.h +++ b/core/math/transform_2d.h @@ -31,7 +31,12 @@ #ifndef TRANSFORM_2D_H #define TRANSFORM_2D_H -#include "core/math/rect2.h" // also includes vector2, math_funcs, and ustring +#include "core/math/math_funcs.h" +#include "core/math/rect2.h" +#include "core/math/vector2.h" +#include "core/templates/vector.h" + +class String; struct _NO_DISCARD_ Transform2D { // Warning #1: basis of Transform2D is stored differently from Basis. In terms of elements array, the basis matrix looks like "on paper": diff --git a/core/math/transform_3d.h b/core/math/transform_3d.h index c16c278e7456..3b4762e2210a 100644 --- a/core/math/transform_3d.h +++ b/core/math/transform_3d.h @@ -28,15 +28,14 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef TRANSFORM_H -#define TRANSFORM_H +#ifndef TRANSFORM_3D_H +#define TRANSFORM_3D_H #include "core/math/aabb.h" #include "core/math/basis.h" #include "core/math/plane.h" -class _NO_DISCARD_ Transform3D { -public: +struct _NO_DISCARD_ Transform3D { Basis basis; Vector3 origin; @@ -265,4 +264,4 @@ _FORCE_INLINE_ Plane Transform3D::xform_inv_fast(const Plane &p_plane, const Tra return Plane(normal, d); } -#endif // TRANSFORM_H +#endif // TRANSFORM_3D_H diff --git a/core/math/triangulate.h b/core/math/triangulate.h index d96bdb8cabfd..0bfcfcb978dd 100644 --- a/core/math/triangulate.h +++ b/core/math/triangulate.h @@ -32,6 +32,7 @@ #define TRIANGULATE_H #include "core/math/vector2.h" +#include "core/templates/vector.h" /* https://www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml diff --git a/core/math/vector2.cpp b/core/math/vector2.cpp index 676a0004eab2..40149e8cc19f 100644 --- a/core/math/vector2.cpp +++ b/core/math/vector2.cpp @@ -30,6 +30,9 @@ #include "vector2.h" +#include "core/math/vector2i.h" +#include "core/string/ustring.h" + real_t Vector2::angle() const { return Math::atan2(y, x); } @@ -202,91 +205,6 @@ Vector2::operator String() const { return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ")"; } -/* Vector2i */ - -Vector2i Vector2i::clamp(const Vector2i &p_min, const Vector2i &p_max) const { - return Vector2i( - CLAMP(x, p_min.x, p_max.x), - CLAMP(y, p_min.y, p_max.y)); -} - -int64_t Vector2i::length_squared() const { - return x * (int64_t)x + y * (int64_t)y; -} - -double Vector2i::length() const { - return Math::sqrt((double)length_squared()); -} - -Vector2i Vector2i::operator+(const Vector2i &p_v) const { - return Vector2i(x + p_v.x, y + p_v.y); -} - -void Vector2i::operator+=(const Vector2i &p_v) { - x += p_v.x; - y += p_v.y; -} - -Vector2i Vector2i::operator-(const Vector2i &p_v) const { - return Vector2i(x - p_v.x, y - p_v.y); -} - -void Vector2i::operator-=(const Vector2i &p_v) { - x -= p_v.x; - y -= p_v.y; -} - -Vector2i Vector2i::operator*(const Vector2i &p_v1) const { - return Vector2i(x * p_v1.x, y * p_v1.y); -} - -Vector2i Vector2i::operator*(const int32_t &rvalue) const { - return Vector2i(x * rvalue, y * rvalue); -} - -void Vector2i::operator*=(const int32_t &rvalue) { - x *= rvalue; - y *= rvalue; -} - -Vector2i Vector2i::operator/(const Vector2i &p_v1) const { - return Vector2i(x / p_v1.x, y / p_v1.y); -} - -Vector2i Vector2i::operator/(const int32_t &rvalue) const { - return Vector2i(x / rvalue, y / rvalue); -} - -void Vector2i::operator/=(const int32_t &rvalue) { - x /= rvalue; - y /= rvalue; -} - -Vector2i Vector2i::operator%(const Vector2i &p_v1) const { - return Vector2i(x % p_v1.x, y % p_v1.y); -} - -Vector2i Vector2i::operator%(const int32_t &rvalue) const { - return Vector2i(x % rvalue, y % rvalue); -} - -void Vector2i::operator%=(const int32_t &rvalue) { - x %= rvalue; - y %= rvalue; -} - -Vector2i Vector2i::operator-() const { - return Vector2i(-x, -y); -} - -bool Vector2i::operator==(const Vector2i &p_vec2) const { - return x == p_vec2.x && y == p_vec2.y; -} - -bool Vector2i::operator!=(const Vector2i &p_vec2) const { - return x != p_vec2.x || y != p_vec2.y; -} - -Vector2i::operator String() const { - return "(" + itos(x) + ", " + itos(y) + ")"; +Vector2::operator Vector2i() const { + return Vector2i(x, y); } diff --git a/core/math/vector2.h b/core/math/vector2.h index af40b9e68d67..92ac5257b0a7 100644 --- a/core/math/vector2.h +++ b/core/math/vector2.h @@ -32,8 +32,8 @@ #define VECTOR2_H #include "core/math/math_funcs.h" -#include "core/string/ustring.h" +class String; struct Vector2i; struct _NO_DISCARD_ Vector2 { @@ -60,10 +60,10 @@ struct _NO_DISCARD_ Vector2 { }; _FORCE_INLINE_ real_t &operator[](int p_idx) { - return p_idx ? y : x; + return coord[p_idx]; } _FORCE_INLINE_ const real_t &operator[](int p_idx) const { - return p_idx ? y : x; + return coord[p_idx]; } _FORCE_INLINE_ void set_all(const real_t p_value) { @@ -167,6 +167,7 @@ struct _NO_DISCARD_ Vector2 { real_t aspect() const { return width / height; } operator String() const; + operator Vector2i() const; _FORCE_INLINE_ Vector2() {} _FORCE_INLINE_ Vector2(const real_t p_x, const real_t p_y) { @@ -179,22 +180,6 @@ _FORCE_INLINE_ Vector2 Vector2::plane_project(const real_t p_d, const Vector2 &p return p_vec - *this * (dot(p_vec) - p_d); } -_FORCE_INLINE_ Vector2 operator*(const float p_scalar, const Vector2 &p_vec) { - return p_vec * p_scalar; -} - -_FORCE_INLINE_ Vector2 operator*(const double p_scalar, const Vector2 &p_vec) { - return p_vec * p_scalar; -} - -_FORCE_INLINE_ Vector2 operator*(const int32_t p_scalar, const Vector2 &p_vec) { - return p_vec * p_scalar; -} - -_FORCE_INLINE_ Vector2 operator*(const int64_t p_scalar, const Vector2 &p_vec) { - return p_vec * p_scalar; -} - _FORCE_INLINE_ Vector2 Vector2::operator+(const Vector2 &p_v) const { return Vector2(x + p_v.x, y + p_v.y); } @@ -279,116 +264,26 @@ Vector2 Vector2::direction_to(const Vector2 &p_to) const { return ret; } -typedef Vector2 Size2; -typedef Vector2 Point2; - -/* INTEGER STUFF */ - -struct _NO_DISCARD_ Vector2i { - enum Axis { - AXIS_X, - AXIS_Y, - }; - - union { - int32_t x = 0; - int32_t width; - }; - union { - int32_t y = 0; - int32_t height; - }; - - _FORCE_INLINE_ int32_t &operator[](int p_idx) { - return p_idx ? y : x; - } - _FORCE_INLINE_ const int32_t &operator[](int p_idx) const { - return p_idx ? y : x; - } - - _FORCE_INLINE_ Vector2i::Axis min_axis_index() const { - return x < y ? Vector2i::AXIS_X : Vector2i::AXIS_Y; - } - - _FORCE_INLINE_ Vector2i::Axis max_axis_index() const { - return x < y ? Vector2i::AXIS_Y : Vector2i::AXIS_X; - } - - Vector2i min(const Vector2i &p_vector2i) const { - return Vector2(MIN(x, p_vector2i.x), MIN(y, p_vector2i.y)); - } - - Vector2i max(const Vector2i &p_vector2i) const { - return Vector2(MAX(x, p_vector2i.x), MAX(y, p_vector2i.y)); - } - - Vector2i operator+(const Vector2i &p_v) const; - void operator+=(const Vector2i &p_v); - Vector2i operator-(const Vector2i &p_v) const; - void operator-=(const Vector2i &p_v); - Vector2i operator*(const Vector2i &p_v1) const; - - Vector2i operator*(const int32_t &rvalue) const; - void operator*=(const int32_t &rvalue); - - Vector2i operator/(const Vector2i &p_v1) const; - Vector2i operator/(const int32_t &rvalue) const; - void operator/=(const int32_t &rvalue); - - Vector2i operator%(const Vector2i &p_v1) const; - Vector2i operator%(const int32_t &rvalue) const; - void operator%=(const int32_t &rvalue); - - Vector2i operator-() const; - bool operator<(const Vector2i &p_vec2) const { return (x == p_vec2.x) ? (y < p_vec2.y) : (x < p_vec2.x); } - bool operator>(const Vector2i &p_vec2) const { return (x == p_vec2.x) ? (y > p_vec2.y) : (x > p_vec2.x); } - - bool operator<=(const Vector2i &p_vec2) const { return x == p_vec2.x ? (y <= p_vec2.y) : (x < p_vec2.x); } - bool operator>=(const Vector2i &p_vec2) const { return x == p_vec2.x ? (y >= p_vec2.y) : (x > p_vec2.x); } - - bool operator==(const Vector2i &p_vec2) const; - bool operator!=(const Vector2i &p_vec2) const; - - int64_t length_squared() const; - double length() const; - - real_t aspect() const { return width / (real_t)height; } - Vector2i sign() const { return Vector2i(SIGN(x), SIGN(y)); } - Vector2i abs() const { return Vector2i(ABS(x), ABS(y)); } - Vector2i clamp(const Vector2i &p_min, const Vector2i &p_max) const; - - operator String() const; - - operator Vector2() const { return Vector2(x, y); } +// Multiplication operators required to workaround issues with LLVM using implicit conversion +// to Vector2i instead for integers where it should not. - inline Vector2i() {} - inline Vector2i(const Vector2 &p_vec2) { - x = (int32_t)p_vec2.x; - y = (int32_t)p_vec2.y; - } - inline Vector2i(const int32_t p_x, const int32_t p_y) { - x = p_x; - y = p_y; - } -}; - -_FORCE_INLINE_ Vector2i operator*(const int32_t &p_scalar, const Vector2i &p_vector) { - return p_vector * p_scalar; +_FORCE_INLINE_ Vector2 operator*(const float p_scalar, const Vector2 &p_vec) { + return p_vec * p_scalar; } -_FORCE_INLINE_ Vector2i operator*(const int64_t &p_scalar, const Vector2i &p_vector) { - return p_vector * p_scalar; +_FORCE_INLINE_ Vector2 operator*(const double p_scalar, const Vector2 &p_vec) { + return p_vec * p_scalar; } -_FORCE_INLINE_ Vector2i operator*(const float &p_scalar, const Vector2i &p_vector) { - return p_vector * p_scalar; +_FORCE_INLINE_ Vector2 operator*(const int32_t p_scalar, const Vector2 &p_vec) { + return p_vec * p_scalar; } -_FORCE_INLINE_ Vector2i operator*(const double &p_scalar, const Vector2i &p_vector) { - return p_vector * p_scalar; +_FORCE_INLINE_ Vector2 operator*(const int64_t p_scalar, const Vector2 &p_vec) { + return p_vec * p_scalar; } -typedef Vector2i Size2i; -typedef Vector2i Point2i; +typedef Vector2 Size2; +typedef Vector2 Point2; #endif // VECTOR2_H diff --git a/core/math/vector2i.cpp b/core/math/vector2i.cpp new file mode 100644 index 000000000000..dfed42e4d6b0 --- /dev/null +++ b/core/math/vector2i.cpp @@ -0,0 +1,125 @@ +/*************************************************************************/ +/* vector2i.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "vector2i.h" + +#include "core/math/vector2.h" +#include "core/string/ustring.h" + +Vector2i Vector2i::clamp(const Vector2i &p_min, const Vector2i &p_max) const { + return Vector2i( + CLAMP(x, p_min.x, p_max.x), + CLAMP(y, p_min.y, p_max.y)); +} + +int64_t Vector2i::length_squared() const { + return x * (int64_t)x + y * (int64_t)y; +} + +double Vector2i::length() const { + return Math::sqrt((double)length_squared()); +} + +Vector2i Vector2i::operator+(const Vector2i &p_v) const { + return Vector2i(x + p_v.x, y + p_v.y); +} + +void Vector2i::operator+=(const Vector2i &p_v) { + x += p_v.x; + y += p_v.y; +} + +Vector2i Vector2i::operator-(const Vector2i &p_v) const { + return Vector2i(x - p_v.x, y - p_v.y); +} + +void Vector2i::operator-=(const Vector2i &p_v) { + x -= p_v.x; + y -= p_v.y; +} + +Vector2i Vector2i::operator*(const Vector2i &p_v1) const { + return Vector2i(x * p_v1.x, y * p_v1.y); +} + +Vector2i Vector2i::operator*(const int32_t &rvalue) const { + return Vector2i(x * rvalue, y * rvalue); +} + +void Vector2i::operator*=(const int32_t &rvalue) { + x *= rvalue; + y *= rvalue; +} + +Vector2i Vector2i::operator/(const Vector2i &p_v1) const { + return Vector2i(x / p_v1.x, y / p_v1.y); +} + +Vector2i Vector2i::operator/(const int32_t &rvalue) const { + return Vector2i(x / rvalue, y / rvalue); +} + +void Vector2i::operator/=(const int32_t &rvalue) { + x /= rvalue; + y /= rvalue; +} + +Vector2i Vector2i::operator%(const Vector2i &p_v1) const { + return Vector2i(x % p_v1.x, y % p_v1.y); +} + +Vector2i Vector2i::operator%(const int32_t &rvalue) const { + return Vector2i(x % rvalue, y % rvalue); +} + +void Vector2i::operator%=(const int32_t &rvalue) { + x %= rvalue; + y %= rvalue; +} + +Vector2i Vector2i::operator-() const { + return Vector2i(-x, -y); +} + +bool Vector2i::operator==(const Vector2i &p_vec2) const { + return x == p_vec2.x && y == p_vec2.y; +} + +bool Vector2i::operator!=(const Vector2i &p_vec2) const { + return x != p_vec2.x || y != p_vec2.y; +} + +Vector2i::operator String() const { + return "(" + itos(x) + ", " + itos(y) + ")"; +} + +Vector2i::operator Vector2() const { + return Vector2((int32_t)x, (int32_t)y); +} diff --git a/core/math/vector2i.h b/core/math/vector2i.h new file mode 100644 index 000000000000..3f5f12d4dd27 --- /dev/null +++ b/core/math/vector2i.h @@ -0,0 +1,149 @@ +/*************************************************************************/ +/* vector2i.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef VECTOR2I_H +#define VECTOR2I_H + +#include "core/math/math_funcs.h" + +class String; +struct Vector2; + +struct _NO_DISCARD_ Vector2i { + enum Axis { + AXIS_X, + AXIS_Y, + }; + + union { + struct { + union { + int32_t x; + int32_t width; + }; + union { + int32_t y; + int32_t height; + }; + }; + + int32_t coord[2] = { 0 }; + }; + + _FORCE_INLINE_ int32_t &operator[](int p_idx) { + return coord[p_idx]; + } + _FORCE_INLINE_ const int32_t &operator[](int p_idx) const { + return coord[p_idx]; + } + + _FORCE_INLINE_ Vector2i::Axis min_axis_index() const { + return x < y ? Vector2i::AXIS_X : Vector2i::AXIS_Y; + } + + _FORCE_INLINE_ Vector2i::Axis max_axis_index() const { + return x < y ? Vector2i::AXIS_Y : Vector2i::AXIS_X; + } + + Vector2i min(const Vector2i &p_vector2i) const { + return Vector2i(MIN(x, p_vector2i.x), MIN(y, p_vector2i.y)); + } + + Vector2i max(const Vector2i &p_vector2i) const { + return Vector2i(MAX(x, p_vector2i.x), MAX(y, p_vector2i.y)); + } + + Vector2i operator+(const Vector2i &p_v) const; + void operator+=(const Vector2i &p_v); + Vector2i operator-(const Vector2i &p_v) const; + void operator-=(const Vector2i &p_v); + Vector2i operator*(const Vector2i &p_v1) const; + + Vector2i operator*(const int32_t &rvalue) const; + void operator*=(const int32_t &rvalue); + + Vector2i operator/(const Vector2i &p_v1) const; + Vector2i operator/(const int32_t &rvalue) const; + void operator/=(const int32_t &rvalue); + + Vector2i operator%(const Vector2i &p_v1) const; + Vector2i operator%(const int32_t &rvalue) const; + void operator%=(const int32_t &rvalue); + + Vector2i operator-() const; + bool operator<(const Vector2i &p_vec2) const { return (x == p_vec2.x) ? (y < p_vec2.y) : (x < p_vec2.x); } + bool operator>(const Vector2i &p_vec2) const { return (x == p_vec2.x) ? (y > p_vec2.y) : (x > p_vec2.x); } + + bool operator<=(const Vector2i &p_vec2) const { return x == p_vec2.x ? (y <= p_vec2.y) : (x < p_vec2.x); } + bool operator>=(const Vector2i &p_vec2) const { return x == p_vec2.x ? (y >= p_vec2.y) : (x > p_vec2.x); } + + bool operator==(const Vector2i &p_vec2) const; + bool operator!=(const Vector2i &p_vec2) const; + + int64_t length_squared() const; + double length() const; + + real_t aspect() const { return width / (real_t)height; } + Vector2i sign() const { return Vector2i(SIGN(x), SIGN(y)); } + Vector2i abs() const { return Vector2i(ABS(x), ABS(y)); } + Vector2i clamp(const Vector2i &p_min, const Vector2i &p_max) const; + + operator String() const; + operator Vector2() const; + + inline Vector2i() {} + inline Vector2i(const int32_t p_x, const int32_t p_y) { + x = p_x; + y = p_y; + } +}; + +// Multiplication operators required to workaround issues with LLVM using implicit conversion. + +_FORCE_INLINE_ Vector2i operator*(const int32_t p_scalar, const Vector2i &p_vector) { + return p_vector * p_scalar; +} + +_FORCE_INLINE_ Vector2i operator*(const int64_t p_scalar, const Vector2i &p_vector) { + return p_vector * p_scalar; +} + +_FORCE_INLINE_ Vector2i operator*(const float p_scalar, const Vector2i &p_vector) { + return p_vector * p_scalar; +} + +_FORCE_INLINE_ Vector2i operator*(const double p_scalar, const Vector2i &p_vector) { + return p_vector * p_scalar; +} + +typedef Vector2i Size2i; +typedef Vector2i Point2i; + +#endif // VECTOR2I_H diff --git a/core/math/vector3.h b/core/math/vector3.h index b62edef40fcb..345329f7f35e 100644 --- a/core/math/vector3.h +++ b/core/math/vector3.h @@ -35,7 +35,8 @@ #include "core/math/vector2.h" #include "core/math/vector3i.h" #include "core/string/ustring.h" -class Basis; + +struct Basis; struct _NO_DISCARD_ Vector3 { static const int AXIS_COUNT = 3; @@ -342,6 +343,9 @@ Vector3 &Vector3::operator*=(const real_t p_scalar) { return *this; } +// Multiplication operators required to workaround issues with LLVM using implicit conversion +// to Vector2i instead for integers where it should not. + _FORCE_INLINE_ Vector3 operator*(const float p_scalar, const Vector3 &p_vec) { return p_vec * p_scalar; } diff --git a/core/math/vector3i.h b/core/math/vector3i.h index 1564ee917308..d166de80aa1f 100644 --- a/core/math/vector3i.h +++ b/core/math/vector3i.h @@ -194,6 +194,12 @@ Vector3i &Vector3i::operator*=(const int32_t p_scalar) { return *this; } +Vector3i Vector3i::operator*(const int32_t p_scalar) const { + return Vector3i(x * p_scalar, y * p_scalar, z * p_scalar); +} + +// Multiplication operators required to workaround issues with LLVM using implicit conversion. + _FORCE_INLINE_ Vector3i operator*(const int32_t p_scalar, const Vector3i &p_vector) { return p_vector * p_scalar; } @@ -210,10 +216,6 @@ _FORCE_INLINE_ Vector3i operator*(const double p_scalar, const Vector3i &p_vecto return p_vector * p_scalar; } -Vector3i Vector3i::operator*(const int32_t p_scalar) const { - return Vector3i(x * p_scalar, y * p_scalar, z * p_scalar); -} - Vector3i &Vector3i::operator/=(const int32_t p_scalar) { x /= p_scalar; y /= p_scalar; diff --git a/core/multiplayer/multiplayer_api.cpp b/core/multiplayer/multiplayer_api.cpp index 627825246a4a..c8cb333e2c0a 100644 --- a/core/multiplayer/multiplayer_api.cpp +++ b/core/multiplayer/multiplayer_api.cpp @@ -32,9 +32,6 @@ #include "core/debugger/engine_debugger.h" #include "core/io/marshalls.h" -#include "core/multiplayer/multiplayer_replicator.h" -#include "core/multiplayer/rpc_manager.h" -#include "scene/main/node.h" #include @@ -42,6 +39,10 @@ #include "core/os/os.h" #endif +MultiplayerReplicationInterface *(*MultiplayerAPI::create_default_replication_interface)(MultiplayerAPI *p_multiplayer) = nullptr; +MultiplayerRPCInterface *(*MultiplayerAPI::create_default_rpc_interface)(MultiplayerAPI *p_multiplayer) = nullptr; +MultiplayerCacheInterface *(*MultiplayerAPI::create_default_cache_interface)(MultiplayerAPI *p_multiplayer) = nullptr; + #ifdef DEBUG_ENABLED void MultiplayerAPI::profile_bandwidth(const String &p_inout, int p_size) { if (EngineDebugger::is_profiling("multiplayer")) { @@ -74,7 +75,7 @@ void MultiplayerAPI::poll() { Error err = multiplayer_peer->get_packet(&packet, len); if (err != OK) { ERR_PRINT("Error getting packet!"); - break; // Something is wrong! + return; // Something is wrong! } remote_sender_id = sender; @@ -82,29 +83,25 @@ void MultiplayerAPI::poll() { remote_sender_id = 0; if (!multiplayer_peer.is_valid()) { - break; // It's also possible that a packet or RPC caused a disconnection, so also check here. + return; // It's also possible that a packet or RPC caused a disconnection, so also check here. } } - if (multiplayer_peer.is_valid() && multiplayer_peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTED) { - replicator->poll(); - } + replicator->on_network_process(); } void MultiplayerAPI::clear() { - replicator->clear(); connected_peers.clear(); - path_get_cache.clear(); - path_send_cache.clear(); packet_cache.clear(); - last_send_cache_id = 1; + cache->clear(); } -void MultiplayerAPI::set_root_node(Node *p_node) { - root_node = p_node; +void MultiplayerAPI::set_root_path(const NodePath &p_path) { + ERR_FAIL_COND_MSG(!p_path.is_absolute() && !p_path.is_empty(), "MultiplayerAPI root path must be absolute."); + root_path = p_path; } -Node *MultiplayerAPI::get_root_node() { - return root_node; +NodePath MultiplayerAPI::get_root_path() const { + return root_path; } void MultiplayerAPI::set_multiplayer_peer(const Ref &p_peer) { @@ -133,6 +130,7 @@ void MultiplayerAPI::set_multiplayer_peer(const Ref &p_peer) { multiplayer_peer->connect("connection_failed", callable_mp(this, &MultiplayerAPI::_connection_failed)); multiplayer_peer->connect("server_disconnected", callable_mp(this, &MultiplayerAPI::_server_disconnected)); } + replicator->on_reset(); } Ref MultiplayerAPI::get_multiplayer_peer() const { @@ -140,7 +138,7 @@ Ref MultiplayerAPI::get_multiplayer_peer() const { } void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(root_node == nullptr, "Multiplayer root node was not initialized. If you are using custom multiplayer, remember to set the root node via MultiplayerAPI.set_root_node before using it."); + ERR_FAIL_COND_MSG(root_path.is_empty(), "Multiplayer root was not initialized. If you are using custom multiplayer, remember to set the root path via MultiplayerAPI.set_root_path before using it."); ERR_FAIL_COND_MSG(p_packet_len < 1, "Invalid packet received. Size too small."); #ifdef DEBUG_ENABLED @@ -152,166 +150,32 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_ switch (packet_type) { case NETWORK_COMMAND_SIMPLIFY_PATH: { - _process_simplify_path(p_from, p_packet, p_packet_len); + cache->process_simplify_path(p_from, p_packet, p_packet_len); } break; case NETWORK_COMMAND_CONFIRM_PATH: { - _process_confirm_path(p_from, p_packet, p_packet_len); + cache->process_confirm_path(p_from, p_packet, p_packet_len); } break; case NETWORK_COMMAND_REMOTE_CALL: { - rpc_manager->process_rpc(p_from, p_packet, p_packet_len); + rpc->process_rpc(p_from, p_packet, p_packet_len); } break; case NETWORK_COMMAND_RAW: { _process_raw(p_from, p_packet, p_packet_len); } break; case NETWORK_COMMAND_SPAWN: { - replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, true); + replicator->on_spawn_receive(p_from, p_packet, p_packet_len); } break; case NETWORK_COMMAND_DESPAWN: { - replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, false); + replicator->on_despawn_receive(p_from, p_packet, p_packet_len); } break; case NETWORK_COMMAND_SYNC: { - replicator->process_sync(p_from, p_packet, p_packet_len); + replicator->on_sync_receive(p_from, p_packet, p_packet_len); } break; } } -void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(p_packet_len < 38, "Invalid packet received. Size too small."); - int ofs = 1; - - String methods_md5; - methods_md5.parse_utf8((const char *)(p_packet + ofs), 32); - ofs += 33; - - int id = decode_uint32(&p_packet[ofs]); - ofs += 4; - - String paths; - paths.parse_utf8((const char *)(p_packet + ofs), p_packet_len - ofs); - - NodePath path = paths; - - if (!path_get_cache.has(p_from)) { - path_get_cache[p_from] = PathGetCache(); - } - - Node *node = root_node->get_node(path); - ERR_FAIL_COND(node == nullptr); - const bool valid_rpc_checksum = rpc_manager->get_rpc_md5(node) == methods_md5; - if (valid_rpc_checksum == false) { - ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); - } - - PathGetCache::NodeInfo ni; - ni.path = path; - - path_get_cache[p_from].nodes[id] = ni; - - // Encode path to send ack. - CharString pname = String(path).utf8(); - int len = encode_cstring(pname.get_data(), nullptr); - - Vector packet; - - packet.resize(1 + 1 + len); - packet.write[0] = NETWORK_COMMAND_CONFIRM_PATH; - packet.write[1] = valid_rpc_checksum; - encode_cstring(pname.get_data(), &packet.write[2]); - - multiplayer_peer->set_transfer_channel(0); - multiplayer_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); - multiplayer_peer->set_target_peer(p_from); - multiplayer_peer->put_packet(packet.ptr(), packet.size()); -} - -void MultiplayerAPI::_process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(p_packet_len < 3, "Invalid packet received. Size too small."); - - const bool valid_rpc_checksum = p_packet[1]; - - String paths; - paths.parse_utf8((const char *)&p_packet[2], p_packet_len - 2); - - NodePath path = paths; - - if (valid_rpc_checksum == false) { - ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); - } - - PathSentCache *psc = path_send_cache.getptr(path); - ERR_FAIL_COND_MSG(!psc, "Invalid packet received. Tries to confirm a path which was not found in cache."); - - Map::Element *E = psc->confirmed_peers.find(p_from); - ERR_FAIL_COND_MSG(!E, "Invalid packet received. Source peer was not found in cache for the given path."); - E->get() = true; -} - -bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target) { - bool has_all_peers = true; - List peers_to_add; // If one is missing, take note to add it. - - for (Set::Element *E = connected_peers.front(); E; E = E->next()) { - if (p_target < 0 && E->get() == -p_target) { - continue; // Continue, excluded. - } - - if (p_target > 0 && E->get() != p_target) { - continue; // Continue, not for this peer. - } - - Map::Element *F = psc->confirmed_peers.find(E->get()); - - if (!F || !F->get()) { - // Path was not cached, or was cached but is unconfirmed. - if (!F) { - // Not cached at all, take note. - peers_to_add.push_back(E->get()); - } - - has_all_peers = false; - } - } - - if (peers_to_add.size() > 0) { - // Those that need to be added, send a message for this. - - // Encode function name. - const CharString path = String(p_path).utf8(); - const int path_len = encode_cstring(path.get_data(), nullptr); - - // Extract MD5 from rpc methods list. - const String methods_md5 = rpc_manager->get_rpc_md5(p_node); - const int methods_md5_len = 33; // 32 + 1 for the `0` that is added by the encoder. - - Vector packet; - packet.resize(1 + 4 + path_len + methods_md5_len); - int ofs = 0; - - packet.write[ofs] = NETWORK_COMMAND_SIMPLIFY_PATH; - ofs += 1; - - ofs += encode_cstring(methods_md5.utf8().get_data(), &packet.write[ofs]); - - ofs += encode_uint32(psc->id, &packet.write[ofs]); - - ofs += encode_cstring(path.get_data(), &packet.write[ofs]); - - for (int &E : peers_to_add) { - multiplayer_peer->set_target_peer(E); // To all of you. - multiplayer_peer->set_transfer_channel(0); - multiplayer_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); - multiplayer_peer->put_packet(packet.ptr(), packet.size()); - - psc->confirmed_peers.insert(E, false); // Insert into confirmed, but as false since it was not confirmed. - } - } - - return has_all_peers; -} - // The variant is compressed and encoded; The first byte contains all the meta // information and the format is: // - The first LSB 5 bits are used for the variant type. @@ -324,7 +188,7 @@ bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentC #define ENCODE_16 1 << 5 #define ENCODE_32 2 << 5 #define ENCODE_64 3 << 5 -Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len) { +Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bool p_allow_object_decoding) { // Unreachable because `VARIANT_MAX` == 27 and `ENCODE_VARIANT_MASK` == 31 CRASH_COND(p_variant.get_type() > VARIANT_META_TYPE_MASK); @@ -385,7 +249,7 @@ Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint } break; default: // Any other case is not yet compressed. - Error err = encode_variant(p_variant, r_buffer, r_len, allow_object_decoding); + Error err = encode_variant(p_variant, r_buffer, r_len, p_allow_object_decoding); if (err != OK) { return err; } @@ -399,7 +263,7 @@ Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint return OK; } -Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len) { +Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len, bool p_allow_object_decoding) { const uint8_t *buf = p_buffer; int len = p_len; @@ -458,7 +322,7 @@ Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const ui } } break; default: - Error err = decode_variant(r_variant, p_buffer, p_len, r_len, allow_object_decoding); + Error err = decode_variant(r_variant, p_buffer, p_len, r_len, p_allow_object_decoding); if (err != OK) { return err; } @@ -467,27 +331,86 @@ Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const ui return OK; } +Error MultiplayerAPI::encode_and_compress_variants(const Variant **p_variants, int p_count, uint8_t *p_buffer, int &r_len, bool *r_raw, bool p_allow_object_decoding) { + r_len = 0; + int size = 0; + + if (p_count == 0) { + if (r_raw) { + *r_raw = true; + } + return OK; + } + + // Try raw encoding optimization. + if (r_raw && p_count == 1) { + *r_raw = false; + const Variant &v = *(p_variants[0]); + if (v.get_type() == Variant::PACKED_BYTE_ARRAY) { + *r_raw = true; + const PackedByteArray pba = v; + if (p_buffer) { + memcpy(p_buffer, pba.ptr(), pba.size()); + } + r_len += pba.size(); + } else { + encode_and_compress_variant(v, p_buffer, size, p_allow_object_decoding); + r_len += size; + } + return OK; + } + + // Regular encoding. + for (int i = 0; i < p_count; i++) { + const Variant &v = *(p_variants[i]); + encode_and_compress_variant(v, p_buffer ? p_buffer + r_len : nullptr, size, p_allow_object_decoding); + r_len += size; + } + return OK; +} + +Error MultiplayerAPI::decode_and_decompress_variants(Vector &r_variants, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw, bool p_allow_object_decoding) { + r_len = 0; + int argc = r_variants.size(); + if (argc == 0 && p_raw) { + return OK; + } + ERR_FAIL_COND_V(p_raw && argc != 1, ERR_INVALID_DATA); + if (p_raw) { + r_len = p_len; + PackedByteArray pba; + pba.resize(p_len); + memcpy(pba.ptrw(), p_buffer, p_len); + r_variants.write[0] = pba; + return OK; + } + + Vector args; + Vector argp; + args.resize(argc); + + for (int i = 0; i < argc; i++) { + ERR_FAIL_COND_V_MSG(r_len >= p_len, ERR_INVALID_DATA, "Invalid packet received. Size too small."); + + int vlen; + Error err = MultiplayerAPI::decode_and_decompress_variant(r_variants.write[i], &p_buffer[r_len], p_len - r_len, &vlen, p_allow_object_decoding); + ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid packet received. Unable to decode state variable."); + r_len += vlen; + } + return OK; +} + void MultiplayerAPI::_add_peer(int p_id) { connected_peers.insert(p_id); - path_get_cache.insert(p_id, PathGetCache()); - if (is_server()) { - replicator->spawn_all(p_id); - } + cache->on_peer_change(p_id, true); + replicator->on_peer_change(p_id, true); emit_signal(SNAME("peer_connected"), p_id); } void MultiplayerAPI::_del_peer(int p_id) { + replicator->on_peer_change(p_id, false); + cache->on_peer_change(p_id, false); connected_peers.erase(p_id); - // Cleanup get cache. - path_get_cache.erase(p_id); - // Cleanup sent cache. - // Some refactoring is needed to make this faster and do paths GC. - List keys; - path_send_cache.get_key_list(&keys); - for (const NodePath &E : keys) { - PathSentCache *psc = path_send_cache.getptr(E); - psc->confirmed_peers.erase(p_id); - } emit_signal(SNAME("peer_disconnected"), p_id); } @@ -500,6 +423,7 @@ void MultiplayerAPI::_connection_failed() { } void MultiplayerAPI::_server_disconnected() { + replicator->on_reset(); emit_signal(SNAME("server_disconnected")); } @@ -537,41 +461,15 @@ void MultiplayerAPI::_process_raw(int p_from, const uint8_t *p_packet, int p_pac } bool MultiplayerAPI::is_cache_confirmed(NodePath p_path, int p_peer) { - const PathSentCache *psc = path_send_cache.getptr(p_path); - ERR_FAIL_COND_V(!psc, false); - const Map::Element *F = psc->confirmed_peers.find(p_peer); - ERR_FAIL_COND_V(!F, false); // Should never happen. - return F->get(); -} - -bool MultiplayerAPI::send_confirm_path(Node *p_node, NodePath p_path, int p_peer_id, int &r_id) { - // See if the path is cached. - PathSentCache *psc = path_send_cache.getptr(p_path); - if (!psc) { - // Path is not cached, create. - path_send_cache[p_path] = PathSentCache(); - psc = path_send_cache.getptr(p_path); - psc->id = last_send_cache_id++; - } - r_id = psc->id; - - // See if all peers have cached path (if so, call can be fast). - return _send_confirm_path(p_node, p_path, psc, p_peer_id); + return cache->is_cache_confirmed(p_path, p_peer); } -Node *MultiplayerAPI::get_cached_node(int p_from, uint32_t p_node_id) { - Map::Element *E = path_get_cache.find(p_from); - ERR_FAIL_COND_V_MSG(!E, nullptr, vformat("No cache found for peer %d.", p_from)); - - Map::Element *F = E->get().nodes.find(p_node_id); - ERR_FAIL_COND_V_MSG(!F, nullptr, vformat("ID %d not found in cache of peer %d.", p_node_id, p_from)); +bool MultiplayerAPI::send_object_cache(Object *p_obj, NodePath p_path, int p_peer_id, int &r_id) { + return cache->send_object_cache(p_obj, p_path, p_peer_id, r_id); +} - PathGetCache::NodeInfo *ni = &F->get(); - Node *node = root_node->get_node(ni->path); - if (!node) { - ERR_PRINT("Failed to get cached path: " + String(ni->path) + "."); - } - return node; +Object *MultiplayerAPI::get_cached_object(int p_from, uint32_t p_cache_id) { + return cache->get_cached_object(p_from, p_cache_id); } int MultiplayerAPI::get_unique_id() const { @@ -612,17 +510,33 @@ bool MultiplayerAPI::is_object_decoding_allowed() const { return allow_object_decoding; } -void MultiplayerAPI::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) { - replicator->scene_enter_exit_notify(p_scene, p_node, p_enter); +String MultiplayerAPI::get_rpc_md5(const Object *p_obj) const { + return rpc->get_rpc_md5(p_obj); } -void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) { - rpc_manager->rpcp(p_node, p_peer_id, p_method, p_arg, p_argcount); +void MultiplayerAPI::rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) { + rpc->rpcp(p_obj, p_peer_id, p_method, p_arg, p_argcount); +} + +Error MultiplayerAPI::spawn(Object *p_object, Variant p_config) { + return replicator->on_spawn(p_object, p_config); +} + +Error MultiplayerAPI::despawn(Object *p_object, Variant p_config) { + return replicator->on_despawn(p_object, p_config); +} + +Error MultiplayerAPI::replication_start(Object *p_object, Variant p_config) { + return replicator->on_replication_start(p_object, p_config); +} + +Error MultiplayerAPI::replication_stop(Object *p_object, Variant p_config) { + return replicator->on_replication_stop(p_object, p_config); } void MultiplayerAPI::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_root_node", "node"), &MultiplayerAPI::set_root_node); - ClassDB::bind_method(D_METHOD("get_root_node"), &MultiplayerAPI::get_root_node); + ClassDB::bind_method(D_METHOD("set_root_path", "path"), &MultiplayerAPI::set_root_path); + ClassDB::bind_method(D_METHOD("get_root_path"), &MultiplayerAPI::get_root_path); ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode", "channel"), &MultiplayerAPI::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(Multiplayer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); ClassDB::bind_method(D_METHOD("has_multiplayer_peer"), &MultiplayerAPI::has_multiplayer_peer); ClassDB::bind_method(D_METHOD("get_multiplayer_peer"), &MultiplayerAPI::get_multiplayer_peer); @@ -638,14 +552,12 @@ void MultiplayerAPI::_bind_methods() { ClassDB::bind_method(D_METHOD("is_refusing_new_connections"), &MultiplayerAPI::is_refusing_new_connections); ClassDB::bind_method(D_METHOD("set_allow_object_decoding", "enable"), &MultiplayerAPI::set_allow_object_decoding); ClassDB::bind_method(D_METHOD("is_object_decoding_allowed"), &MultiplayerAPI::is_object_decoding_allowed); - ClassDB::bind_method(D_METHOD("get_replicator"), &MultiplayerAPI::get_replicator); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_object_decoding"), "set_allow_object_decoding", "is_object_decoding_allowed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_connections"), "set_refuse_new_connections", "is_refusing_new_connections"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer_peer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerPeer", PROPERTY_USAGE_NONE), "set_multiplayer_peer", "get_multiplayer_peer"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root_node", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_root_node", "get_root_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "get_root_path"); ADD_PROPERTY_DEFAULT("refuse_new_connections", false); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replicator", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerReplicator", PROPERTY_USAGE_NONE), "", "get_replicator"); ADD_SIGNAL(MethodInfo("peer_connected", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("peer_disconnected", PropertyInfo(Variant::INT, "id"))); @@ -656,13 +568,23 @@ void MultiplayerAPI::_bind_methods() { } MultiplayerAPI::MultiplayerAPI() { - replicator = memnew(MultiplayerReplicator(this)); - rpc_manager = memnew(RPCManager(this)); - clear(); + if (create_default_replication_interface) { + replicator = Ref(create_default_replication_interface(this)); + } else { + replicator.instantiate(); + } + if (create_default_rpc_interface) { + rpc = Ref(create_default_rpc_interface(this)); + } else { + rpc.instantiate(); + } + if (create_default_cache_interface) { + cache = Ref(create_default_cache_interface(this)); + } else { + cache.instantiate(); + } } MultiplayerAPI::~MultiplayerAPI() { clear(); - memdelete(replicator); - memdelete(rpc_manager); } diff --git a/core/multiplayer/multiplayer_api.h b/core/multiplayer/multiplayer_api.h index 713035428d7f..9fe67615e3b0 100644 --- a/core/multiplayer/multiplayer_api.h +++ b/core/multiplayer/multiplayer_api.h @@ -35,8 +35,54 @@ #include "core/multiplayer/multiplayer_peer.h" #include "core/object/ref_counted.h" -class MultiplayerReplicator; -class RPCManager; +class MultiplayerAPI; + +class MultiplayerReplicationInterface : public RefCounted { + GDCLASS(MultiplayerReplicationInterface, RefCounted); + +public: + virtual void on_peer_change(int p_id, bool p_connected) {} + virtual void on_reset() {} + virtual Error on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { return ERR_UNAVAILABLE; } + virtual Error on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { return ERR_UNAVAILABLE; } + virtual Error on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { return ERR_UNAVAILABLE; } + virtual Error on_spawn(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; } + virtual Error on_despawn(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; } + virtual Error on_replication_start(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; } + virtual Error on_replication_stop(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; } + virtual void on_network_process() {} + + MultiplayerReplicationInterface() {} +}; + +class MultiplayerRPCInterface : public RefCounted { + GDCLASS(MultiplayerRPCInterface, RefCounted); + +public: + // Called by Node.rpc + virtual void rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) {} + virtual void process_rpc(int p_from, const uint8_t *p_packet, int p_packet_len) {} + virtual String get_rpc_md5(const Object *p_obj) const { return String(); } + + MultiplayerRPCInterface() {} +}; + +class MultiplayerCacheInterface : public RefCounted { + GDCLASS(MultiplayerCacheInterface, RefCounted); + +public: + virtual void clear() {} + virtual void on_peer_change(int p_id, bool p_connected) {} + virtual void process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) {} + virtual void process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) {} + + // Returns true if all peers have cached path. + virtual bool send_object_cache(Object *p_obj, NodePath p_path, int p_target, int &p_id) { return false; } + virtual Object *get_cached_object(int p_from, uint32_t p_cache_id) { return nullptr; } + virtual bool is_cache_confirmed(NodePath p_path, int p_peer) { return false; } + + MultiplayerCacheInterface() {} +}; class MultiplayerAPI : public RefCounted { GDCLASS(MultiplayerAPI, RefCounted); @@ -66,67 +112,56 @@ class MultiplayerAPI : public RefCounted { }; private: - //path sent caches - struct PathSentCache { - Map confirmed_peers; - int id; - }; - - //path get caches - struct PathGetCache { - struct NodeInfo { - NodePath path; - ObjectID instance; - }; - - Map nodes; - }; - Ref multiplayer_peer; Set connected_peers; int remote_sender_id = 0; int remote_sender_override = 0; - HashMap path_send_cache; - Map path_get_cache; - int last_send_cache_id; Vector packet_cache; - Node *root_node = nullptr; + NodePath root_path; bool allow_object_decoding = false; - MultiplayerReplicator *replicator = nullptr; - RPCManager *rpc_manager = nullptr; + Ref cache; + Ref replicator; + Ref rpc; protected: static void _bind_methods(); - bool _send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target); void _process_packet(int p_from, const uint8_t *p_packet, int p_packet_len); - void _process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len); - void _process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len); void _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len); public: + static MultiplayerReplicationInterface *(*create_default_replication_interface)(MultiplayerAPI *p_multiplayer); + static MultiplayerRPCInterface *(*create_default_rpc_interface)(MultiplayerAPI *p_multiplayer); + static MultiplayerCacheInterface *(*create_default_cache_interface)(MultiplayerAPI *p_multiplayer); + + static Error encode_and_compress_variant(const Variant &p_variant, uint8_t *p_buffer, int &r_len, bool p_allow_object_decoding); + static Error decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len, bool p_allow_object_decoding); + static Error encode_and_compress_variants(const Variant **p_variants, int p_count, uint8_t *p_buffer, int &r_len, bool *r_raw = nullptr, bool p_allow_object_decoding = false); + static Error decode_and_decompress_variants(Vector &r_variants, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw = false, bool p_allow_object_decoding = false); + void poll(); void clear(); - void set_root_node(Node *p_node); - Node *get_root_node(); + void set_root_path(const NodePath &p_path); + NodePath get_root_path() const; void set_multiplayer_peer(const Ref &p_peer); Ref get_multiplayer_peer() const; Error send_bytes(Vector p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, Multiplayer::TransferMode p_mode = Multiplayer::TRANSFER_MODE_RELIABLE, int p_channel = 0); - Error encode_and_compress_variant(const Variant &p_variant, uint8_t *p_buffer, int &r_len); - Error decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len); - - // Called by Node.rpc - void rpcp(Node *p_node, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount); - // Called by Node._notification - void scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter); - // Called by replicator - bool send_confirm_path(Node *p_node, NodePath p_path, int p_target, int &p_id); - Node *get_cached_node(int p_from, uint32_t p_node_id); + // RPC API + void rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount); + String get_rpc_md5(const Object *p_obj) const; + // Replication API + Error spawn(Object *p_object, Variant p_config); + Error despawn(Object *p_object, Variant p_config); + Error replication_start(Object *p_object, Variant p_config); + Error replication_stop(Object *p_object, Variant p_config); + // Cache API + bool send_object_cache(Object *p_obj, NodePath p_path, int p_target, int &p_id); + Object *get_cached_object(int p_from, uint32_t p_cache_id); bool is_cache_confirmed(NodePath p_path, int p_peer); void _add_peer(int p_id); @@ -148,9 +183,6 @@ class MultiplayerAPI : public RefCounted { void set_allow_object_decoding(bool p_enable); bool is_object_decoding_allowed() const; - MultiplayerReplicator *get_replicator() const { return replicator; } - RPCManager *get_rpc_manager() const { return rpc_manager; } - #ifdef DEBUG_ENABLED void profile_bandwidth(const String &p_inout, int p_size); #endif diff --git a/core/multiplayer/multiplayer_replicator.cpp b/core/multiplayer/multiplayer_replicator.cpp deleted file mode 100644 index e7de8219c734..000000000000 --- a/core/multiplayer/multiplayer_replicator.cpp +++ /dev/null @@ -1,791 +0,0 @@ -/*************************************************************************/ -/* multiplayer_replicator.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "core/multiplayer/multiplayer_replicator.h" - -#include "core/io/marshalls.h" -#include "scene/main/node.h" -#include "scene/resources/packed_scene.h" - -#define MAKE_ROOM(m_amount) \ - if (packet_cache.size() < m_amount) \ - packet_cache.resize(m_amount); - -Error MultiplayerReplicator::_sync_all_default(const ResourceUID::ID &p_scene_id, int p_peer) { - ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); - SceneConfig &cfg = replications[p_scene_id]; - int full_size = 0; - bool same_size = true; - int last_size = 0; - bool all_raw = true; - struct EncodeInfo { - int size = 0; - bool raw = false; - List state; - }; - Map state; - if (tracked_objects.has(p_scene_id)) { - for (const ObjectID &obj_id : tracked_objects[p_scene_id]) { - Object *obj = ObjectDB::get_instance(obj_id); - if (obj) { - struct EncodeInfo info; - Error err = _get_state(cfg.sync_properties, obj, info.state); - ERR_CONTINUE(err); - err = _encode_state(info.state, nullptr, info.size, &info.raw); - ERR_CONTINUE(err); - state[obj_id] = info; - full_size += info.size; - if (last_size && info.size != last_size) { - same_size = false; - } - all_raw = all_raw && info.raw; - last_size = info.size; - } - } - } - // Default implementation do not send empty updates. - if (!full_size) { - return OK; - } -#ifdef DEBUG_ENABLED - if (full_size > 4096 && cfg.sync_interval) { - WARN_PRINT_ONCE(vformat("The timed state update for scene %d is big (%d bytes) consider optimizing it", p_scene_id)); - } -#endif - if (same_size) { - // This is fast and small. Should we allow more than 256 objects per type? - // This costs us 1 byte. - MAKE_ROOM(SYNC_CMD_OFFSET + 1 + 2 + 2 + full_size); - } else { - MAKE_ROOM(SYNC_CMD_OFFSET + 1 + 2 + state.size() * 2 + full_size); - } - int ofs = 0; - uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC | (same_size ? BYTE_OR_ZERO_FLAG : 0); - ofs = 1; - ofs += encode_uint64(p_scene_id, &ptr[ofs]); - ptr[ofs] = cfg.sync_recv++; - ofs += 1; - ofs += encode_uint16(state.size(), &ptr[ofs]); - if (same_size) { - ofs += encode_uint16(last_size + (all_raw ? 1 << 15 : 0), &ptr[ofs]); - } - for (const ObjectID &obj_id : tracked_objects[p_scene_id]) { - if (!state.has(obj_id)) { - continue; - } - struct EncodeInfo &info = state[obj_id]; - Object *obj = ObjectDB::get_instance(obj_id); - ERR_CONTINUE(!obj); - int size = 0; - if (!same_size) { - // We need to encode the size of every object. - ofs += encode_uint16(info.size + (info.raw ? 1 << 15 : 0), &ptr[ofs]); - } - Error err = _encode_state(info.state, &ptr[ofs], size, &info.raw); - ERR_CONTINUE(err); - ofs += size; - } - Ref peer = multiplayer->get_multiplayer_peer(); - peer->set_target_peer(p_peer); - peer->set_transfer_channel(0); - peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_UNRELIABLE); - return peer->put_packet(ptr, ofs); -} - -void MultiplayerReplicator::_process_default_sync(const ResourceUID::ID &p_id, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(p_packet_len < SYNC_CMD_OFFSET + 5, "Invalid spawn packet received"); - ERR_FAIL_COND_MSG(!replications.has(p_id), "Invalid spawn ID received " + itos(p_id)); - SceneConfig &cfg = replications[p_id]; - ERR_FAIL_COND_MSG(cfg.mode != REPLICATION_MODE_SERVER || multiplayer->is_server(), "The default implementation only allows sync packets from the server"); - const bool same_size = p_packet[0] & BYTE_OR_ZERO_FLAG; - int ofs = SYNC_CMD_OFFSET; - int time = p_packet[ofs]; - // Skip old update. - if (time < cfg.sync_recv && cfg.sync_recv - time < 127) { - return; - } - cfg.sync_recv = time; - ofs += 1; - int count = decode_uint16(&p_packet[ofs]); - ofs += 2; -#ifdef DEBUG_ENABLED - ERR_FAIL_COND(!tracked_objects.has(p_id) || tracked_objects[p_id].size() != count); -#else - if (!tracked_objects.has(p_id) || tracked_objects[p_id].size() != count) { - return; - } -#endif - int data_size = 0; - bool raw = false; - if (same_size) { - // This is fast and optimized. - data_size = decode_uint16(&p_packet[ofs]); - raw = (data_size & (1 << 15)) != 0; - data_size = data_size & ~(1 << 15); - ofs += 2; - ERR_FAIL_COND(p_packet_len - ofs < data_size * count); - } - for (const ObjectID &obj_id : tracked_objects[p_id]) { - Object *obj = ObjectDB::get_instance(obj_id); - ERR_CONTINUE(!obj); - if (!same_size) { - // This is slow and wasteful. - data_size = decode_uint16(&p_packet[ofs]); - raw = (data_size & (1 << 15)) != 0; - data_size = data_size & ~(1 << 15); - ofs += 2; - ERR_FAIL_COND(p_packet_len - ofs < data_size); - } - int size = 0; - Error err = _decode_state(cfg.sync_properties, obj, &p_packet[ofs], data_size, size, raw); - ofs += data_size; - ERR_CONTINUE(err); - ERR_CONTINUE(size != data_size); - } -} - -Error MultiplayerReplicator::_send_default_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, Object *p_obj, const NodePath &p_path, bool p_spawn) { - ERR_FAIL_COND_V(p_spawn && !p_obj, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); - Error err; - // Prepare state - List state_variants; - int state_len = 0; - const SceneConfig &cfg = replications[p_scene_id]; - if (p_spawn) { - if ((err = _get_state(cfg.properties, p_obj, state_variants)) != OK) { - return err; - } - } - - bool is_raw = false; - if (state_variants.size() == 1 && state_variants[0].get_type() == Variant::PACKED_BYTE_ARRAY) { - is_raw = true; - const PackedByteArray pba = state_variants[0]; - state_len = pba.size(); - } else if (state_variants.size()) { - err = _encode_state(state_variants, nullptr, state_len); - ERR_FAIL_COND_V(err, err); - } else { - is_raw = true; - } - - int ofs = 0; - - // Prepare simplified path - const Node *root_node = multiplayer->get_root_node(); - ERR_FAIL_COND_V(!root_node, ERR_UNCONFIGURED); - NodePath rel_path = (root_node->get_path()).rel_path_to(p_path); - const Vector names = rel_path.get_names(); - ERR_FAIL_COND_V(names.size() < 2, ERR_INVALID_PARAMETER); - - NodePath parent = NodePath(names.slice(0, names.size() - 1), false); - ERR_FAIL_COND_V_MSG(!root_node->has_node(parent), ERR_INVALID_PARAMETER, "Path not found: " + parent); - - int path_id = 0; - multiplayer->send_confirm_path(root_node->get_node(parent), parent, p_peer_id, path_id); - - // Encode name and parent ID. - CharString cname = String(names[names.size() - 1]).utf8(); - int nlen = encode_cstring(cname.get_data(), nullptr); - MAKE_ROOM(SPAWN_CMD_OFFSET + 4 + 4 + nlen + state_len); - uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) | (is_raw ? BYTE_OR_ZERO_FLAG : 0); - ofs = 1; - ofs += encode_uint64(p_scene_id, &ptr[ofs]); - ofs += encode_uint32(path_id, &ptr[ofs]); - ofs += encode_uint32(nlen, &ptr[ofs]); - ofs += encode_cstring(cname.get_data(), &ptr[ofs]); - - // Encode state. - if (!is_raw) { - _encode_state(state_variants, &ptr[ofs], state_len); - } else if (state_len) { - PackedByteArray pba = state_variants[0]; - memcpy(&ptr[ofs], pba.ptr(), state_len); - } - - Ref peer = multiplayer->get_multiplayer_peer(); - peer->set_target_peer(p_peer_id); - peer->set_transfer_channel(0); - peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); - return peer->put_packet(ptr, ofs + state_len); -} - -void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const ResourceUID::ID &p_scene_id, const uint8_t *p_packet, int p_packet_len, bool p_spawn) { - ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET + 9, "Invalid spawn packet received"); - int ofs = SPAWN_CMD_OFFSET; - uint32_t node_target = decode_uint32(&p_packet[ofs]); - Node *parent = multiplayer->get_cached_node(p_from, node_target); - ofs += 4; - ERR_FAIL_COND_MSG(parent == nullptr, "Invalid packet received. Requested node was not found."); - - uint32_t name_len = decode_uint32(&p_packet[ofs]); - ofs += 4; - ERR_FAIL_COND_MSG(name_len > uint32_t(p_packet_len - ofs), vformat("Invalid spawn packet size: %d, wants: %d", p_packet_len, ofs + name_len)); - ERR_FAIL_COND_MSG(name_len < 1, "Zero spawn name size."); - - const String name = String::utf8((const char *)&p_packet[ofs], name_len); - // We need to make sure no trickery happens here (e.g. despawning a subpath), but we want to allow autogenerated ("@") node names. - ERR_FAIL_COND_MSG(name.validate_node_name() != name.replace("@", ""), vformat("Invalid node name received: '%s'", name)); - ofs += name_len; - - const SceneConfig &cfg = replications[p_scene_id]; - if (cfg.mode == REPLICATION_MODE_SERVER && p_from == 1) { - String scene_path = ResourceUID::get_singleton()->get_id_path(p_scene_id); - if (p_spawn) { - const bool is_raw = ((p_packet[0] & BYTE_OR_ZERO_FLAG) >> BYTE_OR_ZERO_SHIFT) == 1; - - ERR_FAIL_COND_MSG(parent->has_node(name), vformat("Unable to spawn node. Node already exists: %s/%s", parent->get_path(), name)); - RES res = ResourceLoader::load(scene_path); - ERR_FAIL_COND_MSG(!res.is_valid(), "Unable to load scene to spawn at path: " + scene_path); - PackedScene *scene = Object::cast_to(res.ptr()); - ERR_FAIL_COND(!scene); - Node *node = scene->instantiate(); - ERR_FAIL_COND(!node); - replicated_nodes[node->get_instance_id()] = p_scene_id; - _track(p_scene_id, node); - int size; - _decode_state(cfg.properties, node, &p_packet[ofs], p_packet_len - ofs, size, is_raw); - parent->_add_child_nocheck(node, name); - emit_signal(SNAME("spawned"), p_scene_id, node); - } else { - ERR_FAIL_COND_MSG(!parent->has_node(name), vformat("Path not found: %s/%s", parent->get_path(), name)); - Node *node = parent->get_node(name); - ERR_FAIL_COND_MSG(!replicated_nodes.has(node->get_instance_id()), vformat("Trying to despawn a Node that was not replicated: %s/%s", parent->get_path(), name)); - emit_signal(SNAME("despawned"), p_scene_id, node); - _untrack(p_scene_id, node); - replicated_nodes.erase(node->get_instance_id()); - node->queue_delete(); - } - } else { - PackedByteArray data; - if (p_packet_len > ofs) { - data.resize(p_packet_len - ofs); - memcpy(data.ptrw(), &p_packet[ofs], data.size()); - } - if (p_spawn) { - emit_signal(SNAME("spawn_requested"), p_from, p_scene_id, parent, name, data); - } else { - emit_signal(SNAME("despawn_requested"), p_from, p_scene_id, parent, name, data); - } - } -} - -void MultiplayerReplicator::process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn) { - ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET, "Invalid spawn packet received"); - ResourceUID::ID id = decode_uint64(&p_packet[1]); - ERR_FAIL_COND_MSG(!replications.has(id), "Invalid spawn ID received " + itos(id)); - - const SceneConfig &cfg = replications[id]; - if (cfg.on_spawn_despawn_receive.is_valid()) { - int ofs = SPAWN_CMD_OFFSET; - bool is_raw = ((p_packet[0] & BYTE_OR_ZERO_FLAG) >> BYTE_OR_ZERO_SHIFT) == 1; - Variant data; - int left = p_packet_len - ofs; - if (is_raw && left) { - PackedByteArray pba; - pba.resize(left); - memcpy(pba.ptrw(), &p_packet[ofs], pba.size()); - data = pba; - } else if (left) { - ERR_FAIL_COND(decode_variant(data, &p_packet[ofs], left) != OK); - } - - Variant args[4]; - args[0] = p_from; - args[1] = id; - args[2] = data; - args[3] = p_spawn; - const Variant *argp[] = { &args[0], &args[1], &args[2], &args[3] }; - Callable::CallError ce; - Variant ret; - cfg.on_spawn_despawn_receive.call(argp, 4, ret, ce); - ERR_FAIL_COND_MSG(ce.error != Callable::CallError::CALL_OK, "Custom receive function failed"); - } else { - _process_default_spawn_despawn(p_from, id, p_packet, p_packet_len, p_spawn); - } -} - -void MultiplayerReplicator::process_sync(int p_from, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET, "Invalid spawn packet received"); - ResourceUID::ID id = decode_uint64(&p_packet[1]); - ERR_FAIL_COND_MSG(!replications.has(id), "Invalid spawn ID received " + itos(id)); - const SceneConfig &cfg = replications[id]; - if (cfg.on_sync_receive.is_valid()) { - Array objs; - if (tracked_objects.has(id)) { - objs.resize(tracked_objects[id].size()); - int idx = 0; - for (const ObjectID &obj_id : tracked_objects[id]) { - objs[idx++] = ObjectDB::get_instance(obj_id); - } - } - PackedByteArray pba; - pba.resize(p_packet_len - SYNC_CMD_OFFSET); - if (pba.size()) { - memcpy(pba.ptrw(), p_packet + SYNC_CMD_OFFSET, p_packet_len - SYNC_CMD_OFFSET); - } - Variant args[4] = { p_from, id, objs, pba }; - Variant *argp[4] = { args, &args[1], &args[2], &args[3] }; - Callable::CallError ce; - Variant ret; - cfg.on_sync_receive.call((const Variant **)argp, 4, ret, ce); - ERR_FAIL_COND_MSG(ce.error != Callable::CallError::CALL_OK, "Custom sync function failed"); - } else { - ERR_FAIL_COND_MSG(p_from != 1, "Default sync implementation only allow syncing from server to client"); - _process_default_sync(id, p_packet, p_packet_len); - } -} - -Error MultiplayerReplicator::_get_state(const List &p_properties, const Object *p_obj, List &r_variant) { - ERR_FAIL_COND_V_MSG(!p_obj, ERR_INVALID_PARAMETER, "Cannot encode null object"); - for (const StringName &prop : p_properties) { - bool valid = false; - const Variant v = p_obj->get(prop, &valid); - ERR_FAIL_COND_V_MSG(!valid, ERR_INVALID_DATA, vformat("Property '%s' not found.", prop)); - r_variant.push_back(v); - } - return OK; -} - -Error MultiplayerReplicator::_encode_state(const List &p_variants, uint8_t *p_buffer, int &r_len, bool *r_raw) { - r_len = 0; - int size = 0; - - // Try raw encoding optimization. - if (r_raw && p_variants.size() == 1) { - *r_raw = false; - const Variant v = p_variants[0]; - if (v.get_type() == Variant::PACKED_BYTE_ARRAY) { - *r_raw = true; - const PackedByteArray pba = v; - if (p_buffer) { - memcpy(p_buffer, pba.ptr(), pba.size()); - } - r_len += pba.size(); - } else { - multiplayer->encode_and_compress_variant(v, p_buffer, size); - r_len += size; - } - return OK; - } - - // Regular encoding. - for (const Variant &v : p_variants) { - multiplayer->encode_and_compress_variant(v, p_buffer ? p_buffer + r_len : nullptr, size); - r_len += size; - } - return OK; -} - -Error MultiplayerReplicator::_decode_state(const List &p_properties, Object *p_obj, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw) { - r_len = 0; - int argc = p_properties.size(); - if (argc == 0 && p_raw) { - ERR_FAIL_COND_V_MSG(p_len != 0, ERR_INVALID_DATA, "Buffer has trailing bytes."); - return OK; - } - ERR_FAIL_COND_V(p_raw && argc != 1, ERR_INVALID_DATA); - if (p_raw) { - r_len = p_len; - PackedByteArray pba; - pba.resize(p_len); - memcpy(pba.ptrw(), p_buffer, p_len); - p_obj->set(p_properties[0], pba); - return OK; - } - - Vector args; - Vector argp; - args.resize(argc); - - for (int i = 0; i < argc; i++) { - ERR_FAIL_COND_V_MSG(r_len >= p_len, ERR_INVALID_DATA, "Invalid packet received. Size too small."); - - int vlen; - Error err = multiplayer->decode_and_decompress_variant(args.write[i], &p_buffer[r_len], p_len - r_len, &vlen); - ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid packet received. Unable to decode state variable."); - r_len += vlen; - } - ERR_FAIL_COND_V_MSG(p_len - r_len != 0, ERR_INVALID_DATA, "Buffer has trailing bytes."); - - int i = 0; - for (const StringName &prop : p_properties) { - p_obj->set(prop, args[i]); - i += 1; - } - return OK; -} - -Error MultiplayerReplicator::spawn_config(const ResourceUID::ID &p_id, ReplicationMode p_mode, const TypedArray &p_props, const Callable &p_on_send, const Callable &p_on_recv) { - ERR_FAIL_COND_V(p_mode < REPLICATION_MODE_NONE || p_mode > REPLICATION_MODE_CUSTOM, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!ResourceUID::get_singleton()->has_id(p_id), ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V_MSG(p_on_send.is_valid() != p_on_recv.is_valid(), ERR_INVALID_PARAMETER, "Send and receive custom callables must be both valid or both empty"); -#ifdef TOOLS_ENABLED - if (!p_on_send.is_valid()) { - // We allow non scene spawning with custom callables. - String path = ResourceUID::get_singleton()->get_id_path(p_id); - RES res = ResourceLoader::load(path); - ERR_FAIL_COND_V(!res->is_class("PackedScene"), ERR_INVALID_PARAMETER); - } -#endif - if (p_mode == REPLICATION_MODE_NONE) { - if (replications.has(p_id)) { - replications.erase(p_id); - } - } else { - SceneConfig cfg; - cfg.mode = p_mode; - for (int i = 0; i < p_props.size(); i++) { - cfg.properties.push_back(p_props[i]); - } - cfg.on_spawn_despawn_send = p_on_send; - cfg.on_spawn_despawn_receive = p_on_recv; - replications[p_id] = cfg; - } - return OK; -} - -Error MultiplayerReplicator::sync_config(const ResourceUID::ID &p_id, uint64_t p_interval, const TypedArray &p_props, const Callable &p_on_send, const Callable &p_on_recv) { - ERR_FAIL_COND_V(!ResourceUID::get_singleton()->has_id(p_id), ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V_MSG(p_on_send.is_valid() != p_on_recv.is_valid(), ERR_INVALID_PARAMETER, "Send and receive custom callables must be both valid or both empty"); - ERR_FAIL_COND_V(!replications.has(p_id), ERR_UNCONFIGURED); - SceneConfig &cfg = replications[p_id]; - ERR_FAIL_COND_V_MSG(p_interval && cfg.mode != REPLICATION_MODE_SERVER && !p_on_send.is_valid(), ERR_INVALID_PARAMETER, "Timed updates in custom mode are only allowed if custom callbacks are also specified"); - for (int i = 0; i < p_props.size(); i++) { - cfg.sync_properties.push_back(p_props[i]); - } - cfg.on_sync_send = p_on_send; - cfg.on_sync_receive = p_on_recv; - cfg.sync_interval = p_interval * 1000; - return OK; -} - -Error MultiplayerReplicator::_send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, bool p_spawn) { - int data_size = 0; - int is_raw = false; - if (p_data.get_type() == Variant::PACKED_BYTE_ARRAY) { - const PackedByteArray pba = p_data; - is_raw = true; - data_size = p_data.operator PackedByteArray().size(); - } else if (p_data.get_type() == Variant::NIL) { - is_raw = true; - } else { - Error err = encode_variant(p_data, nullptr, data_size); - ERR_FAIL_COND_V(err, err); - } - MAKE_ROOM(SPAWN_CMD_OFFSET + data_size); - uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) + ((is_raw ? 1 : 0) << BYTE_OR_ZERO_SHIFT); - encode_uint64(p_scene_id, &ptr[1]); - if (p_data.get_type() == Variant::PACKED_BYTE_ARRAY) { - const PackedByteArray pba = p_data; - memcpy(&ptr[SPAWN_CMD_OFFSET], pba.ptr(), pba.size()); - } else if (data_size) { - encode_variant(p_data, &ptr[SPAWN_CMD_OFFSET], data_size); - } - Ref peer = multiplayer->get_multiplayer_peer(); - peer->set_target_peer(p_peer_id); - peer->set_transfer_channel(0); - peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); - return peer->put_packet(ptr, SPAWN_CMD_OFFSET + data_size); -} - -Error MultiplayerReplicator::send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) { - ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED); - ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); - const SceneConfig &cfg = replications[p_scene_id]; - if (cfg.on_spawn_despawn_send.is_valid()) { - return _send_spawn_despawn(p_peer_id, p_scene_id, p_data, true); - } else { - ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_server(), ERR_UNAVAILABLE, "Manual despawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests."); - NodePath path = p_path; - Object *obj = p_data.get_type() == Variant::OBJECT ? p_data.get_validated_object() : nullptr; - if (path.is_empty() && obj) { - Node *node = Object::cast_to(obj); - if (node && node->is_inside_tree()) { - path = node->get_path(); - } - } - ERR_FAIL_COND_V_MSG(path.is_empty(), ERR_INVALID_PARAMETER, "Despawn default implementation requires a despawn path, or the data to be a node inside the SceneTree"); - return _send_default_spawn_despawn(p_peer_id, p_scene_id, obj, path, false); - } -} - -Error MultiplayerReplicator::send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) { - ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED); - ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); - const SceneConfig &cfg = replications[p_scene_id]; - if (cfg.on_spawn_despawn_send.is_valid()) { - return _send_spawn_despawn(p_peer_id, p_scene_id, p_data, false); - } else { - ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_server(), ERR_UNAVAILABLE, "Manual spawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests."); - NodePath path = p_path; - Object *obj = p_data.get_type() == Variant::OBJECT ? p_data.get_validated_object() : nullptr; - ERR_FAIL_COND_V_MSG(!obj, ERR_INVALID_PARAMETER, "Spawn default implementation requires the data to be an object."); - if (path.is_empty()) { - Node *node = Object::cast_to(obj); - if (node && node->is_inside_tree()) { - path = node->get_path(); - } - } - ERR_FAIL_COND_V_MSG(path.is_empty(), ERR_INVALID_PARAMETER, "Spawn default implementation requires a spawn path, or the data to be a node inside the SceneTree"); - return _send_default_spawn_despawn(p_peer_id, p_scene_id, obj, path, true); - } -} - -Error MultiplayerReplicator::_spawn_despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer, bool p_spawn) { - ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); - - const SceneConfig &cfg = replications[p_scene_id]; - if (cfg.on_spawn_despawn_send.is_valid()) { - Variant args[4]; - args[0] = p_peer; - args[1] = p_scene_id; - args[2] = p_obj; - args[3] = p_spawn; - const Variant *argp[] = { &args[0], &args[1], &args[2], &args[3] }; - Callable::CallError ce; - Variant ret; - cfg.on_spawn_despawn_send.call(argp, 4, ret, ce); - ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, "Custom send function failed"); - return OK; - } else { - Node *node = Object::cast_to(p_obj); - ERR_FAIL_COND_V_MSG(!p_obj, ERR_INVALID_PARAMETER, "Only nodes can be replicated by the default implementation"); - return _send_default_spawn_despawn(p_peer, p_scene_id, node, node->get_path(), p_spawn); - } -} - -Error MultiplayerReplicator::spawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer) { - return _spawn_despawn(p_scene_id, p_obj, p_peer, true); -} - -Error MultiplayerReplicator::despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer) { - return _spawn_despawn(p_scene_id, p_obj, p_peer, false); -} - -PackedByteArray MultiplayerReplicator::encode_state(const ResourceUID::ID &p_scene_id, const Object *p_obj, bool p_initial) { - PackedByteArray state; - ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), state, vformat("Spawnable not found: %d", p_scene_id)); - const SceneConfig &cfg = replications[p_scene_id]; - int len = 0; - List state_vars; - const List props = p_initial ? cfg.properties : cfg.sync_properties; - Error err = _get_state(props, p_obj, state_vars); - ERR_FAIL_COND_V_MSG(err != OK, state, "Unable to retrieve object state."); - err = _encode_state(state_vars, nullptr, len); - ERR_FAIL_COND_V_MSG(err != OK, state, "Unable to encode object state."); - state.resize(len); - _encode_state(state_vars, state.ptrw(), len); - return state; -} - -Error MultiplayerReplicator::decode_state(const ResourceUID::ID &p_scene_id, Object *p_obj, const PackedByteArray p_data, bool p_initial) { - ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); - const SceneConfig &cfg = replications[p_scene_id]; - const List props = p_initial ? cfg.properties : cfg.sync_properties; - int size; - return _decode_state(props, p_obj, p_data.ptr(), p_data.size(), size); -} - -void MultiplayerReplicator::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) { - if (!multiplayer->has_multiplayer_peer()) { - return; - } - Node *root_node = multiplayer->get_root_node(); - ERR_FAIL_COND(!p_node || !p_node->get_parent() || !root_node); - NodePath path = (root_node->get_path()).rel_path_to(p_node->get_parent()->get_path()); - if (path.is_empty()) { - return; - } - ResourceUID::ID id = ResourceLoader::get_resource_uid(p_scene); - if (!replications.has(id)) { - return; - } - const SceneConfig &cfg = replications[id]; - if (p_enter) { - if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_server()) { - replicated_nodes[p_node->get_instance_id()] = id; - _track(id, p_node); - spawn(id, p_node, 0); - } - emit_signal(SNAME("replicated_instance_added"), id, p_node); - } else { - if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_server() && replicated_nodes.has(p_node->get_instance_id())) { - replicated_nodes.erase(p_node->get_instance_id()); - _untrack(id, p_node); - despawn(id, p_node, 0); - } - emit_signal(SNAME("replicated_instance_removed"), id, p_node); - } -} - -void MultiplayerReplicator::spawn_all(int p_peer) { - for (const KeyValue &E : replicated_nodes) { - // Only server mode adds to replicated_nodes, no need to check it. - Object *obj = ObjectDB::get_instance(E.key); - ERR_CONTINUE(!obj); - Node *node = Object::cast_to(obj); - ERR_CONTINUE(!node); - spawn(E.value, node, p_peer); - } -} - -void MultiplayerReplicator::poll() { - for (KeyValue &E : replications) { - if (!E.value.sync_interval) { - continue; - } - if (E.value.mode == REPLICATION_MODE_SERVER && !multiplayer->is_server()) { - continue; - } - uint64_t time = OS::get_singleton()->get_ticks_usec(); - if (E.value.sync_last + E.value.sync_interval <= time) { - sync_all(E.key, 0); - E.value.sync_last = time; - } - // Handle wrapping. - if (E.value.sync_last > time) { - E.value.sync_last = time; - } - } -} - -void MultiplayerReplicator::track(const ResourceUID::ID &p_scene_id, Object *p_obj) { - ERR_FAIL_COND(!replications.has(p_scene_id)); - const SceneConfig &cfg = replications[p_scene_id]; - ERR_FAIL_COND_MSG(cfg.mode == REPLICATION_MODE_SERVER, "Manual object tracking is not allowed in server mode."); - _track(p_scene_id, p_obj); -} - -void MultiplayerReplicator::_track(const ResourceUID::ID &p_scene_id, Object *p_obj) { - ERR_FAIL_COND(!p_obj); - ERR_FAIL_COND(!replications.has(p_scene_id)); - if (!tracked_objects.has(p_scene_id)) { - tracked_objects[p_scene_id] = List(); - } - tracked_objects[p_scene_id].push_back(p_obj->get_instance_id()); -} - -void MultiplayerReplicator::untrack(const ResourceUID::ID &p_scene_id, Object *p_obj) { - ERR_FAIL_COND(!replications.has(p_scene_id)); - const SceneConfig &cfg = replications[p_scene_id]; - ERR_FAIL_COND_MSG(cfg.mode == REPLICATION_MODE_SERVER, "Manual object tracking is not allowed in server mode."); - _untrack(p_scene_id, p_obj); -} - -void MultiplayerReplicator::_untrack(const ResourceUID::ID &p_scene_id, Object *p_obj) { - ERR_FAIL_COND(!p_obj); - ERR_FAIL_COND(!replications.has(p_scene_id)); - if (tracked_objects.has(p_scene_id)) { - tracked_objects[p_scene_id].erase(p_obj->get_instance_id()); - } -} - -Error MultiplayerReplicator::sync_all(const ResourceUID::ID &p_scene_id, int p_peer) { - ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); - if (!tracked_objects.has(p_scene_id)) { - return OK; - } - const SceneConfig &cfg = replications[p_scene_id]; - if (cfg.on_sync_send.is_valid()) { - Array objs; - if (tracked_objects.has(p_scene_id)) { - objs.resize(tracked_objects[p_scene_id].size()); - int idx = 0; - for (const ObjectID &obj_id : tracked_objects[p_scene_id]) { - objs[idx++] = ObjectDB::get_instance(obj_id); - } - } - Variant args[3] = { p_scene_id, objs, p_peer }; - Variant *argp[3] = { args, &args[1], &args[2] }; - Callable::CallError ce; - Variant ret; - cfg.on_sync_send.call((const Variant **)argp, 3, ret, ce); - ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, "Custom sync function failed"); - return OK; - } else if (cfg.sync_properties.size()) { - return _sync_all_default(p_scene_id, p_peer); - } - return OK; -} - -Error MultiplayerReplicator::send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, Multiplayer::TransferMode p_transfer_mode, int p_channel) { - ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED); - ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); - const SceneConfig &cfg = replications[p_scene_id]; - ERR_FAIL_COND_V_MSG(!cfg.on_sync_send.is_valid(), ERR_UNCONFIGURED, "Sending raw sync messages is only available with custom functions"); - MAKE_ROOM(SYNC_CMD_OFFSET + p_data.size()); - uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC; - encode_uint64(p_scene_id, &ptr[1]); - if (p_data.size()) { - memcpy(&ptr[SYNC_CMD_OFFSET], p_data.ptr(), p_data.size()); - } - Ref peer = multiplayer->get_multiplayer_peer(); - peer->set_target_peer(p_peer_id); - peer->set_transfer_channel(p_channel); - peer->set_transfer_mode(p_transfer_mode); - return peer->put_packet(ptr, SYNC_CMD_OFFSET + p_data.size()); -} - -void MultiplayerReplicator::clear() { - tracked_objects.clear(); - replicated_nodes.clear(); -} - -void MultiplayerReplicator::_bind_methods() { - ClassDB::bind_method(D_METHOD("spawn_config", "scene_id", "spawn_mode", "properties", "custom_send", "custom_receive"), &MultiplayerReplicator::spawn_config, DEFVAL(TypedArray()), DEFVAL(Callable()), DEFVAL(Callable())); - ClassDB::bind_method(D_METHOD("sync_config", "scene_id", "interval", "properties", "custom_send", "custom_receive"), &MultiplayerReplicator::sync_config, DEFVAL(TypedArray()), DEFVAL(Callable()), DEFVAL(Callable())); - ClassDB::bind_method(D_METHOD("despawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::despawn, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("spawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::spawn, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("send_despawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_despawn, DEFVAL(Variant()), DEFVAL(NodePath())); - ClassDB::bind_method(D_METHOD("send_spawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_spawn, DEFVAL(Variant()), DEFVAL(NodePath())); - ClassDB::bind_method(D_METHOD("send_sync", "peer_id", "scene_id", "data", "transfer_mode", "channel"), &MultiplayerReplicator::send_sync, DEFVAL(Multiplayer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); - ClassDB::bind_method(D_METHOD("sync_all", "scene_id", "peer_id"), &MultiplayerReplicator::sync_all, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("track", "scene_id", "object"), &MultiplayerReplicator::track); - ClassDB::bind_method(D_METHOD("untrack", "scene_id", "object"), &MultiplayerReplicator::untrack); - ClassDB::bind_method(D_METHOD("encode_state", "scene_id", "object", "initial"), &MultiplayerReplicator::encode_state, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("decode_state", "scene_id", "object", "data", "initial"), &MultiplayerReplicator::decode_state, DEFVAL(true)); - - ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); - ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); - ADD_SIGNAL(MethodInfo("despawn_requested", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); - ADD_SIGNAL(MethodInfo("spawn_requested", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); - ADD_SIGNAL(MethodInfo("replicated_instance_added", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); - ADD_SIGNAL(MethodInfo("replicated_instance_removed", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); - - BIND_ENUM_CONSTANT(REPLICATION_MODE_NONE); - BIND_ENUM_CONSTANT(REPLICATION_MODE_SERVER); - BIND_ENUM_CONSTANT(REPLICATION_MODE_CUSTOM); -} diff --git a/core/multiplayer/multiplayer_replicator.h b/core/multiplayer/multiplayer_replicator.h deleted file mode 100644 index a9cd6e211eab..000000000000 --- a/core/multiplayer/multiplayer_replicator.h +++ /dev/null @@ -1,138 +0,0 @@ -/*************************************************************************/ -/* multiplayer_replicator.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef MULTIPLAYER_REPLICATOR_H -#define MULTIPLAYER_REPLICATOR_H - -#include "core/multiplayer/multiplayer_api.h" - -#include "core/io/resource_uid.h" -#include "core/templates/hash_map.h" -#include "core/variant/typed_array.h" - -class MultiplayerReplicator : public Object { - GDCLASS(MultiplayerReplicator, Object); - -public: - enum { - SPAWN_CMD_OFFSET = 9, - SYNC_CMD_OFFSET = 9, - }; - - enum ReplicationMode { - REPLICATION_MODE_NONE, - REPLICATION_MODE_SERVER, - REPLICATION_MODE_CUSTOM, - }; - - struct SceneConfig { - ReplicationMode mode; - uint64_t sync_interval = 0; - uint64_t sync_last = 0; - uint8_t sync_recv = 0; - List properties; - List sync_properties; - Callable on_spawn_despawn_send; - Callable on_spawn_despawn_receive; - Callable on_sync_send; - Callable on_sync_receive; - }; - -protected: - static void _bind_methods(); - -private: - enum { - BYTE_OR_ZERO_SHIFT = MultiplayerAPI::CMD_FLAG_0_SHIFT, - }; - - enum { - BYTE_OR_ZERO_FLAG = 1 << BYTE_OR_ZERO_SHIFT, - }; - - MultiplayerAPI *multiplayer = nullptr; - Vector packet_cache; - Map replications; - Map replicated_nodes; - HashMap> tracked_objects; - - // Encoding - Error _get_state(const List &p_properties, const Object *p_obj, List &r_variant); - Error _encode_state(const List &p_variants, uint8_t *p_buffer, int &r_len, bool *r_raw = nullptr); - Error _decode_state(const List &p_cfg, Object *p_obj, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw = false); - - // Spawn - Error _spawn_despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer, bool p_spawn); - Error _send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, bool p_spawn); - void _process_default_spawn_despawn(int p_from, const ResourceUID::ID &p_scene_id, const uint8_t *p_packet, int p_packet_len, bool p_spawn); - Error _send_default_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, Object *p_obj, const NodePath &p_path, bool p_spawn); - - // Sync - void _process_default_sync(const ResourceUID::ID &p_id, const uint8_t *p_packet, int p_packet_len); - Error _sync_all_default(const ResourceUID::ID &p_scene_id, int p_peer); - void _track(const ResourceUID::ID &p_scene_id, Object *p_object); - void _untrack(const ResourceUID::ID &p_scene_id, Object *p_object); - -public: - void clear(); - - // Encoding - PackedByteArray encode_state(const ResourceUID::ID &p_scene_id, const Object *p_node, bool p_initial); - Error decode_state(const ResourceUID::ID &p_scene_id, Object *p_node, PackedByteArray p_data, bool p_initial); - - // Spawn - Error spawn_config(const ResourceUID::ID &p_id, ReplicationMode p_mode, const TypedArray &p_props = TypedArray(), const Callable &p_on_send = Callable(), const Callable &p_on_recv = Callable()); - Error spawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer = 0); - Error despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer = 0); - Error send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data = Variant(), const NodePath &p_path = NodePath()); - Error send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data = Variant(), const NodePath &p_path = NodePath()); - - // Sync - Error sync_config(const ResourceUID::ID &p_id, uint64_t p_interval, const TypedArray &p_props = TypedArray(), const Callable &p_on_send = Callable(), const Callable &p_on_recv = Callable()); - Error sync_all(const ResourceUID::ID &p_scene_id, int p_peer); - Error send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, Multiplayer::TransferMode p_mode, int p_channel); - void track(const ResourceUID::ID &p_scene_id, Object *p_object); - void untrack(const ResourceUID::ID &p_scene_id, Object *p_object); - - // Used by MultiplayerAPI - void spawn_all(int p_peer); - void process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn); - void process_sync(int p_from, const uint8_t *p_packet, int p_packet_len); - void scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter); - void poll(); - - MultiplayerReplicator(MultiplayerAPI *p_multiplayer) { - multiplayer = p_multiplayer; - } -}; - -VARIANT_ENUM_CAST(MultiplayerReplicator::ReplicationMode); - -#endif // MULTIPLAYER_REPLICATOR_H diff --git a/core/object/callable_method_pointer.h b/core/object/callable_method_pointer.h index 53410a9acfc1..3cd9ad381912 100644 --- a/core/object/callable_method_pointer.h +++ b/core/object/callable_method_pointer.h @@ -51,6 +51,14 @@ class CallableCustomMethodPointerBase : public CallableCustom { void _setup(uint32_t *p_base_ptr, uint32_t p_ptr_size); public: + virtual StringName get_method() const { +#ifdef DEBUG_METHODS_ENABLED + return StringName(text); +#else + return StringName(); +#endif + } + #ifdef DEBUG_METHODS_ENABLED void set_text(const char *p_text) { text = p_text; diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index 03774bc36ad9..3df4db9c5ea3 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -732,7 +732,7 @@ void ClassDB::bind_integer_constant(const StringName &p_class, const StringName String enum_name = p_enum; if (!enum_name.is_empty()) { - if (enum_name.find(".") != -1) { + if (enum_name.contains(".")) { enum_name = enum_name.get_slicec('.', 1); } @@ -1632,7 +1632,8 @@ Variant ClassDB::class_get_default_property_value(const StringName &p_class, con // Some properties may have an instantiated Object as default value, // (like Path2D's `curve` used to have), but that's not a good practice. // Instead, those properties should use PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT - // to be auto-instantiated when created in the editor. + // to be auto-instantiated when created in the editor with the following method: + // EditorNode::get_editor_data().instantiate_object_properties(obj); if (var.get_type() == Variant::OBJECT) { Object *obj = var.get_validated_object(); if (obj) { diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py index e961745d96bb..5de1b49026dd 100644 --- a/core/object/make_virtuals.py +++ b/core/object/make_virtuals.py @@ -123,9 +123,9 @@ def generate_version(argcount, const=False, returns=False): callargtext += "," callargtext += " m_ret& r_ret" s = s.replace("$CALLSIBEGIN", "Variant ret = ") - s = s.replace("$CALLSIRET", "r_ret = ret;") + s = s.replace("$CALLSIRET", "r_ret = VariantCaster::cast(ret);") s = s.replace("$CALLPTRRETPASS", "&ret") - s = s.replace("$CALLPTRRET", "r_ret = ret;") + s = s.replace("$CALLPTRRET", "r_ret = (m_ret)ret;") else: s = s.replace("$CALLSIBEGIN", "") s = s.replace("$CALLSIRET", "") diff --git a/core/object/object.h b/core/object/object.h index 63130a1aefe5..1a0a81581d4e 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -700,8 +700,9 @@ class Object { static String get_category_static() { return String(); } virtual String get_class() const { - if (_extension) + if (_extension) { return _extension->class_name.operator String(); + } return "Object"; } virtual String get_save_class() const { return get_class(); } //class stored when saving diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index a18ec4d6ada2..388368d1812d 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -69,7 +69,6 @@ #include "core/math/triangle_mesh.h" #include "core/multiplayer/multiplayer_api.h" #include "core/multiplayer/multiplayer_peer.h" -#include "core/multiplayer/multiplayer_replicator.h" #include "core/object/class_db.h" #include "core/object/undo_redo.h" #include "core/os/main_loop.h" @@ -200,7 +199,6 @@ void register_core_types() { GDREGISTER_VIRTUAL_CLASS(MultiplayerPeer); GDREGISTER_CLASS(MultiplayerPeerExtension); - GDREGISTER_VIRTUAL_CLASS(MultiplayerReplicator); GDREGISTER_CLASS(MultiplayerAPI); GDREGISTER_CLASS(MainLoop); GDREGISTER_CLASS(Translation); diff --git a/core/string/char_utils.h b/core/string/char_utils.h new file mode 100644 index 000000000000..0afd058f0135 --- /dev/null +++ b/core/string/char_utils.h @@ -0,0 +1,92 @@ +/*************************************************************************/ +/* char_utils.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef CHAR_UTILS_H +#define CHAR_UTILS_H + +#include "core/typedefs.h" + +static _FORCE_INLINE_ bool is_ascii_upper_case(char32_t c) { + return (c >= 'A' && c <= 'Z'); +} + +static _FORCE_INLINE_ bool is_ascii_lower_case(char32_t c) { + return (c >= 'a' && c <= 'z'); +} + +static _FORCE_INLINE_ bool is_digit(char32_t c) { + return (c >= '0' && c <= '9'); +} + +static _FORCE_INLINE_ bool is_hex_digit(char32_t c) { + return (is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); +} + +static _FORCE_INLINE_ bool is_binary_digit(char32_t c) { + return (c == '0' || c == '1'); +} + +static _FORCE_INLINE_ bool is_ascii_char(char32_t c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +static _FORCE_INLINE_ bool is_ascii_alphanumeric_char(char32_t c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'); +} + +static _FORCE_INLINE_ bool is_ascii_identifier_char(char32_t c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; +} + +static _FORCE_INLINE_ bool is_symbol(char32_t c) { + return c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t' || c == ' '); +} + +static _FORCE_INLINE_ bool is_control(char32_t p_char) { + return (p_char <= 0x001f) || (p_char >= 0x007f && p_char <= 0x009f); +} + +static _FORCE_INLINE_ bool is_whitespace(char32_t p_char) { + return (p_char == ' ') || (p_char == 0x00a0) || (p_char == 0x1680) || (p_char >= 0x2000 && p_char <= 0x200a) || (p_char == 0x202f) || (p_char == 0x205f) || (p_char == 0x3000) || (p_char == 0x2028) || (p_char == 0x2029) || (p_char >= 0x0009 && p_char <= 0x000d) || (p_char == 0x0085); +} + +static _FORCE_INLINE_ bool is_linebreak(char32_t p_char) { + return (p_char >= 0x000a && p_char <= 0x000d) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029); +} + +static _FORCE_INLINE_ bool is_punct(char32_t p_char) { + return (p_char >= ' ' && p_char <= '/') || (p_char >= ':' && p_char <= '@') || (p_char >= '[' && p_char <= '^') || (p_char == '`') || (p_char >= '{' && p_char <= '~') || (p_char >= 0x2000 && p_char <= 0x206f) || (p_char >= 0x3000 && p_char <= 0x303f); +} + +static _FORCE_INLINE_ bool is_underscore(char32_t p_char) { + return (p_char == '_'); +} + +#endif // CHAR_UTILS_H diff --git a/core/string/locales.h b/core/string/locales.h index 9ef6dee5acb1..32d6608ec26e 100644 --- a/core/string/locales.h +++ b/core/string/locales.h @@ -42,6 +42,7 @@ static const char *locale_renames[][2] = { { "in", "id" }, // Indonesian { "iw", "he" }, // Hebrew { "no", "nb" }, // Norwegian Bokmål + { "C", "en" }, // Locale is not set, fallback to English. { nullptr, nullptr } }; diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp index 61742ac582d4..11674629fc74 100644 --- a/core/string/string_name.cpp +++ b/core/string/string_name.cpp @@ -84,12 +84,15 @@ void StringName::cleanup() { for (int i = 0; i < STRING_TABLE_LEN; i++) { while (_table[i]) { _Data *d = _table[i]; - lost_strings++; - if (d->static_count.get() != d->refcount.get() && OS::get_singleton()->is_stdout_verbose()) { - if (d->cname) { - print_line("Orphan StringName: " + String(d->cname)); - } else { - print_line("Orphan StringName: " + String(d->name)); + if (d->static_count.get() != d->refcount.get()) { + lost_strings++; + + if (OS::get_singleton()->is_stdout_verbose()) { + if (d->cname) { + print_line("Orphan StringName: " + String(d->cname)); + } else { + print_line("Orphan StringName: " + String(d->name)); + } } } diff --git a/core/string/string_name.h b/core/string/string_name.h index 9653d2b4cf89..f767f3e1ec04 100644 --- a/core/string/string_name.h +++ b/core/string/string_name.h @@ -181,6 +181,18 @@ bool operator!=(const char *p_name, const StringName &p_string_name); StringName _scs_create(const char *p_chr, bool p_static = false); +/* + * The SNAME macro is used to speed up StringName creation, as it allows caching it after the first usage in a very efficient way. + * It should NOT be used everywhere, but instead in places where high performance is required and the creation of a StringName + * can be costly. Places where it should be used are: + * - Control::get_theme_*( and Window::get_theme_*( functions. + * - emit_signal(,..) function + * - call_deferred(,..) function + * - Comparisons to a StringName in overriden _set and _get methods. + * + * Use in places that can be called hundreds of times per frame (or more) is recommended, but this situation is very rare. If in doubt, do not use. + */ + #define SNAME(m_arg) ([]() -> const StringName & { static StringName sname = _scs_create(m_arg, true); return sname; })() #endif // STRING_NAME_H diff --git a/core/string/translation.cpp b/core/string/translation.cpp index 674098b06c44..eeac8b0acf60 100644 --- a/core/string/translation.cpp +++ b/core/string/translation.cpp @@ -213,14 +213,6 @@ static _character_accent_pair _character_to_accented[] = { { 'z', U"ź" }, }; -static _FORCE_INLINE_ bool is_upper_case(char32_t c) { - return (c >= 'A' && c <= 'Z'); -} - -static _FORCE_INLINE_ bool is_lower_case(char32_t c) { - return (c >= 'a' && c <= 'z'); -} - Vector TranslationServer::locale_script_info; Map TranslationServer::language_map; @@ -309,15 +301,15 @@ String TranslationServer::standardize_locale(const String &p_locale) const { Vector locale_elements = univ_locale.get_slice("@", 0).split("_"); lang = locale_elements[0]; if (locale_elements.size() >= 2) { - if (locale_elements[1].length() == 4 && is_upper_case(locale_elements[1][0]) && is_lower_case(locale_elements[1][1]) && is_lower_case(locale_elements[1][2]) && is_lower_case(locale_elements[1][3])) { + if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) { script = locale_elements[1]; } - if (locale_elements[1].length() == 2 && is_upper_case(locale_elements[1][0]) && is_upper_case(locale_elements[1][1])) { + if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) { country = locale_elements[1]; } } if (locale_elements.size() >= 3) { - if (locale_elements[2].length() == 2 && is_upper_case(locale_elements[2][0]) && is_upper_case(locale_elements[2][1])) { + if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) { country = locale_elements[2]; } else if (variant_map.has(locale_elements[2].to_lower()) && variant_map[locale_elements[2].to_lower()] == lang) { variant = locale_elements[2].to_lower(); @@ -434,15 +426,15 @@ String TranslationServer::get_locale_name(const String &p_locale) const { Vector locale_elements = locale.split("_"); lang = locale_elements[0]; if (locale_elements.size() >= 2) { - if (locale_elements[1].length() == 4 && is_upper_case(locale_elements[1][0]) && is_lower_case(locale_elements[1][1]) && is_lower_case(locale_elements[1][2]) && is_lower_case(locale_elements[1][3])) { + if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) { script = locale_elements[1]; } - if (locale_elements[1].length() == 2 && is_upper_case(locale_elements[1][0]) && is_upper_case(locale_elements[1][1])) { + if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) { country = locale_elements[1]; } } if (locale_elements.size() >= 3) { - if (locale_elements[2].length() == 2 && is_upper_case(locale_elements[2][0]) && is_upper_case(locale_elements[2][1])) { + if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) { country = locale_elements[2]; } } @@ -544,7 +536,7 @@ Ref TranslationServer::get_translation_object(const String &p_local String l = t->get_locale(); int score = compare_locales(p_locale, l); - if (score > best_score) { + if (score > 0 && score >= best_score) { res = t; best_score = score; if (score == 10) { @@ -566,8 +558,6 @@ StringName TranslationServer::translate(const StringName &p_message, const Strin return p_message; } - ERR_FAIL_COND_V_MSG(locale.length() < 2, p_message, "Could not translate message as configured locale '" + locale + "' is invalid."); - StringName res = _get_message_from_translations(p_message, p_context, locale, false); if (!res && fallback.length() >= 2) { @@ -589,8 +579,6 @@ StringName TranslationServer::translate_plural(const StringName &p_message, cons return p_message_plural; } - ERR_FAIL_COND_V_MSG(locale.length() < 2, p_message, "Could not translate message as configured locale '" + locale + "' is invalid."); - StringName res = _get_message_from_translations(p_message, p_context, locale, true, p_message_plural, p_n); if (!res && fallback.length() >= 2) { @@ -617,13 +605,17 @@ StringName TranslationServer::_get_message_from_translations(const StringName &p String l = t->get_locale(); int score = compare_locales(p_locale, l); - if (score > best_score) { + if (score > 0 && score >= best_score) { StringName r; if (!plural) { - res = t->get_message(p_message, p_context); + r = t->get_message(p_message, p_context); } else { - res = t->get_plural_message(p_message, p_message_plural, p_n, p_context); + r = t->get_plural_message(p_message, p_message_plural, p_n, p_context); + } + if (!r) { + continue; } + res = r; best_score = score; if (score == 10) { break; // Exact match, skip the rest. @@ -911,7 +903,7 @@ String TranslationServer::add_padding(String &p_message, int p_length) const { } const char32_t *TranslationServer::get_accented_version(char32_t p_character) const { - if (!((p_character >= 'a' && p_character <= 'z') || (p_character >= 'A' && p_character <= 'Z'))) { + if (!is_ascii_char(p_character)) { return nullptr; } @@ -933,6 +925,7 @@ bool TranslationServer::is_placeholder(String &p_message, int p_index) const { void TranslationServer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale); ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale); + ClassDB::bind_method(D_METHOD("get_tool_locale"), &TranslationServer::get_tool_locale); ClassDB::bind_method(D_METHOD("compare_locales", "locale_a", "locale_b"), &TranslationServer::compare_locales); ClassDB::bind_method(D_METHOD("standardize_locale", "locale"), &TranslationServer::standardize_locale); diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 93b20601550a..c4edc8c086d3 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -54,34 +54,14 @@ static const int MAX_DECIMALS = 32; -static _FORCE_INLINE_ bool is_digit(char32_t c) { - return (c >= '0' && c <= '9'); -} - -static _FORCE_INLINE_ bool is_hex_digit(char32_t c) { - return (is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); -} - -static _FORCE_INLINE_ bool is_upper_case(char32_t c) { - return (c >= 'A' && c <= 'Z'); -} - -static _FORCE_INLINE_ bool is_lower_case(char32_t c) { - return (c >= 'a' && c <= 'z'); -} - static _FORCE_INLINE_ char32_t lower_case(char32_t c) { - return (is_upper_case(c) ? (c + ('a' - 'A')) : c); + return (is_ascii_upper_case(c) ? (c + ('a' - 'A')) : c); } const char CharString::_null = 0; const char16_t Char16String::_null = 0; const char32_t String::_null = 0; -bool is_symbol(char32_t c) { - return c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t' || c == ' '); -} - bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end) { const String &s = p_s; int beg = CLAMP(p_col, 0, s.length()); @@ -974,21 +954,21 @@ String String::camelcase_to_underscore(bool lowercase) const { int start_index = 0; for (int i = 1; i < this->size(); i++) { - bool is_upper = is_upper_case(cstr[i]); + bool is_upper = is_ascii_upper_case(cstr[i]); bool is_number = is_digit(cstr[i]); bool are_next_2_lower = false; bool is_next_lower = false; bool is_next_number = false; - bool was_precedent_upper = is_upper_case(cstr[i - 1]); + bool was_precedent_upper = is_ascii_upper_case(cstr[i - 1]); bool was_precedent_number = is_digit(cstr[i - 1]); if (i + 2 < this->size()) { - are_next_2_lower = is_lower_case(cstr[i + 1]) && is_lower_case(cstr[i + 2]); + are_next_2_lower = is_ascii_lower_case(cstr[i + 1]) && is_ascii_lower_case(cstr[i + 2]); } if (i + 1 < this->size()) { - is_next_lower = is_lower_case(cstr[i + 1]); + is_next_lower = is_ascii_lower_case(cstr[i + 1]); is_next_number = is_digit(cstr[i + 1]); } @@ -2212,7 +2192,7 @@ bool String::is_numeric() const { return false; } dot = true; - } else if (c < '0' || c > '9') { + } else if (!is_digit(c)) { return false; } } @@ -3080,7 +3060,7 @@ bool String::is_subsequence_of(const String &p_string) const { return _base_is_subsequence_of(p_string, false); } -bool String::is_subsequence_ofi(const String &p_string) const { +bool String::is_subsequence_ofn(const String &p_string) const { return _base_is_subsequence_of(p_string, true); } @@ -3558,6 +3538,10 @@ String String::rstrip(const String &p_chars) const { return substr(0, end + 1); } +bool String::is_network_share_path() const { + return begins_with("//") || begins_with("\\\\"); +} + String String::simplify_path() const { String s = *this; String drive; @@ -3570,6 +3554,9 @@ String String::simplify_path() const { } else if (s.begins_with("user://")) { drive = "user://"; s = s.substr(7, s.length()); + } else if (is_network_share_path()) { + drive = s.substr(0, 2); + s = s.substr(2, s.length() - 2); } else if (s.begins_with("/") || s.begins_with("\\")) { drive = s.substr(0, 1); s = s.substr(1, s.length() - 1); @@ -3684,7 +3671,7 @@ bool String::is_valid_identifier() const { } } - bool valid_char = is_digit(str[i]) || is_lower_case(str[i]) || is_upper_case(str[i]) || str[i] == '_'; + bool valid_char = is_ascii_identifier_char(str[i]); if (!valid_char) { return false; @@ -3709,7 +3696,7 @@ String String::uri_encode() const { String res; for (int i = 0; i < temp.length(); ++i) { char ord = temp[i]; - if (ord == '.' || ord == '-' || ord == '_' || ord == '~' || is_lower_case(ord) || is_upper_case(ord) || is_digit(ord)) { + if (ord == '.' || ord == '-' || ord == '~' || is_ascii_identifier_char(ord)) { res += ord; } else { char h_Val[3]; @@ -3731,9 +3718,9 @@ String String::uri_decode() const { for (int i = 0; i < src.length(); ++i) { if (src[i] == '%' && i + 2 < src.length()) { char ord1 = src[i + 1]; - if (is_digit(ord1) || is_upper_case(ord1)) { + if (is_digit(ord1) || is_ascii_upper_case(ord1)) { char ord2 = src[i + 2]; - if (is_digit(ord2) || is_upper_case(ord2)) { + if (is_digit(ord2) || is_ascii_upper_case(ord2)) { char bytes[3] = { (char)ord1, (char)ord2, 0 }; res += (char)strtol(bytes, nullptr, 16); i += 2; @@ -3860,7 +3847,7 @@ static _FORCE_INLINE_ int _xml_unescape(const char32_t *p_src, int p_src_len, ch for (int i = 2; i < p_src_len; i++) { eat = i + 1; char32_t ct = p_src[i]; - if (ct == ';' || ct < '0' || ct > '9') { + if (ct == ';' || !is_digit(ct)) { break; } } @@ -3990,7 +3977,7 @@ String String::pad_zeros(int p_digits) const { int begin = 0; - while (begin < end && (s[begin] < '0' || s[begin] > '9')) { + while (begin < end && !is_digit(s[begin])) { begin++; } @@ -4035,7 +4022,7 @@ bool String::is_valid_int() const { } for (int i = from; i < len; i++) { - if (operator[](i) < '0' || operator[](i) > '9') { + if (!is_digit(operator[](i))) { return false; // no start with number plz } } @@ -4271,13 +4258,13 @@ bool String::is_relative_path() const { String String::get_base_dir() const { int end = 0; - // url scheme style base + // URL scheme style base. int basepos = find("://"); if (basepos != -1) { end = basepos + 3; } - // windows top level directory base + // Windows top level directory base. if (end == 0) { basepos = find(":/"); if (basepos == -1) { @@ -4288,7 +4275,24 @@ String String::get_base_dir() const { } } - // unix root directory base + // Windows UNC network share path. + if (end == 0) { + if (is_network_share_path()) { + basepos = find("/", 2); + if (basepos == -1) { + basepos = find("\\", 2); + } + int servpos = find("/", basepos + 1); + if (servpos == -1) { + servpos = find("\\", basepos + 1); + } + if (servpos != -1) { + end = servpos + 1; + } + } + } + + // Unix root directory base. if (end == 0) { if (begins_with("/")) { end = 1; diff --git a/core/string/ustring.h b/core/string/ustring.h index 4840c236c07c..1d302b65a724 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -32,6 +32,7 @@ #define USTRING_GODOT_H // Note: Renamed to avoid conflict with ICU header with the same name. +#include "core/string/char_utils.h" #include "core/templates/cowdata.h" #include "core/templates/vector.h" #include "core/typedefs.h" @@ -285,7 +286,7 @@ class String { bool ends_with(const String &p_string) const; bool is_enclosed_in(const String &p_string) const; bool is_subsequence_of(const String &p_string) const; - bool is_subsequence_ofi(const String &p_string) const; + bool is_subsequence_ofn(const String &p_string) const; bool is_quoted() const; Vector bigrams() const; float similarity(const String &p_string) const; @@ -394,6 +395,8 @@ class String { Vector sha256_buffer() const; _FORCE_INLINE_ bool is_empty() const { return length() == 0; } + _FORCE_INLINE_ bool contains(const char *p_str) const { return find(p_str) != -1; } + _FORCE_INLINE_ bool contains(const String &p_str) const { return find(p_str) != -1; } // path functions bool is_absolute_path() const; @@ -405,6 +408,7 @@ class String { String get_file() const; static String humanize_size(uint64_t p_size); String simplify_path() const; + bool is_network_share_path() const; String xml_escape(bool p_escape_quotes = false) const; String xml_unescape() const; @@ -530,7 +534,6 @@ String DTRN(const String &p_text, const String &p_text_plural, int p_n, const St String RTR(const String &p_text, const String &p_context = ""); String RTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = ""); -bool is_symbol(char32_t c); bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end); _FORCE_INLINE_ void sarray_add_str(Vector &arr) { diff --git a/core/templates/rid_owner.h b/core/templates/rid_owner.h index 3ed81e76fd28..95632cdec242 100644 --- a/core/templates/rid_owner.h +++ b/core/templates/rid_owner.h @@ -292,43 +292,32 @@ class RID_Alloc : public RID_AllocBase { _FORCE_INLINE_ uint32_t get_rid_count() const { return alloc_count; } - - _FORCE_INLINE_ T *get_ptr_by_index(uint32_t p_index) { - ERR_FAIL_UNSIGNED_INDEX_V(p_index, alloc_count, nullptr); + void get_owned_list(List *p_owned) { if (THREAD_SAFE) { spin_lock.lock(); } - uint64_t idx = free_list_chunks[p_index / elements_in_chunk][p_index % elements_in_chunk]; - T *ptr = &chunks[idx / elements_in_chunk][idx % elements_in_chunk]; - if (THREAD_SAFE) { - spin_lock.unlock(); - } - return ptr; - } - - _FORCE_INLINE_ RID get_rid_by_index(uint32_t p_index) { - ERR_FAIL_INDEX_V(p_index, alloc_count, RID()); - if (THREAD_SAFE) { - spin_lock.lock(); + for (size_t i = 0; i < max_alloc; i++) { + uint64_t validator = validator_chunks[i / elements_in_chunk][i % elements_in_chunk]; + if (validator != 0xFFFFFFFF) { + p_owned->push_back(_make_from_id((validator << 32) | i)); + } } - uint64_t idx = free_list_chunks[p_index / elements_in_chunk][p_index % elements_in_chunk]; - uint64_t validator = validator_chunks[idx / elements_in_chunk][idx % elements_in_chunk]; - - RID rid = _make_from_id((validator << 32) | idx); if (THREAD_SAFE) { spin_lock.unlock(); } - return rid; } - void get_owned_list(List *p_owned) { + //used for fast iteration in the elements or RIDs + void fill_owned_buffer(RID *p_rid_buffer) { if (THREAD_SAFE) { spin_lock.lock(); } + uint32_t idx = 0; for (size_t i = 0; i < max_alloc; i++) { uint64_t validator = validator_chunks[i / elements_in_chunk][i % elements_in_chunk]; if (validator != 0xFFFFFFFF) { - p_owned->push_back(_make_from_id((validator << 32) | i)); + p_rid_buffer[idx] = _make_from_id((validator << 32) | i); + idx++; } } if (THREAD_SAFE) { @@ -425,18 +414,14 @@ class RID_PtrOwner { return alloc.get_rid_count(); } - _FORCE_INLINE_ RID get_rid_by_index(uint32_t p_index) { - return alloc.get_rid_by_index(p_index); - } - - _FORCE_INLINE_ T *get_ptr_by_index(uint32_t p_index) { - return *alloc.get_ptr_by_index(p_index); - } - _FORCE_INLINE_ void get_owned_list(List *p_owned) { return alloc.get_owned_list(p_owned); } + void fill_owned_buffer(RID *p_rid_buffer) { + alloc.fill_owned_buffer(p_rid_buffer); + } + void set_description(const char *p_descrption) { alloc.set_description(p_descrption); } @@ -485,17 +470,12 @@ class RID_Owner { return alloc.get_rid_count(); } - _FORCE_INLINE_ RID get_rid_by_index(uint32_t p_index) { - return alloc.get_rid_by_index(p_index); - } - - _FORCE_INLINE_ T *get_ptr_by_index(uint32_t p_index) { - return alloc.get_ptr_by_index(p_index); - } - _FORCE_INLINE_ void get_owned_list(List *p_owned) { return alloc.get_owned_list(p_owned); } + void fill_owned_buffer(RID *p_rid_buffer) { + alloc.fill_owned_buffer(p_rid_buffer); + } void set_description(const char *p_descrption) { alloc.set_description(p_descrption); diff --git a/core/templates/vector.h b/core/templates/vector.h index bd4c6ade8632..0877e04e01c4 100644 --- a/core/templates/vector.h +++ b/core/templates/vector.h @@ -95,9 +95,7 @@ class Vector { void append_array(Vector p_other); - bool has(const T &p_val) const { - return find(p_val, 0) != -1; - } + _FORCE_INLINE_ bool has(const T &p_val) const { return find(p_val) != -1; } template void sort_custom() { diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h index 14f49d530cf6..b6fdb4d9020f 100644 --- a/core/variant/binder_common.h +++ b/core/variant/binder_common.h @@ -44,24 +44,42 @@ #include +// Variant cannot define an implicit cast operator for every Object subclass, so the +// casting is done here, to allow binding methods with parameters more specific than Object * + template struct VariantCaster { static _FORCE_INLINE_ T cast(const Variant &p_variant) { - return p_variant; + using TStripped = std::remove_pointer_t; + if constexpr (std::is_base_of::value) { + return Object::cast_to(p_variant); + } else { + return p_variant; + } } }; template struct VariantCaster { static _FORCE_INLINE_ T cast(const Variant &p_variant) { - return p_variant; + using TStripped = std::remove_pointer_t; + if constexpr (std::is_base_of::value) { + return Object::cast_to(p_variant); + } else { + return p_variant; + } } }; template struct VariantCaster { static _FORCE_INLINE_ T cast(const Variant &p_variant) { - return p_variant; + using TStripped = std::remove_pointer_t; + if constexpr (std::is_base_of::value) { + return Object::cast_to(p_variant); + } else { + return p_variant; + } } }; @@ -135,7 +153,13 @@ struct PtrToArg { template struct VariantObjectClassChecker { static _FORCE_INLINE_ bool check(const Variant &p_variant) { - return true; + using TStripped = std::remove_pointer_t; + if constexpr (std::is_base_of::value) { + Object *obj = p_variant; + return Object::cast_to(p_variant) || !obj; + } else { + return true; + } } }; @@ -151,24 +175,6 @@ struct VariantObjectClassChecker &> { } }; -template <> -struct VariantObjectClassChecker { - static _FORCE_INLINE_ bool check(const Variant &p_variant) { - Object *obj = p_variant; - Node *node = p_variant; - return node || !obj; - } -}; - -template <> -struct VariantObjectClassChecker { - static _FORCE_INLINE_ bool check(const Variant &p_variant) { - Object *obj = p_variant; - Control *control = p_variant; - return control || !obj; - } -}; - #ifdef DEBUG_METHODS_ENABLED template diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp index c6a67f01d54f..27792ce111ba 100644 --- a/core/variant/callable.cpp +++ b/core/variant/callable.cpp @@ -114,8 +114,9 @@ ObjectID Callable::get_object_id() const { } StringName Callable::get_method() const { - ERR_FAIL_COND_V_MSG(is_custom(), StringName(), - vformat("Can't get method on CallableCustom \"%s\".", operator String())); + if (is_custom()) { + return get_custom()->get_method(); + } return method; } @@ -310,6 +311,10 @@ Callable::~Callable() { } } +StringName CallableCustom::get_method() const { + ERR_FAIL_V_MSG(StringName(), vformat("Can't get method on CallableCustom \"%s\".", get_as_text())); +} + void CallableCustom::rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const { r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; r_call_error.argument = 0; diff --git a/core/variant/callable.h b/core/variant/callable.h index 855ffa912941..c61870f1941b 100644 --- a/core/variant/callable.h +++ b/core/variant/callable.h @@ -125,6 +125,7 @@ class CallableCustom { virtual String get_as_text() const = 0; virtual CompareEqualFunc get_compare_equal_func() const = 0; virtual CompareLessFunc get_compare_less_func() const = 0; + virtual StringName get_method() const; virtual ObjectID get_object() const = 0; //must always be able to provide an object virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const = 0; virtual void rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const; diff --git a/core/variant/callable_bind.cpp b/core/variant/callable_bind.cpp index 457962176046..797e8afedeb3 100644 --- a/core/variant/callable_bind.cpp +++ b/core/variant/callable_bind.cpp @@ -70,12 +70,19 @@ bool CallableCustomBind::_less_func(const CallableCustom *p_a, const CallableCus CallableCustom::CompareEqualFunc CallableCustomBind::get_compare_equal_func() const { return _equal_func; } + CallableCustom::CompareLessFunc CallableCustomBind::get_compare_less_func() const { return _less_func; } + +StringName CallableCustomBind::get_method() const { + return callable.get_method(); +} + ObjectID CallableCustomBind::get_object() const { return callable.get_object_id(); } + const Callable *CallableCustomBind::get_base_comparator() const { return &callable; } @@ -140,12 +147,19 @@ bool CallableCustomUnbind::_less_func(const CallableCustom *p_a, const CallableC CallableCustom::CompareEqualFunc CallableCustomUnbind::get_compare_equal_func() const { return _equal_func; } + CallableCustom::CompareLessFunc CallableCustomUnbind::get_compare_less_func() const { return _less_func; } + +StringName CallableCustomUnbind::get_method() const { + return callable.get_method(); +} + ObjectID CallableCustomUnbind::get_object() const { return callable.get_object_id(); } + const Callable *CallableCustomUnbind::get_base_comparator() const { return &callable; } diff --git a/core/variant/callable_bind.h b/core/variant/callable_bind.h index ac5797e05f65..4f79a296294c 100644 --- a/core/variant/callable_bind.h +++ b/core/variant/callable_bind.h @@ -47,6 +47,7 @@ class CallableCustomBind : public CallableCustom { virtual String get_as_text() const; virtual CompareEqualFunc get_compare_equal_func() const; virtual CompareLessFunc get_compare_less_func() const; + virtual StringName get_method() const; virtual ObjectID get_object() const; //must always be able to provide an object virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const; virtual const Callable *get_base_comparator() const; @@ -71,6 +72,7 @@ class CallableCustomUnbind : public CallableCustom { virtual String get_as_text() const; virtual CompareEqualFunc get_compare_equal_func() const; virtual CompareLessFunc get_compare_less_func() const; + virtual StringName get_method() const; virtual ObjectID get_object() const; //must always be able to provide an object virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const; virtual const Callable *get_base_comparator() const; diff --git a/core/variant/method_ptrcall.h b/core/variant/method_ptrcall.h index 75a93ac4c86e..d0acf60c2209 100644 --- a/core/variant/method_ptrcall.h +++ b/core/variant/method_ptrcall.h @@ -31,7 +31,6 @@ #ifndef METHOD_PTRCALL_H #define METHOD_PTRCALL_H -#include "core/math/transform_2d.h" #include "core/object/object_id.h" #include "core/typedefs.h" #include "core/variant/variant.h" diff --git a/core/variant/native_ptr.h b/core/variant/native_ptr.h index fe541c8d4bd1..8e9fbbc0a445 100644 --- a/core/variant/native_ptr.h +++ b/core/variant/native_ptr.h @@ -53,22 +53,36 @@ struct GDNativePtr { operator Variant() const { return uint64_t(data); } }; -#define GDVIRTUAL_NATIVE_PTR(m_type) \ - template <> \ - struct GDNativeConstPtr { \ - const m_type *data = nullptr; \ - GDNativeConstPtr(const m_type *p_assign) { data = p_assign; } \ - static const char *get_name() { return "const " #m_type; } \ - operator const m_type *() const { return data; } \ - operator Variant() const { return uint64_t(data); } \ - }; \ - template <> \ - struct GDNativePtr { \ - m_type *data = nullptr; \ - GDNativePtr(m_type *p_assign) { data = p_assign; } \ - static const char *get_name() { return #m_type; } \ - operator m_type *() const { return data; } \ - operator Variant() const { return uint64_t(data); } \ +#define GDVIRTUAL_NATIVE_PTR(m_type) \ + template <> \ + struct GDNativeConstPtr { \ + const m_type *data = nullptr; \ + GDNativeConstPtr() {} \ + GDNativeConstPtr(const m_type *p_assign) { data = p_assign; } \ + static const char *get_name() { return "const " #m_type; } \ + operator const m_type *() const { return data; } \ + operator Variant() const { return uint64_t(data); } \ + }; \ + template <> \ + struct VariantCaster> { \ + static _FORCE_INLINE_ GDNativeConstPtr cast(const Variant &p_variant) { \ + return GDNativeConstPtr((const m_type *)p_variant.operator uint64_t()); \ + } \ + }; \ + template <> \ + struct GDNativePtr { \ + m_type *data = nullptr; \ + GDNativePtr() {} \ + GDNativePtr(m_type *p_assign) { data = p_assign; } \ + static const char *get_name() { return #m_type; } \ + operator m_type *() const { return data; } \ + operator Variant() const { return uint64_t(data); } \ + }; \ + template <> \ + struct VariantCaster> { \ + static _FORCE_INLINE_ GDNativePtr cast(const Variant &p_variant) { \ + return GDNativePtr((m_type *)p_variant.operator uint64_t()); \ + } \ }; template diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index 38610c4f2925..fcfa5303889e 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -38,8 +38,6 @@ #include "core/math/math_funcs.h" #include "core/string/print_string.h" #include "core/variant/variant_parser.h" -#include "scene/gui/control.h" -#include "scene/main/node.h" String Variant::get_type_name(Variant::Type p_type) { switch (p_type) { @@ -2004,22 +2002,6 @@ Object *Variant::get_validated_object() const { } } -Variant::operator Node *() const { - if (type == OBJECT) { - return Object::cast_to(_get_obj().obj); - } else { - return nullptr; - } -} - -Variant::operator Control *() const { - if (type == OBJECT) { - return Object::cast_to(_get_obj().obj); - } else { - return nullptr; - } -} - Variant::operator Dictionary() const { if (type == DICTIONARY) { return *reinterpret_cast(_data._mem); @@ -3414,7 +3396,7 @@ String Variant::get_call_error_text(Object *p_base, const StringName &p_method, } String class_name = p_base->get_class(); - Ref + //]]> \n"; + config["serviceWorker"] = p_name + ".service.worker.js"; } // Replaces HTML string @@ -188,35 +187,46 @@ Error EditorExportPlatformJavaScript::_add_manifest_icon(const String &p_path, c } Error EditorExportPlatformJavaScript::_build_pwa(const Ref &p_preset, const String p_path, const Vector &p_shared_objects) { + String proj_name = ProjectSettings::get_singleton()->get_setting("application/config/name"); + if (proj_name.is_empty()) { + proj_name = "Godot Game"; + } + // Service worker const String dir = p_path.get_base_dir(); const String name = p_path.get_file().get_basename(); const ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type"); Map replaces; - replaces["@GODOT_VERSION@"] = "1"; - replaces["@GODOT_NAME@"] = name; + replaces["@GODOT_VERSION@"] = String::num_int64(OS::get_singleton()->get_unix_time()) + "|" + String::num_int64(OS::get_singleton()->get_ticks_usec()); + replaces["@GODOT_NAME@"] = proj_name.substr(0, 16); replaces["@GODOT_OFFLINE_PAGE@"] = name + ".offline.html"; - Array files; - replaces["@GODOT_OPT_CACHE@"] = Variant(files).to_json_string(); - files.push_back(name + ".html"); - files.push_back(name + ".js"); - files.push_back(name + ".wasm"); - files.push_back(name + ".pck"); - files.push_back(name + ".offline.html"); + + // Files cached during worker install. + Array cache_files; + cache_files.push_back(name + ".html"); + cache_files.push_back(name + ".js"); + cache_files.push_back(name + ".offline.html"); if (p_preset->get("html/export_icon")) { - files.push_back(name + ".icon.png"); - files.push_back(name + ".apple-touch-icon.png"); + cache_files.push_back(name + ".icon.png"); + cache_files.push_back(name + ".apple-touch-icon.png"); } if (mode == EXPORT_MODE_THREADS) { - files.push_back(name + ".worker.js"); - files.push_back(name + ".audio.worklet.js"); - } else if (mode == EXPORT_MODE_GDNATIVE) { - files.push_back(name + ".side.wasm"); + cache_files.push_back(name + ".worker.js"); + cache_files.push_back(name + ".audio.worklet.js"); + } + replaces["@GODOT_CACHE@"] = Variant(cache_files).to_json_string(); + + // Heavy files that are cached on demand. + Array opt_cache_files; + opt_cache_files.push_back(name + ".wasm"); + opt_cache_files.push_back(name + ".pck"); + if (mode == EXPORT_MODE_GDNATIVE) { + opt_cache_files.push_back(name + ".side.wasm"); for (int i = 0; i < p_shared_objects.size(); i++) { - files.push_back(p_shared_objects[i].path.get_file()); + opt_cache_files.push_back(p_shared_objects[i].path.get_file()); } } - replaces["@GODOT_CACHE@"] = Variant(files).to_json_string(); + replaces["@GODOT_OPT_CACHE@"] = Variant(opt_cache_files).to_json_string(); const String sw_path = dir.plus_file(name + ".service.worker.js"); Vector sw; @@ -256,10 +266,6 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref & const int orientation = CLAMP(int(p_preset->get("progressive_web_app/orientation")), 0, 3); Dictionary manifest; - String proj_name = ProjectSettings::get_singleton()->get_setting("application/config/name"); - if (proj_name.is_empty()) { - proj_name = "Godot Game"; - } manifest["name"] = proj_name; manifest["start_url"] = "./" + name + ".html"; manifest["display"] = String::utf8(modes[display]); @@ -658,7 +664,7 @@ EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() { Ref theme = EditorNode::get_singleton()->get_editor_theme(); if (theme.is_valid()) { - stop_icon = theme->get_icon("Stop", "EditorIcons"); + stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons")); } else { stop_icon.instantiate(); } diff --git a/platform/javascript/export/export_plugin.h b/platform/javascript/export/export_plugin.h index c55a881911be..278e31743070 100644 --- a/platform/javascript/export/export_plugin.h +++ b/platform/javascript/export/export_plugin.h @@ -87,7 +87,7 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform { icon.instantiate(); const String icon_path = String(GLOBAL_GET("application/config/icon")).strip_edges(); if (icon_path.is_empty() || ImageLoader::load_image(icon_path, icon) != OK) { - return EditorNode::get_singleton()->get_editor_theme()->get_icon("DefaultProjectIcon", "EditorIcons")->get_image(); + return EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("DefaultProjectIcon"), SNAME("EditorIcons"))->get_image(); } return icon; } diff --git a/platform/javascript/godot_js.h b/platform/javascript/godot_js.h index 15de8b5dc77b..2cb5c3025c9b 100644 --- a/platform/javascript/godot_js.h +++ b/platform/javascript/godot_js.h @@ -49,6 +49,8 @@ extern void godot_js_os_fs_sync(void (*p_callback)()); extern int godot_js_os_execute(const char *p_json); extern void godot_js_os_shell_open(const char *p_uri); extern int godot_js_os_hw_concurrency_get(); +extern int godot_js_pwa_cb(void (*p_callback)()); +extern int godot_js_pwa_update(); // Input extern void godot_js_input_mouse_button_cb(int (*p_callback)(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers)); diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp index 45aa68ce7ccf..c94630286281 100644 --- a/platform/javascript/http_client_javascript.cpp +++ b/platform/javascript/http_client_javascript.cpp @@ -87,6 +87,11 @@ Error HTTPClientJavaScript::request(Method p_method, const String &p_url, const ERR_FAIL_COND_V(port < 0, ERR_UNCONFIGURED); ERR_FAIL_COND_V(!p_url.begins_with("/"), ERR_INVALID_PARAMETER); + Error err = verify_headers(p_headers); + if (err) { + return err; + } + String url = (use_tls ? "https://" : "http://") + host + ":" + itos(port) + p_url; Vector keeper; Vector c_strings; diff --git a/platform/javascript/javascript_singleton.cpp b/platform/javascript/javascript_singleton.cpp index b10e8007c04d..77858bff0124 100644 --- a/platform/javascript/javascript_singleton.cpp +++ b/platform/javascript/javascript_singleton.cpp @@ -30,6 +30,7 @@ #include "api/javascript_singleton.h" #include "emscripten.h" +#include "os_javascript.h" extern "C" { extern void godot_js_os_download_buffer(const uint8_t *p_buf, int p_buf_size, const char *p_name, const char *p_mime); @@ -355,3 +356,10 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { void JavaScript::download_buffer(Vector p_arr, const String &p_name, const String &p_mime) { godot_js_os_download_buffer(p_arr.ptr(), p_arr.size(), p_name.utf8().get_data(), p_mime.utf8().get_data()); } + +bool JavaScript::pwa_needs_update() const { + return OS_JavaScript::get_singleton()->pwa_needs_update(); +} +Error JavaScript::pwa_update() { + return OS_JavaScript::get_singleton()->pwa_update(); +} diff --git a/platform/javascript/js/engine/config.js b/platform/javascript/js/engine/config.js index ba61b14eb7f7..2e5e1ed0d1a2 100644 --- a/platform/javascript/js/engine/config.js +++ b/platform/javascript/js/engine/config.js @@ -106,6 +106,13 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused- * @default */ experimentalVK: false, + /** + * The progressive web app service worker to install. + * @memberof EngineConfig + * @default + * @type {string} + */ + serviceWorker: '', /** * @ignore * @type {Array.} @@ -225,6 +232,8 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused- */ Config.prototype.update = function (opts) { const config = opts || {}; + // NOTE: We must explicitly pass the default, accessing it via + // the key will fail due to closure compiler renames. function parse(key, def) { if (typeof (config[key]) === 'undefined') { return def; @@ -247,6 +256,7 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused- this.persistentDrops = parse('persistentDrops', this.persistentDrops); this.experimentalVK = parse('experimentalVK', this.experimentalVK); this.focusCanvas = parse('focusCanvas', this.focusCanvas); + this.serviceWorker = parse('serviceWorker', this.serviceWorker); this.gdnativeLibs = parse('gdnativeLibs', this.gdnativeLibs); this.fileSizes = parse('fileSizes', this.fileSizes); this.args = parse('args', this.args); diff --git a/platform/javascript/js/engine/engine.js b/platform/javascript/js/engine/engine.js index 17a8df9e294e..d2ba595083e4 100644 --- a/platform/javascript/js/engine/engine.js +++ b/platform/javascript/js/engine/engine.js @@ -189,6 +189,9 @@ const Engine = (function () { preloader.preloadedFiles.length = 0; // Clear memory me.rtenv['callMain'](me.config.args); initPromise = null; + if (me.config.serviceWorker && 'serviceWorker' in navigator) { + navigator.serviceWorker.register(me.config.serviceWorker); + } resolve(); }); }); diff --git a/platform/javascript/js/libs/library_godot_input.js b/platform/javascript/js/libs/library_godot_input.js index 7a4d0d81260a..1e64c260f8d9 100644 --- a/platform/javascript/js/libs/library_godot_input.js +++ b/platform/javascript/js/libs/library_godot_input.js @@ -87,7 +87,7 @@ const GodotInputGamepads = { }, init: function (onchange) { - GodotEventListeners.samples = []; + GodotInputGamepads.samples = []; function add(pad) { const guid = GodotInputGamepads.get_guid(pad); const c_id = GodotRuntime.allocString(pad.id); diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js index 662d2154438c..12d06a8d5141 100644 --- a/platform/javascript/js/libs/library_godot_os.js +++ b/platform/javascript/js/libs/library_godot_os.js @@ -368,3 +368,58 @@ const GodotEventListeners = { }, }; mergeInto(LibraryManager.library, GodotEventListeners); + +const GodotPWA = { + + $GodotPWA__deps: ['$GodotRuntime', '$GodotEventListeners'], + $GodotPWA: { + hasUpdate: false, + + updateState: function (cb, reg) { + if (!reg) { + return; + } + if (!reg.active) { + return; + } + if (reg.waiting) { + GodotPWA.hasUpdate = true; + cb(); + } + GodotEventListeners.add(reg, 'updatefound', function () { + const installing = reg.installing; + GodotEventListeners.add(installing, 'statechange', function () { + if (installing.state === 'installed') { + GodotPWA.hasUpdate = true; + cb(); + } + }); + }); + }, + }, + + godot_js_pwa_cb__sig: 'vi', + godot_js_pwa_cb: function (p_update_cb) { + if ('serviceWorker' in navigator) { + const cb = GodotRuntime.get_func(p_update_cb); + navigator.serviceWorker.getRegistration().then(GodotPWA.updateState.bind(null, cb)); + } + }, + + godot_js_pwa_update__sig: 'i', + godot_js_pwa_update: function () { + if ('serviceWorker' in navigator && GodotPWA.hasUpdate) { + navigator.serviceWorker.getRegistration().then(function (reg) { + if (!reg || !reg.waiting) { + return; + } + reg.waiting.postMessage('update'); + }); + return 0; + } + return 1; + }, +}; + +autoAddDeps(GodotPWA, '$GodotPWA'); +mergeInto(LibraryManager.library, GodotPWA); diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index 39eea13ca10d..de5ca44f9d02 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -45,6 +45,7 @@ #include #include +#include "api/javascript_singleton.h" #include "godot_js.h" void OS_JavaScript::alert(const String &p_alert, const String &p_title) { @@ -203,6 +204,19 @@ void OS_JavaScript::file_access_close_callback(const String &p_file, int p_flags } } +void OS_JavaScript::update_pwa_state_callback() { + if (OS_JavaScript::get_singleton()) { + OS_JavaScript::get_singleton()->pwa_is_waiting = true; + } + if (JavaScript::get_singleton()) { + JavaScript::get_singleton()->emit_signal("pwa_update_available"); + } +} + +Error OS_JavaScript::pwa_update() { + return godot_js_pwa_update() ? FAILED : OK; +} + bool OS_JavaScript::is_userfs_persistent() const { return idb_available; } @@ -226,6 +240,8 @@ OS_JavaScript::OS_JavaScript() { godot_js_config_locale_get(locale_ptr, 16); setenv("LANG", locale_ptr, true); + godot_js_pwa_cb(&OS_JavaScript::update_pwa_state_callback); + if (AudioDriverJavaScript::is_available()) { #ifdef NO_THREADS audio_drivers.push_back(memnew(AudioDriverScriptProcessor)); diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 3404fe9dcf43..9e272f9aa186 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -45,11 +45,13 @@ class OS_JavaScript : public OS_Unix { bool idb_is_syncing = false; bool idb_available = false; bool idb_needs_sync = false; + bool pwa_is_waiting = false; static void main_loop_callback(); static void file_access_close_callback(const String &p_file, int p_flags); static void fs_sync_callback(); + static void update_pwa_state_callback(); protected: void initialize() override; @@ -65,6 +67,9 @@ class OS_JavaScript : public OS_Unix { // Override return type to make writing static callbacks less tedious. static OS_JavaScript *get_singleton(); + bool pwa_needs_update() const { return pwa_is_waiting; } + Error pwa_update(); + void initialize_joypads() override; MainLoop *get_main_loop() const override; diff --git a/platform/linuxbsd/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp index e9369fefdda8..b4ec7924f6a7 100644 --- a/platform/linuxbsd/crash_handler_linuxbsd.cpp +++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp @@ -33,7 +33,6 @@ #include "core/config/project_settings.h" #include "core/os/os.h" #include "core/version.h" -#include "core/version_hash.gen.h" #include "main/main.h" #ifdef DEBUG_ENABLED @@ -71,10 +70,10 @@ static void handle_crash(int sig) { } // Print the engine version just before, so that people are reminded to include the version in backtrace reports. - if (String(VERSION_HASH).length() != 0) { - fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); + if (String(VERSION_HASH).is_empty()) { + fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME); } else { - fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); + fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH); } fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); char **strings = backtrace_symbols(bt_buffer, size); diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index 0ce627ca4fb3..bf9c9b1766a3 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -33,6 +33,7 @@ #ifdef X11_ENABLED #include "core/config/project_settings.h" +#include "core/math/math_funcs.h" #include "core/string/print_string.h" #include "core/string/ustring.h" #include "detect_prime_x11.h" @@ -323,20 +324,21 @@ void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { //flush pending motion events _flush_mouse_motion(); - WindowData &main_window = windows[MAIN_WINDOW_ID]; + WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; + WindowData &window = windows[window_id]; if (XGrabPointer( - x11_display, main_window.x11_window, True, + x11_display, window.x11_window, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, - GrabModeAsync, GrabModeAsync, windows[MAIN_WINDOW_ID].x11_window, None, CurrentTime) != GrabSuccess) { + GrabModeAsync, GrabModeAsync, window.x11_window, None, CurrentTime) != GrabSuccess) { ERR_PRINT("NO GRAB"); } if (mouse_mode == MOUSE_MODE_CAPTURED) { - center.x = main_window.size.width / 2; - center.y = main_window.size.height / 2; + center.x = window.size.width / 2; + center.y = window.size.height / 2; - XWarpPointer(x11_display, None, main_window.x11_window, + XWarpPointer(x11_display, None, window.x11_window, 0, 0, 0, 0, (int)center.x, (int)center.y); Input::get_singleton()->set_mouse_position(center); @@ -358,7 +360,8 @@ void DisplayServerX11::mouse_warp_to_position(const Point2i &p_to) { if (mouse_mode == MOUSE_MODE_CAPTURED) { last_mouse_pos = p_to; } else { - XWarpPointer(x11_display, None, windows[MAIN_WINDOW_ID].x11_window, + WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; + XWarpPointer(x11_display, None, windows[window_id].x11_window, 0, 0, 0, 0, (int)p_to.x, (int)p_to.y); } } @@ -1052,6 +1055,67 @@ int DisplayServerX11::screen_get_dpi(int p_screen) const { return 96; } +float DisplayServerX11::screen_get_refresh_rate(int p_screen) const { + _THREAD_SAFE_METHOD_ + + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); + } + + //invalid screen? + ERR_FAIL_INDEX_V(p_screen, get_screen_count(), SCREEN_REFRESH_RATE_FALLBACK); + + //Use xrandr to get screen refresh rate. + if (xrandr_ext_ok) { + XRRScreenResources *screen_info = XRRGetScreenResources(x11_display, windows[MAIN_WINDOW_ID].x11_window); + if (screen_info) { + RRMode current_mode = 0; + xrr_monitor_info *monitors = nullptr; + + if (xrr_get_monitors) { + int count = 0; + monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count); + ERR_FAIL_INDEX_V(p_screen, count, SCREEN_REFRESH_RATE_FALLBACK); + } else { + ERR_PRINT("An error occured while trying to get the screen refresh rate."); + return SCREEN_REFRESH_RATE_FALLBACK; + } + + bool found_active_mode = false; + for (int crtc = 0; crtc < screen_info->ncrtc; crtc++) { // Loop through outputs to find which one is currently outputting. + XRRCrtcInfo *monitor_info = XRRGetCrtcInfo(x11_display, screen_info, screen_info->crtcs[crtc]); + if (monitor_info->x != monitors[p_screen].x || monitor_info->y != monitors[p_screen].y) { // If X and Y aren't the same as the monitor we're looking for, this isn't the right monitor. Continue. + continue; + } + + if (monitor_info->mode != None) { + current_mode = monitor_info->mode; + found_active_mode = true; + break; + } + } + + if (found_active_mode) { + for (int mode = 0; mode < screen_info->nmode; mode++) { + XRRModeInfo m_info = screen_info->modes[mode]; + if (m_info.id == current_mode) { + // Snap to nearest 0.01 to stay consistent with other platforms. + return Math::snapped((float)m_info.dotClock / ((float)m_info.hTotal * (float)m_info.vTotal), 0.01); + } + } + } + + ERR_PRINT("An error occured while trying to get the screen refresh rate."); // We should have returned the refresh rate by now. An error must have occured. + return SCREEN_REFRESH_RATE_FALLBACK; + } else { + ERR_PRINT("An error occured while trying to get the screen refresh rate."); + return SCREEN_REFRESH_RATE_FALLBACK; + } + } + ERR_PRINT("An error occured while trying to get the screen refresh rate."); + return SCREEN_REFRESH_RATE_FALLBACK; +} + bool DisplayServerX11::screen_is_touchscreen(int p_screen) const { _THREAD_SAFE_METHOD_ @@ -1154,6 +1218,24 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) { windows.erase(p_id); } +int64_t DisplayServerX11::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { + ERR_FAIL_COND_V(!windows.has(p_window), 0); + switch (p_handle_type) { + case DISPLAY_HANDLE: { + return (int64_t)x11_display; + } + case WINDOW_HANDLE: { + return (int64_t)windows[p_window].x11_window; + } + case WINDOW_VIEW: { + return 0; // Not supported. + } + default: { + return 0; + } + } +} + void DisplayServerX11::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; @@ -1314,8 +1396,9 @@ int DisplayServerX11::window_get_current_screen(WindowID p_window) const { void DisplayServerX11::gl_window_make_current(DisplayServer::WindowID p_window_id) { #if defined(GLES3_ENABLED) - if (gl_manager) + if (gl_manager) { gl_manager->window_make_current(p_window_id); + } #endif } @@ -1752,7 +1835,7 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { Hints hints; Atom property; hints.flags = 2; - hints.decorations = window_get_flag(WINDOW_FLAG_BORDERLESS, p_window) ? 0 : 1; + hints.decorations = wd.borderless ? 0 : 1; property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); if (property != None) { XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); @@ -1804,6 +1887,7 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); } break; + case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: case WINDOW_MODE_FULLSCREEN: { //Remove full-screen wd.fullscreen = false; @@ -1856,6 +1940,7 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); } break; + case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: case WINDOW_MODE_FULLSCREEN: { wd.last_position_before_fs = wd.position; @@ -2396,7 +2481,7 @@ Key DisplayServerX11::keyboard_get_keycode_from_physical(Key p_keycode) const { Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK; unsigned int xkeycode = KeyMappingX11::get_xlibcode(keycode_no_mod); KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, 0, 0); - if (xkeysym >= 'a' && xkeysym <= 'z') { + if (is_ascii_lower_case(xkeysym)) { xkeysym -= ('a' - 'A'); } @@ -3334,7 +3419,7 @@ void DisplayServerX11::process_events() { DEBUG_LOG_X11("[%u] FocusIn window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode); WindowData &wd = windows[window_id]; - + last_focused_window = window_id; wd.focused = true; if (wd.xic) { @@ -3534,9 +3619,9 @@ void DisplayServerX11::process_events() { // The X11 API requires filtering one-by-one through the motion // notify events, in order to figure out which event is the one // generated by warping the mouse pointer. - + WindowID focused_window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; while (true) { - if (mouse_mode == MOUSE_MODE_CAPTURED && event.xmotion.x == windows[MAIN_WINDOW_ID].size.width / 2 && event.xmotion.y == windows[MAIN_WINDOW_ID].size.height / 2) { + if (mouse_mode == MOUSE_MODE_CAPTURED && event.xmotion.x == windows[focused_window_id].size.width / 2 && event.xmotion.y == windows[focused_window_id].size.height / 2) { //this is likely the warp event since it was warped here center = Vector2(event.xmotion.x, event.xmotion.y); break; @@ -3611,9 +3696,8 @@ void DisplayServerX11::process_events() { // Reset to prevent lingering motion xi.relative_motion.x = 0; xi.relative_motion.y = 0; - if (mouse_mode == MOUSE_MODE_CAPTURED) { - pos = Point2i(windows[MAIN_WINDOW_ID].size.width / 2, windows[MAIN_WINDOW_ID].size.height / 2); + pos = Point2i(windows[focused_window_id].size.width / 2, windows[focused_window_id].size.height / 2); } Ref mm; diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index 8929f528d656..2d07361deb49 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -143,7 +143,7 @@ class DisplayServerX11 : public DisplayServer { bool borderless = false; bool resize_disabled = false; Vector2i last_position_before_fs; - bool focused = false; + bool focused = true; bool minimized = false; unsigned int focus_order = 0; @@ -151,6 +151,8 @@ class DisplayServerX11 : public DisplayServer { Map windows; + WindowID last_focused_window = INVALID_WINDOW_ID; + WindowID window_id_counter = MAIN_WINDOW_ID; WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect); @@ -301,6 +303,7 @@ class DisplayServerX11 : public DisplayServer { virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; #if defined(DBUS_ENABLED) @@ -316,6 +319,8 @@ class DisplayServerX11 : public DisplayServer { virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override; + virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override; + virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override; virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override; diff --git a/platform/linuxbsd/gl_manager_x11.cpp b/platform/linuxbsd/gl_manager_x11.cpp index 1721d0e0b37c..d3fb1d670543 100644 --- a/platform/linuxbsd/gl_manager_x11.cpp +++ b/platform/linuxbsd/gl_manager_x11.cpp @@ -68,8 +68,9 @@ static int ctxErrorHandler(Display *dpy, XErrorEvent *ev) { int GLManager_X11::_find_or_create_display(Display *p_x11_display) { for (unsigned int n = 0; n < _displays.size(); n++) { const GLDisplay &d = _displays[n]; - if (d.x11_display == p_x11_display) + if (d.x11_display == p_x11_display) { return n; + } } // create @@ -82,8 +83,7 @@ int GLManager_X11::_find_or_create_display(Display *p_x11_display) { GLDisplay &d = _displays[new_display_id]; d.context = memnew(GLManager_X11_Private); - ; - d.context->glx_context = 0; + d.context->glx_context = nullptr; //Error err = _create_context(d); _create_context(d); @@ -124,7 +124,7 @@ Error GLManager_X11::_create_context(GLDisplay &gl_display) { }; int fbcount; - GLXFBConfig fbconfig = 0; + GLXFBConfig fbconfig = nullptr; XVisualInfo *vi = nullptr; gl_display.x_swa.event_mask = StructureNotifyMask; @@ -137,8 +137,9 @@ Error GLManager_X11::_create_context(GLDisplay &gl_display) { for (int i = 0; i < fbcount; i++) { vi = (XVisualInfo *)glXGetVisualFromFBConfig(x11_display, fbc[i]); - if (!vi) + if (!vi) { continue; + } XRenderPictFormat *pict_format = XRenderFindVisualFormat(x11_display, vi->visual); if (!pict_format) { @@ -262,22 +263,26 @@ void GLManager_X11::window_destroy(DisplayServer::WindowID p_window_id) { } void GLManager_X11::release_current() { - if (!_current_window) + if (!_current_window) { return; + } glXMakeCurrent(_x_windisp.x11_display, None, nullptr); } void GLManager_X11::window_make_current(DisplayServer::WindowID p_window_id) { - if (p_window_id == -1) + if (p_window_id == -1) { return; + } GLWindow &win = _windows[p_window_id]; - if (!win.in_use) + if (!win.in_use) { return; + } // noop - if (&win == _current_window) + if (&win == _current_window) { return; + } const GLDisplay &disp = get_display(win.gldisplay_id); @@ -287,8 +292,9 @@ void GLManager_X11::window_make_current(DisplayServer::WindowID p_window_id) { } void GLManager_X11::make_current() { - if (!_current_window) + if (!_current_window) { return; + } if (!_current_window->in_use) { WARN_PRINT("current window not in use!"); return; @@ -301,8 +307,9 @@ void GLManager_X11::swap_buffers() { // NO NEED TO CALL SWAP BUFFERS for each window... // see https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glXSwapBuffers.xml - if (!_current_window) + if (!_current_window) { return; + } if (!_current_window->in_use) { WARN_PRINT("current window not in use!"); return; @@ -335,19 +342,23 @@ void GLManager_X11::set_use_vsync(bool p_use) { } // we need an active window to get a display to set the vsync - if (!_current_window) + if (!_current_window) { return; + } const GLDisplay &disp = get_current_display(); if (!setup) { setup = true; String extensions = glXQueryExtensionsString(disp.x11_display, DefaultScreen(disp.x11_display)); - if (extensions.find("GLX_EXT_swap_control") != -1) + if (extensions.find("GLX_EXT_swap_control") != -1) { glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalEXT"); - if (extensions.find("GLX_MESA_swap_control") != -1) + } + if (extensions.find("GLX_MESA_swap_control") != -1) { glXSwapIntervalMESA = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalMESA"); - if (extensions.find("GLX_SGI_swap_control") != -1) + } + if (extensions.find("GLX_SGI_swap_control") != -1) { glXSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalSGI"); + } } int val = p_use ? 1 : 0; if (glXSwapIntervalMESA) { @@ -357,8 +368,9 @@ void GLManager_X11::set_use_vsync(bool p_use) { } else if (glXSwapIntervalEXT) { GLXDrawable drawable = glXGetCurrentDrawable(); glXSwapIntervalEXT(disp.x11_display, drawable, val); - } else + } else { return; + } use_vsync = p_use; } diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp index 5eda42fea650..8e963238e31e 100644 --- a/platform/linuxbsd/joypad_linux.cpp +++ b/platform/linuxbsd/joypad_linux.cpp @@ -333,8 +333,9 @@ void JoypadLinux::open_joypad(const char *p_path) { } // Check if the device supports basic gamepad events - if (!(test_bit(EV_KEY, evbit) && test_bit(EV_ABS, evbit) && - test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit))) { + bool has_abs_left = (test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit)); + bool has_abs_right = (test_bit(ABS_RX, absbit) && test_bit(ABS_RY, absbit)); + if (!(test_bit(EV_KEY, evbit) && test_bit(EV_ABS, evbit) && (has_abs_left || has_abs_right))) { close(fd); return; } diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index b5f127bb16d7..e95a8656367d 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -448,7 +448,7 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { // Create needed directories for decided trash can location. { - DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + DirAccessRef dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); Error err = dir_access->make_dir_recursive(trash_path); // Issue an error if trash can is not created properly. @@ -457,7 +457,6 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\"/files"); err = dir_access->make_dir_recursive(trash_path + "/info"); ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\"/info"); - memdelete(dir_access); } // The trash can is successfully created, now we check that we don't exceed our file name length limit. @@ -497,16 +496,15 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { String trash_info = "[Trash Info]\nPath=" + p_path.uri_encode() + "\nDeletionDate=" + timestamp + "\n"; { Error err; - FileAccess *file = FileAccess::open(trash_path + "/info/" + file_name + ".trashinfo", FileAccess::WRITE, &err); + FileAccessRef file = FileAccess::open(trash_path + "/info/" + file_name + ".trashinfo", FileAccess::WRITE, &err); ERR_FAIL_COND_V_MSG(err != OK, err, "Can't create trashinfo file:" + trash_path + "/info/" + file_name + ".trashinfo"); file->store_string(trash_info); file->close(); // Rename our resource before moving it to the trash can. - DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + DirAccessRef dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); err = dir_access->rename(p_path, p_path.get_base_dir() + "/" + file_name); ERR_FAIL_COND_V_MSG(err != OK, err, "Can't rename file \"" + p_path + "\""); - memdelete(dir_access); } // Move the given resource to the trash can. diff --git a/platform/osx/SCsub b/platform/osx/SCsub index 8ba106d1c2d9..d72a75af043e 100644 --- a/platform/osx/SCsub +++ b/platform/osx/SCsub @@ -6,14 +6,21 @@ from platform_methods import run_in_subprocess import platform_osx_builders files = [ - "crash_handler_osx.mm", "os_osx.mm", + "godot_application.mm", + "godot_application_delegate.mm", + "crash_handler_osx.mm", + "osx_terminal_logger.mm", "display_server_osx.mm", + "godot_content_view.mm", + "godot_window_delegate.mm", + "godot_window.mm", + "key_mapping_osx.mm", "godot_main_osx.mm", "dir_access_osx.mm", "joypad_osx.cpp", "vulkan_context_osx.mm", - "gl_manager_osx.mm", + "gl_manager_osx_legacy.mm", ] prog = env.add_program("#bin/godot", files) diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm index 16be941308e3..3e640b3bf315 100644 --- a/platform/osx/crash_handler_osx.mm +++ b/platform/osx/crash_handler_osx.mm @@ -33,7 +33,6 @@ #include "core/config/project_settings.h" #include "core/os/os.h" #include "core/version.h" -#include "core/version_hash.gen.h" #include "main/main.h" #include @@ -94,10 +93,10 @@ static void handle_crash(int sig) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); // Print the engine version just before, so that people are reminded to include the version in backtrace reports. - if (String(VERSION_HASH).length() != 0) { - fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); + if (String(VERSION_HASH).is_empty()) { + fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME); } else { - fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); + fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH); } fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); char **strings = backtrace_symbols(bt_buffer, size); diff --git a/platform/osx/detect.py b/platform/osx/detect.py index c67791b340fe..0ff93bedb400 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -78,14 +78,16 @@ def configure(env): env["osxcross"] = True if env["arch"] == "arm64": - print("Building for macOS 10.15+, platform arm64.") - env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15"]) - env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15"]) + print("Building for macOS 11.0+, platform arm64.") + env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.0"]) + env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.0"]) else: print("Building for macOS 10.12+, platform x86_64.") env.Append(CCFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) env.Append(LINKFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) + env.Append(CCFLAGS=["-fobjc-arc"]) + if not "osxcross" in env: # regular native build if env["macports_clang"] != "no": mpprefix = os.environ.get("MACPORTS_PREFIX", "/opt/local") @@ -188,6 +190,8 @@ def configure(env): env.Append(CCFLAGS=["-Wno-deprecated-declarations"]) # Disable deprecation warnings env.Append(LINKFLAGS=["-framework", "OpenGL"]) + env.Append(LINKFLAGS=["-rpath", "@executable_path/../Frameworks", "-rpath", "@executable_path"]) + if env["vulkan"]: env.Append(CPPDEFINES=["VULKAN_ENABLED"]) env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "QuartzCore", "-framework", "IOSurface"]) diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h index d609a84e5067..eaf03d298eb6 100644 --- a/platform/osx/display_server_osx.h +++ b/platform/osx/display_server_osx.h @@ -37,13 +37,13 @@ #include "servers/display_server.h" #if defined(GLES3_ENABLED) -#include "gl_manager_osx.h" -#endif +#include "gl_manager_osx_legacy.h" +#endif // GLES3_ENABLED #if defined(VULKAN_ENABLED) #include "drivers/vulkan/rendering_device_vulkan.h" #include "platform/osx/vulkan_context_osx.h" -#endif +#endif // VULKAN_ENABLED #include #include @@ -59,27 +59,8 @@ class DisplayServerOSX : public DisplayServer { _THREAD_SAFE_CLASS_ public: - void _send_event(NSEvent *p_event); - NSMenu *_get_dock_menu() const; - void _menu_callback(id p_sender); - -#if defined(GLES3_ENABLED) - GLManager_OSX *gl_manager = nullptr; -#endif -#if defined(VULKAN_ENABLED) - VulkanContextOSX *context_vulkan = nullptr; - RenderingDeviceVulkan *rendering_device_vulkan = nullptr; -#endif - - const NSMenu *_get_menu_root(const String &p_menu_root) const; - NSMenu *_get_menu_root(const String &p_menu_root); - - NSMenu *apple_menu = nullptr; - NSMenu *dock_menu = nullptr; - Map submenu; - struct KeyEvent { - WindowID window_id; + WindowID window_id = INVALID_WINDOW_ID; unsigned int osx_state = false; bool pressed = false; bool echo = false; @@ -89,20 +70,6 @@ class DisplayServerOSX : public DisplayServer { uint32_t unicode = 0; }; - struct WarpEvent { - NSTimeInterval timestamp; - NSPoint delta; - }; - - List warp_events; - NSTimeInterval last_warp = 0; - bool ignore_warp = false; - - float display_max_scale = 1.f; - - Vector key_event_buffer; - int key_event_pos; - struct WindowData { id window_delegate; id window_object; @@ -116,8 +83,6 @@ class DisplayServerOSX : public DisplayServer { Size2i max_size; Size2i size; - bool mouse_down_control = false; - bool im_active = false; Size2i im_position; @@ -140,47 +105,102 @@ class DisplayServerOSX : public DisplayServer { bool no_focus = false; }; +private: +#if defined(GLES3_ENABLED) + GLManager_OSX *gl_manager = nullptr; +#endif +#if defined(VULKAN_ENABLED) + VulkanContextOSX *context_vulkan = nullptr; + RenderingDeviceVulkan *rendering_device_vulkan = nullptr; +#endif + String rendering_driver; + + NSMenu *apple_menu = nullptr; + NSMenu *dock_menu = nullptr; + Map submenu; + + struct WarpEvent { + NSTimeInterval timestamp; + NSPoint delta; + }; + List warp_events; + NSTimeInterval last_warp = 0; + bool ignore_warp = false; + + Vector key_event_buffer; + int key_event_pos = 0; + Point2i im_selection; String im_text; - Map windows; + CGEventSourceRef event_source; + MouseMode mouse_mode = MOUSE_MODE_VISIBLE; + MouseButton last_button_state = MouseButton::NONE; + + bool drop_events = false; + bool in_dispatch_input_event = false; + + struct LayoutInfo { + String name; + String code; + }; + Vector kbd_layouts; + int current_layout = 0; + bool keyboard_layout_dirty = true; + WindowID last_focused_window = INVALID_WINDOW_ID; WindowID window_id_counter = MAIN_WINDOW_ID; + float display_max_scale = 1.f; + Point2i origin; + bool displays_arrangement_dirty = true; - WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect); - void _update_window(WindowData p_wd); - void _send_window_event(const WindowData &wd, WindowEvent p_event); - static void _dispatch_input_events(const Ref &p_event); - void _dispatch_input_event(const Ref &p_event); - WindowID _find_window_id(id p_window); + CursorShape cursor_shape = CURSOR_ARROW; + NSCursor *cursors[CURSOR_MAX]; + Map> cursors_cache; + + Map windows; + const NSMenu *_get_menu_root(const String &p_menu_root) const; + NSMenu *_get_menu_root(const String &p_menu_root); + + WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect); + void _update_window_style(WindowData p_wd); void _set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window); + void _update_displays_arrangement(); Point2i _get_screens_origin() const; Point2i _get_native_screen_position(int p_screen) const; + static void _displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info); + static void _dispatch_input_events(const Ref &p_event); + void _dispatch_input_event(const Ref &p_event); void _push_input(const Ref &p_event); void _process_key_events(); - void _release_pressed_events(); + void _update_keyboard_layouts(); + static void _keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info); - String rendering_driver; + static NSCursor *_cursor_from_selector(SEL p_selector, SEL p_fallback = nil); - id autoreleasePool; - CGEventSourceRef eventSource; +public: + NSMenu *get_dock_menu() const; + void menu_callback(id p_sender); - CursorShape cursor_shape; - NSCursor *cursors[CURSOR_MAX]; - Map> cursors_cache; + bool has_window(WindowID p_window) const; + WindowData &get_window(WindowID p_window); - MouseMode mouse_mode; - Point2i last_mouse_pos; - MouseButton last_button_state = MouseButton::NONE; + void send_event(NSEvent *p_event); + void send_window_event(const WindowData &p_wd, WindowEvent p_event); + void release_pressed_events(); + void get_key_modifier_state(unsigned int p_osx_state, Ref r_state) const; + void update_mouse_pos(WindowData &p_wd, NSPoint p_location_in_window); + void push_to_key_event_buffer(const KeyEvent &p_event); + void update_im_text(const Point2i &p_selection, const String &p_text); + void set_last_focused_window(WindowID p_window); - bool window_focused; - bool drop_events; - bool in_dispatch_input_event = false; + void window_update(WindowID p_window); + void window_destroy(WindowID p_window); + void window_resize(WindowID p_window, int p_width, int p_height); -public: virtual bool has_feature(Feature p_feature) const override; virtual String get_name() const override; @@ -214,8 +234,10 @@ class DisplayServerOSX : public DisplayServer { virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; + bool update_mouse_wrap(WindowData &p_wd, NSPoint &r_delta, NSPoint &r_mpos, NSTimeInterval p_timestamp); virtual void mouse_warp_to_position(const Point2i &p_to) override; virtual Point2i mouse_get_position() const override; + void mouse_set_button_state(MouseButton p_state); virtual MouseButton mouse_get_button_state() const override; virtual void clipboard_set(const String &p_text) override; @@ -228,6 +250,7 @@ class DisplayServerOSX : public DisplayServer { virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual float screen_get_max_scale() const override; virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Vector get_window_list() const override; @@ -282,6 +305,8 @@ class DisplayServerOSX : public DisplayServer { virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override; + virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override; + virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override; virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void gl_window_make_current(DisplayServer::WindowID p_window_id) override; @@ -292,6 +317,7 @@ class DisplayServerOSX : public DisplayServer { virtual Point2i ime_get_selection() const override; virtual String ime_get_text() const override; + void cursor_update_shape(); virtual void cursor_set_shape(CursorShape p_shape) override; virtual CursorShape cursor_get_shape() const override; virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override; diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 1340aad9b20c..2691664b96a8 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -30,6 +30,11 @@ #include "display_server_osx.h" +#include "godot_content_view.h" +#include "godot_menu_item.h" +#include "godot_window.h" +#include "godot_window_delegate.h" +#include "key_mapping_osx.h" #include "os_osx.h" #include "core/io/marshalls.h" @@ -47,217 +52,122 @@ #if defined(GLES3_ENABLED) #include "drivers/gles3/rasterizer_gles3.h" - -#import #endif #if defined(VULKAN_ENABLED) #include "servers/rendering/renderer_rd/renderer_compositor_rd.h" - -#include -#endif - -#ifndef NSAppKitVersionNumber10_14 -#define NSAppKitVersionNumber10_14 1671 #endif -#define DS_OSX ((DisplayServerOSX *)(DisplayServerOSX::get_singleton())) - -static bool ignore_momentum_scroll = false; - -static void _get_key_modifier_state(unsigned int p_osx_state, Ref r_state) { - r_state->set_shift_pressed((p_osx_state & NSEventModifierFlagShift)); - r_state->set_ctrl_pressed((p_osx_state & NSEventModifierFlagControl)); - r_state->set_alt_pressed((p_osx_state & NSEventModifierFlagOption)); - r_state->set_meta_pressed((p_osx_state & NSEventModifierFlagCommand)); -} - -static Vector2i _get_mouse_pos(DisplayServerOSX::WindowData &p_wd, NSPoint p_locationInWindow) { - const NSRect contentRect = [p_wd.window_view frame]; - const float scale = DS_OSX->screen_get_max_scale(); - p_wd.mouse_pos.x = p_locationInWindow.x * scale; - p_wd.mouse_pos.y = (contentRect.size.height - p_locationInWindow.y) * scale; - DS_OSX->last_mouse_pos = p_wd.mouse_pos; - Input::get_singleton()->set_mouse_position(p_wd.mouse_pos); - return p_wd.mouse_pos; -} - -static void _push_to_key_event_buffer(const DisplayServerOSX::KeyEvent &p_event) { - Vector &buffer = DS_OSX->key_event_buffer; - if (DS_OSX->key_event_pos >= buffer.size()) { - buffer.resize(1 + DS_OSX->key_event_pos); - } - buffer.write[DS_OSX->key_event_pos++] = p_event; -} - -static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { - if ([NSCursor respondsToSelector:selector]) { - id object = [NSCursor performSelector:selector]; - if ([object isKindOfClass:[NSCursor class]]) { - return object; +const NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) const { + const NSMenu *menu = nullptr; + if (p_menu_root == "") { + // Main menu. + menu = [NSApp mainMenu]; + } else if (p_menu_root.to_lower() == "_dock") { + // macOS dock menu. + menu = dock_menu; + } else { + // Submenu. + if (submenu.has(p_menu_root)) { + menu = submenu[p_menu_root]; } } - if (fallback) { - // Fallback should be a reasonable default, no need to check. - return [NSCursor performSelector:fallback]; - } - return [NSCursor arrowCursor]; -} - -/*************************************************************************/ -/* GlobalMenuItem */ -/*************************************************************************/ - -@interface GlobalMenuItem : NSObject { -@public - Callable callback; - Variant meta; - bool checkable; -} - -@end - -@implementation GlobalMenuItem -@end - -/*************************************************************************/ -/* GodotWindowDelegate */ -/*************************************************************************/ - -@interface GodotWindowDelegate : NSObject { - DisplayServerOSX::WindowID window_id; -} - -- (void)windowWillClose:(NSNotification *)notification; -- (void)setWindowID:(DisplayServerOSX::WindowID)wid; - -@end - -@implementation GodotWindowDelegate - -- (void)setWindowID:(DisplayServerOSX::WindowID)wid { - window_id = wid; -} - -- (BOOL)windowShouldClose:(id)sender { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return YES; + if (menu == apple_menu) { + // Do not allow to change Apple menu. + return nullptr; } - DS_OSX->_send_window_event(DS_OSX->windows[window_id], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); - return NO; + return menu; } -- (void)windowWillClose:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return; - } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - while (wd.transient_children.size()) { - DS_OSX->window_set_transient(wd.transient_children.front()->get(), DisplayServerOSX::INVALID_WINDOW_ID); - } - - if (wd.transient_parent != DisplayServerOSX::INVALID_WINDOW_ID) { - DS_OSX->window_set_transient(window_id, DisplayServerOSX::INVALID_WINDOW_ID); - } - -#if defined(GLES3_ENABLED) - if (DS_OSX->gl_manager) { - DS_OSX->gl_manager->window_destroy(window_id); - } -#endif -#ifdef VULKAN_ENABLED - if (DS_OSX->context_vulkan) { - DS_OSX->context_vulkan->window_destroy(window_id); +NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) { + NSMenu *menu = nullptr; + if (p_menu_root == "") { + // Main menu. + menu = [NSApp mainMenu]; + } else if (p_menu_root.to_lower() == "_dock") { + // macOS dock menu. + menu = dock_menu; + } else { + // Submenu. + if (!submenu.has(p_menu_root)) { + NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]]; + submenu[p_menu_root] = n_menu; + } + menu = submenu[p_menu_root]; } -#endif - - DS_OSX->windows.erase(window_id); -} - -- (void)windowDidEnterFullScreen:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return; + if (menu == apple_menu) { + // Do not allow to change Apple menu. + return nullptr; } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - wd.fullscreen = true; - - [wd.window_object setContentMinSize:NSMakeSize(0, 0)]; - [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; - // Force window resize event. - [self windowDidResize:notification]; + return menu; } -- (void)windowDidExitFullScreen:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return; - } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - wd.fullscreen = false; - - const float scale = DS_OSX->screen_get_max_scale(); - if (wd.min_size != Size2i()) { - Size2i size = wd.min_size / scale; - [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)]; - } - if (wd.max_size != Size2i()) { - Size2i size = wd.max_size / scale; - [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; - } - - if (wd.resize_disabled) { - [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; - } - - if (wd.on_top) { - [wd.window_object setLevel:NSFloatingWindowLevel]; - } - // Force window resize event. - [self windowDidResize:notification]; -} +DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect) { + WindowID id; + const float scale = screen_get_max_scale(); + { + WindowData wd; -- (void)windowDidChangeBackingProperties:(NSNotification *)notification { - if (!DisplayServerOSX::get_singleton()) { - return; - } + wd.window_delegate = [[GodotWindowDelegate alloc] init]; + ERR_FAIL_COND_V_MSG(wd.window_delegate == nil, INVALID_WINDOW_ID, "Can't create a window delegate"); + [wd.window_delegate setWindowID:window_id_counter]; - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + Point2i position = p_rect.position; + // OS X native y-coordinate relative to _get_screens_origin() is negative, + // Godot passes a positive value. + position.y *= -1; + position += _get_screens_origin(); - CGFloat newBackingScaleFactor = [wd.window_object backingScaleFactor]; - CGFloat oldBackingScaleFactor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue]; + // initWithContentRect uses bottom-left corner of the window’s frame as origin. + wd.window_object = [[GodotWindow alloc] + initWithContentRect:NSMakeRect(position.x / scale, (position.y - p_rect.size.height) / scale, p_rect.size.width / scale, p_rect.size.height / scale) + styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable + backing:NSBackingStoreBuffered + defer:NO]; + ERR_FAIL_COND_V_MSG(wd.window_object == nil, INVALID_WINDOW_ID, "Can't create a window"); + [wd.window_object setWindowID:window_id_counter]; - if (newBackingScaleFactor != oldBackingScaleFactor) { - //Set new display scale and window size - const float scale = DS_OSX->screen_get_max_scale(); - const NSRect contentRect = [wd.window_view frame]; + wd.window_view = [[GodotContentView alloc] init]; + ERR_FAIL_COND_V_MSG(wd.window_view == nil, INVALID_WINDOW_ID, "Can't create a window view"); + [wd.window_view setWindowID:window_id_counter]; + [wd.window_view setWantsLayer:TRUE]; - wd.size.width = contentRect.size.width * scale; - wd.size.height = contentRect.size.height * scale; + [wd.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; + [wd.window_object setContentView:wd.window_view]; + [wd.window_object setDelegate:wd.window_delegate]; + [wd.window_object setAcceptsMouseMovedEvents:YES]; + [wd.window_object setRestorable:NO]; + [wd.window_object setColorSpace:[NSColorSpace sRGBColorSpace]]; - DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_DPI_CHANGE); + if ([wd.window_object respondsToSelector:@selector(setTabbingMode:)]) { + [wd.window_object setTabbingMode:NSWindowTabbingModeDisallowed]; + } CALayer *layer = [wd.window_view layer]; if (layer) { layer.contentsScale = scale; } - //Force window resize event - [self windowDidResize:notification]; +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + Error err = context_vulkan->window_create(window_id_counter, p_vsync_mode, wd.window_view, p_rect.size.width, p_rect.size.height); + ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan context"); + } +#endif +#if defined(GLES3_ENABLED) + if (gl_manager) { + Error err = gl_manager->window_create(window_id_counter, wd.window_view, p_rect.size.width, p_rect.size.height); + ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL context"); + } +#endif + id = window_id_counter++; + windows[id] = wd; } -} -- (void)windowDidResize:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return; - } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + WindowData &wd = windows[id]; + window_set_mode(p_mode, id); const NSRect contentRect = [wd.window_view frame]; - - const float scale = DS_OSX->screen_get_max_scale(); wd.size.width = contentRect.size.width * scale; wd.size.height = contentRect.size.height * scale; @@ -267,1201 +177,447 @@ - (void)windowDidResize:(NSNotification *)notification { } #if defined(GLES3_ENABLED) - if (DS_OSX->gl_manager) { - DS_OSX->gl_manager->window_resize(window_id, wd.size.width, wd.size.height); + if (gl_manager) { + gl_manager->window_resize(id, wd.size.width, wd.size.height); } #endif #if defined(VULKAN_ENABLED) - if (DS_OSX->context_vulkan) { - DS_OSX->context_vulkan->window_resize(window_id, wd.size.width, wd.size.height); + if (context_vulkan) { + context_vulkan->window_resize(id, wd.size.width, wd.size.height); } #endif - if (!wd.rect_changed_callback.is_null()) { - Variant size = Rect2i(DS_OSX->window_get_position(window_id), DS_OSX->window_get_size(window_id)); - Variant *sizep = &size; - Variant ret; - Callable::CallError ce; - wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); - } + return id; } -- (void)windowDidMove:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return; - } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - DS_OSX->_release_pressed_events(); +void DisplayServerOSX::_update_window_style(WindowData p_wd) { + bool borderless_full = false; - if (!wd.rect_changed_callback.is_null()) { - Variant size = Rect2i(DS_OSX->window_get_position(window_id), DS_OSX->window_get_size(window_id)); - Variant *sizep = &size; - Variant ret; - Callable::CallError ce; - wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); - } -} + if (p_wd.borderless) { + NSRect frameRect = [p_wd.window_object frame]; + NSRect screenRect = [[p_wd.window_object screen] frame]; -- (void)windowDidBecomeKey:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return; + // Check if our window covers up the screen. + if (frameRect.origin.x <= screenRect.origin.x && frameRect.origin.y <= frameRect.origin.y && + frameRect.size.width >= screenRect.size.width && frameRect.size.height >= screenRect.size.height) { + borderless_full = true; + } } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - if (DS_OSX->mouse_mode == DisplayServer::MOUSE_MODE_CAPTURED) { - const NSRect contentRect = [wd.window_view frame]; - NSRect pointInWindowRect = NSMakeRect(contentRect.size.width / 2, contentRect.size.height / 2, 0, 0); - NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; - CGPoint lMouseWarpPos = { pointOnScreen.x, CGDisplayBounds(CGMainDisplayID()).size.height - pointOnScreen.y }; - CGWarpMouseCursorPosition(lMouseWarpPos); + if (borderless_full) { + // If the window covers up the screen set the level to above the main menu and hide on deactivate. + [p_wd.window_object setLevel:NSMainMenuWindowLevel + 1]; + [p_wd.window_object setHidesOnDeactivate:YES]; } else { - _ALLOW_DISCARD_ _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); - Input::get_singleton()->set_mouse_position(wd.mouse_pos); - } - - DS_OSX->window_focused = true; - DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN); -} - -- (void)windowDidResignKey:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return; + // Reset these when our window is not a borderless window that covers up the screen. + if (p_wd.on_top && !p_wd.fullscreen) { + [p_wd.window_object setLevel:NSFloatingWindowLevel]; + } else { + [p_wd.window_object setLevel:NSNormalWindowLevel]; + } + [p_wd.window_object setHidesOnDeactivate:NO]; } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - DS_OSX->window_focused = false; - - DS_OSX->_release_pressed_events(); - DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT); } -- (void)windowDidMiniaturize:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return; - } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - DS_OSX->window_focused = false; - - DS_OSX->_release_pressed_events(); - DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT); -} +void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window) { + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; -- (void)windowDidDeminiaturize:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + if (!OS::get_singleton()->is_layered_allowed()) { return; } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - DS_OSX->window_focused = true; - DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN); -} - -@end - -/*************************************************************************/ -/* GodotContentView */ -/*************************************************************************/ - + if (wd.layered_window != p_enabled) { + if (p_enabled) { + [wd.window_object setBackgroundColor:[NSColor clearColor]]; + [wd.window_object setOpaque:NO]; + [wd.window_object setHasShadow:NO]; + CALayer *layer = [wd.window_view layer]; + if (layer) { + [layer setBackgroundColor:[NSColor clearColor].CGColor]; + [layer setOpaque:NO]; + } #if defined(GLES3_ENABLED) -@interface GodotContentView : NSOpenGLView { -#else -@interface GodotContentView : NSView { -#endif - - DisplayServerOSX::WindowID window_id; - NSTrackingArea *trackingArea; - NSMutableAttributedString *markedText; - bool imeInputEventInProgress; -} - -- (void)cancelComposition; -- (CALayer *)makeBackingLayer; -- (BOOL)wantsUpdateLayer; -- (void)updateLayer; -- (void)setWindowID:(DisplayServerOSX::WindowID)wid; - -@end - -@implementation GodotContentView - -- (void)setWindowID:(DisplayServerOSX::WindowID)wid { - window_id = wid; -} - -+ (void)initialize { - if (self == [GodotContentView class]) { - // nothing left to do here at the moment.. - } -} - -- (CALayer *)makeBackingLayer { -#if defined(VULKAN_ENABLED) - if (DS_OSX->context_vulkan) { - CALayer *layer = [[CAMetalLayer class] layer]; - return layer; - } + if (gl_manager) { + gl_manager->window_set_per_pixel_transparency_enabled(p_window, true); + } #endif - return [super makeBackingLayer]; -} - -- (void)updateLayer { + wd.layered_window = true; + } else { + [wd.window_object setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1]]; + [wd.window_object setOpaque:YES]; + [wd.window_object setHasShadow:YES]; + CALayer *layer = [wd.window_view layer]; + if (layer) { + [layer setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1].CGColor]; + [layer setOpaque:YES]; + } #if defined(GLES3_ENABLED) - if (DS_OSX->gl_manager) { - DS_OSX->gl_manager->window_update(window_id); - } + if (gl_manager) { + gl_manager->window_set_per_pixel_transparency_enabled(p_window, false); + } #endif -#if defined(VULKAN_ENABLED) - if (DS_OSX->context_vulkan) { - [super updateLayer]; + wd.layered_window = false; + } + NSRect frameRect = [wd.window_object frame]; + [wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:YES]; + [wd.window_object setFrame:frameRect display:YES]; } -#endif } -- (BOOL)wantsUpdateLayer { - return YES; -} - -- (id)init { - self = [super init]; - trackingArea = nil; - imeInputEventInProgress = false; - [self updateTrackingAreas]; -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 - [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; -#else - [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; -#endif - markedText = [[NSMutableAttributedString alloc] init]; - return self; -} - -- (void)dealloc { - [trackingArea release]; - [markedText release]; - [super dealloc]; -} - -static const NSRange kEmptyRange = { NSNotFound, 0 }; - -- (BOOL)hasMarkedText { - return (markedText.length > 0); -} - -- (NSRange)markedRange { - return NSMakeRange(0, markedText.length); -} - -- (NSRange)selectedRange { - return kEmptyRange; -} - -- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange { - if ([aString isKindOfClass:[NSAttributedString class]]) { - [markedText initWithAttributedString:aString]; - } else { - [markedText initWithString:aString]; - } - if (markedText.length == 0) { - [self unmarkText]; - return; - } +void DisplayServerOSX::_update_displays_arrangement() { + origin = Point2i(); - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - if (wd.im_active) { - imeInputEventInProgress = true; - DS_OSX->im_text.parse_utf8([[markedText mutableString] UTF8String]); - DS_OSX->im_selection = Point2i(selectedRange.location, selectedRange.length); - - OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); - } -} - -- (void)doCommandBySelector:(SEL)aSelector { - if ([self respondsToSelector:aSelector]) { - [self performSelector:aSelector]; + for (int i = 0; i < get_screen_count(); i++) { + Point2i position = _get_native_screen_position(i); + if (position.x < origin.x) { + origin.x = position.x; + } + if (position.y > origin.y) { + origin.y = position.y; + } } + displays_arrangement_dirty = false; } -- (void)unmarkText { - imeInputEventInProgress = false; - [[markedText mutableString] setString:@""]; - - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - if (wd.im_active) { - DS_OSX->im_text = String(); - DS_OSX->im_selection = Point2i(); +Point2i DisplayServerOSX::_get_screens_origin() const { + // Returns the native top-left screen coordinate of the smallest rectangle + // that encompasses all screens. Needed in get_screen_position(), + // window_get_position, and window_set_position() + // to convert between OS X native screen coordinates and the ones expected by Godot. - OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); + if (displays_arrangement_dirty) { + const_cast(this)->_update_displays_arrangement(); } -} -- (NSArray *)validAttributesForMarkedText { - return [NSArray array]; -} - -- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { - return nil; -} - -- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint { - return 0; -} - -- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { - ERR_FAIL_COND_V(!DS_OSX->windows.has(window_id), NSMakeRect(0, 0, 0, 0)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - const NSRect contentRect = [wd.window_view frame]; - const float scale = DS_OSX->screen_get_max_scale(); - NSRect pointInWindowRect = NSMakeRect(wd.im_position.x / scale, contentRect.size.height - (wd.im_position.y / scale) - 1, 0, 0); - NSPoint pointOnScreen = [wd.window_object convertRectToScreen:pointInWindowRect].origin; - - return NSMakeRect(pointOnScreen.x, pointOnScreen.y, 0, 0); -} - -- (void)cancelComposition { - [self unmarkText]; - NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext]; - [currentInputContext discardMarkedText]; -} - -- (void)insertText:(id)aString { - [self insertText:aString replacementRange:NSMakeRange(0, 0)]; + return origin; } -- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange { - NSEvent *event = [NSApp currentEvent]; - - NSString *characters; - if ([aString isKindOfClass:[NSAttributedString class]]) { - characters = [aString string]; - } else { - characters = (NSString *)aString; - } - - NSCharacterSet *ctrlChars = [NSCharacterSet controlCharacterSet]; - NSCharacterSet *wsnlChars = [NSCharacterSet whitespaceAndNewlineCharacterSet]; - if ([characters rangeOfCharacterFromSet:ctrlChars].length && [characters rangeOfCharacterFromSet:wsnlChars].length == 0) { - NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext]; - [currentInputContext discardMarkedText]; - [self cancelComposition]; - return; +Point2i DisplayServerOSX::_get_native_screen_position(int p_screen) const { + NSArray *screenArray = [NSScreen screens]; + if ((NSUInteger)p_screen < [screenArray count]) { + NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; + // Return the top-left corner of the screen, for OS X the y starts at the bottom. + return Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * screen_get_max_scale(); } - Char16String text; - text.resize([characters length] + 1); - [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; - - String u32text; - u32text.parse_utf16(text.ptr(), text.length()); - - for (int i = 0; i < u32text.length(); i++) { - const char32_t codepoint = u32text[i]; - if ((codepoint & 0xFF00) == 0xF700) { - continue; - } - - DisplayServerOSX::KeyEvent ke; - - ke.window_id = window_id; - ke.osx_state = [event modifierFlags]; - ke.pressed = true; - ke.echo = false; - ke.raw = false; // IME input event - ke.keycode = Key::NONE; - ke.physical_keycode = Key::NONE; - ke.unicode = codepoint; - - _push_to_key_event_buffer(ke); - } - [self cancelComposition]; + return Point2i(); } -- (NSDragOperation)draggingEntered:(id)sender { - return NSDragOperationCopy; +void DisplayServerOSX::_displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info) { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (ds) { + ds->displays_arrangement_dirty = true; + } } -- (NSDragOperation)draggingUpdated:(id)sender { - return NSDragOperationCopy; +void DisplayServerOSX::_dispatch_input_events(const Ref &p_event) { + ((DisplayServerOSX *)(get_singleton()))->_dispatch_input_event(p_event); } -- (BOOL)performDragOperation:(id)sender { - ERR_FAIL_COND_V(!DS_OSX->windows.has(window_id), NO); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - if (!wd.drop_files_callback.is_null()) { - Vector files; - NSPasteboard *pboard = [sender draggingPasteboard]; - -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 - NSArray *items = pboard.pasteboardItems; - for (NSPasteboardItem *item in items) { - NSString *path = [item stringForType:NSPasteboardTypeFileURL]; - NSString *ns = [NSURL URLWithString:path].path; - char *utfs = strdup([ns UTF8String]); - String ret; - ret.parse_utf8(utfs); - free(utfs); - files.push_back(ret); - } -#else - NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType]; - for (NSString *ns in filenames) { - char *utfs = strdup([ns UTF8String]); - String ret; - ret.parse_utf8(utfs); - free(utfs); - files.push_back(ret); - } -#endif +void DisplayServerOSX::_dispatch_input_event(const Ref &p_event) { + _THREAD_SAFE_METHOD_ + if (!in_dispatch_input_event) { + in_dispatch_input_event = true; - Variant v = files; - Variant *vp = &v; + Variant ev = p_event; + Variant *evp = &ev; Variant ret; Callable::CallError ce; - wd.drop_files_callback.call((const Variant **)&vp, 1, ret, ce); - } - - return NO; -} - -- (BOOL)isOpaque { - return YES; -} -- (BOOL)canBecomeKeyView { - if (DS_OSX->windows.has(window_id)) { - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - if (wd.no_focus) { - return NO; + Ref event_from_window = p_event; + if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) { + // Send to a window. + if (windows.has(event_from_window->get_window_id())) { + Callable callable = windows[event_from_window->get_window_id()].input_event_callback; + if (callable.is_null()) { + return; + } + callable.call((const Variant **)&evp, 1, ret, ce); + } + } else { + // Send to all windows. + for (Map::Element *E = windows.front(); E; E = E->next()) { + Callable callable = E->get().input_event_callback; + if (callable.is_null()) { + continue; + } + callable.call((const Variant **)&evp, 1, ret, ce); + } } - } - return YES; -} -- (BOOL)acceptsFirstResponder { - return YES; -} - -- (void)cursorUpdate:(NSEvent *)event { - DisplayServer::CursorShape p_shape = DS_OSX->cursor_shape; - DS_OSX->cursor_shape = DisplayServer::CURSOR_MAX; - DS_OSX->cursor_set_shape(p_shape); -} - -static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, MouseButton index, MouseButton mask, bool pressed) { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - if (pressed) { - DS_OSX->last_button_state |= mask; - } else { - DS_OSX->last_button_state &= (MouseButton)~mask; - } - - Ref mb; - mb.instantiate(); - mb->set_window_id(window_id); - const Vector2 pos = _get_mouse_pos(wd, [event locationInWindow]); - _get_key_modifier_state([event modifierFlags], mb); - mb->set_button_index(index); - mb->set_pressed(pressed); - mb->set_position(pos); - mb->set_global_position(pos); - mb->set_button_mask(DS_OSX->last_button_state); - if (index == MouseButton::LEFT && pressed) { - mb->set_double_click([event clickCount] == 2); - } - - Input::get_singleton()->parse_input_event(mb); -} - -- (void)mouseDown:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - if (([event modifierFlags] & NSEventModifierFlagControl)) { - wd.mouse_down_control = true; - _mouseDownEvent(window_id, event, MouseButton::RIGHT, MouseButton::MASK_RIGHT, true); - } else { - wd.mouse_down_control = false; - _mouseDownEvent(window_id, event, MouseButton::LEFT, MouseButton::MASK_LEFT, true); + in_dispatch_input_event = false; } } -- (void)mouseDragged:(NSEvent *)event { - [self mouseMoved:event]; -} - -- (void)mouseUp:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - if (wd.mouse_down_control) { - _mouseDownEvent(window_id, event, MouseButton::RIGHT, MouseButton::MASK_RIGHT, false); - } else { - _mouseDownEvent(window_id, event, MouseButton::LEFT, MouseButton::MASK_LEFT, false); - } +void DisplayServerOSX::_push_input(const Ref &p_event) { + Ref ev = p_event; + Input::get_singleton()->parse_input_event(ev); } -- (void)mouseMoved:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; +void DisplayServerOSX::_process_key_events() { + Ref k; + for (int i = 0; i < key_event_pos; i++) { + const KeyEvent &ke = key_event_buffer[i]; + if (ke.raw) { + // Non IME input - no composite characters, pass events as is. + k.instantiate(); - NSPoint delta = NSMakePoint([event deltaX], [event deltaY]); - NSPoint mpos = [event locationInWindow]; + k->set_window_id(ke.window_id); + get_key_modifier_state(ke.osx_state, k); + k->set_pressed(ke.pressed); + k->set_echo(ke.echo); + k->set_keycode(ke.keycode); + k->set_physical_keycode((Key)ke.physical_keycode); + k->set_unicode(ke.unicode); - if (DS_OSX->ignore_warp) { - // Discard late events, before warp - if (([event timestamp]) < DS_OSX->last_warp) { - return; - } - DS_OSX->ignore_warp = false; - return; - } + _push_input(k); + } else { + // IME input. + if ((i == 0 && ke.keycode == Key::NONE) || (i > 0 && key_event_buffer[i - 1].keycode == Key::NONE)) { + k.instantiate(); - if (DS_OSX->mouse_mode == DisplayServer::MOUSE_MODE_CONFINED || DS_OSX->mouse_mode == DisplayServer::MOUSE_MODE_CONFINED_HIDDEN) { - // Discard late events - if (([event timestamp]) < DS_OSX->last_warp) { - return; - } + k->set_window_id(ke.window_id); + get_key_modifier_state(ke.osx_state, k); + k->set_pressed(ke.pressed); + k->set_echo(ke.echo); + k->set_keycode(Key::NONE); + k->set_physical_keycode(Key::NONE); + k->set_unicode(ke.unicode); - // Warp affects next event delta, subtract previous warp deltas - List::Element *F = DS_OSX->warp_events.front(); - while (F) { - if (F->get().timestamp < [event timestamp]) { - List::Element *E = F; - delta.x -= E->get().delta.x; - delta.y -= E->get().delta.y; - F = F->next(); - DS_OSX->warp_events.erase(E); - } else { - F = F->next(); + _push_input(k); } - } - - // Confine mouse position to the window, and update delta - NSRect frame = [wd.window_object frame]; - NSPoint conf_pos = mpos; - conf_pos.x = CLAMP(conf_pos.x + delta.x, 0.f, frame.size.width); - conf_pos.y = CLAMP(conf_pos.y - delta.y, 0.f, frame.size.height); - delta.x = conf_pos.x - mpos.x; - delta.y = mpos.y - conf_pos.y; - mpos = conf_pos; - - // Move mouse cursor - NSRect pointInWindowRect = NSMakeRect(conf_pos.x, conf_pos.y, 0, 0); - conf_pos = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; - conf_pos.y = CGDisplayBounds(CGMainDisplayID()).size.height - conf_pos.y; - CGWarpMouseCursorPosition(conf_pos); - - // Save warp data - DS_OSX->last_warp = [[NSProcessInfo processInfo] systemUptime]; - DisplayServerOSX::WarpEvent ev; - ev.timestamp = DS_OSX->last_warp; - ev.delta = delta; - DS_OSX->warp_events.push_back(ev); - } - - Ref mm; - mm.instantiate(); - - mm->set_window_id(window_id); - mm->set_button_mask(DS_OSX->last_button_state); - const Vector2i pos = _get_mouse_pos(wd, mpos); - mm->set_position(pos); - mm->set_pressure([event pressure]); - if ([event subtype] == NSEventSubtypeTabletPoint) { - const NSPoint p = [event tilt]; - mm->set_tilt(Vector2(p.x, p.y)); - } - mm->set_global_position(pos); - mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); - const Vector2i relativeMotion = Vector2i(delta.x, delta.y) * DS_OSX->screen_get_max_scale(); - mm->set_relative(relativeMotion); - _get_key_modifier_state([event modifierFlags], mm); - - Input::get_singleton()->parse_input_event(mm); -} - -- (void)rightMouseDown:(NSEvent *)event { - _mouseDownEvent(window_id, event, MouseButton::RIGHT, MouseButton::MASK_RIGHT, true); -} + if (ke.keycode != Key::NONE) { + k.instantiate(); -- (void)rightMouseDragged:(NSEvent *)event { - [self mouseMoved:event]; -} + k->set_window_id(ke.window_id); + get_key_modifier_state(ke.osx_state, k); + k->set_pressed(ke.pressed); + k->set_echo(ke.echo); + k->set_keycode(ke.keycode); + k->set_physical_keycode((Key)ke.physical_keycode); -- (void)rightMouseUp:(NSEvent *)event { - _mouseDownEvent(window_id, event, MouseButton::RIGHT, MouseButton::MASK_RIGHT, false); -} + if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == Key::NONE) { + k->set_unicode(key_event_buffer[i + 1].unicode); + } -- (void)otherMouseDown:(NSEvent *)event { - if ((int)[event buttonNumber] == 2) { - _mouseDownEvent(window_id, event, MouseButton::MIDDLE, MouseButton::MASK_MIDDLE, true); - } else if ((int)[event buttonNumber] == 3) { - _mouseDownEvent(window_id, event, MouseButton::MB_XBUTTON1, MouseButton::MASK_XBUTTON1, true); - } else if ((int)[event buttonNumber] == 4) { - _mouseDownEvent(window_id, event, MouseButton::MB_XBUTTON2, MouseButton::MASK_XBUTTON2, true); - } else { - return; + _push_input(k); + } + } } -} -- (void)otherMouseDragged:(NSEvent *)event { - [self mouseMoved:event]; + key_event_pos = 0; } -- (void)otherMouseUp:(NSEvent *)event { - if ((int)[event buttonNumber] == 2) { - _mouseDownEvent(window_id, event, MouseButton::MIDDLE, MouseButton::MASK_MIDDLE, false); - } else if ((int)[event buttonNumber] == 3) { - _mouseDownEvent(window_id, event, MouseButton::MB_XBUTTON1, MouseButton::MASK_XBUTTON1, false); - } else if ((int)[event buttonNumber] == 4) { - _mouseDownEvent(window_id, event, MouseButton::MB_XBUTTON2, MouseButton::MASK_XBUTTON2, false); - } else { - return; - } -} +void DisplayServerOSX::_update_keyboard_layouts() { + kbd_layouts.clear(); + current_layout = 0; -- (void)mouseExited:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + TISInputSourceRef cur_source = TISCopyCurrentKeyboardInputSource(); + NSString *cur_name = (__bridge NSString *)TISGetInputSourceProperty(cur_source, kTISPropertyLocalizedName); + CFRelease(cur_source); - if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) { - DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_EXIT); - } -} + // Enum IME layouts. + NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode }; + NSArray *list_ime = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_ime, false); + for (NSUInteger i = 0; i < [list_ime count]; i++) { + LayoutInfo ly; + NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName); + ly.name.parse_utf8([name UTF8String]); -- (void)mouseEntered:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + NSArray *langs = (__bridge NSArray *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyInputSourceLanguages); + ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]); + kbd_layouts.push_back(ly); - if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) { - DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_ENTER); + if ([name isEqualToString:cur_name]) { + current_layout = kbd_layouts.size() - 1; + } } - DisplayServer::CursorShape p_shape = DS_OSX->cursor_shape; - DS_OSX->cursor_shape = DisplayServer::CURSOR_MAX; - DS_OSX->cursor_set_shape(p_shape); -} + // Enum plain keyboard layouts. + NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout }; + NSArray *list_kbd = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_kbd, false); + for (NSUInteger i = 0; i < [list_kbd count]; i++) { + LayoutInfo ly; + NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName); + ly.name.parse_utf8([name UTF8String]); -- (void)magnifyWithEvent:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + NSArray *langs = (__bridge NSArray *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyInputSourceLanguages); + ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]); + kbd_layouts.push_back(ly); - Ref ev; - ev.instantiate(); - ev->set_window_id(window_id); - _get_key_modifier_state([event modifierFlags], ev); - ev->set_position(_get_mouse_pos(wd, [event locationInWindow])); - ev->set_factor([event magnification] + 1.0); + if ([name isEqualToString:cur_name]) { + current_layout = kbd_layouts.size() - 1; + } + } - Input::get_singleton()->parse_input_event(ev); + keyboard_layout_dirty = false; } -- (void)viewDidChangeBackingProperties { - // nothing left to do here -} - -- (void)updateTrackingAreas { - if (trackingArea != nil) { - [self removeTrackingArea:trackingArea]; - [trackingArea release]; - } - - NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingCursorUpdate | NSTrackingInVisibleRect; - trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; - - [self addTrackingArea:trackingArea]; - [super updateTrackingAreas]; -} - -static bool isNumpadKey(unsigned int key) { - static const unsigned int table[] = { - 0x41, /* kVK_ANSI_KeypadDecimal */ - 0x43, /* kVK_ANSI_KeypadMultiply */ - 0x45, /* kVK_ANSI_KeypadPlus */ - 0x47, /* kVK_ANSI_KeypadClear */ - 0x4b, /* kVK_ANSI_KeypadDivide */ - 0x4c, /* kVK_ANSI_KeypadEnter */ - 0x4e, /* kVK_ANSI_KeypadMinus */ - 0x51, /* kVK_ANSI_KeypadEquals */ - 0x52, /* kVK_ANSI_Keypad0 */ - 0x53, /* kVK_ANSI_Keypad1 */ - 0x54, /* kVK_ANSI_Keypad2 */ - 0x55, /* kVK_ANSI_Keypad3 */ - 0x56, /* kVK_ANSI_Keypad4 */ - 0x57, /* kVK_ANSI_Keypad5 */ - 0x58, /* kVK_ANSI_Keypad6 */ - 0x59, /* kVK_ANSI_Keypad7 */ - 0x5b, /* kVK_ANSI_Keypad8 */ - 0x5c, /* kVK_ANSI_Keypad9 */ - 0x5f, /* kVK_JIS_KeypadComma */ - 0x00 - }; - for (int i = 0; table[i] != 0; i++) { - if (key == table[i]) { - return true; - } +void DisplayServerOSX::_keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info) { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (ds) { + ds->keyboard_layout_dirty = true; } - return false; } -// Keyboard symbol translation table -static const Key _osx_to_godot_table[128] = { - /* 00 */ Key::A, - /* 01 */ Key::S, - /* 02 */ Key::D, - /* 03 */ Key::F, - /* 04 */ Key::H, - /* 05 */ Key::G, - /* 06 */ Key::Z, - /* 07 */ Key::X, - /* 08 */ Key::C, - /* 09 */ Key::V, - /* 0a */ Key::SECTION, /* ISO Section */ - /* 0b */ Key::B, - /* 0c */ Key::Q, - /* 0d */ Key::W, - /* 0e */ Key::E, - /* 0f */ Key::R, - /* 10 */ Key::Y, - /* 11 */ Key::T, - /* 12 */ Key::KEY_1, - /* 13 */ Key::KEY_2, - /* 14 */ Key::KEY_3, - /* 15 */ Key::KEY_4, - /* 16 */ Key::KEY_6, - /* 17 */ Key::KEY_5, - /* 18 */ Key::EQUAL, - /* 19 */ Key::KEY_9, - /* 1a */ Key::KEY_7, - /* 1b */ Key::MINUS, - /* 1c */ Key::KEY_8, - /* 1d */ Key::KEY_0, - /* 1e */ Key::BRACERIGHT, - /* 1f */ Key::O, - /* 20 */ Key::U, - /* 21 */ Key::BRACELEFT, - /* 22 */ Key::I, - /* 23 */ Key::P, - /* 24 */ Key::ENTER, - /* 25 */ Key::L, - /* 26 */ Key::J, - /* 27 */ Key::APOSTROPHE, - /* 28 */ Key::K, - /* 29 */ Key::SEMICOLON, - /* 2a */ Key::BACKSLASH, - /* 2b */ Key::COMMA, - /* 2c */ Key::SLASH, - /* 2d */ Key::N, - /* 2e */ Key::M, - /* 2f */ Key::PERIOD, - /* 30 */ Key::TAB, - /* 31 */ Key::SPACE, - /* 32 */ Key::QUOTELEFT, - /* 33 */ Key::BACKSPACE, - /* 34 */ Key::UNKNOWN, - /* 35 */ Key::ESCAPE, - /* 36 */ Key::META, - /* 37 */ Key::META, - /* 38 */ Key::SHIFT, - /* 39 */ Key::CAPSLOCK, - /* 3a */ Key::ALT, - /* 3b */ Key::CTRL, - /* 3c */ Key::SHIFT, - /* 3d */ Key::ALT, - /* 3e */ Key::CTRL, - /* 3f */ Key::UNKNOWN, /* Function */ - /* 40 */ Key::UNKNOWN, /* F17 */ - /* 41 */ Key::KP_PERIOD, - /* 42 */ Key::UNKNOWN, - /* 43 */ Key::KP_MULTIPLY, - /* 44 */ Key::UNKNOWN, - /* 45 */ Key::KP_ADD, - /* 46 */ Key::UNKNOWN, - /* 47 */ Key::NUMLOCK, /* Really KeypadClear... */ - /* 48 */ Key::VOLUMEUP, /* VolumeUp */ - /* 49 */ Key::VOLUMEDOWN, /* VolumeDown */ - /* 4a */ Key::VOLUMEMUTE, /* Mute */ - /* 4b */ Key::KP_DIVIDE, - /* 4c */ Key::KP_ENTER, - /* 4d */ Key::UNKNOWN, - /* 4e */ Key::KP_SUBTRACT, - /* 4f */ Key::UNKNOWN, /* F18 */ - /* 50 */ Key::UNKNOWN, /* F19 */ - /* 51 */ Key::EQUAL, /* KeypadEqual */ - /* 52 */ Key::KP_0, - /* 53 */ Key::KP_1, - /* 54 */ Key::KP_2, - /* 55 */ Key::KP_3, - /* 56 */ Key::KP_4, - /* 57 */ Key::KP_5, - /* 58 */ Key::KP_6, - /* 59 */ Key::KP_7, - /* 5a */ Key::UNKNOWN, /* F20 */ - /* 5b */ Key::KP_8, - /* 5c */ Key::KP_9, - /* 5d */ Key::YEN, /* JIS Yen */ - /* 5e */ Key::UNDERSCORE, /* JIS Underscore */ - /* 5f */ Key::COMMA, /* JIS KeypadComma */ - /* 60 */ Key::F5, - /* 61 */ Key::F6, - /* 62 */ Key::F7, - /* 63 */ Key::F3, - /* 64 */ Key::F8, - /* 65 */ Key::F9, - /* 66 */ Key::UNKNOWN, /* JIS Eisu */ - /* 67 */ Key::F11, - /* 68 */ Key::UNKNOWN, /* JIS Kana */ - /* 69 */ Key::F13, - /* 6a */ Key::F16, - /* 6b */ Key::F14, - /* 6c */ Key::UNKNOWN, - /* 6d */ Key::F10, - /* 6e */ Key::MENU, - /* 6f */ Key::F12, - /* 70 */ Key::UNKNOWN, - /* 71 */ Key::F15, - /* 72 */ Key::INSERT, /* Really Help... */ - /* 73 */ Key::HOME, - /* 74 */ Key::PAGEUP, - /* 75 */ Key::KEY_DELETE, - /* 76 */ Key::F4, - /* 77 */ Key::END, - /* 78 */ Key::F2, - /* 79 */ Key::PAGEDOWN, - /* 7a */ Key::F1, - /* 7b */ Key::LEFT, - /* 7c */ Key::RIGHT, - /* 7d */ Key::DOWN, - /* 7e */ Key::UP, - /* 7f */ Key::UNKNOWN, -}; - -// Translates a OS X keycode to a Godot keycode -static Key translateKey(unsigned int key) { - if (key >= 128) { - return Key::UNKNOWN; - } - - return _osx_to_godot_table[key]; -} - -// Translates a Godot keycode back to a OSX keycode -static unsigned int unmapKey(Key key) { - for (int i = 0; i <= 126; i++) { - if (_osx_to_godot_table[i] == key) { - return i; - } - } - return 127; -} - -struct _KeyCodeMap { - UniChar kchar; - Key kcode; -}; - -static const _KeyCodeMap _keycodes[55] = { - { '`', Key::QUOTELEFT }, - { '~', Key::ASCIITILDE }, - { '0', Key::KEY_0 }, - { '1', Key::KEY_1 }, - { '2', Key::KEY_2 }, - { '3', Key::KEY_3 }, - { '4', Key::KEY_4 }, - { '5', Key::KEY_5 }, - { '6', Key::KEY_6 }, - { '7', Key::KEY_7 }, - { '8', Key::KEY_8 }, - { '9', Key::KEY_9 }, - { '-', Key::MINUS }, - { '_', Key::UNDERSCORE }, - { '=', Key::EQUAL }, - { '+', Key::PLUS }, - { 'q', Key::Q }, - { 'w', Key::W }, - { 'e', Key::E }, - { 'r', Key::R }, - { 't', Key::T }, - { 'y', Key::Y }, - { 'u', Key::U }, - { 'i', Key::I }, - { 'o', Key::O }, - { 'p', Key::P }, - { '[', Key::BRACELEFT }, - { ']', Key::BRACERIGHT }, - { '{', Key::BRACELEFT }, - { '}', Key::BRACERIGHT }, - { 'a', Key::A }, - { 's', Key::S }, - { 'd', Key::D }, - { 'f', Key::F }, - { 'g', Key::G }, - { 'h', Key::H }, - { 'j', Key::J }, - { 'k', Key::K }, - { 'l', Key::L }, - { ';', Key::SEMICOLON }, - { ':', Key::COLON }, - { '\'', Key::APOSTROPHE }, - { '\"', Key::QUOTEDBL }, - { '\\', Key::BACKSLASH }, - { '#', Key::NUMBERSIGN }, - { 'z', Key::Z }, - { 'x', Key::X }, - { 'c', Key::C }, - { 'v', Key::V }, - { 'b', Key::B }, - { 'n', Key::N }, - { 'm', Key::M }, - { ',', Key::COMMA }, - { '.', Key::PERIOD }, - { '/', Key::SLASH } -}; - -static Key remapKey(unsigned int key, unsigned int state) { - if (isNumpadKey(key)) { - return translateKey(key); - } - - TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); - if (!currentKeyboard) { - return translateKey(key); - } - - CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); - if (!layoutData) { - return translateKey(key); - } - - const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData); - - UInt32 keysDown = 0; - UniChar chars[4]; - UniCharCount realLength; - - OSStatus err = UCKeyTranslate(keyboardLayout, - key, - kUCKeyActionDisplay, - (state >> 8) & 0xFF, - LMGetKbdType(), - kUCKeyTranslateNoDeadKeysBit, - &keysDown, - sizeof(chars) / sizeof(chars[0]), - &realLength, - chars); - - if (err != noErr) { - return translateKey(key); - } - - for (unsigned int i = 0; i < 55; i++) { - if (_keycodes[i].kchar == chars[0]) { - return _keycodes[i].kcode; - } - } - return translateKey(key); -} - -- (void)keyDown:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - ignore_momentum_scroll = true; - - // Ignore all input if IME input is in progress - if (!imeInputEventInProgress) { - NSString *characters = [event characters]; - NSUInteger length = [characters length]; - - if (!wd.im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) { - // Fallback unicode character handler used if IME is not active - Char16String text; - text.resize([characters length] + 1); - [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; - - String u32text; - u32text.parse_utf16(text.ptr(), text.length()); - - for (int i = 0; i < u32text.length(); i++) { - const char32_t codepoint = u32text[i]; - - DisplayServerOSX::KeyEvent ke; - - ke.window_id = window_id; - ke.osx_state = [event modifierFlags]; - ke.pressed = true; - ke.echo = [event isARepeat]; - ke.keycode = remapKey([event keyCode], [event modifierFlags]); - ke.physical_keycode = translateKey([event keyCode]); - ke.raw = true; - ke.unicode = codepoint; - - _push_to_key_event_buffer(ke); - } - } else { - DisplayServerOSX::KeyEvent ke; - - ke.window_id = window_id; - ke.osx_state = [event modifierFlags]; - ke.pressed = true; - ke.echo = [event isARepeat]; - ke.keycode = remapKey([event keyCode], [event modifierFlags]); - ke.physical_keycode = translateKey([event keyCode]); - ke.raw = false; - ke.unicode = 0; - - _push_to_key_event_buffer(ke); +NSCursor *DisplayServerOSX::_cursor_from_selector(SEL p_selector, SEL p_fallback) { + if ([NSCursor respondsToSelector:p_selector]) { + id object = [NSCursor performSelector:p_selector]; + if ([object isKindOfClass:[NSCursor class]]) { + return object; } } - - // Pass events to IME handler - if (wd.im_active) { - [self interpretKeyEvents:[NSArray arrayWithObject:event]]; + if (p_fallback) { + // Fallback should be a reasonable default, no need to check. + return [NSCursor performSelector:p_fallback]; } + return [NSCursor arrowCursor]; } -- (void)flagsChanged:(NSEvent *)event { - ignore_momentum_scroll = true; - - // Ignore all input if IME input is in progress - if (!imeInputEventInProgress) { - DisplayServerOSX::KeyEvent ke; +NSMenu *DisplayServerOSX::get_dock_menu() const { + return dock_menu; +} - ke.window_id = window_id; - ke.echo = false; - ke.raw = true; +void DisplayServerOSX::menu_callback(id p_sender) { + if (![p_sender representedObject]) { + return; + } - int key = [event keyCode]; - int mod = [event modifierFlags]; + GodotMenuItem *value = [p_sender representedObject]; - if (key == 0x36 || key == 0x37) { - if (mod & NSEventModifierFlagCommand) { - mod &= ~NSEventModifierFlagCommand; - ke.pressed = true; - } else { - ke.pressed = false; - } - } else if (key == 0x38 || key == 0x3c) { - if (mod & NSEventModifierFlagShift) { - mod &= ~NSEventModifierFlagShift; - ke.pressed = true; - } else { - ke.pressed = false; - } - } else if (key == 0x3a || key == 0x3d) { - if (mod & NSEventModifierFlagOption) { - mod &= ~NSEventModifierFlagOption; - ke.pressed = true; - } else { - ke.pressed = false; - } - } else if (key == 0x3b || key == 0x3e) { - if (mod & NSEventModifierFlagControl) { - mod &= ~NSEventModifierFlagControl; - ke.pressed = true; + if (value) { + if (value->checkable) { + if ([p_sender state] == NSControlStateValueOff) { + [p_sender setState:NSControlStateValueOn]; } else { - ke.pressed = false; + [p_sender setState:NSControlStateValueOff]; } - } else { - return; } - ke.osx_state = mod; - ke.keycode = remapKey(key, mod); - ke.physical_keycode = translateKey(key); - ke.unicode = 0; - - _push_to_key_event_buffer(ke); + if (value->callback != Callable()) { + Variant tag = value->meta; + Variant *tagp = &tag; + Variant ret; + Callable::CallError ce; + value->callback.call((const Variant **)&tagp, 1, ret, ce); + } } } -- (void)keyUp:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - // Ignore all input if IME input is in progress - if (!imeInputEventInProgress) { - NSString *characters = [event characters]; - NSUInteger length = [characters length]; - - // Fallback unicode character handler used if IME is not active - if (!wd.im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) { - Char16String text; - text.resize([characters length] + 1); - [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; - - String u32text; - u32text.parse_utf16(text.ptr(), text.length()); - - for (int i = 0; i < u32text.length(); i++) { - const char32_t codepoint = u32text[i]; - DisplayServerOSX::KeyEvent ke; +bool DisplayServerOSX::has_window(WindowID p_window) const { + return windows.has(p_window); +} - ke.window_id = window_id; - ke.osx_state = [event modifierFlags]; - ke.pressed = false; - ke.echo = [event isARepeat]; - ke.keycode = remapKey([event keyCode], [event modifierFlags]); - ke.physical_keycode = translateKey([event keyCode]); - ke.raw = true; - ke.unicode = codepoint; +DisplayServerOSX::WindowData &DisplayServerOSX::get_window(WindowID p_window) { + return windows[p_window]; +} - _push_to_key_event_buffer(ke); - } - } else { - DisplayServerOSX::KeyEvent ke; +void DisplayServerOSX::send_event(NSEvent *p_event) { + // Special case handling of command-period, which is traditionally a special + // shortcut in macOS and doesn't arrive at our regular keyDown handler. + if ([p_event type] == NSEventTypeKeyDown) { + if (([p_event modifierFlags] & NSEventModifierFlagCommand) && [p_event keyCode] == 0x2f) { + Ref k; + k.instantiate(); - ke.window_id = window_id; - ke.osx_state = [event modifierFlags]; - ke.pressed = false; - ke.echo = [event isARepeat]; - ke.keycode = remapKey([event keyCode], [event modifierFlags]); - ke.physical_keycode = translateKey([event keyCode]); - ke.raw = true; - ke.unicode = 0; + get_key_modifier_state([p_event modifierFlags], k); + k->set_window_id(DisplayServerOSX::INVALID_WINDOW_ID); + k->set_pressed(true); + k->set_keycode(Key::PERIOD); + k->set_physical_keycode(Key::PERIOD); + k->set_echo([p_event isARepeat]); - _push_to_key_event_buffer(ke); + Input::get_singleton()->parse_input_event(k); } } } -inline void sendScrollEvent(DisplayServer::WindowID window_id, MouseButton button, double factor, int modifierFlags) { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - MouseButton mask = mouse_button_to_mask(button); - - Ref sc; - sc.instantiate(); - - sc->set_window_id(window_id); - _get_key_modifier_state(modifierFlags, sc); - sc->set_button_index(button); - sc->set_factor(factor); - sc->set_pressed(true); - sc->set_position(wd.mouse_pos); - sc->set_global_position(wd.mouse_pos); - DS_OSX->last_button_state |= (MouseButton)mask; - sc->set_button_mask(DS_OSX->last_button_state); - - Input::get_singleton()->parse_input_event(sc); - - sc.instantiate(); - sc->set_window_id(window_id); - sc->set_button_index(button); - sc->set_factor(factor); - sc->set_pressed(false); - sc->set_position(wd.mouse_pos); - sc->set_global_position(wd.mouse_pos); - DS_OSX->last_button_state &= (MouseButton)~mask; - sc->set_button_mask(DS_OSX->last_button_state); +void DisplayServerOSX::send_window_event(const WindowData &wd, WindowEvent p_event) { + _THREAD_SAFE_METHOD_ - Input::get_singleton()->parse_input_event(sc); + if (!wd.event_callback.is_null()) { + Variant event = int(p_event); + Variant *eventp = &event; + Variant ret; + Callable::CallError ce; + wd.event_callback.call((const Variant **)&eventp, 1, ret, ce); + } } -inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy, int modifierFlags) { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - Ref pg; - pg.instantiate(); - - pg->set_window_id(window_id); - _get_key_modifier_state(modifierFlags, pg); - pg->set_position(wd.mouse_pos); - pg->set_delta(Vector2(-dx, -dy)); - - Input::get_singleton()->parse_input_event(pg); +void DisplayServerOSX::release_pressed_events() { + _THREAD_SAFE_METHOD_ + if (Input::get_singleton()) { + Input::get_singleton()->release_pressed_events(); + } } -- (void)scrollWheel:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - double deltaX, deltaY; - - _ALLOW_DISCARD_ _get_mouse_pos(wd, [event locationInWindow]); - - deltaX = [event scrollingDeltaX]; - deltaY = [event scrollingDeltaY]; - - if ([event hasPreciseScrollingDeltas]) { - deltaX *= 0.03; - deltaY *= 0.03; - } +void DisplayServerOSX::get_key_modifier_state(unsigned int p_osx_state, Ref r_state) const { + r_state->set_shift_pressed((p_osx_state & NSEventModifierFlagShift)); + r_state->set_ctrl_pressed((p_osx_state & NSEventModifierFlagControl)); + r_state->set_alt_pressed((p_osx_state & NSEventModifierFlagOption)); + r_state->set_meta_pressed((p_osx_state & NSEventModifierFlagCommand)); +} - if ([event momentumPhase] != NSEventPhaseNone) { - if (ignore_momentum_scroll) { - return; - } - } else { - ignore_momentum_scroll = false; - } +void DisplayServerOSX::update_mouse_pos(DisplayServerOSX::WindowData &p_wd, NSPoint p_location_in_window) { + const NSRect content_rect = [p_wd.window_view frame]; + const float scale = screen_get_max_scale(); + p_wd.mouse_pos.x = p_location_in_window.x * scale; + p_wd.mouse_pos.y = (content_rect.size.height - p_location_in_window.y) * scale; + Input::get_singleton()->set_mouse_position(p_wd.mouse_pos); +} - if ([event phase] != NSEventPhaseNone || [event momentumPhase] != NSEventPhaseNone) { - sendPanEvent(window_id, deltaX, deltaY, [event modifierFlags]); - } else { - if (fabs(deltaX)) { - sendScrollEvent(window_id, 0 > deltaX ? MouseButton::WHEEL_RIGHT : MouseButton::WHEEL_LEFT, fabs(deltaX * 0.3), [event modifierFlags]); - } - if (fabs(deltaY)) { - sendScrollEvent(window_id, 0 < deltaY ? MouseButton::WHEEL_UP : MouseButton::WHEEL_DOWN, fabs(deltaY * 0.3), [event modifierFlags]); - } +void DisplayServerOSX::push_to_key_event_buffer(const DisplayServerOSX::KeyEvent &p_event) { + if (key_event_pos >= key_event_buffer.size()) { + key_event_buffer.resize(1 + key_event_pos); } + key_event_buffer.write[key_event_pos++] = p_event; } -@end - -/*************************************************************************/ -/* GodotWindow */ -/*************************************************************************/ +void DisplayServerOSX::update_im_text(const Point2i &p_selection, const String &p_text) { + im_selection = p_selection; + im_text = p_text; -@interface GodotWindow : NSWindow { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); } -@end - -@implementation GodotWindow +void DisplayServerOSX::set_last_focused_window(WindowID p_window) { + last_focused_window = p_window; +} -- (BOOL)canBecomeKeyWindow { - // Required for NSBorderlessWindowMask windows - for (Map::Element *E = DS_OSX->windows.front(); E; E = E->next()) { - if (E->get().window_object == self) { - if (E->get().no_focus) { - return NO; - } - } +void DisplayServerOSX::window_update(WindowID p_window) { +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->window_update(p_window); } - return YES; +#endif } -- (BOOL)canBecomeMainWindow { - // Required for NSBorderlessWindowMask windows - for (Map::Element *E = DS_OSX->windows.front(); E; E = E->next()) { - if (E->get().window_object == self) { - if (E->get().no_focus) { - return NO; - } - } +void DisplayServerOSX::window_destroy(WindowID p_window) { +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->window_destroy(p_window); + } +#endif +#ifdef VULKAN_ENABLED + if (context_vulkan) { + context_vulkan->window_destroy(p_window); } - return YES; +#endif + windows.erase(p_window); } -@end - -/*************************************************************************/ -/* DisplayServerOSX */ -/*************************************************************************/ +void DisplayServerOSX::window_resize(WindowID p_window, int p_width, int p_height) { +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->window_resize(p_window, p_width, p_height); + } +#endif +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + context_vulkan->window_resize(p_window, p_width, p_height); + } +#endif +} bool DisplayServerOSX::has_feature(Feature p_feature) const { switch (p_feature) { @@ -1492,57 +648,13 @@ - (BOOL)canBecomeMainWindow { return "OSX"; } -const NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) const { - const NSMenu *menu = nullptr; - if (p_menu_root == "") { - // Main menu.x - menu = [NSApp mainMenu]; - } else if (p_menu_root.to_lower() == "_dock") { - // macOS dock menu. - menu = dock_menu; - } else { - // Submenu. - if (submenu.has(p_menu_root)) { - menu = submenu[p_menu_root]; - } - } - if (menu == apple_menu) { - // Do not allow to change Apple menu. - return nullptr; - } - return menu; -} - -NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) { - NSMenu *menu = nullptr; - if (p_menu_root == "") { - // Main menu. - menu = [NSApp mainMenu]; - } else if (p_menu_root.to_lower() == "_dock") { - // macOS dock menu. - menu = dock_menu; - } else { - // Submenu. - if (!submenu.has(p_menu_root)) { - NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]]; - submenu[p_menu_root] = n_menu; - } - menu = submenu[p_menu_root]; - } - if (menu == apple_menu) { - // Do not allow to change Apple menu. - return nullptr; - } - return menu; -} - void DisplayServerOSX::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag) { _THREAD_SAFE_METHOD_ NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""]; - GlobalMenuItem *obj = [[[GlobalMenuItem alloc] init] autorelease]; + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; obj->meta = p_tag; obj->checkable = false; @@ -1556,7 +668,7 @@ - (BOOL)canBecomeMainWindow { NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""]; - GlobalMenuItem *obj = [[[GlobalMenuItem alloc] init] autorelease]; + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; obj->meta = p_tag; obj->checkable = true; @@ -1612,7 +724,7 @@ - (BOOL)canBecomeMainWindow { if (menu) { const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - GlobalMenuItem *obj = [menu_item representedObject]; + GodotMenuItem *obj = [menu_item representedObject]; if (obj) { return obj->checkable; } @@ -1628,7 +740,7 @@ - (BOOL)canBecomeMainWindow { if (menu) { const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - GlobalMenuItem *obj = [menu_item representedObject]; + GodotMenuItem *obj = [menu_item representedObject]; if (obj) { return obj->callback; } @@ -1644,7 +756,7 @@ - (BOOL)canBecomeMainWindow { if (menu) { const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - GlobalMenuItem *obj = [menu_item representedObject]; + GodotMenuItem *obj = [menu_item representedObject]; if (obj) { return obj->meta; } @@ -1660,10 +772,8 @@ - (BOOL)canBecomeMainWindow { if (menu) { const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - char *utfs = strdup([[menu_item title] UTF8String]); String ret; - ret.parse_utf8(utfs); - free(utfs); + ret.parse_utf8([[menu_item title] UTF8String]); return ret; } } @@ -1718,7 +828,7 @@ - (BOOL)canBecomeMainWindow { } NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - GlobalMenuItem *obj = [menu_item representedObject]; + GodotMenuItem *obj = [menu_item representedObject]; obj->checkable = p_checkable; } } @@ -1734,7 +844,7 @@ - (BOOL)canBecomeMainWindow { } NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - GlobalMenuItem *obj = [menu_item representedObject]; + GodotMenuItem *obj = [menu_item representedObject]; obj->callback = p_callback; } } @@ -1750,7 +860,7 @@ - (BOOL)canBecomeMainWindow { } NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - GlobalMenuItem *obj = [menu_item representedObject]; + GodotMenuItem *obj = [menu_item representedObject]; obj->meta = p_tag; } } @@ -1867,7 +977,6 @@ - (BOOL)canBecomeMainWindow { p_callback.call((const Variant **)&buttonp, 1, ret, ce); } - [window release]; return OK; } @@ -1889,10 +998,8 @@ - (BOOL)canBecomeMainWindow { [window runModal]; - char *utfs = strdup([[input stringValue] UTF8String]); String ret; - ret.parse_utf8(utfs); - free(utfs); + ret.parse_utf8([[input stringValue] UTF8String]); if (!p_callback.is_null()) { Variant text = ret; @@ -1902,7 +1009,6 @@ - (BOOL)canBecomeMainWindow { p_callback.call((const Variant **)&textp, 1, ret, ce); } - [window release]; return OK; } @@ -1913,7 +1019,8 @@ - (BOOL)canBecomeMainWindow { return; } - WindowData &wd = windows[MAIN_WINDOW_ID]; + WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; + WindowData &wd = windows[window_id]; if (p_mode == MOUSE_MODE_CAPTURED) { // Apple Docs state that the display parameter is not used. // "This parameter is not used. By default, you may pass kCGDirectMainDisplay." @@ -1956,9 +1063,7 @@ - (BOOL)canBecomeMainWindow { mouse_mode = p_mode; if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { - CursorShape p_shape = cursor_shape; - cursor_shape = DisplayServer::CURSOR_MAX; - cursor_set_shape(p_shape); + cursor_update_shape(); } } @@ -1966,24 +1071,82 @@ - (BOOL)canBecomeMainWindow { return mouse_mode; } +bool DisplayServerOSX::update_mouse_wrap(WindowData &p_wd, NSPoint &r_delta, NSPoint &r_mpos, NSTimeInterval p_timestamp) { + _THREAD_SAFE_METHOD_ + + if (ignore_warp) { + // Discard late events, before warp. + if (p_timestamp < last_warp) { + return true; + } + ignore_warp = false; + return true; + } + + if (mouse_mode == DisplayServer::MOUSE_MODE_CONFINED || mouse_mode == DisplayServer::MOUSE_MODE_CONFINED_HIDDEN) { + // Discard late events. + if (p_timestamp < last_warp) { + return true; + } + + // Warp affects next event delta, subtract previous warp deltas. + List::Element *F = warp_events.front(); + while (F) { + if (F->get().timestamp < p_timestamp) { + List::Element *E = F; + r_delta.x -= E->get().delta.x; + r_delta.y -= E->get().delta.y; + F = F->next(); + warp_events.erase(E); + } else { + F = F->next(); + } + } + + // Confine mouse position to the window, and update delta. + NSRect frame = [p_wd.window_object frame]; + NSPoint conf_pos = r_mpos; + conf_pos.x = CLAMP(conf_pos.x + r_delta.x, 0.f, frame.size.width); + conf_pos.y = CLAMP(conf_pos.y - r_delta.y, 0.f, frame.size.height); + r_delta.x = conf_pos.x - r_mpos.x; + r_delta.y = r_mpos.y - conf_pos.y; + r_mpos = conf_pos; + + // Move mouse cursor. + NSRect point_in_window_rect = NSMakeRect(conf_pos.x, conf_pos.y, 0, 0); + conf_pos = [[p_wd.window_view window] convertRectToScreen:point_in_window_rect].origin; + conf_pos.y = CGDisplayBounds(CGMainDisplayID()).size.height - conf_pos.y; + CGWarpMouseCursorPosition(conf_pos); + + // Save warp data. + last_warp = [[NSProcessInfo processInfo] systemUptime]; + + DisplayServerOSX::WarpEvent ev; + ev.timestamp = last_warp; + ev.delta = r_delta; + warp_events.push_back(ev); + } + + return false; +} + void DisplayServerOSX::mouse_warp_to_position(const Point2i &p_to) { _THREAD_SAFE_METHOD_ - if (mouse_mode == MOUSE_MODE_CAPTURED) { - last_mouse_pos = p_to; - } else { - WindowData &wd = windows[MAIN_WINDOW_ID]; + if (mouse_mode != MOUSE_MODE_CAPTURED) { + WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; + WindowData &wd = windows[window_id]; - //local point in window coords + // Local point in window coords. const NSRect contentRect = [wd.window_view frame]; const float scale = screen_get_max_scale(); NSRect pointInWindowRect = NSMakeRect(p_to.x / scale, contentRect.size.height - (p_to.y / scale - 1), 0, 0); NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; - //point in scren coords + // Point in scren coords. CGPoint lMouseWarpPos = { pointOnScreen.x, CGDisplayBounds(CGMainDisplayID()).size.height - pointOnScreen.y }; - //do the warping + // Do the warping. CGEventSourceRef lEventRef = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState); CGEventSourceSetLocalEventsSuppressionInterval(lEventRef, 0.0); CGAssociateMouseAndMouseCursorPosition(false); @@ -2009,6 +1172,10 @@ - (BOOL)canBecomeMainWindow { return Vector2i(); } +void DisplayServerOSX::mouse_set_button_state(MouseButton p_state) { + last_button_state = p_state; +} + MouseButton DisplayServerOSX::mouse_get_button_state() const { return last_button_state; } @@ -2032,69 +1199,24 @@ - (BOOL)canBecomeMainWindow { NSDictionary *options = [NSDictionary dictionary]; BOOL ok = [pasteboard canReadObjectForClasses:classArray options:options]; - - if (!ok) { - return ""; - } - - NSArray *objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options]; - NSString *string = [objectsToPaste objectAtIndex:0]; - - char *utfs = strdup([string UTF8String]); - String ret; - ret.parse_utf8(utfs); - free(utfs); - - return ret; -} - -int DisplayServerOSX::get_screen_count() const { - _THREAD_SAFE_METHOD_ - - NSArray *screenArray = [NSScreen screens]; - return [screenArray count]; -} - -// Returns the native top-left screen coordinate of the smallest rectangle -// that encompasses all screens. Needed in get_screen_position(), -// window_get_position, and window_set_position() -// to convert between OS X native screen coordinates and the ones expected by Godot - -static bool displays_arrangement_dirty = true; -static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info) { - displays_arrangement_dirty = true; -} - -Point2i DisplayServerOSX::_get_screens_origin() const { - static Point2i origin; - - if (displays_arrangement_dirty) { - origin = Point2i(); - - for (int i = 0; i < get_screen_count(); i++) { - Point2i position = _get_native_screen_position(i); - if (position.x < origin.x) { - origin.x = position.x; - } - if (position.y > origin.y) { - origin.y = position.y; - } - } - displays_arrangement_dirty = false; + + if (!ok) { + return ""; } - return origin; + NSArray *objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options]; + NSString *string = [objectsToPaste objectAtIndex:0]; + + String ret; + ret.parse_utf8([string UTF8String]); + return ret; } -Point2i DisplayServerOSX::_get_native_screen_position(int p_screen) const { - NSArray *screenArray = [NSScreen screens]; - if ((NSUInteger)p_screen < [screenArray count]) { - NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; - // Return the top-left corner of the screen, for OS X the y starts at the bottom - return Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * screen_get_max_scale(); - } +int DisplayServerOSX::get_screen_count() const { + _THREAD_SAFE_METHOD_ - return Point2i(); + NSArray *screenArray = [NSScreen screens]; + return [screenArray count]; } Point2i DisplayServerOSX::screen_get_position(int p_screen) const { @@ -2106,7 +1228,7 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay Point2i position = _get_native_screen_position(p_screen) - _get_screens_origin(); // OS X native y-coordinate relative to _get_screens_origin() is negative, - // Godot expects a positive value + // Godot expects a positive value. position.y *= -1; return position; } @@ -2120,7 +1242,7 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay NSArray *screenArray = [NSScreen screens]; if ((NSUInteger)p_screen < [screenArray count]) { - // Note: Use frame to get the whole screen size + // Note: Use frame to get the whole screen size. NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; return Size2i(nsrect.size.width, nsrect.size.height) * screen_get_max_scale(); } @@ -2199,6 +1321,24 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay return Rect2i(); } +float DisplayServerOSX::screen_get_refresh_rate(int p_screen) const { + _THREAD_SAFE_METHOD_ + + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); + } + + NSArray *screenArray = [NSScreen screens]; + if ((NSUInteger)p_screen < [screenArray count]) { + NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription]; + const CGDisplayModeRef displayMode = CGDisplayCopyDisplayMode([[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); + const double displayRefreshRate = CGDisplayModeGetRefreshRate(displayMode); + return (float)displayRefreshRate; + } + ERR_PRINT("An error occured while trying to get the screen refresh rate."); + return SCREEN_REFRESH_RATE_FALLBACK; +} + Vector DisplayServerOSX::get_window_list() const { _THREAD_SAFE_METHOD_ @@ -2232,56 +1372,6 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay } } -void DisplayServerOSX::_send_window_event(const WindowData &wd, WindowEvent p_event) { - _THREAD_SAFE_METHOD_ - - if (!wd.event_callback.is_null()) { - Variant event = int(p_event); - Variant *eventp = &event; - Variant ret; - Callable::CallError ce; - wd.event_callback.call((const Variant **)&eventp, 1, ret, ce); - } -} - -DisplayServerOSX::WindowID DisplayServerOSX::_find_window_id(id p_window) { - for (Map::Element *E = windows.front(); E; E = E->next()) { - if (E->get().window_object == p_window) { - return E->key(); - } - } - return INVALID_WINDOW_ID; -} - -void DisplayServerOSX::_update_window(WindowData p_wd) { - bool borderless_full = false; - - if (p_wd.borderless) { - NSRect frameRect = [p_wd.window_object frame]; - NSRect screenRect = [[p_wd.window_object screen] frame]; - - // Check if our window covers up the screen - if (frameRect.origin.x <= screenRect.origin.x && frameRect.origin.y <= frameRect.origin.y && - frameRect.size.width >= screenRect.size.width && frameRect.size.height >= screenRect.size.height) { - borderless_full = true; - } - } - - if (borderless_full) { - // If the window covers up the screen set the level to above the main menu and hide on deactivate - [p_wd.window_object setLevel:NSMainMenuWindowLevel + 1]; - [p_wd.window_object setHidesOnDeactivate:YES]; - } else { - // Reset these when our window is not a borderless window that covers up the screen - if (p_wd.on_top && !p_wd.fullscreen) { - [p_wd.window_object setLevel:NSFloatingWindowLevel]; - } else { - [p_wd.window_object setLevel:NSNormalWindowLevel]; - } - [p_wd.window_object setHidesOnDeactivate:NO]; - } -} - void DisplayServerOSX::delete_sub_window(WindowID p_id) { _THREAD_SAFE_METHOD_ @@ -2294,24 +1384,6 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay [wd.window_object close]; } -void DisplayServerOSX::window_set_title(const String &p_title, WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - - [wd.window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]]; -} - -void DisplayServerOSX::window_set_mouse_passthrough(const Vector &p_region, WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - - wd.mpath = p_region; -} - void DisplayServerOSX::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) { _THREAD_SAFE_METHOD_ @@ -2350,6 +1422,24 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay wd.drop_files_callback = p_callable; } +void DisplayServerOSX::window_set_title(const String &p_title, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + [wd.window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]]; +} + +void DisplayServerOSX::window_set_mouse_passthrough(const Vector &p_region, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + wd.mpath = p_region; +} + int DisplayServerOSX::window_get_current_screen(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), -1); @@ -2381,39 +1471,6 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay } } -void DisplayServerOSX::window_set_transient(WindowID p_window, WindowID p_parent) { - _THREAD_SAFE_METHOD_ - ERR_FAIL_COND(p_window == p_parent); - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd_window = windows[p_window]; - - ERR_FAIL_COND(wd_window.transient_parent == p_parent); - - ERR_FAIL_COND_MSG(wd_window.on_top, "Windows with the 'on top' can't become transient."); - if (p_parent == INVALID_WINDOW_ID) { - //remove transient - ERR_FAIL_COND(wd_window.transient_parent == INVALID_WINDOW_ID); - ERR_FAIL_COND(!windows.has(wd_window.transient_parent)); - - WindowData &wd_parent = windows[wd_window.transient_parent]; - - wd_window.transient_parent = INVALID_WINDOW_ID; - wd_parent.transient_children.erase(p_window); - - [wd_window.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; - } else { - ERR_FAIL_COND(!windows.has(p_parent)); - ERR_FAIL_COND_MSG(wd_window.transient_parent != INVALID_WINDOW_ID, "Window already has a transient parent"); - WindowData &wd_parent = windows[p_parent]; - - wd_window.transient_parent = p_parent; - wd_parent.transient_children.insert(p_window); - - [wd_window.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; - } -} - Point2i DisplayServerOSX::window_get_position(WindowID p_window) const { _THREAD_SAFE_METHOD_ @@ -2425,14 +1482,14 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay const NSRect nsrect = [wd.window_object convertRectToScreen:contentRect]; Point2i pos; - // Return the position of the top-left corner, for OS X the y starts at the bottom + // Return the position of the top-left corner, for OS X the y starts at the bottom. const float scale = screen_get_max_scale(); pos.x = nsrect.origin.x; pos.y = (nsrect.origin.y + nsrect.size.height); pos *= scale; pos -= _get_screens_origin(); // OS X native y-coordinate relative to _get_screens_origin() is negative, - // Godot expects a positive value + // Godot expects a positive value. pos.y *= -1; return pos; } @@ -2445,7 +1502,7 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay Point2i position = p_position; // OS X native y-coordinate relative to _get_screens_origin() is negative, - // Godot passes a positive value + // Godot passes a positive value. position.y *= -1; position += _get_screens_origin(); position /= screen_get_max_scale(); @@ -2461,8 +1518,41 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay [wd.window_object setFrameTopLeftPoint:NSMakePoint(position.x - offset.x, position.y - offset.y)]; - _update_window(wd); - _ALLOW_DISCARD_ _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); + _update_window_style(wd); + update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); +} + +void DisplayServerOSX::window_set_transient(WindowID p_window, WindowID p_parent) { + _THREAD_SAFE_METHOD_ + ERR_FAIL_COND(p_window == p_parent); + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd_window = windows[p_window]; + + ERR_FAIL_COND(wd_window.transient_parent == p_parent); + + ERR_FAIL_COND_MSG(wd_window.on_top, "Windows with the 'on top' can't become transient."); + if (p_parent == INVALID_WINDOW_ID) { + // Remove transient. + ERR_FAIL_COND(wd_window.transient_parent == INVALID_WINDOW_ID); + ERR_FAIL_COND(!windows.has(wd_window.transient_parent)); + + WindowData &wd_parent = windows[wd_window.transient_parent]; + + wd_window.transient_parent = INVALID_WINDOW_ID; + wd_parent.transient_children.erase(p_window); + + [wd_window.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; + } else { + ERR_FAIL_COND(!windows.has(p_parent)); + ERR_FAIL_COND_MSG(wd_window.transient_parent != INVALID_WINDOW_ID, "Window already has a transient parent"); + WindowData &wd_parent = windows[p_parent]; + + wd_window.transient_parent = p_parent; + wd_parent.transient_children.insert(p_window); + + [wd_window.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; + } } void DisplayServerOSX::window_set_max_size(const Size2i p_size, WindowID p_window) { @@ -2537,97 +1627,30 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay NSRect new_frame = NSMakeRect(0, 0, size.x, size.y); new_frame = [wd.window_object frameRectForContentRect:new_frame]; - - new_frame.origin.x = top_left.x; - new_frame.origin.y = top_left.y - new_frame.size.height; - - [wd.window_object setFrame:new_frame display:YES]; - - _update_window(wd); -} - -Size2i DisplayServerOSX::window_get_size(WindowID p_window) const { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); - const WindowData &wd = windows[p_window]; - return wd.size; -} - -Size2i DisplayServerOSX::window_get_real_size(WindowID p_window) const { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); - const WindowData &wd = windows[p_window]; - NSRect frame = [wd.window_object frame]; - return Size2i(frame.size.width, frame.size.height) * screen_get_max_scale(); -} - -bool DisplayServerOSX::window_is_maximize_allowed(WindowID p_window) const { - return true; -} - -void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window) { - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - - if (!OS_OSX::get_singleton()->is_layered_allowed()) { - return; - } - if (wd.layered_window != p_enabled) { - if (p_enabled) { - [wd.window_object setBackgroundColor:[NSColor clearColor]]; - [wd.window_object setOpaque:NO]; - [wd.window_object setHasShadow:NO]; - CALayer *layer = [wd.window_view layer]; - if (layer) { - [layer setOpaque:NO]; - } -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - //TODO - implement transparency for Vulkan - } -#endif -#if defined(GLES3_ENABLED) - if (gl_manager) { - //TODO - reimplement OpenGLES - } -#endif - wd.layered_window = true; - } else { - [wd.window_object setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1]]; - [wd.window_object setOpaque:YES]; - [wd.window_object setHasShadow:YES]; - CALayer *layer = [wd.window_view layer]; - if (layer) { - [layer setOpaque:YES]; - } -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - //TODO - implement transparency for Vulkan - } -#endif -#if defined(GLES3_ENABLED) - if (gl_manager) { - //TODO - reimplement OpenGLES - } -#endif - wd.layered_window = false; - } -#if defined(GLES3_ENABLED) - if (gl_manager) { - //TODO - reimplement OpenGLES - } -#endif -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - //TODO - implement transparency for Vulkan - } -#endif - NSRect frameRect = [wd.window_object frame]; - [wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:YES]; - [wd.window_object setFrame:frameRect display:YES]; - } + + new_frame.origin.x = top_left.x; + new_frame.origin.y = top_left.y - new_frame.size.height; + + [wd.window_object setFrame:new_frame display:YES]; + + _update_window_style(wd); +} + +Size2i DisplayServerOSX::window_get_size(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); + const WindowData &wd = windows[p_window]; + return wd.size; +} + +Size2i DisplayServerOSX::window_get_real_size(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); + const WindowData &wd = windows[p_window]; + NSRect frame = [wd.window_object frame]; + return Size2i(frame.size.width, frame.size.height) * screen_get_max_scale(); } void DisplayServerOSX::window_set_mode(WindowMode p_mode, WindowID p_window) { @@ -2638,22 +1661,21 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay WindowMode old_mode = window_get_mode(p_window); if (old_mode == p_mode) { - return; // do nothing + return; // Do nothing. } switch (old_mode) { case WINDOW_MODE_WINDOWED: { - //do nothing + // Do nothing. } break; case WINDOW_MODE_MINIMIZED: { [wd.window_object deminiaturize:nil]; } break; + case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: case WINDOW_MODE_FULLSCREEN: { [wd.window_object setLevel:NSNormalWindowLevel]; - if (wd.layered_window) { - _set_window_per_pixel_transparency_enabled(true, p_window); - } - if (wd.resize_disabled) { //restore resize disabled + _set_window_per_pixel_transparency_enabled(true, p_window); + if (wd.resize_disabled) { // Restore resize disabled. [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; } if (wd.min_size != Size2i()) { @@ -2676,16 +1698,17 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay switch (p_mode) { case WINDOW_MODE_WINDOWED: { - //do nothing + // Do nothing. } break; case WINDOW_MODE_MINIMIZED: { [wd.window_object performMiniaturize:nil]; } break; + case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: case WINDOW_MODE_FULLSCREEN: { - if (wd.layered_window) - _set_window_per_pixel_transparency_enabled(false, p_window); - if (wd.resize_disabled) //fullscreen window should be resizable to work + _set_window_per_pixel_transparency_enabled(false, p_window); + if (wd.resize_disabled) { // Fullscreen window should be resizable to work. [wd.window_object setStyleMask:[wd.window_object styleMask] | NSWindowStyleMaskResizable]; + } [wd.window_object setContentMinSize:NSMakeSize(0, 0)]; [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; [wd.window_object toggleFullScreen:nil]; @@ -2705,7 +1728,7 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED); const WindowData &wd = windows[p_window]; - if (wd.fullscreen) { //if fullscreen, it's not in another mode + if (wd.fullscreen) { // If fullscreen, it's not in another mode. return WINDOW_MODE_FULLSCREEN; } if ([wd.window_object isZoomed] && !wd.resize_disabled) { @@ -2717,10 +1740,14 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay } } - // all other discarded, return windowed. + // All other discarded, return windowed. return WINDOW_MODE_WINDOWED; } +bool DisplayServerOSX::window_is_maximize_allowed(WindowID p_window) const { + return true; +} + void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { _THREAD_SAFE_METHOD_ @@ -2730,7 +1757,7 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay switch (p_flag) { case WINDOW_FLAG_RESIZE_DISABLED: { wd.resize_disabled = p_enabled; - if (wd.fullscreen) { //fullscreen window should be resizable, style will be applied on exiting fs + if (wd.fullscreen) { // Fullscreen window should be resizable, style will be applied on exiting fullscreen. return; } if (p_enabled) { @@ -2740,7 +1767,7 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay } } break; case WINDOW_FLAG_BORDERLESS: { - // OrderOut prevents a lose focus bug with the window + // OrderOut prevents a lose focus bug with the window. if ([wd.window_object isVisible]) { [wd.window_object orderOut:nil]; } @@ -2748,15 +1775,14 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay if (p_enabled) { [wd.window_object setStyleMask:NSWindowStyleMaskBorderless]; } else { - if (wd.layered_window) - _set_window_per_pixel_transparency_enabled(false, p_window); + _set_window_per_pixel_transparency_enabled(false, p_window); [wd.window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (wd.resize_disabled ? 0 : NSWindowStyleMaskResizable)]; - // Force update of the window styles + // Force update of the window styles. NSRect frameRect = [wd.window_object frame]; [wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO]; [wd.window_object setFrame:frameRect display:NO]; } - _update_window(wd); + _update_window_style(wd); if ([wd.window_object isVisible]) { if (wd.no_focus) { [wd.window_object orderFront:nil]; @@ -2777,9 +1803,8 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay } } break; case WINDOW_FLAG_TRANSPARENT: { - wd.layered_window = p_enabled; if (p_enabled) { - [wd.window_object setStyleMask:NSWindowStyleMaskBorderless]; // force borderless + [wd.window_object setStyleMask:NSWindowStyleMaskBorderless]; // Force borderless. } else if (!wd.borderless) { [wd.window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (wd.resize_disabled ? 0 : NSWindowStyleMaskResizable)]; } @@ -2882,28 +1907,103 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay wd.im_position = p_pos; } -bool DisplayServerOSX::get_swap_cancel_ok() { - return false; +DisplayServer::WindowID DisplayServerOSX::get_window_at_screen_position(const Point2i &p_position) const { + Point2i position = p_position; + position.y *= -1; + position += _get_screens_origin(); + position /= screen_get_max_scale(); + + NSInteger wnum = [NSWindow windowNumberAtPoint:NSMakePoint(position.x, position.y) belowWindowWithWindowNumber:0 /*topmost*/]; + for (Map::Element *E = windows.front(); E; E = E->next()) { + if ([E->get().window_object windowNumber] == wnum) { + return E->key(); + } + } + return INVALID_WINDOW_ID; } -void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) { +int64_t DisplayServerOSX::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { + ERR_FAIL_COND_V(!windows.has(p_window), 0); + switch (p_handle_type) { + case DISPLAY_HANDLE: { + return 0; // Not supported. + } + case WINDOW_HANDLE: { + return (int64_t)windows[p_window].window_object; + } + case WINDOW_VIEW: { + return (int64_t)windows[p_window].window_view; + } + default: { + return 0; + } + } +} + +void DisplayServerOSX::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { _THREAD_SAFE_METHOD_ - ERR_FAIL_INDEX(p_shape, CURSOR_MAX); + ERR_FAIL_COND(!windows.has(p_window)); + windows[p_window].instance_id = p_instance; +} - if (cursor_shape == p_shape) { - return; +ObjectID DisplayServerOSX::window_get_attached_instance_id(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), ObjectID()); + return windows[p_window].instance_id; +} + +void DisplayServerOSX::gl_window_make_current(DisplayServer::WindowID p_window_id) { +#if defined(GLES3_ENABLED) + gl_manager->window_make_current(p_window_id); +#endif +} + +void DisplayServerOSX::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { + _THREAD_SAFE_METHOD_ +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->set_use_vsync(p_vsync_mode); + } +#endif +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + context_vulkan->set_vsync_mode(p_window, p_vsync_mode); } +#endif +} - if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) { - cursor_shape = p_shape; - return; +DisplayServer::VSyncMode DisplayServerOSX::window_get_vsync_mode(WindowID p_window) const { + _THREAD_SAFE_METHOD_ +#if defined(GLES3_ENABLED) + if (gl_manager) { + return (gl_manager->is_using_vsync() ? DisplayServer::VSyncMode::VSYNC_ENABLED : DisplayServer::VSyncMode::VSYNC_DISABLED); + } +#endif +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + return context_vulkan->get_vsync_mode(p_window); } +#endif + return DisplayServer::VSYNC_ENABLED; +} + +Point2i DisplayServerOSX::ime_get_selection() const { + return im_selection; +} + +String DisplayServerOSX::ime_get_text() const { + return im_text; +} + +void DisplayServerOSX::cursor_update_shape() { + _THREAD_SAFE_METHOD_ - if (cursors[p_shape] != nullptr) { - [cursors[p_shape] set]; + if (cursors[cursor_shape] != nullptr) { + [cursors[cursor_shape] set]; } else { - switch (p_shape) { + switch (cursor_shape) { case CURSOR_ARROW: [[NSCursor arrowCursor] set]; break; @@ -2932,16 +2032,16 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay [[NSCursor operationNotAllowedCursor] set]; break; case CURSOR_VSIZE: - [_cursorFromSelector(@selector(_windowResizeNorthSouthCursor), @selector(resizeUpDownCursor)) set]; + [_cursor_from_selector(@selector(_windowResizeNorthSouthCursor), @selector(resizeUpDownCursor)) set]; break; case CURSOR_HSIZE: - [_cursorFromSelector(@selector(_windowResizeEastWestCursor), @selector(resizeLeftRightCursor)) set]; + [_cursor_from_selector(@selector(_windowResizeEastWestCursor), @selector(resizeLeftRightCursor)) set]; break; case CURSOR_BDIAGSIZE: - [_cursorFromSelector(@selector(_windowResizeNorthEastSouthWestCursor)) set]; + [_cursor_from_selector(@selector(_windowResizeNorthEastSouthWestCursor)) set]; break; case CURSOR_FDIAGSIZE: - [_cursorFromSelector(@selector(_windowResizeNorthWestSouthEastCursor)) set]; + [_cursor_from_selector(@selector(_windowResizeNorthWestSouthEastCursor)) set]; break; case CURSOR_MOVE: [[NSCursor arrowCursor] set]; @@ -2953,14 +2053,30 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay [[NSCursor resizeLeftRightCursor] set]; break; case CURSOR_HELP: - [_cursorFromSelector(@selector(_helpCursor)) set]; + [_cursor_from_selector(@selector(_helpCursor)) set]; break; default: { } } } +} + +void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_INDEX(p_shape, CURSOR_MAX); + + if (cursor_shape == p_shape) { + return; + } cursor_shape = p_shape; + + if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) { + return; + } + + cursor_update_shape(); } DisplayServerOSX::CursorShape DisplayServerOSX::cursor_get_shape() const { @@ -3055,7 +2171,6 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay NSCursor *cursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSMakePoint(p_hotspot.x, p_hotspot.y)]; - [cursors[p_shape] release]; cursors[p_shape] = cursor; Vector params; @@ -3068,94 +2183,32 @@ static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplay [cursor set]; } } - - [imgrep release]; - [nsimage release]; } else { - // Reset to default system cursor + // Reset to default system cursor. if (cursors[p_shape] != nullptr) { - [cursors[p_shape] release]; cursors[p_shape] = nullptr; } - CursorShape c = cursor_shape; - cursor_shape = CURSOR_MAX; - cursor_set_shape(c); + cursor_update_shape(); cursors_cache.erase(p_shape); } } -struct LayoutInfo { - String name; - String code; -}; - -static Vector kbd_layouts; -static int current_layout = 0; -static bool keyboard_layout_dirty = true; -static void keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info) { - kbd_layouts.clear(); - current_layout = 0; - keyboard_layout_dirty = true; -} - -void _update_keyboard_layouts() { - @autoreleasepool { - TISInputSourceRef cur_source = TISCopyCurrentKeyboardInputSource(); - NSString *cur_name = (NSString *)TISGetInputSourceProperty(cur_source, kTISPropertyLocalizedName); - CFRelease(cur_source); - - // Enum IME layouts - NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode }; - NSArray *list_ime = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_ime, false); - for (NSUInteger i = 0; i < [list_ime count]; i++) { - LayoutInfo ly; - NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName); - ly.name.parse_utf8([name UTF8String]); - - NSArray *langs = (NSArray *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyInputSourceLanguages); - ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]); - kbd_layouts.push_back(ly); - - if ([name isEqualToString:cur_name]) { - current_layout = kbd_layouts.size() - 1; - } - } - [list_ime release]; - - // Enum plain keyboard layouts - NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout }; - NSArray *list_kbd = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_kbd, false); - for (NSUInteger i = 0; i < [list_kbd count]; i++) { - LayoutInfo ly; - NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName); - ly.name.parse_utf8([name UTF8String]); - - NSArray *langs = (NSArray *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyInputSourceLanguages); - ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]); - kbd_layouts.push_back(ly); - - if ([name isEqualToString:cur_name]) { - current_layout = kbd_layouts.size() - 1; - } - } - [list_kbd release]; - } - - keyboard_layout_dirty = false; +bool DisplayServerOSX::get_swap_cancel_ok() { + return false; } int DisplayServerOSX::keyboard_get_layout_count() const { if (keyboard_layout_dirty) { - _update_keyboard_layouts(); + const_cast(this)->_update_keyboard_layouts(); } return kbd_layouts.size(); } void DisplayServerOSX::keyboard_set_current_layout(int p_index) { if (keyboard_layout_dirty) { - _update_keyboard_layouts(); + const_cast(this)->_update_keyboard_layouts(); } ERR_FAIL_INDEX(p_index, kbd_layouts.size()); @@ -3163,31 +2216,29 @@ void _update_keyboard_layouts() { NSString *cur_name = [NSString stringWithUTF8String:kbd_layouts[p_index].name.utf8().get_data()]; NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout }; - NSArray *list_kbd = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_kbd, false); + NSArray *list_kbd = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_kbd, false); for (NSUInteger i = 0; i < [list_kbd count]; i++) { - NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName); + NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName); if ([name isEqualToString:cur_name]) { - TISSelectInputSource((TISInputSourceRef)[list_kbd objectAtIndex:i]); + TISSelectInputSource((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i]); break; } } - [list_kbd release]; NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode }; - NSArray *list_ime = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_ime, false); + NSArray *list_ime = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_ime, false); for (NSUInteger i = 0; i < [list_ime count]; i++) { - NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName); + NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName); if ([name isEqualToString:cur_name]) { - TISSelectInputSource((TISInputSourceRef)[list_ime objectAtIndex:i]); + TISSelectInputSource((__bridge TISInputSourceRef)[list_ime objectAtIndex:i]); break; } } - [list_ime release]; } int DisplayServerOSX::keyboard_get_current_layout() const { if (keyboard_layout_dirty) { - _update_keyboard_layouts(); + const_cast(this)->_update_keyboard_layouts(); } return current_layout; @@ -3195,7 +2246,7 @@ void _update_keyboard_layouts() { String DisplayServerOSX::keyboard_get_layout_language(int p_index) const { if (keyboard_layout_dirty) { - _update_keyboard_layouts(); + const_cast(this)->_update_keyboard_layouts(); } ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), ""); @@ -3204,138 +2255,22 @@ void _update_keyboard_layouts() { String DisplayServerOSX::keyboard_get_layout_name(int p_index) const { if (keyboard_layout_dirty) { - _update_keyboard_layouts(); - } - - ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), ""); - return kbd_layouts[p_index].name; -} - -Key DisplayServerOSX::keyboard_get_keycode_from_physical(Key p_keycode) const { - if (p_keycode == Key::PAUSE) { - return p_keycode; - } - - Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK; - Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK; - unsigned int osx_keycode = unmapKey((Key)keycode_no_mod); - return (Key)(remapKey(osx_keycode, 0) | modifiers); -} - -void DisplayServerOSX::_push_input(const Ref &p_event) { - Ref ev = p_event; - Input::get_singleton()->parse_input_event(ev); -} - -void DisplayServerOSX::_release_pressed_events() { - _THREAD_SAFE_METHOD_ - if (Input::get_singleton()) { - Input::get_singleton()->release_pressed_events(); - } -} - -NSMenu *DisplayServerOSX::_get_dock_menu() const { - return dock_menu; -} - -void DisplayServerOSX::_menu_callback(id p_sender) { - if (![p_sender representedObject]) { - return; - } - - GlobalMenuItem *value = [p_sender representedObject]; - - if (value) { - if (value->checkable) { - if ([p_sender state] == NSControlStateValueOff) { - [p_sender setState:NSControlStateValueOn]; - } else { - [p_sender setState:NSControlStateValueOff]; - } - } - - if (value->callback != Callable()) { - Variant tag = value->meta; - Variant *tagp = &tag; - Variant ret; - Callable::CallError ce; - value->callback.call((const Variant **)&tagp, 1, ret, ce); - } - } -} - -void DisplayServerOSX::_send_event(NSEvent *p_event) { - // special case handling of command-period, which is traditionally a special - // shortcut in macOS and doesn't arrive at our regular keyDown handler. - if ([p_event type] == NSEventTypeKeyDown) { - if (([p_event modifierFlags] & NSEventModifierFlagCommand) && [p_event keyCode] == 0x2f) { - Ref k; - k.instantiate(); - - _get_key_modifier_state([p_event modifierFlags], k); - k->set_window_id(DisplayServerOSX::INVALID_WINDOW_ID); - k->set_pressed(true); - k->set_keycode(Key::PERIOD); - k->set_physical_keycode(Key::PERIOD); - k->set_echo([p_event isARepeat]); - - Input::get_singleton()->parse_input_event(k); - } + const_cast(this)->_update_keyboard_layouts(); } -} - -void DisplayServerOSX::_process_key_events() { - Ref k; - for (int i = 0; i < key_event_pos; i++) { - const KeyEvent &ke = key_event_buffer[i]; - if (ke.raw) { - // Non IME input - no composite characters, pass events as is - k.instantiate(); - - k->set_window_id(ke.window_id); - _get_key_modifier_state(ke.osx_state, k); - k->set_pressed(ke.pressed); - k->set_echo(ke.echo); - k->set_keycode(ke.keycode); - k->set_physical_keycode((Key)ke.physical_keycode); - k->set_unicode(ke.unicode); - - _push_input(k); - } else { - // IME input - if ((i == 0 && ke.keycode == Key::NONE) || (i > 0 && key_event_buffer[i - 1].keycode == Key::NONE)) { - k.instantiate(); - - k->set_window_id(ke.window_id); - _get_key_modifier_state(ke.osx_state, k); - k->set_pressed(ke.pressed); - k->set_echo(ke.echo); - k->set_keycode(Key::NONE); - k->set_physical_keycode(Key::NONE); - k->set_unicode(ke.unicode); - - _push_input(k); - } - if (ke.keycode != Key::NONE) { - k.instantiate(); - - k->set_window_id(ke.window_id); - _get_key_modifier_state(ke.osx_state, k); - k->set_pressed(ke.pressed); - k->set_echo(ke.echo); - k->set_keycode(ke.keycode); - k->set_physical_keycode((Key)ke.physical_keycode); - - if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == Key::NONE) { - k->set_unicode(key_event_buffer[i + 1].unicode); - } - _push_input(k); - } - } + ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), ""); + return kbd_layouts[p_index].name; +} + +Key DisplayServerOSX::keyboard_get_keycode_from_physical(Key p_keycode) const { + if (p_keycode == Key::PAUSE) { + return p_keycode; } - key_event_pos = 0; + Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK; + Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK; + unsigned int osx_keycode = KeyMappingOSX::unmap_key((Key)keycode_no_mod); + return (Key)(KeyMappingOSX::remap_key(osx_keycode, 0) | modifiers); } void DisplayServerOSX::process_events() { @@ -3363,8 +2298,8 @@ void _update_keyboard_layouts() { for (Map::Element *E = windows.front(); E; E = E->next()) { WindowData &wd = E->get(); if (wd.mpath.size() > 0) { - const Vector2 mpos = _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); - if (Geometry2D::is_point_in_polygon(mpos, wd.mpath)) { + update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); + if (Geometry2D::is_point_in_polygon(wd.mouse_pos, wd.mpath)) { if ([wd.window_object ignoresMouseEvents]) { [wd.window_object setIgnoresMouseEvents:NO]; } @@ -3379,9 +2314,6 @@ void _update_keyboard_layouts() { } } } - - [autoreleasePool drain]; - autoreleasePool = [[NSAutoreleasePool alloc] init]; } void DisplayServerOSX::force_process_and_drop_events() { @@ -3392,6 +2324,18 @@ void _update_keyboard_layouts() { drop_events = false; } +void DisplayServerOSX::release_rendering_thread() { +} + +void DisplayServerOSX::make_rendering_thread() { +} + +void DisplayServerOSX::swap_buffers() { +#if defined(GLES3_ENABLED) + gl_manager->swap_buffers(); +#endif +} + void DisplayServerOSX::set_native_icon(const String &p_filename) { _THREAD_SAFE_METHOD_ @@ -3404,10 +2348,10 @@ void _update_keyboard_layouts() { f->get_buffer((uint8_t *)&data.write[0], len); memdelete(f); - NSData *icon_data = [[[NSData alloc] initWithBytes:&data.write[0] length:len] autorelease]; + NSData *icon_data = [[NSData alloc] initWithBytes:&data.write[0] length:len]; ERR_FAIL_COND_MSG(!icon_data, "Error reading icon data."); - NSImage *icon = [[[NSImage alloc] initWithData:icon_data] autorelease]; + NSImage *icon = [[NSImage alloc] initWithData:icon_data]; ERR_FAIL_COND_MSG(!icon, "Error loading icon."); [NSApp setApplicationIconImage:icon]; @@ -3450,94 +2394,6 @@ void _update_keyboard_layouts() { [nsimg addRepresentation:imgrep]; [NSApp setApplicationIconImage:nsimg]; - - [imgrep release]; - [nsimg release]; -} - -void DisplayServerOSX::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { - _THREAD_SAFE_METHOD_ -#if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->swap_buffers(); - } -#endif -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->set_vsync_mode(p_window, p_vsync_mode); - } -#endif -} - -DisplayServer::VSyncMode DisplayServerOSX::window_get_vsync_mode(WindowID p_window) const { - _THREAD_SAFE_METHOD_ -#if defined(GLES3_ENABLED) - if (gl_manager) { - return (gl_manager->is_using_vsync() ? DisplayServer::VSyncMode::VSYNC_ENABLED : DisplayServer::VSyncMode::VSYNC_DISABLED); - } -#endif -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - return context_vulkan->get_vsync_mode(p_window); - } -#endif - return DisplayServer::VSYNC_ENABLED; -} - -Vector DisplayServerOSX::get_rendering_drivers_func() { - Vector drivers; - -#if defined(VULKAN_ENABLED) - drivers.push_back("vulkan"); -#endif -#if defined(GLES3_ENABLED) - drivers.push_back("opengl3"); -#endif - - return drivers; -} - -void DisplayServerOSX::gl_window_make_current(DisplayServer::WindowID p_window_id) { -#if defined(GLES3_ENABLED) - gl_manager->window_make_current(p_window_id); -#endif -} - -Point2i DisplayServerOSX::ime_get_selection() const { - return im_selection; -} - -String DisplayServerOSX::ime_get_text() const { - return im_text; -} - -DisplayServer::WindowID DisplayServerOSX::get_window_at_screen_position(const Point2i &p_position) const { - Point2i position = p_position; - position.y *= -1; - position += _get_screens_origin(); - position /= screen_get_max_scale(); - - NSInteger wnum = [NSWindow windowNumberAtPoint:NSMakePoint(position.x, position.y) belowWindowWithWindowNumber:0 /*topmost*/]; - for (Map::Element *E = windows.front(); E; E = E->next()) { - if ([E->get().window_object windowNumber] == wnum) { - return E->key(); - } - } - return INVALID_WINDOW_ID; -} - -void DisplayServerOSX::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - windows[p_window].instance_id = p_instance; -} - -ObjectID DisplayServerOSX::window_get_attached_instance_id(WindowID p_window) const { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND_V(!windows.has(p_window), ObjectID()); - return windows[p_window].instance_id; } DisplayServer *DisplayServerOSX::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { @@ -3548,179 +2404,48 @@ void _update_keyboard_layouts() { return ds; } -DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect) { - WindowID id; - const float scale = screen_get_max_scale(); - { - WindowData wd; - - wd.window_delegate = [[GodotWindowDelegate alloc] init]; - ERR_FAIL_COND_V_MSG(wd.window_delegate == nil, INVALID_WINDOW_ID, "Can't create a window delegate"); - [wd.window_delegate setWindowID:window_id_counter]; - - Point2i position = p_rect.position; - // OS X native y-coordinate relative to _get_screens_origin() is negative, - // Godot passes a positive value - position.y *= -1; - position += _get_screens_origin(); - - // initWithContentRect uses bottom-left corner of the window’s frame as origin. - wd.window_object = [[GodotWindow alloc] - initWithContentRect:NSMakeRect(position.x / scale, (position.y - p_rect.size.height) / scale, p_rect.size.width / scale, p_rect.size.height / scale) - styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable - backing:NSBackingStoreBuffered - defer:NO]; - ERR_FAIL_COND_V_MSG(wd.window_object == nil, INVALID_WINDOW_ID, "Can't create a window"); - - wd.window_view = [[GodotContentView alloc] init]; - ERR_FAIL_COND_V_MSG(wd.window_view == nil, INVALID_WINDOW_ID, "Can't create a window view"); - [wd.window_view setWindowID:window_id_counter]; - [wd.window_view setWantsLayer:TRUE]; - - [wd.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; - [wd.window_object setContentView:wd.window_view]; - [wd.window_object setDelegate:wd.window_delegate]; - [wd.window_object setAcceptsMouseMovedEvents:YES]; - [wd.window_object setRestorable:NO]; - [wd.window_object setColorSpace:[NSColorSpace sRGBColorSpace]]; - - if ([wd.window_object respondsToSelector:@selector(setTabbingMode:)]) { - [wd.window_object setTabbingMode:NSWindowTabbingModeDisallowed]; - } - - CALayer *layer = [wd.window_view layer]; - if (layer) { - layer.contentsScale = scale; - } +Vector DisplayServerOSX::get_rendering_drivers_func() { + Vector drivers; #if defined(VULKAN_ENABLED) - if (context_vulkan) { - Error err = context_vulkan->window_create(window_id_counter, p_vsync_mode, wd.window_view, p_rect.size.width, p_rect.size.height); - ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan context"); - } -#endif -#if defined(GLES3_ENABLED) - if (gl_manager) { - Error err = gl_manager->window_create(window_id_counter, wd.window_view, p_rect.size.width, p_rect.size.height); - ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL context"); - } + drivers.push_back("vulkan"); #endif - id = window_id_counter++; - windows[id] = wd; - } - - WindowData &wd = windows[id]; - window_set_mode(p_mode, id); - - const NSRect contentRect = [wd.window_view frame]; - wd.size.width = contentRect.size.width * scale; - wd.size.height = contentRect.size.height * scale; - - CALayer *layer = [wd.window_view layer]; - if (layer) { - layer.contentsScale = scale; - } - #if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->window_resize(id, wd.size.width, wd.size.height); - } -#endif -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->window_resize(id, wd.size.width, wd.size.height); - } + drivers.push_back("opengl3"); #endif - return id; -} - -void DisplayServerOSX::_dispatch_input_events(const Ref &p_event) { - ((DisplayServerOSX *)(get_singleton()))->_dispatch_input_event(p_event); -} - -void DisplayServerOSX::_dispatch_input_event(const Ref &p_event) { - _THREAD_SAFE_METHOD_ - if (!in_dispatch_input_event) { - in_dispatch_input_event = true; - - Variant ev = p_event; - Variant *evp = &ev; - Variant ret; - Callable::CallError ce; - - Ref event_from_window = p_event; - if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) { - //send to a window - if (windows.has(event_from_window->get_window_id())) { - Callable callable = windows[event_from_window->get_window_id()].input_event_callback; - if (callable.is_null()) { - return; - } - callable.call((const Variant **)&evp, 1, ret, ce); - } - } else { - //send to all windows - for (Map::Element *E = windows.front(); E; E = E->next()) { - Callable callable = E->get().input_event_callback; - if (callable.is_null()) { - continue; - } - callable.call((const Variant **)&evp, 1, ret, ce); - } - } - - in_dispatch_input_event = false; - } -} - -void DisplayServerOSX::release_rendering_thread() { -} - -void DisplayServerOSX::make_rendering_thread() { + return drivers; } -void DisplayServerOSX::swap_buffers() { -#if defined(GLES3_ENABLED) - gl_manager->swap_buffers(); -#endif +void DisplayServerOSX::register_osx_driver() { + register_create_function("osx", create_func, get_rendering_drivers_func); } DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); r_error = OK; - drop_events = false; memset(cursors, 0, sizeof(cursors)); - cursor_shape = CURSOR_ARROW; - - key_event_pos = 0; - mouse_mode = MOUSE_MODE_VISIBLE; - - autoreleasePool = [[NSAutoreleasePool alloc] init]; - - eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); - ERR_FAIL_COND(!eventSource); - CGEventSourceSetLocalEventsSuppressionInterval(eventSource, 0.0); + event_source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + ERR_FAIL_COND(!event_source); - keyboard_layout_dirty = true; - displays_arrangement_dirty = true; + CGEventSourceSetLocalEventsSuppressionInterval(event_source, 0.0); int screen_count = get_screen_count(); for (int i = 0; i < screen_count; i++) { display_max_scale = fmax(display_max_scale, screen_get_scale(i)); } - // Register to be notified on keyboard layout changes + // Register to be notified on keyboard layout changes. CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), - nullptr, keyboard_layout_changed, + nullptr, _keyboard_layout_changed, kTISNotifySelectedKeyboardInputSourceChanged, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately); - // Register to be notified on displays arrangement changes - CGDisplayRegisterReconfigurationCallback(displays_arrangement_changed, nullptr); + // Register to be notified on displays arrangement changes. + CGDisplayRegisterReconfigurationCallback(_displays_arrangement_changed, nullptr); NSMenuItem *menu_item; NSString *title; @@ -3730,11 +2455,11 @@ void _update_keyboard_layouts() { nsappname = [[NSProcessInfo processInfo] processName]; } - // Setup Dock menu + // Setup Dock menu. dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"]; - // Setup Apple menu - apple_menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; + // Setup Apple menu. + apple_menu = [[NSMenu alloc] initWithTitle:@""]; title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname]; [apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""]; @@ -3744,7 +2469,6 @@ void _update_keyboard_layouts() { menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Services", nil) action:nil keyEquivalent:@""]; [apple_menu setSubmenu:services forItem:menu_item]; [NSApp setServicesMenu:services]; - [services release]; [apple_menu addItem:[NSMenuItem separatorItem]]; @@ -3761,7 +2485,7 @@ void _update_keyboard_layouts() { title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname]; [apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; - // Add items to the menu bar + // Add items to the menu bar. NSMenu *main_menu = [NSApp mainMenu]; menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""]; [main_menu setSubmenu:apple_menu forItem:menu_item]; @@ -3823,15 +2547,7 @@ Point2i window_position( } DisplayServerOSX::~DisplayServerOSX() { - if (dock_menu) { - [dock_menu release]; - } - - for (Map::Element *E = submenu.front(); E; E = E->next()) { - [E->get() release]; - } - - //destroy all windows + // Destroy all windows. for (Map::Element *E = windows.front(); E;) { Map::Element *F = E; E = E->next(); @@ -3839,7 +2555,7 @@ Point2i window_position( [F->get().window_object close]; } - //destroy drivers + // Destroy drivers. #if defined(GLES3_ENABLED) if (gl_manager) { memdelete(gl_manager); @@ -3860,11 +2576,7 @@ Point2i window_position( #endif CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), nullptr, kTISNotifySelectedKeyboardInputSourceChanged, nullptr); - CGDisplayRemoveReconfigurationCallback(displays_arrangement_changed, nullptr); + CGDisplayRemoveReconfigurationCallback(_displays_arrangement_changed, nullptr); cursors_cache.clear(); } - -void DisplayServerOSX::register_osx_driver() { - register_create_function("osx", create_func, get_rendering_drivers_func); -} diff --git a/platform/osx/export/export_plugin.cpp b/platform/osx/export/export_plugin.cpp index 4d1f72f5c97c..f0b58efb63f0 100644 --- a/platform/osx/export/export_plugin.cpp +++ b/platform/osx/export/export_plugin.cpp @@ -777,6 +777,24 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Resources"); } + Vector translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations"); + if (translations.size() > 0) { + { + String fname = tmp_app_path_name + "/Contents/Resources/en.lproj"; + tmp_app_dir->make_dir_recursive(fname); + FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); + } + + for (const String &E : translations) { + Ref tr = ResourceLoader::load(E); + if (tr.is_valid()) { + String fname = tmp_app_path_name + "/Contents/Resources/" + tr->get_locale() + ".lproj"; + tmp_app_dir->make_dir_recursive(fname); + FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); + } + } + } + // Now process our template. bool found_binary = false; Vector dylibs_found; diff --git a/platform/osx/export/export_plugin.h b/platform/osx/export/export_plugin.h index 0c2ac90206f5..931ce7e41ab0 100644 --- a/platform/osx/export/export_plugin.h +++ b/platform/osx/export/export_plugin.h @@ -87,7 +87,7 @@ class EditorExportPlatformOSX : public EditorExportPlatform { for (int i = 0; i < pname.length(); i++) { char32_t c = pname[i]; - if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) { + if (!(is_ascii_alphanumeric_char(c) || c == '-' || c == '.')) { if (r_error) { *r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c)); } diff --git a/platform/osx/gl_manager_osx.h b/platform/osx/gl_manager_osx_legacy.h similarity index 81% rename from platform/osx/gl_manager_osx.h rename to platform/osx/gl_manager_osx_legacy.h index 0229b672a2f5..b5a1b9dd985f 100644 --- a/platform/osx/gl_manager_osx.h +++ b/platform/osx/gl_manager_osx_legacy.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* gl_manager_osx.h */ +/* gl_manager_osx_legacy.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef GL_MANAGER_OSX_H -#define GL_MANAGER_OSX_H +#ifndef GL_MANAGER_OSX_LEGACY_H +#define GL_MANAGER_OSX_LEGACY_H #if defined(OSX_ENABLED) && defined(GLES3_ENABLED) @@ -50,29 +50,21 @@ class GLManager_OSX { private: struct GLWindow { - GLWindow() { in_use = false; } - bool in_use; + int width = 0; + int height = 0; - DisplayServer::WindowID window_id; - int width; - int height; - - id window_view; - NSOpenGLContext *context; + id window_view = nullptr; + NSOpenGLContext *context = nullptr; }; - LocalVector _windows; - - NSOpenGLContext *_shared_context = nullptr; - GLWindow *_current_window; + Map windows; - Error _create_context(GLWindow &win); - void _internal_set_current_window(GLWindow *p_win); + NSOpenGLContext *shared_context = nullptr; + DisplayServer::WindowID current_window = DisplayServer::INVALID_WINDOW_ID; - GLWindow &get_window(unsigned int id) { return _windows[id]; } - const GLWindow &get_window(unsigned int id) const { return _windows[id]; } + Error create_context(GLWindow &win); - bool use_vsync; + bool use_vsync = false; ContextType context_type; public: @@ -80,7 +72,6 @@ class GLManager_OSX { void window_destroy(DisplayServer::WindowID p_window_id); void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height); - // get directly from the cached GLWindow int window_get_width(DisplayServer::WindowID p_window_id = 0); int window_get_height(DisplayServer::WindowID p_window_id = 0); @@ -91,6 +82,7 @@ class GLManager_OSX { void window_make_current(DisplayServer::WindowID p_window_id); void window_update(DisplayServer::WindowID p_window_id); + void window_set_per_pixel_transparency_enabled(DisplayServer::WindowID p_window_id, bool p_enabled); Error initialize(); @@ -101,6 +93,5 @@ class GLManager_OSX { ~GLManager_OSX(); }; -#endif // defined(OSX_ENABLED) && defined(GLES3_ENABLED) - -#endif // GL_MANAGER_OSX_H +#endif // OSX_ENABLED && GLES3_ENABLED +#endif // GL_MANAGER_OSX_LEGACY_H diff --git a/platform/osx/gl_manager_osx.mm b/platform/osx/gl_manager_osx_legacy.mm similarity index 73% rename from platform/osx/gl_manager_osx.mm rename to platform/osx/gl_manager_osx_legacy.mm index 3e70de8523fc..fbe64e32a32f 100644 --- a/platform/osx/gl_manager_osx.mm +++ b/platform/osx/gl_manager_osx_legacy.mm @@ -1,5 +1,5 @@ /*************************************************************************/ -/* gl_manager_osx.mm */ +/* gl_manager_osx_legacy.mm */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "gl_manager_osx.h" +#include "gl_manager_osx_legacy.h" #ifdef OSX_ENABLED #ifdef GLES3_ENABLED @@ -36,7 +36,7 @@ #include #include -Error GLManager_OSX::_create_context(GLWindow &win) { +Error GLManager_OSX::create_context(GLWindow &win) { NSOpenGLPixelFormatAttribute attributes[] = { NSOpenGLPFADoubleBuffer, NSOpenGLPFAClosestPolicy, @@ -50,10 +50,10 @@ NSOpenGLPixelFormat *pixel_format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; ERR_FAIL_COND_V(pixel_format == nil, ERR_CANT_CREATE); - win.context = [[NSOpenGLContext alloc] initWithFormat:pixel_format shareContext:_shared_context]; + win.context = [[NSOpenGLContext alloc] initWithFormat:pixel_format shareContext:shared_context]; ERR_FAIL_COND_V(win.context == nil, ERR_CANT_CREATE); - if (_shared_context == nullptr) { - _shared_context = win.context; + if (shared_context == nullptr) { + shared_context = win.context; } [win.context setView:win.window_view]; @@ -63,40 +63,27 @@ } Error GLManager_OSX::window_create(DisplayServer::WindowID p_window_id, id p_view, int p_width, int p_height) { - if (p_window_id >= (int)_windows.size()) { - _windows.resize(p_window_id + 1); - } - - GLWindow &win = _windows[p_window_id]; - win.in_use = true; - win.window_id = p_window_id; + GLWindow win; win.width = p_width; win.height = p_height; win.window_view = p_view; - if (_create_context(win) != OK) { - _windows.remove_at(_windows.size() - 1); + if (create_context(win) != OK) { return FAILED; } - window_make_current(_windows.size() - 1); + windows[p_window_id] = win; + window_make_current(p_window_id); return OK; } -void GLManager_OSX::_internal_set_current_window(GLWindow *p_win) { - _current_window = p_win; -} - void GLManager_OSX::window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) { - if (p_window_id == -1) { + if (!windows.has(p_window_id)) { return; } - GLWindow &win = _windows[p_window_id]; - if (!win.in_use) { - return; - } + GLWindow &win = windows[p_window_id]; win.width = p_width; win.height = p_height; @@ -116,24 +103,37 @@ } int GLManager_OSX::window_get_width(DisplayServer::WindowID p_window_id) { - return get_window(p_window_id).width; + if (!windows.has(p_window_id)) { + return 0; + } + + GLWindow &win = windows[p_window_id]; + return win.width; } int GLManager_OSX::window_get_height(DisplayServer::WindowID p_window_id) { - return get_window(p_window_id).height; + if (!windows.has(p_window_id)) { + return 0; + } + + GLWindow &win = windows[p_window_id]; + return win.height; } void GLManager_OSX::window_destroy(DisplayServer::WindowID p_window_id) { - GLWindow &win = get_window(p_window_id); - win.in_use = false; + if (!windows.has(p_window_id)) { + return; + } - if (_current_window == &win) { - _current_window = nullptr; + if (current_window == p_window_id) { + current_window = DisplayServer::INVALID_WINDOW_ID; } + + windows.erase(p_window_id); } void GLManager_OSX::release_current() { - if (!_current_window) { + if (current_window == DisplayServer::INVALID_WINDOW_ID) { return; } @@ -141,63 +141,59 @@ } void GLManager_OSX::window_make_current(DisplayServer::WindowID p_window_id) { - if (p_window_id == -1) { - return; - } - - GLWindow &win = _windows[p_window_id]; - if (!win.in_use) { + if (current_window == p_window_id) { return; } - - if (&win == _current_window) { + if (!windows.has(p_window_id)) { return; } + GLWindow &win = windows[p_window_id]; [win.context makeCurrentContext]; - _internal_set_current_window(&win); + current_window = p_window_id; } void GLManager_OSX::make_current() { - if (!_current_window) { + if (current_window == DisplayServer::INVALID_WINDOW_ID) { return; } - if (!_current_window->in_use) { - WARN_PRINT("current window not in use!"); + if (!windows.has(current_window)) { return; } - [_current_window->context makeCurrentContext]; + + GLWindow &win = windows[current_window]; + [win.context makeCurrentContext]; } void GLManager_OSX::swap_buffers() { - // NO NEED TO CALL SWAP BUFFERS for each window... - // see https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glXSwapBuffers.xml - - if (!_current_window) { - return; - } - if (!_current_window->in_use) { - WARN_PRINT("current window not in use!"); - return; + for (Map::Element *E = windows.front(); E; E = E->next()) { + [E->get().context flushBuffer]; } - [_current_window->context flushBuffer]; } void GLManager_OSX::window_update(DisplayServer::WindowID p_window_id) { - if (p_window_id == -1) { + if (!windows.has(p_window_id)) { return; } - GLWindow &win = _windows[p_window_id]; - if (!win.in_use) { - return; - } + GLWindow &win = windows[p_window_id]; + [win.context update]; +} - if (&win == _current_window) { +void GLManager_OSX::window_set_per_pixel_transparency_enabled(DisplayServer::WindowID p_window_id, bool p_enabled) { + if (!windows.has(p_window_id)) { return; } + GLWindow &win = windows[p_window_id]; + if (p_enabled) { + GLint opacity = 0; + [win.context setValues:&opacity forParameter:NSOpenGLContextParameterSurfaceOpacity]; + } else { + GLint opacity = 1; + [win.context setValues:&opacity forParameter:NSOpenGLContextParameterSurfaceOpacity]; + } [win.context update]; } @@ -207,6 +203,7 @@ void GLManager_OSX::set_use_vsync(bool p_use) { use_vsync = p_use; + CGLContextObj ctx = CGLGetCurrentContext(); if (ctx) { GLint swapInterval = p_use ? 1 : 0; @@ -221,8 +218,6 @@ GLManager_OSX::GLManager_OSX(ContextType p_context_type) { context_type = p_context_type; - use_vsync = false; - _current_window = nullptr; } GLManager_OSX::~GLManager_OSX() { diff --git a/platform/osx/godot_application.h b/platform/osx/godot_application.h new file mode 100644 index 000000000000..8d48a659f3b0 --- /dev/null +++ b/platform/osx/godot_application.h @@ -0,0 +1,42 @@ +/*************************************************************************/ +/* godot_application.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GODOT_APPLICATION_H +#define GODOT_APPLICATION_H + +#include "core/os/os.h" + +#import +#import + +@interface GodotApplication : NSApplication +@end + +#endif // GODOT_APPLICATION_H diff --git a/platform/osx/godot_application.mm b/platform/osx/godot_application.mm new file mode 100644 index 000000000000..00a58700e819 --- /dev/null +++ b/platform/osx/godot_application.mm @@ -0,0 +1,53 @@ +/*************************************************************************/ +/* godot_application.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "godot_application.h" + +#include "display_server_osx.h" + +@implementation GodotApplication + +- (void)sendEvent:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (ds) { + ds->send_event(event); + } + + // From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost + // This works around an AppKit bug, where key up events while holding + // down the command key don't get sent to the key window. + if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) { + [[self keyWindow] sendEvent:event]; + } else { + [super sendEvent:event]; + } +} + +@end diff --git a/platform/osx/godot_application_delegate.h b/platform/osx/godot_application_delegate.h new file mode 100644 index 000000000000..8eec762d8f7c --- /dev/null +++ b/platform/osx/godot_application_delegate.h @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* godot_application_delegate.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GODOT_APPLICATION_DELEGATE_H +#define GODOT_APPLICATION_DELEGATE_H + +#include "core/os/os.h" + +#import +#import + +@interface GodotApplicationDelegate : NSObject +- (void)forceUnbundledWindowActivationHackStep1; +- (void)forceUnbundledWindowActivationHackStep2; +- (void)forceUnbundledWindowActivationHackStep3; +@end + +#endif // GODOT_APPLICATION_DELEGATE_H diff --git a/platform/osx/godot_application_delegate.mm b/platform/osx/godot_application_delegate.mm new file mode 100644 index 000000000000..be284ba543e5 --- /dev/null +++ b/platform/osx/godot_application_delegate.mm @@ -0,0 +1,132 @@ +/*************************************************************************/ +/* godot_application_delegate.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "godot_application_delegate.h" + +#include "display_server_osx.h" +#include "os_osx.h" + +@implementation GodotApplicationDelegate + +- (void)forceUnbundledWindowActivationHackStep1 { + // Step 1: Switch focus to macOS SystemUIServer process. + // Required to perform step 2, TransformProcessType will fail if app is already the in focus. + for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.systemuiserver"]) { + [app activateWithOptions:NSApplicationActivateIgnoringOtherApps]; + break; + } + [self performSelector:@selector(forceUnbundledWindowActivationHackStep2) + withObject:nil + afterDelay:0.02]; +} + +- (void)forceUnbundledWindowActivationHackStep2 { + // Step 2: Register app as foreground process. + ProcessSerialNumber psn = { 0, kCurrentProcess }; + (void)TransformProcessType(&psn, kProcessTransformToForegroundApplication); + [self performSelector:@selector(forceUnbundledWindowActivationHackStep3) withObject:nil afterDelay:0.02]; +} + +- (void)forceUnbundledWindowActivationHackStep3 { + // Step 3: Switch focus back to app window. + [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps]; +} + +- (void)applicationDidFinishLaunching:(NSNotification *)notice { + NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; + if (nsappname == nil || isatty(STDOUT_FILENO) || isatty(STDIN_FILENO) || isatty(STDERR_FILENO)) { + // If the executable is started from terminal or is not bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored). + [self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02]; + } +} + +- (void)applicationDidResignActive:(NSNotification *)notification { + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); + } +} + +- (void)applicationDidBecomeActive:(NSNotification *)notification { + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); + } +} + +- (void)globalMenuCallback:(id)sender { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (ds) { + return ds->menu_callback(sender); + } +} + +- (NSMenu *)applicationDockMenu:(NSApplication *)sender { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (ds) { + return ds->get_dock_menu(); + } else { + return nullptr; + } +} + +- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename { + // Note: may be called called before main loop init! + OS_OSX *os = (OS_OSX *)OS::get_singleton(); + if (os) { + os->set_open_with_filename(String::utf8([filename UTF8String])); + } + +#ifdef TOOLS_ENABLED + // Open new instance. + if (os && os->get_main_loop()) { + List args; + args.push_back(os->get_open_with_filename()); + String exec = os->get_executable_path(); + os->create_process(exec, args); + } +#endif + return YES; +} + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (ds) { + ds->send_window_event(ds->get_window(DisplayServerOSX::MAIN_WINDOW_ID), DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); + } + return NSTerminateCancel; +} + +- (void)showAbout:(id)sender { + OS_OSX *os = (OS_OSX *)OS::get_singleton(); + if (os && os->get_main_loop()) { + os->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT); + } +} + +@end diff --git a/platform/osx/godot_content_view.h b/platform/osx/godot_content_view.h new file mode 100644 index 000000000000..7942d716dc14 --- /dev/null +++ b/platform/osx/godot_content_view.h @@ -0,0 +1,65 @@ +/*************************************************************************/ +/* godot_content_view.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GODOT_CONTENT_VIEW_H +#define GODOT_CONTENT_VIEW_H + +#include "servers/display_server.h" + +#import +#import + +#if defined(GLES3_ENABLED) +#import +#define RootView NSOpenGLView +#else +#define RootView NSView +#endif + +#import + +@interface GodotContentView : RootView { + DisplayServer::WindowID window_id; + NSTrackingArea *tracking_area; + NSMutableAttributedString *marked_text; + bool ime_input_event_in_progress; + bool mouse_down_control; + bool ignore_momentum_scroll; +} + +- (void)processScrollEvent:(NSEvent *)event button:(MouseButton)button factor:(double)factor; +- (void)processPanEvent:(NSEvent *)event dx:(double)dx dy:(double)dy; +- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index mask:(MouseButton)mask pressed:(bool)pressed; +- (void)setWindowID:(DisplayServer::WindowID)wid; +- (void)cancelComposition; + +@end + +#endif // GODOT_CONTENT_VIEW_H diff --git a/platform/osx/godot_content_view.mm b/platform/osx/godot_content_view.mm new file mode 100644 index 000000000000..4e831e1ccc86 --- /dev/null +++ b/platform/osx/godot_content_view.mm @@ -0,0 +1,760 @@ +/*************************************************************************/ +/* godot_content_view.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "godot_content_view.h" + +#include "display_server_osx.h" +#include "key_mapping_osx.h" + +@implementation GodotContentView + +- (id)init { + self = [super init]; + window_id = DisplayServer::INVALID_WINDOW_ID; + tracking_area = nil; + ime_input_event_in_progress = false; + mouse_down_control = false; + ignore_momentum_scroll = false; + [self updateTrackingAreas]; + + if (@available(macOS 10.13, *)) { + [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; +#if !defined(__aarch64__) // Do not build deprectead 10.13 code on ARM. + } else { + [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; +#endif + } + marked_text = [[NSMutableAttributedString alloc] init]; + return self; +} + +- (void)setWindowID:(DisplayServerOSX::WindowID)wid { + window_id = wid; +} + +// MARK: Backing Layer + +- (CALayer *)makeBackingLayer { + return [[CAMetalLayer class] layer]; +} + +- (void)updateLayer { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + ds->window_update(window_id); + [super updateLayer]; +} + +- (BOOL)wantsUpdateLayer { + return YES; +} + +- (BOOL)isOpaque { + return YES; +} + +// MARK: IME + +- (BOOL)hasMarkedText { + return (marked_text.length > 0); +} + +- (NSRange)markedRange { + return NSMakeRange(0, marked_text.length); +} + +- (NSRange)selectedRange { + static const NSRange kEmptyRange = { NSNotFound, 0 }; + return kEmptyRange; +} + +- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange { + if ([aString isKindOfClass:[NSAttributedString class]]) { + marked_text = [[NSMutableAttributedString alloc] initWithAttributedString:aString]; + } else { + marked_text = [[NSMutableAttributedString alloc] initWithString:aString]; + } + if (marked_text.length == 0) { + [self unmarkText]; + return; + } + + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + if (wd.im_active) { + ime_input_event_in_progress = true; + ds->update_im_text(Point2i(selectedRange.location, selectedRange.length), String::utf8([[marked_text mutableString] UTF8String])); + } +} + +- (void)unmarkText { + ime_input_event_in_progress = false; + [[marked_text mutableString] setString:@""]; + + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + if (wd.im_active) { + ds->update_im_text(Point2i(), String()); + } +} + +- (NSArray *)validAttributesForMarkedText { + return [NSArray array]; +} + +- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { + return nil; +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint { + return 0; +} + +- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return NSMakeRect(0, 0, 0, 0); + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + const NSRect content_rect = [wd.window_view frame]; + const float scale = ds->screen_get_max_scale(); + NSRect point_in_window_rect = NSMakeRect(wd.im_position.x / scale, content_rect.size.height - (wd.im_position.y / scale) - 1, 0, 0); + NSPoint point_on_screen = [wd.window_object convertRectToScreen:point_in_window_rect].origin; + + return NSMakeRect(point_on_screen.x, point_on_screen.y, 0, 0); +} + +- (void)cancelComposition { + [self unmarkText]; + [[NSTextInputContext currentInputContext] discardMarkedText]; +} + +- (void)insertText:(id)aString { + [self insertText:aString replacementRange:NSMakeRange(0, 0)]; +} + +- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange { + NSEvent *event = [NSApp currentEvent]; + + NSString *characters; + if ([aString isKindOfClass:[NSAttributedString class]]) { + characters = [aString string]; + } else { + characters = (NSString *)aString; + } + + NSCharacterSet *ctrl_chars = [NSCharacterSet controlCharacterSet]; + NSCharacterSet *wsnl_chars = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + if ([characters rangeOfCharacterFromSet:ctrl_chars].length && [characters rangeOfCharacterFromSet:wsnl_chars].length == 0) { + [[NSTextInputContext currentInputContext] discardMarkedText]; + [self cancelComposition]; + return; + } + + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + [self cancelComposition]; + return; + } + + Char16String text; + text.resize([characters length] + 1); + [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; + + String u32text; + u32text.parse_utf16(text.ptr(), text.length()); + + for (int i = 0; i < u32text.length(); i++) { + const char32_t codepoint = u32text[i]; + if ((codepoint & 0xFF00) == 0xF700) { + continue; + } + + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.osx_state = [event modifierFlags]; + ke.pressed = true; + ke.echo = false; + ke.raw = false; // IME input event. + ke.keycode = Key::NONE; + ke.physical_keycode = Key::NONE; + ke.unicode = codepoint; + + ds->push_to_key_event_buffer(ke); + } + [self cancelComposition]; +} + +// MARK: Drag and drop + +- (NSDragOperation)draggingEntered:(id)sender { + return NSDragOperationCopy; +} + +- (NSDragOperation)draggingUpdated:(id)sender { + return NSDragOperationCopy; +} + +- (BOOL)performDragOperation:(id)sender { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return NO; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + if (!wd.drop_files_callback.is_null()) { + Vector files; + NSPasteboard *pboard = [sender draggingPasteboard]; + + if (@available(macOS 10.13, *)) { + NSArray *items = pboard.pasteboardItems; + for (NSPasteboardItem *item in items) { + NSString *url = [item stringForType:NSPasteboardTypeFileURL]; + NSString *file = [NSURL URLWithString:url].path; + files.push_back(String::utf8([file UTF8String])); + } +#if !defined(__aarch64__) // Do not build deprectead 10.13 code on ARM. + } else { + NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType]; + for (NSString *file in filenames) { + files.push_back(String::utf8([file UTF8String])); + } +#endif + } + + Variant v = files; + Variant *vp = &v; + Variant ret; + Callable::CallError ce; + wd.drop_files_callback.call((const Variant **)&vp, 1, ret, ce); + } + + return NO; +} + +// MARK: Focus + +- (BOOL)canBecomeKeyView { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return YES; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + return !wd.no_focus; +} + +- (BOOL)acceptsFirstResponder { + return YES; +} + +// MARK: Mouse + +- (void)cursorUpdate:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds) { + return; + } + + ds->cursor_update_shape(); +} + +- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index mask:(MouseButton)mask pressed:(bool)pressed { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + MouseButton last_button_state = ds->mouse_get_button_state(); + + if (pressed) { + last_button_state |= mask; + } else { + last_button_state &= (MouseButton)~mask; + } + ds->mouse_set_button_state(last_button_state); + + Ref mb; + mb.instantiate(); + mb->set_window_id(window_id); + ds->update_mouse_pos(wd, [event locationInWindow]); + ds->get_key_modifier_state([event modifierFlags], mb); + mb->set_button_index(index); + mb->set_pressed(pressed); + mb->set_position(wd.mouse_pos); + mb->set_global_position(wd.mouse_pos); + mb->set_button_mask(last_button_state); + if (index == MouseButton::LEFT && pressed) { + mb->set_double_click([event clickCount] == 2); + } + + Input::get_singleton()->parse_input_event(mb); +} + +- (void)mouseDown:(NSEvent *)event { + if (([event modifierFlags] & NSEventModifierFlagControl)) { + mouse_down_control = true; + [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:true]; + } else { + mouse_down_control = false; + [self processMouseEvent:event index:MouseButton::LEFT mask:MouseButton::MASK_LEFT pressed:true]; + } +} + +- (void)mouseDragged:(NSEvent *)event { + [self mouseMoved:event]; +} + +- (void)mouseUp:(NSEvent *)event { + if (mouse_down_control) { + [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:false]; + } else { + [self processMouseEvent:event index:MouseButton::LEFT mask:MouseButton::MASK_LEFT pressed:false]; + } +} + +- (void)mouseMoved:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + NSPoint delta = NSMakePoint([event deltaX], [event deltaY]); + NSPoint mpos = [event locationInWindow]; + + if (ds->update_mouse_wrap(wd, delta, mpos, [event timestamp])) { + return; + } + + Ref mm; + mm.instantiate(); + + mm->set_window_id(window_id); + mm->set_button_mask(ds->mouse_get_button_state()); + ds->update_mouse_pos(wd, mpos); + mm->set_position(wd.mouse_pos); + mm->set_pressure([event pressure]); + if ([event subtype] == NSEventSubtypeTabletPoint) { + const NSPoint p = [event tilt]; + mm->set_tilt(Vector2(p.x, p.y)); + } + mm->set_global_position(wd.mouse_pos); + mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); + const Vector2i relativeMotion = Vector2i(delta.x, delta.y) * ds->screen_get_max_scale(); + mm->set_relative(relativeMotion); + ds->get_key_modifier_state([event modifierFlags], mm); + + Input::get_singleton()->parse_input_event(mm); +} + +- (void)rightMouseDown:(NSEvent *)event { + [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:true]; +} + +- (void)rightMouseDragged:(NSEvent *)event { + [self mouseMoved:event]; +} + +- (void)rightMouseUp:(NSEvent *)event { + [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:false]; +} + +- (void)otherMouseDown:(NSEvent *)event { + if ((int)[event buttonNumber] == 2) { + [self processMouseEvent:event index:MouseButton::MIDDLE mask:MouseButton::MASK_MIDDLE pressed:true]; + } else if ((int)[event buttonNumber] == 3) { + [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 mask:MouseButton::MASK_XBUTTON1 pressed:true]; + } else if ((int)[event buttonNumber] == 4) { + [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 mask:MouseButton::MASK_XBUTTON2 pressed:true]; + } else { + return; + } +} + +- (void)otherMouseDragged:(NSEvent *)event { + [self mouseMoved:event]; +} + +- (void)otherMouseUp:(NSEvent *)event { + if ((int)[event buttonNumber] == 2) { + [self processMouseEvent:event index:MouseButton::MIDDLE mask:MouseButton::MASK_MIDDLE pressed:false]; + } else if ((int)[event buttonNumber] == 3) { + [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 mask:MouseButton::MASK_XBUTTON1 pressed:false]; + } else if ((int)[event buttonNumber] == 4) { + [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 mask:MouseButton::MASK_XBUTTON2 pressed:false]; + } else { + return; + } +} + +- (void)mouseExited:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_CAPTURED) { + ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_EXIT); + } +} + +- (void)mouseEntered:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_CAPTURED) { + ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_ENTER); + } + + ds->cursor_update_shape(); +} + +- (void)magnifyWithEvent:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + Ref ev; + ev.instantiate(); + ev->set_window_id(window_id); + ds->get_key_modifier_state([event modifierFlags], ev); + ds->update_mouse_pos(wd, [event locationInWindow]); + ev->set_position(wd.mouse_pos); + ev->set_factor([event magnification] + 1.0); + + Input::get_singleton()->parse_input_event(ev); +} + +- (void)updateTrackingAreas { + if (tracking_area != nil) { + [self removeTrackingArea:tracking_area]; + } + + NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingCursorUpdate | NSTrackingInVisibleRect; + tracking_area = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; + + [self addTrackingArea:tracking_area]; + [super updateTrackingAreas]; +} + +// MARK: Keyboard + +- (void)keyDown:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + ignore_momentum_scroll = true; + + // Ignore all input if IME input is in progress. + if (!ime_input_event_in_progress) { + NSString *characters = [event characters]; + NSUInteger length = [characters length]; + + if (!wd.im_active && length > 0 && keycode_has_unicode(KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]))) { + // Fallback unicode character handler used if IME is not active. + Char16String text; + text.resize([characters length] + 1); + [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; + + String u32text; + u32text.parse_utf16(text.ptr(), text.length()); + + for (int i = 0; i < u32text.length(); i++) { + const char32_t codepoint = u32text[i]; + + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.osx_state = [event modifierFlags]; + ke.pressed = true; + ke.echo = [event isARepeat]; + ke.keycode = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]); + ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]); + ke.raw = true; + ke.unicode = codepoint; + + ds->push_to_key_event_buffer(ke); + } + } else { + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.osx_state = [event modifierFlags]; + ke.pressed = true; + ke.echo = [event isARepeat]; + ke.keycode = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]); + ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]); + ke.raw = false; + ke.unicode = 0; + + ds->push_to_key_event_buffer(ke); + } + } + + // Pass events to IME handler + if (wd.im_active) { + [self interpretKeyEvents:[NSArray arrayWithObject:event]]; + } +} + +- (void)flagsChanged:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + ignore_momentum_scroll = true; + + // Ignore all input if IME input is in progress + if (!ime_input_event_in_progress) { + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.echo = false; + ke.raw = true; + + int key = [event keyCode]; + int mod = [event modifierFlags]; + + if (key == 0x36 || key == 0x37) { + if (mod & NSEventModifierFlagCommand) { + mod &= ~NSEventModifierFlagCommand; + ke.pressed = true; + } else { + ke.pressed = false; + } + } else if (key == 0x38 || key == 0x3c) { + if (mod & NSEventModifierFlagShift) { + mod &= ~NSEventModifierFlagShift; + ke.pressed = true; + } else { + ke.pressed = false; + } + } else if (key == 0x3a || key == 0x3d) { + if (mod & NSEventModifierFlagOption) { + mod &= ~NSEventModifierFlagOption; + ke.pressed = true; + } else { + ke.pressed = false; + } + } else if (key == 0x3b || key == 0x3e) { + if (mod & NSEventModifierFlagControl) { + mod &= ~NSEventModifierFlagControl; + ke.pressed = true; + } else { + ke.pressed = false; + } + } else { + return; + } + + ke.osx_state = mod; + ke.keycode = KeyMappingOSX::remap_key(key, mod); + ke.physical_keycode = KeyMappingOSX::translate_key(key); + ke.unicode = 0; + + ds->push_to_key_event_buffer(ke); + } +} + +- (void)keyUp:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + // Ignore all input if IME input is in progress. + if (!ime_input_event_in_progress) { + NSString *characters = [event characters]; + NSUInteger length = [characters length]; + + // Fallback unicode character handler used if IME is not active. + if (!wd.im_active && length > 0 && keycode_has_unicode(KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]))) { + Char16String text; + text.resize([characters length] + 1); + [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; + + String u32text; + u32text.parse_utf16(text.ptr(), text.length()); + + for (int i = 0; i < u32text.length(); i++) { + const char32_t codepoint = u32text[i]; + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.osx_state = [event modifierFlags]; + ke.pressed = false; + ke.echo = [event isARepeat]; + ke.keycode = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]); + ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]); + ke.raw = true; + ke.unicode = codepoint; + + ds->push_to_key_event_buffer(ke); + } + } else { + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.osx_state = [event modifierFlags]; + ke.pressed = false; + ke.echo = [event isARepeat]; + ke.keycode = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]); + ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]); + ke.raw = true; + ke.unicode = 0; + + ds->push_to_key_event_buffer(ke); + } + } +} + +// MARK: Scroll and pan + +- (void)processScrollEvent:(NSEvent *)event button:(MouseButton)button factor:(double)factor { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + MouseButton mask = mouse_button_to_mask(button); + + Ref sc; + sc.instantiate(); + + sc->set_window_id(window_id); + ds->get_key_modifier_state([event modifierFlags], sc); + sc->set_button_index(button); + sc->set_factor(factor); + sc->set_pressed(true); + sc->set_position(wd.mouse_pos); + sc->set_global_position(wd.mouse_pos); + MouseButton last_button_state = ds->mouse_get_button_state() | (MouseButton)mask; + sc->set_button_mask(last_button_state); + ds->mouse_set_button_state(last_button_state); + + Input::get_singleton()->parse_input_event(sc); + + sc.instantiate(); + sc->set_window_id(window_id); + sc->set_button_index(button); + sc->set_factor(factor); + sc->set_pressed(false); + sc->set_position(wd.mouse_pos); + sc->set_global_position(wd.mouse_pos); + last_button_state &= (MouseButton)~mask; + sc->set_button_mask(last_button_state); + ds->mouse_set_button_state(last_button_state); + + Input::get_singleton()->parse_input_event(sc); +} + +- (void)processPanEvent:(NSEvent *)event dx:(double)dx dy:(double)dy { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + Ref pg; + pg.instantiate(); + + pg->set_window_id(window_id); + ds->get_key_modifier_state([event modifierFlags], pg); + pg->set_position(wd.mouse_pos); + pg->set_delta(Vector2(-dx, -dy)); + + Input::get_singleton()->parse_input_event(pg); +} + +- (void)scrollWheel:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + ds->update_mouse_pos(wd, [event locationInWindow]); + + double delta_x = [event scrollingDeltaX]; + double delta_y = [event scrollingDeltaY]; + + if ([event hasPreciseScrollingDeltas]) { + delta_x *= 0.03; + delta_y *= 0.03; + } + + if ([event momentumPhase] != NSEventPhaseNone) { + if (ignore_momentum_scroll) { + return; + } + } else { + ignore_momentum_scroll = false; + } + + if ([event phase] != NSEventPhaseNone || [event momentumPhase] != NSEventPhaseNone) { + [self processPanEvent:event dx:delta_x dy:delta_y]; + } else { + if (fabs(delta_x)) { + [self processScrollEvent:event button:(0 > delta_x ? MouseButton::WHEEL_RIGHT : MouseButton::WHEEL_LEFT) factor:fabs(delta_x * 0.3)]; + } + if (fabs(delta_y)) { + [self processScrollEvent:event button:(0 < delta_y ? MouseButton::WHEEL_UP : MouseButton::WHEEL_DOWN) factor:fabs(delta_y * 0.3)]; + } + } +} + +@end diff --git a/platform/osx/godot_main_osx.mm b/platform/osx/godot_main_osx.mm index 64a93f7292cd..7fabfaa1b72b 100644 --- a/platform/osx/godot_main_osx.mm +++ b/platform/osx/godot_main_osx.mm @@ -37,7 +37,7 @@ int main(int argc, char **argv) { #if defined(VULKAN_ENABLED) - // MoltenVK - enable full component swizzling support + // MoltenVK - enable full component swizzling support. setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1); #endif @@ -45,13 +45,14 @@ int main(int argc, char **argv) { const char *dbg_arg = "-NSDocumentRevisionsDebugMode"; printf("arguments\n"); for (int i = 0; i < argc; i++) { - if (strcmp(dbg_arg, argv[i]) == 0) + if (strcmp(dbg_arg, argv[i]) == 0) { first_arg = i + 2; + } printf("%i: %s\n", i, argv[i]); }; #ifdef DEBUG_ENABLED - // lets report the path we made current after all that + // Lets report the path we made current after all that. char cwd[4096]; getcwd(cwd, 4096); printf("Current path: %s\n", cwd); @@ -60,23 +61,25 @@ int main(int argc, char **argv) { OS_OSX os; Error err; - // We must override main when testing is enabled + // We must override main when testing is enabled. TEST_MAIN_OVERRIDE - if (os.open_with_filename != "") { - char *argv_c = (char *)malloc(os.open_with_filename.utf8().size()); - memcpy(argv_c, os.open_with_filename.utf8().get_data(), os.open_with_filename.utf8().size()); + if (os.get_open_with_filename() != "") { + char *argv_c = (char *)malloc(os.get_open_with_filename().utf8().size()); + memcpy(argv_c, os.get_open_with_filename().utf8().get_data(), os.get_open_with_filename().utf8().size()); err = Main::setup(argv[0], 1, &argv_c); free(argv_c); } else { err = Main::setup(argv[0], argc - first_arg, &argv[first_arg]); } - if (err != OK) + if (err != OK) { return 255; + } - if (Main::start()) - os.run(); // it is actually the OS that decides how to run + if (Main::start()) { + os.run(); // It is actually the OS that decides how to run. + } Main::cleanup(); diff --git a/platform/osx/godot_menu_item.h b/platform/osx/godot_menu_item.h new file mode 100644 index 000000000000..50c4709c1884 --- /dev/null +++ b/platform/osx/godot_menu_item.h @@ -0,0 +1,52 @@ +/*************************************************************************/ +/* godot_menu_item.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GODOT_MENU_ITEM_H +#define GODOT_MENU_ITEM_H + +#include "servers/display_server.h" + +#import +#import + +@interface GodotMenuItem : NSObject { +@public + Callable callback; + Variant meta; + int id; + bool checkable; +} + +@end + +@implementation GodotMenuItem +@end + +#endif // GODOT_MENU_ITEM_H diff --git a/platform/osx/godot_window.h b/platform/osx/godot_window.h new file mode 100644 index 000000000000..16ff101142af --- /dev/null +++ b/platform/osx/godot_window.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* godot_window.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GODOT_WINDOW_H +#define GODOT_WINDOW_H + +#include "servers/display_server.h" + +#import +#import + +@interface GodotWindow : NSWindow { + DisplayServer::WindowID window_id; +} + +- (void)setWindowID:(DisplayServer::WindowID)wid; + +@end + +#endif //GODOT_WINDOW_H diff --git a/platform/osx/godot_window.mm b/platform/osx/godot_window.mm new file mode 100644 index 000000000000..772a2ddb9f9c --- /dev/null +++ b/platform/osx/godot_window.mm @@ -0,0 +1,69 @@ +/*************************************************************************/ +/* godot_window.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "godot_window.h" + +#include "display_server_osx.h" + +@implementation GodotWindow + +- (id)init { + self = [super init]; + window_id = DisplayServer::INVALID_WINDOW_ID; + return self; +} + +- (void)setWindowID:(DisplayServerOSX::WindowID)wid { + window_id = wid; +} + +- (BOOL)canBecomeKeyWindow { + // Required for NSWindowStyleMaskBorderless windows. + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return YES; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + return !wd.no_focus; +} + +- (BOOL)canBecomeMainWindow { + // Required for NSWindowStyleMaskBorderless windows. + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return YES; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + return !wd.no_focus; +} + +@end diff --git a/platform/osx/godot_window_delegate.h b/platform/osx/godot_window_delegate.h new file mode 100644 index 000000000000..8a1f681fcd87 --- /dev/null +++ b/platform/osx/godot_window_delegate.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* godot_window_delegate.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GODOT_WINDOW_DELEGATE_H +#define GODOT_WINDOW_DELEGATE_H + +#include "servers/display_server.h" + +#import +#import + +@interface GodotWindowDelegate : NSObject { + DisplayServer::WindowID window_id; +} + +- (void)setWindowID:(DisplayServer::WindowID)wid; + +@end + +#endif //GODOT_WINDOW_DELEGATE_H diff --git a/platform/osx/godot_window_delegate.mm b/platform/osx/godot_window_delegate.mm new file mode 100644 index 000000000000..1742be987d75 --- /dev/null +++ b/platform/osx/godot_window_delegate.mm @@ -0,0 +1,254 @@ +/*************************************************************************/ +/* godot_window_delegate.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "godot_window_delegate.h" + +#include "display_server_osx.h" + +@implementation GodotWindowDelegate + +- (void)setWindowID:(DisplayServer::WindowID)wid { + window_id = wid; +} + +- (BOOL)windowShouldClose:(id)sender { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return YES; + } + + ds->send_window_event(ds->get_window(window_id), DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); + return NO; +} + +- (void)windowWillClose:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + while (wd.transient_children.size()) { + ds->window_set_transient(wd.transient_children.front()->get(), DisplayServerOSX::INVALID_WINDOW_ID); + } + + if (wd.transient_parent != DisplayServerOSX::INVALID_WINDOW_ID) { + ds->window_set_transient(window_id, DisplayServerOSX::INVALID_WINDOW_ID); + } + + ds->window_destroy(window_id); +} + +- (void)windowDidEnterFullScreen:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + wd.fullscreen = true; + // Reset window size limits. + [wd.window_object setContentMinSize:NSMakeSize(0, 0)]; + [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; + + // Force window resize event. + [self windowDidResize:notification]; +} + +- (void)windowDidExitFullScreen:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + wd.fullscreen = false; + + // Set window size limits. + const float scale = ds->screen_get_max_scale(); + if (wd.min_size != Size2i()) { + Size2i size = wd.min_size / scale; + [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)]; + } + if (wd.max_size != Size2i()) { + Size2i size = wd.max_size / scale; + [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; + } + + // Restore resizability state. + if (wd.resize_disabled) { + [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; + } + + // Restore on-top state. + if (wd.on_top) { + [wd.window_object setLevel:NSFloatingWindowLevel]; + } + + // Force window resize event. + [self windowDidResize:notification]; +} + +- (void)windowDidChangeBackingProperties:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + CGFloat new_scale_factor = [wd.window_object backingScaleFactor]; + CGFloat old_scale_factor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue]; + + if (new_scale_factor != old_scale_factor) { + // Set new display scale and window size. + const float scale = ds->screen_get_max_scale(); + const NSRect content_rect = [wd.window_view frame]; + + wd.size.width = content_rect.size.width * scale; + wd.size.height = content_rect.size.height * scale; + + ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_DPI_CHANGE); + + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } + + //Force window resize event + [self windowDidResize:notification]; + } +} + +- (void)windowDidResize:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + const NSRect content_rect = [wd.window_view frame]; + const float scale = ds->screen_get_max_scale(); + wd.size.width = content_rect.size.width * scale; + wd.size.height = content_rect.size.height * scale; + + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } + + ds->window_resize(window_id, wd.size.width, wd.size.height); + + if (!wd.rect_changed_callback.is_null()) { + Variant size = Rect2i(ds->window_get_position(window_id), ds->window_get_size(window_id)); + Variant *sizep = &size; + Variant ret; + Callable::CallError ce; + wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); + } +} + +- (void)windowDidMove:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + ds->release_pressed_events(); + + if (!wd.rect_changed_callback.is_null()) { + Variant size = Rect2i(ds->window_get_position(window_id), ds->window_get_size(window_id)); + Variant *sizep = &size; + Variant ret; + Callable::CallError ce; + wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); + } +} + +- (void)windowDidBecomeKey:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + if (ds->mouse_get_mode() == DisplayServer::MOUSE_MODE_CAPTURED) { + const NSRect content_rect = [wd.window_view frame]; + NSRect point_in_window_rect = NSMakeRect(content_rect.size.width / 2, content_rect.size.height / 2, 0, 0); + NSPoint point_on_screen = [[wd.window_view window] convertRectToScreen:point_in_window_rect].origin; + CGPoint mouse_warp_pos = { point_on_screen.x, CGDisplayBounds(CGMainDisplayID()).size.height - point_on_screen.y }; + CGWarpMouseCursorPosition(mouse_warp_pos); + } else { + ds->update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); + } + + ds->set_last_focused_window(window_id); + ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN); +} + +- (void)windowDidResignKey:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + ds->release_pressed_events(); + ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT); +} + +- (void)windowDidMiniaturize:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + ds->release_pressed_events(); + ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT); +} + +- (void)windowDidDeminiaturize:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + ds->set_last_focused_window(window_id); + ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN); +} + +@end diff --git a/platform/osx/joypad_osx.cpp b/platform/osx/joypad_osx.cpp index c2356f12cd09..d518206f0476 100644 --- a/platform/osx/joypad_osx.cpp +++ b/platform/osx/joypad_osx.cpp @@ -80,7 +80,7 @@ int joypad::get_hid_element_state(rec_element *p_element) const { if (IOHIDDeviceGetValue(device_ref, p_element->ref, &valueRef) == kIOReturnSuccess) { value = (SInt32)IOHIDValueGetIntegerValue(valueRef); - /* record min and max for auto calibration */ + // Record min and max for auto calibration. if (value < p_element->min) { p_element->min = value; } @@ -179,7 +179,7 @@ void joypad::add_hid_element(IOHIDElementRef p_element) { break; } - if (list) { /* add to list */ + if (list) { // Add to list. rec_element element; element.ref = p_element; @@ -280,7 +280,7 @@ static String _hex_str(uint8_t p_byte) { bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) { p_joy->device_ref = p_device_ref; - /* get device name */ + // Get device name. String name; char c_name[256]; CFTypeRef refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDProductKey)); @@ -319,7 +319,7 @@ bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) { sprintf(uid, "%08x%08x%08x%08x", OSSwapHostToBigInt32(3), OSSwapHostToBigInt32(vendor), OSSwapHostToBigInt32(product_id), OSSwapHostToBigInt32(version)); input->joy_connection_changed(id, true, name, uid); } else { - //bluetooth device + // Bluetooth device. String guid = "05000000"; for (int i = 0; i < 12; i++) { if (i < name.size()) @@ -445,7 +445,7 @@ static HatMask process_hat_value(int p_min, int p_max, int p_value, bool p_offse void JoypadOSX::poll_joypads() const { while (CFRunLoopRunInMode(GODOT_JOY_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) { - /* no-op. Pending callbacks will fire. */ + // No-op. Pending callbacks will fire. } } @@ -568,7 +568,7 @@ void JoypadOSX::config_hid_manager(CFArrayRef p_matching_array) const { IOHIDManagerScheduleWithRunLoop(hid_manager, runloop, GODOT_JOY_LOOP_RUN_MODE); while (CFRunLoopRunInMode(GODOT_JOY_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) { - /* no-op. Callback fires once per existing device. */ + // No-op. Callback fires once per existing device. } } diff --git a/platform/osx/joypad_osx.h b/platform/osx/joypad_osx.h index 8ea1033a779f..4ca7fb169869 100644 --- a/platform/osx/joypad_osx.h +++ b/platform/osx/joypad_osx.h @@ -66,7 +66,7 @@ struct joypad { int id = 0; bool offset_hat = false; - io_service_t ffservice = 0; /* Interface for force feedback, 0 = no ff */ + io_service_t ffservice = 0; // Interface for force feedback, 0 = no ff. FFCONSTANTFORCE ff_constant_force; FFDeviceObjectReference ff_device = nullptr; FFEffectObjectReference ff_object = nullptr; diff --git a/platform/osx/key_mapping_osx.h b/platform/osx/key_mapping_osx.h new file mode 100644 index 000000000000..252cc907bbb4 --- /dev/null +++ b/platform/osx/key_mapping_osx.h @@ -0,0 +1,52 @@ +/*************************************************************************/ +/* key_mapping_osx.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef KEY_MAPPING_OSX_H +#define KEY_MAPPING_OSX_H + +#include "core/os/keyboard.h" + +class KeyMappingOSX { + KeyMappingOSX() {} + + static bool is_numpad_key(unsigned int key); + +public: + // Mappings input. + static Key translate_key(unsigned int key); + static unsigned int unmap_key(Key key); + static Key remap_key(unsigned int key, unsigned int state); + + // Mapping for menu shortcuts. + static String keycode_get_native_string(Key p_keycode); + static unsigned int keycode_get_native_mask(Key p_keycode); +}; + +#endif // KEY_MAPPING_OSX_H diff --git a/platform/osx/key_mapping_osx.mm b/platform/osx/key_mapping_osx.mm new file mode 100644 index 000000000000..fde92068244b --- /dev/null +++ b/platform/osx/key_mapping_osx.mm @@ -0,0 +1,477 @@ +/*************************************************************************/ +/* key_mapping_osx.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "key_mapping_osx.h" + +#include +#include + +bool KeyMappingOSX::is_numpad_key(unsigned int key) { + static const unsigned int table[] = { + 0x41, /* kVK_ANSI_KeypadDecimal */ + 0x43, /* kVK_ANSI_KeypadMultiply */ + 0x45, /* kVK_ANSI_KeypadPlus */ + 0x47, /* kVK_ANSI_KeypadClear */ + 0x4b, /* kVK_ANSI_KeypadDivide */ + 0x4c, /* kVK_ANSI_KeypadEnter */ + 0x4e, /* kVK_ANSI_KeypadMinus */ + 0x51, /* kVK_ANSI_KeypadEquals */ + 0x52, /* kVK_ANSI_Keypad0 */ + 0x53, /* kVK_ANSI_Keypad1 */ + 0x54, /* kVK_ANSI_Keypad2 */ + 0x55, /* kVK_ANSI_Keypad3 */ + 0x56, /* kVK_ANSI_Keypad4 */ + 0x57, /* kVK_ANSI_Keypad5 */ + 0x58, /* kVK_ANSI_Keypad6 */ + 0x59, /* kVK_ANSI_Keypad7 */ + 0x5b, /* kVK_ANSI_Keypad8 */ + 0x5c, /* kVK_ANSI_Keypad9 */ + 0x5f, /* kVK_JIS_KeypadComma */ + 0x00 + }; + for (int i = 0; table[i] != 0; i++) { + if (key == table[i]) { + return true; + } + } + return false; +} + +// Keyboard symbol translation table. +static const Key _osx_to_godot_table[128] = { + /* 00 */ Key::A, + /* 01 */ Key::S, + /* 02 */ Key::D, + /* 03 */ Key::F, + /* 04 */ Key::H, + /* 05 */ Key::G, + /* 06 */ Key::Z, + /* 07 */ Key::X, + /* 08 */ Key::C, + /* 09 */ Key::V, + /* 0a */ Key::SECTION, /* ISO Section */ + /* 0b */ Key::B, + /* 0c */ Key::Q, + /* 0d */ Key::W, + /* 0e */ Key::E, + /* 0f */ Key::R, + /* 10 */ Key::Y, + /* 11 */ Key::T, + /* 12 */ Key::KEY_1, + /* 13 */ Key::KEY_2, + /* 14 */ Key::KEY_3, + /* 15 */ Key::KEY_4, + /* 16 */ Key::KEY_6, + /* 17 */ Key::KEY_5, + /* 18 */ Key::EQUAL, + /* 19 */ Key::KEY_9, + /* 1a */ Key::KEY_7, + /* 1b */ Key::MINUS, + /* 1c */ Key::KEY_8, + /* 1d */ Key::KEY_0, + /* 1e */ Key::BRACERIGHT, + /* 1f */ Key::O, + /* 20 */ Key::U, + /* 21 */ Key::BRACELEFT, + /* 22 */ Key::I, + /* 23 */ Key::P, + /* 24 */ Key::ENTER, + /* 25 */ Key::L, + /* 26 */ Key::J, + /* 27 */ Key::APOSTROPHE, + /* 28 */ Key::K, + /* 29 */ Key::SEMICOLON, + /* 2a */ Key::BACKSLASH, + /* 2b */ Key::COMMA, + /* 2c */ Key::SLASH, + /* 2d */ Key::N, + /* 2e */ Key::M, + /* 2f */ Key::PERIOD, + /* 30 */ Key::TAB, + /* 31 */ Key::SPACE, + /* 32 */ Key::QUOTELEFT, + /* 33 */ Key::BACKSPACE, + /* 34 */ Key::UNKNOWN, + /* 35 */ Key::ESCAPE, + /* 36 */ Key::META, + /* 37 */ Key::META, + /* 38 */ Key::SHIFT, + /* 39 */ Key::CAPSLOCK, + /* 3a */ Key::ALT, + /* 3b */ Key::CTRL, + /* 3c */ Key::SHIFT, + /* 3d */ Key::ALT, + /* 3e */ Key::CTRL, + /* 3f */ Key::UNKNOWN, /* Function */ + /* 40 */ Key::UNKNOWN, /* F17 */ + /* 41 */ Key::KP_PERIOD, + /* 42 */ Key::UNKNOWN, + /* 43 */ Key::KP_MULTIPLY, + /* 44 */ Key::UNKNOWN, + /* 45 */ Key::KP_ADD, + /* 46 */ Key::UNKNOWN, + /* 47 */ Key::NUMLOCK, /* Really KeypadClear... */ + /* 48 */ Key::VOLUMEUP, /* VolumeUp */ + /* 49 */ Key::VOLUMEDOWN, /* VolumeDown */ + /* 4a */ Key::VOLUMEMUTE, /* Mute */ + /* 4b */ Key::KP_DIVIDE, + /* 4c */ Key::KP_ENTER, + /* 4d */ Key::UNKNOWN, + /* 4e */ Key::KP_SUBTRACT, + /* 4f */ Key::UNKNOWN, /* F18 */ + /* 50 */ Key::UNKNOWN, /* F19 */ + /* 51 */ Key::EQUAL, /* KeypadEqual */ + /* 52 */ Key::KP_0, + /* 53 */ Key::KP_1, + /* 54 */ Key::KP_2, + /* 55 */ Key::KP_3, + /* 56 */ Key::KP_4, + /* 57 */ Key::KP_5, + /* 58 */ Key::KP_6, + /* 59 */ Key::KP_7, + /* 5a */ Key::UNKNOWN, /* F20 */ + /* 5b */ Key::KP_8, + /* 5c */ Key::KP_9, + /* 5d */ Key::YEN, /* JIS Yen */ + /* 5e */ Key::UNDERSCORE, /* JIS Underscore */ + /* 5f */ Key::COMMA, /* JIS KeypadComma */ + /* 60 */ Key::F5, + /* 61 */ Key::F6, + /* 62 */ Key::F7, + /* 63 */ Key::F3, + /* 64 */ Key::F8, + /* 65 */ Key::F9, + /* 66 */ Key::UNKNOWN, /* JIS Eisu */ + /* 67 */ Key::F11, + /* 68 */ Key::UNKNOWN, /* JIS Kana */ + /* 69 */ Key::F13, + /* 6a */ Key::F16, + /* 6b */ Key::F14, + /* 6c */ Key::UNKNOWN, + /* 6d */ Key::F10, + /* 6e */ Key::MENU, + /* 6f */ Key::F12, + /* 70 */ Key::UNKNOWN, + /* 71 */ Key::F15, + /* 72 */ Key::INSERT, /* Really Help... */ + /* 73 */ Key::HOME, + /* 74 */ Key::PAGEUP, + /* 75 */ Key::KEY_DELETE, + /* 76 */ Key::F4, + /* 77 */ Key::END, + /* 78 */ Key::F2, + /* 79 */ Key::PAGEDOWN, + /* 7a */ Key::F1, + /* 7b */ Key::LEFT, + /* 7c */ Key::RIGHT, + /* 7d */ Key::DOWN, + /* 7e */ Key::UP, + /* 7f */ Key::UNKNOWN, +}; + +// Translates a OS X keycode to a Godot keycode. +Key KeyMappingOSX::translate_key(unsigned int key) { + if (key >= 128) { + return Key::UNKNOWN; + } + + return _osx_to_godot_table[key]; +} + +// Translates a Godot keycode back to a OSX keycode. +unsigned int KeyMappingOSX::unmap_key(Key key) { + for (int i = 0; i <= 126; i++) { + if (_osx_to_godot_table[i] == key) { + return i; + } + } + return 127; +} + +struct _KeyCodeMap { + UniChar kchar; + Key kcode; +}; + +static const _KeyCodeMap _keycodes[55] = { + { '`', Key::QUOTELEFT }, + { '~', Key::ASCIITILDE }, + { '0', Key::KEY_0 }, + { '1', Key::KEY_1 }, + { '2', Key::KEY_2 }, + { '3', Key::KEY_3 }, + { '4', Key::KEY_4 }, + { '5', Key::KEY_5 }, + { '6', Key::KEY_6 }, + { '7', Key::KEY_7 }, + { '8', Key::KEY_8 }, + { '9', Key::KEY_9 }, + { '-', Key::MINUS }, + { '_', Key::UNDERSCORE }, + { '=', Key::EQUAL }, + { '+', Key::PLUS }, + { 'q', Key::Q }, + { 'w', Key::W }, + { 'e', Key::E }, + { 'r', Key::R }, + { 't', Key::T }, + { 'y', Key::Y }, + { 'u', Key::U }, + { 'i', Key::I }, + { 'o', Key::O }, + { 'p', Key::P }, + { '[', Key::BRACELEFT }, + { ']', Key::BRACERIGHT }, + { '{', Key::BRACELEFT }, + { '}', Key::BRACERIGHT }, + { 'a', Key::A }, + { 's', Key::S }, + { 'd', Key::D }, + { 'f', Key::F }, + { 'g', Key::G }, + { 'h', Key::H }, + { 'j', Key::J }, + { 'k', Key::K }, + { 'l', Key::L }, + { ';', Key::SEMICOLON }, + { ':', Key::COLON }, + { '\'', Key::APOSTROPHE }, + { '\"', Key::QUOTEDBL }, + { '\\', Key::BACKSLASH }, + { '#', Key::NUMBERSIGN }, + { 'z', Key::Z }, + { 'x', Key::X }, + { 'c', Key::C }, + { 'v', Key::V }, + { 'b', Key::B }, + { 'n', Key::N }, + { 'm', Key::M }, + { ',', Key::COMMA }, + { '.', Key::PERIOD }, + { '/', Key::SLASH } +}; + +// Remap key according to current keyboard layout. +Key KeyMappingOSX::remap_key(unsigned int key, unsigned int state) { + if (is_numpad_key(key)) { + return translate_key(key); + } + + TISInputSourceRef current_keyboard = TISCopyCurrentKeyboardInputSource(); + if (!current_keyboard) { + return translate_key(key); + } + + CFDataRef layout_data = (CFDataRef)TISGetInputSourceProperty(current_keyboard, kTISPropertyUnicodeKeyLayoutData); + if (!layout_data) { + return translate_key(key); + } + + const UCKeyboardLayout *keyboard_layout = (const UCKeyboardLayout *)CFDataGetBytePtr(layout_data); + + UInt32 keys_down = 0; + UniChar chars[4]; + UniCharCount real_length; + + OSStatus err = UCKeyTranslate(keyboard_layout, + key, + kUCKeyActionDisplay, + (state >> 8) & 0xFF, + LMGetKbdType(), + kUCKeyTranslateNoDeadKeysBit, + &keys_down, + sizeof(chars) / sizeof(chars[0]), + &real_length, + chars); + + if (err != noErr) { + return translate_key(key); + } + + for (unsigned int i = 0; i < 55; i++) { + if (_keycodes[i].kchar == chars[0]) { + return _keycodes[i].kcode; + } + } + return translate_key(key); +} + +struct _KeyCodeText { + Key code; + char32_t text; +}; + +static const _KeyCodeText _native_keycodes[] = { + /* clang-format off */ + {Key::ESCAPE ,0x001B}, + {Key::TAB ,0x0009}, + {Key::BACKTAB ,0x007F}, + {Key::BACKSPACE ,0x0008}, + {Key::ENTER ,0x000D}, + {Key::INSERT ,NSInsertFunctionKey}, + {Key::KEY_DELETE ,0x007F}, + {Key::PAUSE ,NSPauseFunctionKey}, + {Key::PRINT ,NSPrintScreenFunctionKey}, + {Key::SYSREQ ,NSSysReqFunctionKey}, + {Key::CLEAR ,NSClearLineFunctionKey}, + {Key::HOME ,0x2196}, + {Key::END ,0x2198}, + {Key::LEFT ,0x001C}, + {Key::UP ,0x001E}, + {Key::RIGHT ,0x001D}, + {Key::DOWN ,0x001F}, + {Key::PAGEUP ,0x21DE}, + {Key::PAGEDOWN ,0x21DF}, + {Key::NUMLOCK ,NSClearLineFunctionKey}, + {Key::SCROLLLOCK ,NSScrollLockFunctionKey}, + {Key::F1 ,NSF1FunctionKey}, + {Key::F2 ,NSF2FunctionKey}, + {Key::F3 ,NSF3FunctionKey}, + {Key::F4 ,NSF4FunctionKey}, + {Key::F5 ,NSF5FunctionKey}, + {Key::F6 ,NSF6FunctionKey}, + {Key::F7 ,NSF7FunctionKey}, + {Key::F8 ,NSF8FunctionKey}, + {Key::F9 ,NSF9FunctionKey}, + {Key::F10 ,NSF10FunctionKey}, + {Key::F11 ,NSF11FunctionKey}, + {Key::F12 ,NSF12FunctionKey}, + {Key::F13 ,NSF13FunctionKey}, + {Key::F14 ,NSF14FunctionKey}, + {Key::F15 ,NSF15FunctionKey}, + {Key::F16 ,NSF16FunctionKey}, //* ... NSF35FunctionKey */ + {Key::MENU ,NSMenuFunctionKey}, + {Key::HELP ,NSHelpFunctionKey}, + {Key::STOP ,NSStopFunctionKey}, + {Key::LAUNCH0 ,NSUserFunctionKey}, + {Key::SPACE ,0x0020}, + {Key::EXCLAM ,'!'}, + {Key::QUOTEDBL ,'\"'}, + {Key::NUMBERSIGN ,'#'}, + {Key::DOLLAR ,'$'}, + {Key::PERCENT ,'\%'}, + {Key::AMPERSAND ,'&'}, + {Key::APOSTROPHE ,'\''}, + {Key::PARENLEFT ,'('}, + {Key::PARENRIGHT ,')'}, + {Key::ASTERISK ,'*'}, + {Key::PLUS ,'+'}, + {Key::COMMA ,','}, + {Key::MINUS ,'-'}, + {Key::PERIOD ,'.'}, + {Key::SLASH ,'/'}, + {Key::KEY_0 ,'0'}, + {Key::KEY_1 ,'1'}, + {Key::KEY_2 ,'2'}, + {Key::KEY_3 ,'3'}, + {Key::KEY_4 ,'4'}, + {Key::KEY_5 ,'5'}, + {Key::KEY_6 ,'6'}, + {Key::KEY_7 ,'7'}, + {Key::KEY_8 ,'8'}, + {Key::KEY_9 ,'9'}, + {Key::COLON ,':'}, + {Key::SEMICOLON ,';'}, + {Key::LESS ,'<'}, + {Key::EQUAL ,'='}, + {Key::GREATER ,'>'}, + {Key::QUESTION ,'?'}, + {Key::AT ,'@'}, + {Key::A ,'a'}, + {Key::B ,'b'}, + {Key::C ,'c'}, + {Key::D ,'d'}, + {Key::E ,'e'}, + {Key::F ,'f'}, + {Key::G ,'g'}, + {Key::H ,'h'}, + {Key::I ,'i'}, + {Key::J ,'j'}, + {Key::K ,'k'}, + {Key::L ,'l'}, + {Key::M ,'m'}, + {Key::N ,'n'}, + {Key::O ,'o'}, + {Key::P ,'p'}, + {Key::Q ,'q'}, + {Key::R ,'r'}, + {Key::S ,'s'}, + {Key::T ,'t'}, + {Key::U ,'u'}, + {Key::V ,'v'}, + {Key::W ,'w'}, + {Key::X ,'x'}, + {Key::Y ,'y'}, + {Key::Z ,'z'}, + {Key::BRACKETLEFT ,'['}, + {Key::BACKSLASH ,'\\'}, + {Key::BRACKETRIGHT ,']'}, + {Key::ASCIICIRCUM ,'^'}, + {Key::UNDERSCORE ,'_'}, + {Key::QUOTELEFT ,'`'}, + {Key::BRACELEFT ,'{'}, + {Key::BAR ,'|'}, + {Key::BRACERIGHT ,'}'}, + {Key::ASCIITILDE ,'~'}, + {Key::NONE ,0x0000} + /* clang-format on */ +}; + +String KeyMappingOSX::keycode_get_native_string(Key p_keycode) { + const _KeyCodeText *kct = &_native_keycodes[0]; + + while (kct->text) { + if (kct->code == p_keycode) { + return String::chr(kct->text); + } + kct++; + } + return String(); +} + +unsigned int KeyMappingOSX::keycode_get_native_mask(Key p_keycode) { + unsigned int mask = 0; + if ((p_keycode & KeyModifierMask::CTRL) != Key::NONE) { + mask |= NSEventModifierFlagControl; + } + if ((p_keycode & KeyModifierMask::ALT) != Key::NONE) { + mask |= NSEventModifierFlagOption; + } + if ((p_keycode & KeyModifierMask::SHIFT) != Key::NONE) { + mask |= NSEventModifierFlagShift; + } + if ((p_keycode & KeyModifierMask::META) != Key::NONE) { + mask |= NSEventModifierFlagCommand; + } + if ((p_keycode & KeyModifierMask::KPAD) != Key::NONE) { + mask |= NSEventModifierFlagNumericPad; + } + return mask; +} diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index 60dec1fe3f70..5bb5b3320e47 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -40,9 +40,7 @@ #include "servers/audio_server.h" class OS_OSX : public OS_Unix { - virtual void delete_main_loop() override; - - bool force_quit; + bool force_quit = false; JoypadOSX *joypad_osx = nullptr; @@ -55,13 +53,15 @@ class OS_OSX : public OS_Unix { CrashHandler crash_handler; - MainLoop *main_loop; + CFRunLoopObserverRef pre_wait_observer; - static void pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context); + MainLoop *main_loop = nullptr; -public: String open_with_filename; + static _FORCE_INLINE_ String get_framework_executable(const String &p_path); + static void pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context); + protected: virtual void initialize_core() override; virtual void initialize() override; @@ -70,8 +70,12 @@ class OS_OSX : public OS_Unix { virtual void initialize_joypads() override; virtual void set_main_loop(MainLoop *p_main_loop) override; + virtual void delete_main_loop() override; public: + String get_open_with_filename() const; + void set_open_with_filename(const String &p_path); + virtual String get_name() const override; virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; @@ -89,26 +93,27 @@ class OS_OSX : public OS_Unix { virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override; - Error shell_open(String p_uri) override; + virtual Error shell_open(String p_uri) override; - String get_locale() const override; + virtual String get_locale() const override; virtual String get_executable_path() const override; virtual Error create_process(const String &p_path, const List &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error create_instance(const List &p_arguments, ProcessID *r_child_id = nullptr) override; - virtual String get_unique_id() const override; //++ + virtual String get_unique_id() const override; virtual bool _check_internal_feature_support(const String &p_feature) override; - void run(); - virtual void disable_crash_handler() override; virtual bool is_disable_crash_handler() const override; virtual Error move_to_trash(const String &p_path) override; + void run(); + OS_OSX(); + ~OS_OSX(); }; #endif diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 32d0e6dd94ec..9288e658cf81 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -31,241 +31,45 @@ #include "os_osx.h" #include "core/version_generated.gen.h" +#include "main/main.h" #include "dir_access_osx.h" #include "display_server_osx.h" -#include "main/main.h" +#include "godot_application.h" +#include "godot_application_delegate.h" +#include "osx_terminal_logger.h" #include #include #include -#include - -#define DS_OSX ((DisplayServerOSX *)(DisplayServerOSX::get_singleton())) - -/*************************************************************************/ -/* GodotApplication */ -/*************************************************************************/ - -@interface GodotApplication : NSApplication -@end - -@implementation GodotApplication - -- (void)sendEvent:(NSEvent *)event { - if (DS_OSX) { - DS_OSX->_send_event(event); - } - - // From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost - // This works around an AppKit bug, where key up events while holding - // down the command key don't get sent to the key window. - if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) { - [[self keyWindow] sendEvent:event]; - } else { - [super sendEvent:event]; - } -} - -@end - -/*************************************************************************/ -/* GodotApplicationDelegate */ -/*************************************************************************/ - -@interface GodotApplicationDelegate : NSObject -- (void)forceUnbundledWindowActivationHackStep1; -- (void)forceUnbundledWindowActivationHackStep2; -- (void)forceUnbundledWindowActivationHackStep3; -@end - -@implementation GodotApplicationDelegate - -- (void)forceUnbundledWindowActivationHackStep1 { - // Step 1: Switch focus to macOS SystemUIServer process. - // Required to perform step 2, TransformProcessType will fail if app is already the in focus. - for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.systemuiserver"]) { - [app activateWithOptions:NSApplicationActivateIgnoringOtherApps]; - break; - } - [self performSelector:@selector(forceUnbundledWindowActivationHackStep2) - withObject:nil - afterDelay:0.02]; -} - -- (void)forceUnbundledWindowActivationHackStep2 { - // Step 2: Register app as foreground process. - ProcessSerialNumber psn = { 0, kCurrentProcess }; - (void)TransformProcessType(&psn, kProcessTransformToForegroundApplication); - [self performSelector:@selector(forceUnbundledWindowActivationHackStep3) withObject:nil afterDelay:0.02]; -} - -- (void)forceUnbundledWindowActivationHackStep3 { - // Step 3: Switch focus back to app window. - [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps]; -} - -- (void)applicationDidFinishLaunching:(NSNotification *)notice { - NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; - if (nsappname == nil || isatty(STDOUT_FILENO) || isatty(STDIN_FILENO) || isatty(STDERR_FILENO)) { - // If the executable is started from terminal or is not bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored). - [self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02]; - } -} - -- (void)applicationDidResignActive:(NSNotification *)notification { - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); - } -} -- (void)applicationDidBecomeActive:(NSNotification *)notification { - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); - } -} - -- (void)globalMenuCallback:(id)sender { - if (DS_OSX) { - return DS_OSX->_menu_callback(sender); - } -} - -- (NSMenu *)applicationDockMenu:(NSApplication *)sender { - if (DS_OSX) { - return DS_OSX->_get_dock_menu(); +_FORCE_INLINE_ String OS_OSX::get_framework_executable(const String &p_path) { + // Append framework executable name, or return as is if p_path is not a framework. + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (da->dir_exists(p_path) && da->file_exists(p_path.plus_file(p_path.get_file().get_basename()))) { + return p_path.plus_file(p_path.get_file().get_basename()); } else { - return nullptr; - } -} - -- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename { - // Note: may be called called before main loop init! - char *utfs = strdup([filename UTF8String]); - ((OS_OSX *)OS_OSX::get_singleton())->open_with_filename.parse_utf8(utfs); - free(utfs); - -#ifdef TOOLS_ENABLED - // Open new instance - if (OS_OSX::get_singleton()->get_main_loop()) { - List args; - args.push_back(((OS_OSX *)OS_OSX::get_singleton())->open_with_filename); - String exec = OS_OSX::get_singleton()->get_executable_path(); - OS_OSX::get_singleton()->create_process(exec, args); - } -#endif - return YES; -} - -- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { - if (DS_OSX) { - DS_OSX->_send_window_event(DS_OSX->windows[DisplayServerOSX::MAIN_WINDOW_ID], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); - } - return NSTerminateCancel; -} - -- (void)showAbout:(id)sender { - if (OS_OSX::get_singleton()->get_main_loop()) { - OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT); + return p_path; } } -@end - -/*************************************************************************/ -/* OSXTerminalLogger */ -/*************************************************************************/ - -class OSXTerminalLogger : public StdLogger { -public: - virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR) { - if (!should_log(true)) { - return; - } - - const char *err_details; - if (p_rationale && p_rationale[0]) - err_details = p_rationale; - else - err_details = p_code; - - switch (p_type) { - case ERR_WARNING: - os_log_info(OS_LOG_DEFAULT, - "WARNING: %{public}s\nat: %{public}s (%{public}s:%i)", - err_details, p_function, p_file, p_line); - logf_error("\E[1;33mWARNING:\E[0;93m %s\n", err_details); - logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); - break; - case ERR_SCRIPT: - os_log_error(OS_LOG_DEFAULT, - "SCRIPT ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", - err_details, p_function, p_file, p_line); - logf_error("\E[1;35mSCRIPT ERROR:\E[0;95m %s\n", err_details); - logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); - break; - case ERR_SHADER: - os_log_error(OS_LOG_DEFAULT, - "SHADER ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", - err_details, p_function, p_file, p_line); - logf_error("\E[1;36mSHADER ERROR:\E[0;96m %s\n", err_details); - logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); - break; - case ERR_ERROR: - default: - os_log_error(OS_LOG_DEFAULT, - "ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", - err_details, p_function, p_file, p_line); - logf_error("\E[1;31mERROR:\E[0;91m %s\n", err_details); - logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); - break; - } - } -}; - -/*************************************************************************/ -/* OS_OSX */ -/*************************************************************************/ - -String OS_OSX::get_unique_id() const { - static String serial_number; - - if (serial_number.is_empty()) { - io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); - CFStringRef serialNumberAsCFString = nullptr; - if (platformExpert) { - serialNumberAsCFString = (CFStringRef)IORegistryEntryCreateCFProperty(platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0); - IOObjectRelease(platformExpert); - } +void OS_OSX::pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context) { + // Prevent main loop from sleeping and redraw window during resize / modal popups. - NSString *serialNumberAsNSString = nil; - if (serialNumberAsCFString) { - serialNumberAsNSString = [NSString stringWithString:(NSString *)serialNumberAsCFString]; - CFRelease(serialNumberAsCFString); + if (get_singleton()->get_main_loop()) { + Main::force_redraw(); + if (!Main::is_iterating()) { // Avoid cyclic loop. + Main::iteration(); } - - serial_number = [serialNumberAsNSString UTF8String]; } - return serial_number; + CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Prevent main loop from sleeping. } -void OS_OSX::alert(const String &p_alert, const String &p_title) { - NSAlert *window = [[NSAlert alloc] init]; - NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()]; - NSString *ns_alert = [NSString stringWithUTF8String:p_alert.utf8().get_data()]; - - [window addButtonWithTitle:@"OK"]; - [window setMessageText:ns_title]; - [window setInformativeText:ns_alert]; - [window setAlertStyle:NSAlertStyleWarning]; +void OS_OSX::initialize() { + crash_handler.initialize(); - id key_window = [[NSApplication sharedApplication] keyWindow]; - [window runModal]; - [window release]; - if (key_window) { - [key_window makeKeyAndOrderFront:nil]; - } + initialize_core(); } void OS_OSX::initialize_core() { @@ -276,17 +80,6 @@ virtual void log_error(const char *p_function, const char *p_file, int p_line, c DirAccess::make_default(DirAccess::ACCESS_FILESYSTEM); } -void OS_OSX::initialize_joypads() { - joypad_osx = memnew(JoypadOSX(Input::get_singleton())); -} - -void OS_OSX::initialize() { - crash_handler.initialize(); - - initialize_core(); - //ensure_user_data_dir(); -} - void OS_OSX::finalize() { #ifdef COREMIDI_ENABLED midi_driver.close(); @@ -299,42 +92,63 @@ virtual void log_error(const char *p_function, const char *p_file, int p_line, c } } +void OS_OSX::initialize_joypads() { + joypad_osx = memnew(JoypadOSX(Input::get_singleton())); +} + void OS_OSX::set_main_loop(MainLoop *p_main_loop) { main_loop = p_main_loop; } void OS_OSX::delete_main_loop() { - if (!main_loop) + if (!main_loop) { return; + } + memdelete(main_loop); main_loop = nullptr; } +String OS_OSX::get_open_with_filename() const { + return open_with_filename; +} + +void OS_OSX::set_open_with_filename(const String &p_path) { + open_with_filename = p_path; +} + String OS_OSX::get_name() const { return "macOS"; } -_FORCE_INLINE_ String _get_framework_executable(const String p_path) { - // Append framework executable name, or return as is if p_path is not a framework. - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (da->dir_exists(p_path) && da->file_exists(p_path.plus_file(p_path.get_file().get_basename()))) { - return p_path.plus_file(p_path.get_file().get_basename()); - } else { - return p_path; +void OS_OSX::alert(const String &p_alert, const String &p_title) { + NSAlert *window = [[NSAlert alloc] init]; + NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()]; + NSString *ns_alert = [NSString stringWithUTF8String:p_alert.utf8().get_data()]; + + [window addButtonWithTitle:@"OK"]; + [window setMessageText:ns_title]; + [window setInformativeText:ns_alert]; + [window setAlertStyle:NSAlertStyleWarning]; + + id key_window = [[NSApplication sharedApplication] keyWindow]; + [window runModal]; + if (key_window) { + [key_window makeKeyAndOrderFront:nil]; } } Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { - String path = _get_framework_executable(p_path); + String path = get_framework_executable(p_path); if (!FileAccess::exists(path)) { - // This code exists so gdnative can load .dylib files from within the executable path. - path = _get_framework_executable(get_executable_path().get_base_dir().plus_file(p_path.get_file())); + // Load .dylib or framework from within the executable path. + path = get_framework_executable(get_executable_path().get_base_dir().plus_file(p_path.get_file())); } if (!FileAccess::exists(path)) { - // This code exists so gdnative can load .dylib files from a standard macOS location. - path = _get_framework_executable(get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(p_path.get_file())); + // Load .dylib or framework from a standard macOS location. + path = get_framework_executable(get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(p_path.get_file())); } p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); @@ -393,8 +207,8 @@ _FORCE_INLINE_ String _get_framework_executable(const String p_path) { NSBundle *main = [NSBundle mainBundle]; if (main) { - NSString *resourcePath = [main resourcePath]; - ret.parse_utf8([resourcePath UTF8String]); + NSString *resource_path = [main resourcePath]; + ret.parse_utf8([resource_path UTF8String]); } return ret; } @@ -404,9 +218,9 @@ _FORCE_INLINE_ String _get_framework_executable(const String p_path) { NSBundle *main = [NSBundle mainBundle]; if (main) { - NSString *iconPath = [[main infoDictionary] objectForKey:@"CFBundleIconFile"]; - if (iconPath) { - ret.parse_utf8([iconPath UTF8String]); + NSString *icon_path = [[main infoDictionary] objectForKey:@"CFBundleIconFile"]; + if (icon_path) { + ret.parse_utf8([icon_path UTF8String]); } } return ret; @@ -449,9 +263,7 @@ _FORCE_INLINE_ String _get_framework_executable(const String p_path) { if (found) { NSArray *paths = NSSearchPathForDirectoriesInDomains(id, NSUserDomainMask, YES); if (paths && [paths count] >= 1) { - char *utfs = strdup([[paths firstObject] UTF8String]); - ret.parse_utf8(utfs); - free(utfs); + ret.parse_utf8([[paths firstObject] UTF8String]); } } @@ -475,12 +287,9 @@ _FORCE_INLINE_ String _get_framework_executable(const String p_path) { } String OS_OSX::get_executable_path() const { - int ret; - pid_t pid; char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; - - pid = getpid(); - ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf)); + int pid = getpid(); + pid_t ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf)); if (ret <= 0) { return OS::get_executable_path(); } else { @@ -491,18 +300,6 @@ _FORCE_INLINE_ String _get_framework_executable(const String p_path) { } } -Error OS_OSX::create_instance(const List &p_arguments, ProcessID *r_child_id) { - // If executable is bundled, always execute editor instances as an app bundle to ensure app window is registered and activated correctly. - NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; - if (nsappname != nil) { - String path; - path.parse_utf8([[[NSBundle mainBundle] bundlePath] UTF8String]); - return create_process(path, p_arguments, r_child_id, false); - } else { - return create_process(get_executable_path(), p_arguments, r_child_id, false); - } -} - Error OS_OSX::create_process(const String &p_path, const List &p_arguments, ProcessID *r_child_id, bool p_open_console) { if (@available(macOS 10.15, *)) { // Use NSWorkspace if path is an .app bundle. @@ -532,7 +329,6 @@ _FORCE_INLINE_ String _get_framework_executable(const String p_path) { dispatch_semaphore_signal(lock); }]; dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, 20000000000)); // 20 sec timeout, wait for app to launch. - dispatch_release(lock); if (err == OK) { if (r_child_id) { @@ -549,17 +345,66 @@ _FORCE_INLINE_ String _get_framework_executable(const String p_path) { } } -void OS_OSX::pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context) { - // Prevent main loop from sleeping and redraw window during resize / modal popups. +Error OS_OSX::create_instance(const List &p_arguments, ProcessID *r_child_id) { + // If executable is bundled, always execute editor instances as an app bundle to ensure app window is registered and activated correctly. + NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; + if (nsappname != nil) { + String path; + path.parse_utf8([[[NSBundle mainBundle] bundlePath] UTF8String]); + return create_process(path, p_arguments, r_child_id, false); + } else { + return create_process(get_executable_path(), p_arguments, r_child_id, false); + } +} - if (get_singleton()->get_main_loop()) { - Main::force_redraw(); - if (!Main::is_iterating()) { // Avoid cyclic loop. - Main::iteration(); +String OS_OSX::get_unique_id() const { + static String serial_number; + + if (serial_number.is_empty()) { + io_service_t platform_expert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); + CFStringRef serial_number_cf_string = nullptr; + if (platform_expert) { + serial_number_cf_string = (CFStringRef)IORegistryEntryCreateCFProperty(platform_expert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0); + IOObjectRelease(platform_expert); + } + + NSString *serial_number_ns_string = nil; + if (serial_number_cf_string) { + serial_number_ns_string = [NSString stringWithString:(__bridge NSString *)serial_number_cf_string]; + CFRelease(serial_number_cf_string); + } + + if (serial_number_ns_string) { + serial_number.parse_utf8([serial_number_ns_string UTF8String]); } } - CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Prevent main loop from sleeping. + return serial_number; +} + +bool OS_OSX::_check_internal_feature_support(const String &p_feature) { + return p_feature == "pc"; +} + +void OS_OSX::disable_crash_handler() { + crash_handler.disable(); +} + +bool OS_OSX::is_disable_crash_handler() const { + return crash_handler.is_disabled(); +} + +Error OS_OSX::move_to_trash(const String &p_path) { + NSFileManager *fm = [NSFileManager defaultManager]; + NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())]; + NSError *err; + + if (![fm trashItemAtURL:url resultingItemURL:nil error:&err]) { + ERR_PRINT("trashItemAtURL error: " + String::utf8(err.localizedDescription.UTF8String)); + return FAILED; + } + + return OK; } void OS_OSX::run() { @@ -571,14 +416,11 @@ _FORCE_INLINE_ String _get_framework_executable(const String p_path) { main_loop->initialize(); - CFRunLoopObserverRef pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr); - CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); - bool quit = false; while (!force_quit && !quit) { @try { if (DisplayServer::get_singleton()) { - DisplayServer::get_singleton()->process_events(); // get rid of pending events + DisplayServer::get_singleton()->process_events(); // Get rid of pending events. } joypad_osx->process_joypads(); @@ -590,25 +432,9 @@ _FORCE_INLINE_ String _get_framework_executable(const String p_path) { } }; - CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); - CFRelease(pre_wait_observer); - main_loop->finalize(); } -Error OS_OSX::move_to_trash(const String &p_path) { - NSFileManager *fm = [NSFileManager defaultManager]; - NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())]; - NSError *err; - - if (![fm trashItemAtURL:url resultingItemURL:nil error:&err]) { - ERR_PRINT("trashItemAtURL error: " + String::utf8(err.localizedDescription.UTF8String)); - return FAILED; - } - - return OK; -} - OS_OSX::OS_OSX() { main_loop = nullptr; force_quit = false; @@ -623,17 +449,17 @@ _FORCE_INLINE_ String _get_framework_executable(const String p_path) { DisplayServerOSX::register_osx_driver(); - // Implicitly create shared NSApplication instance + // Implicitly create shared NSApplication instance. [GodotApplication sharedApplication]; - // In case we are unbundled, make us a proper UI application + // In case we are unbundled, make us a proper UI application. [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; // Menu bar setup must go between sharedApplication above and // finishLaunching below, in order to properly emulate the behavior - // of NSApplicationMain + // of NSApplicationMain. - NSMenu *main_menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; + NSMenu *main_menu = [[NSMenu alloc] initWithTitle:@""]; [NSApp setMainMenu:main_menu]; [NSApp finishLaunching]; @@ -641,7 +467,10 @@ _FORCE_INLINE_ String _get_framework_executable(const String p_path) { ERR_FAIL_COND(!delegate); [NSApp setDelegate:delegate]; - //process application:openFile: event + pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr); + CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); + + // Process application:openFile: event. while (true) { NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny @@ -659,14 +488,7 @@ _FORCE_INLINE_ String _get_framework_executable(const String p_path) { [NSApp activateIgnoringOtherApps:YES]; } -bool OS_OSX::_check_internal_feature_support(const String &p_feature) { - return p_feature == "pc"; -} - -void OS_OSX::disable_crash_handler() { - crash_handler.disable(); -} - -bool OS_OSX::is_disable_crash_handler() const { - return crash_handler.is_disabled(); +OS_OSX::~OS_OSX() { + CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); + CFRelease(pre_wait_observer); } diff --git a/platform/osx/osx_terminal_logger.h b/platform/osx/osx_terminal_logger.h new file mode 100644 index 000000000000..8413509c4b38 --- /dev/null +++ b/platform/osx/osx_terminal_logger.h @@ -0,0 +1,44 @@ +/*************************************************************************/ +/* osx_terminal_logger.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef OSX_TERMINAL_LOGGER_H +#define OSX_TERMINAL_LOGGER_H + +#ifdef OSX_ENABLED + +#include "core/io/logger.h" + +class OSXTerminalLogger : public StdLogger { +public: + virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR) override; +}; + +#endif // OSX_ENABLED +#endif // OSX_TERMINAL_LOGGER_H diff --git a/platform/osx/osx_terminal_logger.mm b/platform/osx/osx_terminal_logger.mm new file mode 100644 index 000000000000..c1dca111a739 --- /dev/null +++ b/platform/osx/osx_terminal_logger.mm @@ -0,0 +1,81 @@ +/*************************************************************************/ +/* osx_terminal_logger.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "osx_terminal_logger.h" + +#ifdef OSX_ENABLED + +#include + +void OSXTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) { + if (!should_log(true)) { + return; + } + + const char *err_details; + if (p_rationale && p_rationale[0]) + err_details = p_rationale; + else + err_details = p_code; + + switch (p_type) { + case ERR_WARNING: + os_log_info(OS_LOG_DEFAULT, + "WARNING: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); + logf_error("\E[1;33mWARNING:\E[0;93m %s\n", err_details); + logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); + break; + case ERR_SCRIPT: + os_log_error(OS_LOG_DEFAULT, + "SCRIPT ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); + logf_error("\E[1;35mSCRIPT ERROR:\E[0;95m %s\n", err_details); + logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); + break; + case ERR_SHADER: + os_log_error(OS_LOG_DEFAULT, + "SHADER ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); + logf_error("\E[1;36mSHADER ERROR:\E[0;96m %s\n", err_details); + logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); + break; + case ERR_ERROR: + default: + os_log_error(OS_LOG_DEFAULT, + "ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); + logf_error("\E[1;31mERROR:\E[0;91m %s\n", err_details); + logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); + break; + } +} + +#endif // OSX_ENABLED diff --git a/platform/osx/vulkan_context_osx.mm b/platform/osx/vulkan_context_osx.mm index f32fab1eeef9..bdabc24c2817 100644 --- a/platform/osx/vulkan_context_osx.mm +++ b/platform/osx/vulkan_context_osx.mm @@ -44,7 +44,7 @@ createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; createInfo.pNext = nullptr; createInfo.flags = 0; - createInfo.pView = p_window; + createInfo.pView = (__bridge const void *)p_window; VkSurfaceKHR surface; VkResult err = vkCreateMacOSSurfaceMVK(get_instance(), &createInfo, nullptr, &surface); diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp index 71e9d9acbdf5..5064f6b97fd4 100644 --- a/platform/windows/crash_handler_windows.cpp +++ b/platform/windows/crash_handler_windows.cpp @@ -33,7 +33,6 @@ #include "core/config/project_settings.h" #include "core/os/os.h" #include "core/version.h" -#include "core/version_hash.gen.h" #include "main/main.h" #ifdef CRASH_HANDLER_EXCEPTION @@ -179,10 +178,10 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { } // Print the engine version just before, so that people are reminded to include the version in backtrace reports. - if (String(VERSION_HASH).length() != 0) { - fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); + if (String(VERSION_HASH).is_empty()) { + fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME); } else { - fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); + fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH); } fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index c9e2251b35ac..20268b3f6aba 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -97,7 +97,10 @@ String DisplayServerWindows::get_name() const { void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) { if (windows.has(MAIN_WINDOW_ID) && (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED || p_mode == MOUSE_MODE_CONFINED_HIDDEN)) { // Mouse is grabbed (captured or confined). - WindowData &wd = windows[MAIN_WINDOW_ID]; + + WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; + + WindowData &wd = windows[window_id]; RECT clipRect; GetClientRect(wd.hWnd, &clipRect); @@ -323,6 +326,12 @@ typedef struct { Rect2i rect; } EnumRectData; +typedef struct { + int count; + int screen; + float rate; +} EnumRefreshRateData; + static BOOL CALLBACK _MonitorEnumProcSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { EnumSizeData *data = (EnumSizeData *)dwData; if (data->count == data->screen) { @@ -360,6 +369,26 @@ static BOOL CALLBACK _MonitorEnumProcUsableSize(HMONITOR hMonitor, HDC hdcMonito return TRUE; } +static BOOL CALLBACK _MonitorEnumProcRefreshRate(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { + EnumRefreshRateData *data = (EnumRefreshRateData *)dwData; + if (data->count == data->screen) { + MONITORINFOEXW minfo; + memset(&minfo, 0, sizeof(minfo)); + minfo.cbSize = sizeof(minfo); + GetMonitorInfoW(hMonitor, &minfo); + + DEVMODEW dm; + memset(&dm, 0, sizeof(dm)); + dm.dmSize = sizeof(dm); + EnumDisplaySettingsW(minfo.szDevice, ENUM_CURRENT_SETTINGS, &dm); + + data->rate = dm.dmDisplayFrequency; + } + + data->count++; + return TRUE; +} + Rect2i DisplayServerWindows::screen_get_usable_rect(int p_screen) const { _THREAD_SAFE_METHOD_ @@ -443,6 +472,13 @@ int DisplayServerWindows::screen_get_dpi(int p_screen) const { EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcDpi, (LPARAM)&data); return data.dpi; } +float DisplayServerWindows::screen_get_refresh_rate(int p_screen) const { + _THREAD_SAFE_METHOD_ + + EnumRefreshRateData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, SCREEN_REFRESH_RATE_FALLBACK }; + EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcRefreshRate, (LPARAM)&data); + return data.rate; +} bool DisplayServerWindows::screen_is_touchscreen(int p_screen) const { #ifndef _MSC_VER @@ -503,13 +539,22 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) { wd.borderless = true; } - if (p_flags & WINDOW_FLAG_ALWAYS_ON_TOP_BIT && p_mode != WINDOW_MODE_FULLSCREEN) { + if (p_flags & WINDOW_FLAG_ALWAYS_ON_TOP_BIT && p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { wd.always_on_top = true; } if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) { wd.no_focus = true; } + // Inherit icons from MAIN_WINDOW for all sub windows. + HICON mainwindow_icon = (HICON)SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_GETICON, ICON_SMALL, 0); + if (mainwindow_icon) { + SendMessage(windows[window_id].hWnd, WM_SETICON, ICON_SMALL, (LPARAM)mainwindow_icon); + } + mainwindow_icon = (HICON)SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_GETICON, ICON_BIG, 0); + if (mainwindow_icon) { + SendMessage(windows[window_id].hWnd, WM_SETICON, ICON_BIG, (LPARAM)mainwindow_icon); + } return window_id; } @@ -570,6 +615,24 @@ void DisplayServerWindows::gl_window_make_current(DisplayServer::WindowID p_wind #endif } +int64_t DisplayServerWindows::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { + ERR_FAIL_COND_V(!windows.has(p_window), 0); + switch (p_handle_type) { + case DISPLAY_HANDLE: { + return 0; // Not supported. + } + case WINDOW_HANDLE: { + return (int64_t)windows[p_window].hWnd; + } + case WINDOW_VIEW: { + return 0; // Not supported. + } + default: { + return 0; + } + } +} + void DisplayServerWindows::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { _THREAD_SAFE_METHOD_ @@ -688,6 +751,15 @@ void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_wi Vector2 ofs = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window)); window_set_position(ofs + screen_get_position(p_screen), p_window); } + + // Don't let the mouse leave the window when resizing to a smaller resolution. + if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { + RECT crect; + GetClientRect(wd.hWnd, &crect); + ClientToScreen(wd.hWnd, (POINT *)&crect.left); + ClientToScreen(wd.hWnd, (POINT *)&crect.right); + ClipCursor(&crect); + } } Point2i DisplayServerWindows::window_get_position(WindowID p_window) const { @@ -916,7 +988,7 @@ Size2i DisplayServerWindows::window_get_real_size(WindowID p_window) const { return Size2(); } -void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) { +void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) { // Windows docs for window styles: // https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles // https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles @@ -929,6 +1001,9 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre if (p_fullscreen || p_borderless) { r_style |= WS_POPUP; // p_borderless was WS_EX_TOOLWINDOW in the past. + if (p_fullscreen && p_multiwindow_fs) { + r_style |= WS_BORDER; // Allows child windows to be displayed on top of full screen. + } } else { if (p_resizable) { if (p_maximized) { @@ -959,7 +1034,7 @@ void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repain DWORD style = 0; DWORD style_ex = 0; - _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.borderless, wd.resizable, wd.maximized, wd.no_focus, style, style_ex); + _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.maximized, wd.no_focus, style, style_ex); SetWindowLongPtr(wd.hWnd, GWL_STYLE, style); SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex); @@ -979,10 +1054,11 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - if (wd.fullscreen && p_mode != WINDOW_MODE_FULLSCREEN) { + if (wd.fullscreen && p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { RECT rect; wd.fullscreen = false; + wd.multiwindow_fs = false; wd.maximized = wd.was_maximized; if (wd.pre_fs_valid) { @@ -1021,7 +1097,15 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) wd.minimized = true; } - if (p_mode == WINDOW_MODE_FULLSCREEN && !wd.fullscreen) { + if (p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { + wd.multiwindow_fs = false; + _update_window_style(false); + } else { + wd.multiwindow_fs = true; + _update_window_style(false); + } + + if ((p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) && !wd.fullscreen) { if (wd.minimized) { ShowWindow(wd.hWnd, SW_RESTORE); } @@ -1050,6 +1134,15 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) SystemParametersInfoA(SPI_SETMOUSETRAILS, 0, 0, 0); } } + + // Don't let the mouse leave the window when resizing to a smaller resolution. + if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { + RECT crect; + GetClientRect(wd.hWnd, &crect); + ClientToScreen(wd.hWnd, (POINT *)&crect.left); + ClientToScreen(wd.hWnd, (POINT *)&crect.right); + ClipCursor(&crect); + } } DisplayServer::WindowMode DisplayServerWindows::window_get_mode(WindowID p_window) const { @@ -1059,7 +1152,11 @@ DisplayServer::WindowMode DisplayServerWindows::window_get_mode(WindowID p_windo const WindowData &wd = windows[p_window]; if (wd.fullscreen) { - return WINDOW_MODE_FULLSCREEN; + if (wd.multiwindow_fs) { + return WINDOW_MODE_FULLSCREEN; + } else { + return WINDOW_MODE_EXCLUSIVE_FULLSCREEN; + } } else if (wd.minimized) { return WINDOW_MODE_MINIMIZED; } else if (wd.maximized) { @@ -2610,98 +2707,77 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } break; - case WM_MOVE: { - if (!IsIconic(windows[window_id].hWnd)) { - int x = int16_t(LOWORD(lParam)); - int y = int16_t(HIWORD(lParam)); - windows[window_id].last_pos = Point2(x, y); - - if (!windows[window_id].rect_changed_callback.is_null()) { - Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height); - Variant *sizep = &size; - Variant ret; - Callable::CallError ce; - windows[window_id].rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); + + case WM_WINDOWPOSCHANGED: { + Rect2i window_client_rect; + Rect2i window_rect; + { + RECT rect; + GetClientRect(hWnd, &rect); + ClientToScreen(hWnd, (POINT *)&rect.left); + ClientToScreen(hWnd, (POINT *)&rect.right); + window_client_rect = Rect2i(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); + + RECT wrect; + GetWindowRect(hWnd, &wrect); + window_rect = Rect2i(wrect.left, wrect.top, wrect.right - wrect.left, wrect.bottom - wrect.top); + } + + WINDOWPOS *window_pos_params = (WINDOWPOS *)lParam; + WindowData &window = windows[window_id]; + + bool rect_changed = false; + if (!(window_pos_params->flags & SWP_NOSIZE) || window_pos_params->flags & SWP_FRAMECHANGED) { + int screen_id = window_get_current_screen(window_id); + Size2i screen_size = screen_get_size(screen_id); + Point2i screen_position = screen_get_position(screen_id); + + window.maximized = false; + window.minimized = false; + window.fullscreen = false; + + if (IsIconic(hWnd)) { + window.minimized = true; + } else if (IsZoomed(hWnd)) { + window.maximized = true; + } else if (window_rect.position == screen_position && window_rect.size == screen_size) { + window.fullscreen = true; } - } - } break; - case WM_SIZE: { - // Ignore window size change when a SIZE_MINIMIZED event is triggered. - if (wParam != SIZE_MINIMIZED) { - // The new width and height of the client area. - int window_w = LOWORD(lParam); - int window_h = HIWORD(lParam); - - // Set new value to the size if it isn't preserved. - if (window_w > 0 && window_h > 0 && !windows[window_id].preserve_window_size) { - windows[window_id].width = window_w; - windows[window_id].height = window_h; + + if (!window.minimized) { + window.width = window_client_rect.size.width; + window.height = window_client_rect.size.height; #if defined(VULKAN_ENABLED) if (context_vulkan && window_created) { - context_vulkan->window_resize(window_id, windows[window_id].width, windows[window_id].height); + context_vulkan->window_resize(window_id, window.width, window.height); } #endif + rect_changed = true; + } + } - } else { // If the size is preserved. - windows[window_id].preserve_window_size = false; + if (!window.minimized && (!(window_pos_params->flags & SWP_NOMOVE) || window_pos_params->flags & SWP_FRAMECHANGED)) { + window.last_pos = window_client_rect.position; + rect_changed = true; + } - // Restore the old size. - window_set_size(Size2(windows[window_id].width, windows[window_id].height), window_id); + if (rect_changed) { + if (!window.rect_changed_callback.is_null()) { + Variant size = Rect2i(window.last_pos.x, window.last_pos.y, window.width, window.height); + const Variant *args[] = { &size }; + Variant ret; + Callable::CallError ce; + window.rect_changed_callback.call(args, 1, ret, ce); } - } else { // When the window has been minimized, preserve its size. - windows[window_id].preserve_window_size = true; } - // Call windows rect change callback. - if (!windows[window_id].rect_changed_callback.is_null()) { - Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height); - Variant *size_ptr = &size; - Variant ret; - Callable::CallError ce; - windows[window_id].rect_changed_callback.call((const Variant **)&size_ptr, 1, ret, ce); - } - - // The window has been maximized. - if (wParam == SIZE_MAXIMIZED) { - windows[window_id].maximized = true; - windows[window_id].minimized = false; - } - // The window has been minimized. - else if (wParam == SIZE_MINIMIZED) { - windows[window_id].maximized = false; - windows[window_id].minimized = true; - windows[window_id].preserve_window_size = false; - } - // The window has been resized, but neither the SIZE_MINIMIZED nor SIZE_MAXIMIZED value applies. - else if (wParam == SIZE_RESTORED) { - windows[window_id].maximized = false; - windows[window_id].minimized = false; - } -#if 0 - if (is_layered_allowed() && layered_window) { - DeleteObject(hBitmap); - - RECT r; - GetWindowRect(hWnd, &r); - dib_size = Size2i(r.right - r.left, r.bottom - r.top); - - BITMAPINFO bmi; - ZeroMemory(&bmi, sizeof(BITMAPINFO)); - bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - bmi.bmiHeader.biWidth = dib_size.x; - bmi.bmiHeader.biHeight = dib_size.y; - bmi.bmiHeader.biPlanes = 1; - bmi.bmiHeader.biBitCount = 32; - bmi.bmiHeader.biCompression = BI_RGB; - bmi.bmiHeader.biSizeImage = dib_size.x * dib_size.y * 4; - hBitmap = CreateDIBSection(hDC_dib, &bmi, DIB_RGB_COLORS, (void **)&dib_data, nullptr, 0x0); - SelectObject(hDC_dib, hBitmap); - - ZeroMemory(dib_data, dib_size.x * dib_size.y * 4); - } -#endif + // Return here to prevent WM_MOVE and WM_SIZE from being sent + // See: https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-windowposchanged#remarks + return 0; + } break; + case WM_ENTERSIZEMOVE: { Input::get_singleton()->release_pressed_events(); windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); @@ -2879,6 +2955,9 @@ void DisplayServerWindows::_process_activate_event(WindowID p_window_id, WPARAM alt_mem = false; control_mem = false; shift_mem = false; + + // Restore mouse mode. + _set_mouse_mode_impl(mouse_mode); } else { // WM_INACTIVE. Input::get_singleton()->release_pressed_events(); _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_OUT); @@ -3046,7 +3125,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, DWORD dwExStyle; DWORD dwStyle; - _get_window_style(window_id_counter == MAIN_WINDOW_ID, p_mode == WINDOW_MODE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT), dwStyle, dwExStyle); + _get_window_style(window_id_counter == MAIN_WINDOW_ID, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT), dwStyle, dwExStyle); RECT WindowRect; @@ -3055,7 +3134,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, WindowRect.top = p_rect.position.y; WindowRect.bottom = p_rect.position.y + p_rect.size.y; - if (p_mode == WINDOW_MODE_FULLSCREEN) { + if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { int nearest_area = 0; Rect2i screen_rect; for (int i = 0; i < get_screen_count(); i++) { @@ -3098,7 +3177,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, windows.erase(id); return INVALID_WINDOW_ID; } - if (p_mode != WINDOW_MODE_FULLSCREEN) { + if (p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { wd.pre_fs_valid = true; } diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 3593dc1a05ae..d36ca97ebee1 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -326,12 +326,12 @@ class DisplayServerWindows : public DisplayServer { Vector mpath; - bool preserve_window_size = false; bool pre_fs_valid = false; RECT pre_fs_rect; bool maximized = false; bool minimized = false; bool fullscreen = false; + bool multiwindow_fs = false; bool borderless = false; bool resizable = true; bool window_focused = false; @@ -401,7 +401,7 @@ class DisplayServerWindows : public DisplayServer { WNDPROC user_proc = nullptr; void _send_window_event(const WindowData &wd, WindowEvent p_event); - void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex); + void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex); MouseMode mouse_mode; int restore_mouse_trails = 0; @@ -458,6 +458,7 @@ class DisplayServerWindows : public DisplayServer { virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW) override; @@ -472,6 +473,8 @@ class DisplayServerWindows : public DisplayServer { virtual void show_window(WindowID p_window) override; virtual void delete_sub_window(WindowID p_window) override; + virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override; + virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override; virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override; diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp index 68762db3a99f..d30d0afc5c36 100644 --- a/platform/windows/export/export_plugin.cpp +++ b/platform/windows/export/export_plugin.cpp @@ -76,8 +76,8 @@ void EditorExportPlatformWindows::get_export_options(List *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray())); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/company_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Company Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_description"), "")); @@ -89,6 +89,7 @@ void EditorExportPlatformWindows::_rcedit_add_data(const Ref String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit"); if (rcedit_path.is_empty()) { + WARN_PRINT("The rcedit tool is not configured in the Editor Settings (Export > Windows > Rcedit). No custom icon or app information data will be embedded in the exported executable."); return; } @@ -327,3 +328,46 @@ Error EditorExportPlatformWindows::_code_sign(const Ref &p_p return OK; } + +bool EditorExportPlatformWindows::can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const { + String err = ""; + bool valid = EditorExportPlatformPC::can_export(p_preset, err, r_missing_templates); + + String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit"); + if (rcedit_path.is_empty()) { + err += TTR("The rcedit tool must be configured in the Editor Settings (Export > Windows > Rcedit) to change the icon or app information data.") + "\n"; + } + + String icon_path = ProjectSettings::get_singleton()->globalize_path(p_preset->get("application/icon")); + if (!icon_path.is_empty() && !FileAccess::exists(icon_path)) { + err += TTR("Invalid icon path:") + " " + icon_path + "\n"; + } + + // Only non-negative integers can exist in the version string. + + String file_version = p_preset->get("application/file_version"); + if (!file_version.is_empty()) { + PackedStringArray version_array = file_version.split(".", false); + if (version_array.size() != 4 || !version_array[0].is_valid_int() || + !version_array[1].is_valid_int() || !version_array[2].is_valid_int() || + !version_array[3].is_valid_int() || file_version.find("-") > -1) { + err += TTR("Invalid file version:") + " " + file_version + "\n"; + } + } + + String product_version = p_preset->get("application/product_version"); + if (!product_version.is_empty()) { + PackedStringArray version_array = product_version.split(".", false); + if (version_array.size() != 4 || !version_array[0].is_valid_int() || + !version_array[1].is_valid_int() || !version_array[2].is_valid_int() || + !version_array[3].is_valid_int() || product_version.find("-") > -1) { + err += TTR("Invalid product version:") + " " + product_version + "\n"; + } + } + + if (!err.is_empty()) { + r_error = err; + } + + return valid; +} diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h index 351333aa4240..89e5b1b635c1 100644 --- a/platform/windows/export/export_plugin.h +++ b/platform/windows/export/export_plugin.h @@ -47,6 +47,7 @@ class EditorExportPlatformWindows : public EditorExportPlatformPC { virtual Error sign_shared_object(const Ref &p_preset, bool p_debug, const String &p_path) override; virtual void get_export_options(List *r_options) override; virtual bool get_export_option_visibility(const String &p_option, const Map &p_options) const override; + virtual bool can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const override; }; #endif diff --git a/platform/windows/godot_windows.cpp b/platform/windows/godot_windows.cpp index 7819ab9a32ad..618d5670d2c9 100644 --- a/platform/windows/godot_windows.cpp +++ b/platform/windows/godot_windows.cpp @@ -39,7 +39,7 @@ #ifndef TOOLS_ENABLED #if defined _MSC_VER #pragma section("pck", read) -__declspec(allocate("pck")) static char dummy[8] = { 0 }; +__declspec(allocate("pck")) static const char dummy[8] = { 0 }; #elif defined __GNUC__ static const char dummy[8] __attribute__((section("pck"), used)) = { 0 }; #endif @@ -140,6 +140,11 @@ int widechar_main(int argc, wchar_t **argv) { setlocale(LC_CTYPE, ""); +#ifndef TOOLS_ENABLED + // Workaround to prevent LTCG (MSVC LTO) from removing "pck" section + const char *dummy_guard = dummy; +#endif + char **argv_utf8 = new char *[argc]; for (int i = 0; i < argc; ++i) { diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 06b8fea681a3..d84453107106 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -83,15 +83,23 @@ static String format_error_message(DWORD id) { return msg; } +void RedirectStream(const char *p_file_name, const char *p_mode, FILE *p_cpp_stream, const DWORD p_std_handle) { + const HANDLE h_existing = GetStdHandle(p_std_handle); + if (h_existing != INVALID_HANDLE_VALUE) { // Redirect only if attached console have a valid handle. + const HANDLE h_cpp = reinterpret_cast(_get_osfhandle(_fileno(p_cpp_stream))); + if (h_cpp == INVALID_HANDLE_VALUE) { // Redirect only if it's not already redirected to the pipe or file. + FILE *fp = p_cpp_stream; + freopen_s(&fp, p_file_name, p_mode, p_cpp_stream); // Redirect stream. + setvbuf(p_cpp_stream, nullptr, _IONBF, 0); // Disable stream buffering. + } + } +} + void RedirectIOToConsole() { if (AttachConsole(ATTACH_PARENT_PROCESS)) { - FILE *fpstdin = stdin; - FILE *fpstdout = stdout; - FILE *fpstderr = stderr; - - freopen_s(&fpstdin, "CONIN$", "r", stdin); - freopen_s(&fpstdout, "CONOUT$", "w", stdout); - freopen_s(&fpstderr, "CONOUT$", "w", stderr); + RedirectStream("CONIN$", "r", stdin, STD_INPUT_HANDLE); + RedirectStream("CONOUT$", "w", stdout, STD_OUTPUT_HANDLE); + RedirectStream("CONOUT$", "w", stderr, STD_ERROR_HANDLE); printf("\n"); // Make sure our output is starting from the new line. } @@ -385,14 +393,14 @@ Error OS_Windows::execute(const String &p_path, const List &p_arguments, } inherit_handles = true; } - DWORD creaton_flags = NORMAL_PRIORITY_CLASS; + DWORD creation_flags = NORMAL_PRIORITY_CLASS; if (p_open_console) { - creaton_flags |= CREATE_NEW_CONSOLE; + creation_flags |= CREATE_NEW_CONSOLE; } else { - creaton_flags |= CREATE_NO_WINDOW; + creation_flags |= CREATE_NO_WINDOW; } - int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, inherit_handles, creaton_flags, nullptr, nullptr, si_w, &pi.pi); + int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, inherit_handles, creation_flags, nullptr, nullptr, si_w, &pi.pi); if (!ret && r_pipe) { CloseHandle(pipe[0]); // Cleanup pipe handles. CloseHandle(pipe[1]); @@ -446,14 +454,14 @@ Error OS_Windows::create_process(const String &p_path, const List &p_arg ZeroMemory(&pi.pi, sizeof(pi.pi)); LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si; - DWORD creaton_flags = NORMAL_PRIORITY_CLASS; + DWORD creation_flags = NORMAL_PRIORITY_CLASS; if (p_open_console) { - creaton_flags |= CREATE_NEW_CONSOLE; + creation_flags |= CREATE_NEW_CONSOLE; } else { - creaton_flags |= CREATE_NO_WINDOW; + creation_flags |= CREATE_NO_WINDOW; } - int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, creaton_flags, nullptr, nullptr, si_w, &pi.pi); + int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, creation_flags, nullptr, nullptr, si_w, &pi.pi); ERR_FAIL_COND_V_MSG(ret == 0, ERR_CANT_FORK, "Could not create child process: " + command); ProcessID pid = pi.pi.dwProcessId; diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp index 4916eb573cd0..decb3d0dd829 100644 --- a/scene/2d/animated_sprite_2d.cpp +++ b/scene/2d/animated_sprite_2d.cpp @@ -417,7 +417,7 @@ void AnimatedSprite2D::_reset_timeout() { void AnimatedSprite2D::set_animation(const StringName &p_animation) { ERR_FAIL_COND_MSG(frames == nullptr, vformat("There is no animation with name '%s'.", p_animation)); - ERR_FAIL_COND_MSG(frames->get_animation_names().find(p_animation) == -1, vformat("There is no animation with name '%s'.", p_animation)); + ERR_FAIL_COND_MSG(!frames->get_animation_names().has(p_animation), vformat("There is no animation with name '%s'.", p_animation)); if (animation == p_animation) { return; diff --git a/scene/2d/collision_object_2d.cpp b/scene/2d/collision_object_2d.cpp index 0f4e3c8bedcf..70c7e48fd46a 100644 --- a/scene/2d/collision_object_2d.cpp +++ b/scene/2d/collision_object_2d.cpp @@ -268,7 +268,7 @@ uint32_t CollisionObject2D::create_shape_owner(Object *p_owner) { id = shapes.back()->key() + 1; } - sd.owner = p_owner; + sd.owner_id = p_owner ? p_owner->get_instance_id() : ObjectID(); shapes[id] = sd; @@ -382,7 +382,7 @@ Transform2D CollisionObject2D::shape_owner_get_transform(uint32_t p_owner) const Object *CollisionObject2D::shape_owner_get_owner(uint32_t p_owner) const { ERR_FAIL_COND_V(!shapes.has(p_owner), nullptr); - return shapes[p_owner].owner; + return ObjectDB::get_instance(shapes[p_owner].owner_id); } void CollisionObject2D::shape_owner_add_shape(uint32_t p_owner, const Ref &p_shape) { diff --git a/scene/2d/collision_object_2d.h b/scene/2d/collision_object_2d.h index 9463b2c42977..f2b7eecc7bdd 100644 --- a/scene/2d/collision_object_2d.h +++ b/scene/2d/collision_object_2d.h @@ -59,7 +59,7 @@ class CollisionObject2D : public Node2D { PhysicsServer2D::BodyMode body_mode = PhysicsServer2D::BODY_MODE_STATIC; struct ShapeData { - Object *owner = nullptr; + ObjectID owner_id; Transform2D xform; struct Shape { Ref shape; diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index b6d1e5c93458..11c036ac9c39 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -335,6 +335,42 @@ Ref GPUParticles2D::get_texture() const { void GPUParticles2D::_validate_property(PropertyInfo &property) const { } +void GPUParticles2D::emit_particle(const Transform2D &p_transform2d, const Vector2 &p_velocity2d, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) { + Transform3D transform; + transform.basis.set_axis(0, Vector3(p_transform2d.get_axis(0).x, p_transform2d.get_axis(0).y, 0)); + transform.basis.set_axis(1, Vector3(p_transform2d.get_axis(1).x, p_transform2d.get_axis(1).y, 0)); + transform.set_origin(Vector3(p_transform2d.get_origin().x, p_transform2d.get_origin().y, 0)); + Vector3 velocity = Vector3(p_velocity2d.x, p_velocity2d.y, 0); + + RS::get_singleton()->particles_emit(particles, transform, velocity, p_color, p_custom, p_emit_flags); +} + +void GPUParticles2D::_attach_sub_emitter() { + Node *n = get_node_or_null(sub_emitter); + if (n) { + GPUParticles2D *sen = Object::cast_to(n); + if (sen && sen != this) { + RS::get_singleton()->particles_set_subemitter(particles, sen->particles); + } + } +} + +void GPUParticles2D::set_sub_emitter(const NodePath &p_path) { + if (is_inside_tree()) { + RS::get_singleton()->particles_set_subemitter(particles, RID()); + } + + sub_emitter = p_path; + + if (is_inside_tree() && sub_emitter != NodePath()) { + _attach_sub_emitter(); + } +} + +NodePath GPUParticles2D::get_sub_emitter() const { + return sub_emitter; +} + void GPUParticles2D::restart() { RS::get_singleton()->particles_restart(particles); RS::get_singleton()->particles_set_emitting(particles, true); @@ -462,6 +498,16 @@ void GPUParticles2D::_notification(int p_what) { #endif } + if (p_what == NOTIFICATION_ENTER_TREE) { + if (sub_emitter != NodePath()) { + _attach_sub_emitter(); + } + } + + if (p_what == NOTIFICATION_EXIT_TREE) { + RS::get_singleton()->particles_set_subemitter(particles, RID()); + } + if (p_what == NOTIFICATION_PAUSED || p_what == NOTIFICATION_UNPAUSED) { if (can_process()) { RS::get_singleton()->particles_set_speed_scale(particles, speed_scale); @@ -523,6 +569,11 @@ void GPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("restart"), &GPUParticles2D::restart); + ClassDB::bind_method(D_METHOD("set_sub_emitter", "path"), &GPUParticles2D::set_sub_emitter); + ClassDB::bind_method(D_METHOD("get_sub_emitter"), &GPUParticles2D::get_sub_emitter); + + ClassDB::bind_method(D_METHOD("emit_particle", "xform", "velocity", "color", "custom", "flags"), &GPUParticles2D::emit_particle); + ClassDB::bind_method(D_METHOD("set_trail_enabled", "enabled"), &GPUParticles2D::set_trail_enabled); ClassDB::bind_method(D_METHOD("set_trail_length", "secs"), &GPUParticles2D::set_trail_length); @@ -538,6 +589,7 @@ void GPUParticles2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); ADD_PROPERTY_DEFAULT("emitting", true); // Workaround for doctool in headless mode, as dummy rasterizer always returns false. ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "sub_emitter", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GPUParticles2D"), "set_sub_emitter", "get_sub_emitter"); ADD_GROUP("Time", ""); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater"), "set_lifetime", "get_lifetime"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot"); @@ -566,6 +618,12 @@ void GPUParticles2D::_bind_methods() { BIND_ENUM_CONSTANT(DRAW_ORDER_INDEX); BIND_ENUM_CONSTANT(DRAW_ORDER_LIFETIME); BIND_ENUM_CONSTANT(DRAW_ORDER_REVERSE_LIFETIME); + + BIND_ENUM_CONSTANT(EMIT_FLAG_POSITION); + BIND_ENUM_CONSTANT(EMIT_FLAG_ROTATION_SCALE); + BIND_ENUM_CONSTANT(EMIT_FLAG_VELOCITY); + BIND_ENUM_CONSTANT(EMIT_FLAG_COLOR); + BIND_ENUM_CONSTANT(EMIT_FLAG_CUSTOM); } GPUParticles2D::GPUParticles2D() { diff --git a/scene/2d/gpu_particles_2d.h b/scene/2d/gpu_particles_2d.h index aa9a8da12929..fc95ae27b2be 100644 --- a/scene/2d/gpu_particles_2d.h +++ b/scene/2d/gpu_particles_2d.h @@ -79,6 +79,8 @@ class GPUParticles2D : public Node2D { RID mesh; + void _attach_sub_emitter(); + protected: static void _bind_methods(); virtual void _validate_property(PropertyInfo &property) const override; @@ -139,6 +141,19 @@ class GPUParticles2D : public Node2D { TypedArray get_configuration_warnings() const override; + void set_sub_emitter(const NodePath &p_path); + NodePath get_sub_emitter() const; + + enum EmitFlags { + EMIT_FLAG_POSITION = RS::PARTICLES_EMIT_FLAG_POSITION, + EMIT_FLAG_ROTATION_SCALE = RS::PARTICLES_EMIT_FLAG_ROTATION_SCALE, + EMIT_FLAG_VELOCITY = RS::PARTICLES_EMIT_FLAG_VELOCITY, + EMIT_FLAG_COLOR = RS::PARTICLES_EMIT_FLAG_COLOR, + EMIT_FLAG_CUSTOM = RS::PARTICLES_EMIT_FLAG_CUSTOM + }; + + void emit_particle(const Transform2D &p_transform, const Vector2 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags); + void restart(); Rect2 capture_rect() const; GPUParticles2D(); @@ -146,5 +161,6 @@ class GPUParticles2D : public Node2D { }; VARIANT_ENUM_CAST(GPUParticles2D::DrawOrder) +VARIANT_ENUM_CAST(GPUParticles2D::EmitFlags) #endif // PARTICLES_2D_H diff --git a/scene/2d/line_2d.cpp b/scene/2d/line_2d.cpp index 1a6aaecaa8b1..7f2290bdc731 100644 --- a/scene/2d/line_2d.cpp +++ b/scene/2d/line_2d.cpp @@ -133,7 +133,7 @@ int Line2D::get_point_count() const { void Line2D::clear_points() { int count = _points.size(); if (count > 0) { - _points.resize(0); + _points.clear(); update(); } } diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp index e5df0897719b..fad54070a502 100644 --- a/scene/2d/navigation_obstacle_2d.cpp +++ b/scene/2d/navigation_obstacle_2d.cpp @@ -53,9 +53,9 @@ void NavigationObstacle2D::_validate_property(PropertyInfo &p_property) const { void NavigationObstacle2D::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_READY: { - initialize_agent(); + case NOTIFICATION_ENTER_TREE: { parent_node2d = Object::cast_to(get_parent()); + reevaluate_agent_radius(); if (parent_node2d != nullptr) { // place agent on navigation map first or else the RVO agent callback creation fails silently later NavigationServer2D::get_singleton()->agent_set_map(get_rid(), parent_node2d->get_world_2d()->get_navigation_map()); @@ -83,6 +83,7 @@ void NavigationObstacle2D::_notification(int p_what) { NavigationObstacle2D::NavigationObstacle2D() { agent = NavigationServer2D::get_singleton()->agent_create(); + initialize_agent(); } NavigationObstacle2D::~NavigationObstacle2D() { @@ -110,7 +111,7 @@ void NavigationObstacle2D::initialize_agent() { void NavigationObstacle2D::reevaluate_agent_radius() { if (!estimate_radius) { NavigationServer2D::get_singleton()->agent_set_radius(agent, radius); - } else if (parent_node2d) { + } else if (parent_node2d && parent_node2d->is_inside_tree()) { NavigationServer2D::get_singleton()->agent_set_radius(agent, estimate_agent_radius()); } } diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index 34f5830d8df5..e685ad8f67af 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -104,8 +104,8 @@ void NavigationPolygon::_set_polygons(const TypedArray> &p_array } } -Array NavigationPolygon::_get_polygons() const { - Array ret; +TypedArray> NavigationPolygon::_get_polygons() const { + TypedArray> ret; ret.resize(polygons.size()); for (int i = 0; i < ret.size(); i++) { ret[i] = polygons[i].indices; @@ -122,8 +122,8 @@ void NavigationPolygon::_set_outlines(const TypedArray> &p_array rect_cache_dirty = true; } -Array NavigationPolygon::_get_outlines() const { - Array ret; +TypedArray> NavigationPolygon::_get_outlines() const { + TypedArray> ret; ret.resize(outlines.size()); for (int i = 0; i < ret.size(); i++) { ret[i] = outlines[i]; @@ -299,7 +299,7 @@ void NavigationPolygon::make_polygons_from_outlines() { } polygons.clear(); - vertices.resize(0); + vertices.clear(); Map points; for (List::Element *I = out_poly.front(); I; I = I->next()) { diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h index 012debb58483..487a57840165 100644 --- a/scene/2d/navigation_region_2d.h +++ b/scene/2d/navigation_region_2d.h @@ -55,10 +55,10 @@ class NavigationPolygon : public Resource { static void _bind_methods(); void _set_polygons(const TypedArray> &p_array); - Array _get_polygons() const; + TypedArray> _get_polygons() const; void _set_outlines(const TypedArray> &p_array); - Array _get_outlines() const; + TypedArray> _get_outlines() const; public: #ifdef TOOLS_ENABLED diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index 9331340e1b8a..9d2654324390 100644 --- a/scene/2d/node_2d.cpp +++ b/scene/2d/node_2d.cpp @@ -413,7 +413,7 @@ void Node2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_relative_transform_to_parent", "parent"), &Node2D::get_relative_transform_to_parent); ADD_GROUP("Transform", ""); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_RANGE, "-99999,99999,0,or_lesser,or_greater,noslider,suffix:px"), "set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_lesser,or_greater,noslider,suffix:px"), "set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians"), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scale"), "set_scale", "get_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "skew", PROPERTY_HINT_RANGE, "-89.9,89.9,0.1,radians"), "set_skew", "get_skew"); diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index b2cc8164b6e9..fb611addf8b3 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -34,8 +34,8 @@ #include "scene/scene_string_names.h" void PhysicsBody2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("move_and_collide", "linear_velocity", "test_only", "safe_margin"), &PhysicsBody2D::_move, DEFVAL(false), DEFVAL(0.08)); - ClassDB::bind_method(D_METHOD("test_move", "from", "linear_velocity", "collision", "safe_margin"), &PhysicsBody2D::test_move, DEFVAL(Variant()), DEFVAL(0.08)); + ClassDB::bind_method(D_METHOD("move_and_collide", "distance", "test_only", "safe_margin"), &PhysicsBody2D::_move, DEFVAL(false), DEFVAL(0.08)); + ClassDB::bind_method(D_METHOD("test_move", "from", "distance", "collision", "safe_margin"), &PhysicsBody2D::test_move, DEFVAL(Variant()), DEFVAL(0.08)); ClassDB::bind_method(D_METHOD("get_collision_exceptions"), &PhysicsBody2D::get_collision_exceptions); ClassDB::bind_method(D_METHOD("add_collision_exception_with", "body"), &PhysicsBody2D::add_collision_exception_with); @@ -54,11 +54,8 @@ PhysicsBody2D::~PhysicsBody2D() { } } -Ref PhysicsBody2D::_move(const Vector2 &p_linear_velocity, bool p_test_only, real_t p_margin) { - // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky. - double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); - - PhysicsServer2D::MotionParameters parameters(get_global_transform(), p_linear_velocity * delta, p_margin); +Ref PhysicsBody2D::_move(const Vector2 &p_distance, bool p_test_only, real_t p_margin) { + PhysicsServer2D::MotionParameters parameters(get_global_transform(), p_distance, p_margin); PhysicsServer2D::MotionResult result; if (move_and_collide(parameters, result, p_test_only)) { @@ -129,7 +126,7 @@ bool PhysicsBody2D::move_and_collide(const PhysicsServer2D::MotionParameters &p_ return colliding; } -bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_linear_velocity, const Ref &r_collision, real_t p_margin) { +bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_distance, const Ref &r_collision, real_t p_margin) { ERR_FAIL_COND_V(!is_inside_tree(), false); PhysicsServer2D::MotionResult *r = nullptr; @@ -141,10 +138,7 @@ bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_linear r = &temp_result; } - // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky. - double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); - - PhysicsServer2D::MotionParameters parameters(p_from, p_linear_velocity * delta, p_margin); + PhysicsServer2D::MotionParameters parameters(p_from, p_distance, p_margin); bool colliding = PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), parameters, r); @@ -1162,7 +1156,7 @@ bool CharacterBody2D::move_and_slide() { if (motion_mode == MOTION_MODE_GROUNDED) { _move_and_slide_grounded(delta, was_on_floor); } else { - _move_and_slide_free(delta); + _move_and_slide_floating(delta); } // Compute real velocity. @@ -1350,7 +1344,7 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo } } -void CharacterBody2D::_move_and_slide_free(double p_delta) { +void CharacterBody2D::_move_and_slide_floating(double p_delta) { Vector2 motion = motion_velocity * p_delta; platform_rid = RID(); @@ -1376,7 +1370,7 @@ void CharacterBody2D::_move_and_slide_free(double p_delta) { break; } - if (free_mode_min_slide_angle != 0 && result.get_angle(-motion_velocity.normalized()) < free_mode_min_slide_angle + FLOOR_ANGLE_THRESHOLD) { + if (wall_min_slide_angle != 0 && result.get_angle(-motion_velocity.normalized()) < wall_min_slide_angle + FLOOR_ANGLE_THRESHOLD) { motion = Vector2(); } else if (first_slide) { Vector2 motion_slide_norm = result.remainder.slide(result.collision_normal).normalized(); @@ -1668,12 +1662,12 @@ void CharacterBody2D::set_floor_snap_length(real_t p_floor_snap_length) { floor_snap_length = p_floor_snap_length; } -real_t CharacterBody2D::get_free_mode_min_slide_angle() const { - return free_mode_min_slide_angle; +real_t CharacterBody2D::get_wall_min_slide_angle() const { + return wall_min_slide_angle; } -void CharacterBody2D::set_free_mode_min_slide_angle(real_t p_radians) { - free_mode_min_slide_angle = p_radians; +void CharacterBody2D::set_wall_min_slide_angle(real_t p_radians) { + wall_min_slide_angle = p_radians; } const Vector2 &CharacterBody2D::get_up_direction() const { @@ -1681,7 +1675,7 @@ const Vector2 &CharacterBody2D::get_up_direction() const { } void CharacterBody2D::set_up_direction(const Vector2 &p_up_direction) { - ERR_FAIL_COND_MSG(p_up_direction == Vector2(), "up_direction can't be equal to Vector2.ZERO, consider using Free motion mode instead."); + ERR_FAIL_COND_MSG(p_up_direction == Vector2(), "up_direction can't be equal to Vector2.ZERO, consider using Floating motion mode instead."); up_direction = p_up_direction.normalized(); } @@ -1728,8 +1722,8 @@ void CharacterBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_floor_max_angle", "radians"), &CharacterBody2D::set_floor_max_angle); ClassDB::bind_method(D_METHOD("get_floor_snap_length"), &CharacterBody2D::get_floor_snap_length); ClassDB::bind_method(D_METHOD("set_floor_snap_length", "floor_snap_length"), &CharacterBody2D::set_floor_snap_length); - ClassDB::bind_method(D_METHOD("get_free_mode_min_slide_angle"), &CharacterBody2D::get_free_mode_min_slide_angle); - ClassDB::bind_method(D_METHOD("set_free_mode_min_slide_angle", "radians"), &CharacterBody2D::set_free_mode_min_slide_angle); + ClassDB::bind_method(D_METHOD("get_wall_min_slide_angle"), &CharacterBody2D::get_wall_min_slide_angle); + ClassDB::bind_method(D_METHOD("set_wall_min_slide_angle", "radians"), &CharacterBody2D::set_wall_min_slide_angle); ClassDB::bind_method(D_METHOD("get_up_direction"), &CharacterBody2D::get_up_direction); ClassDB::bind_method(D_METHOD("set_up_direction", "up_direction"), &CharacterBody2D::set_up_direction); ClassDB::bind_method(D_METHOD("set_motion_mode", "mode"), &CharacterBody2D::set_motion_mode); @@ -1754,14 +1748,12 @@ void CharacterBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_slide_collision", "slide_idx"), &CharacterBody2D::_get_slide_collision); ClassDB::bind_method(D_METHOD("get_last_slide_collision"), &CharacterBody2D::_get_last_slide_collision); - ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_mode", PROPERTY_HINT_ENUM, "Grounded,Free", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_motion_mode", "get_motion_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_mode", PROPERTY_HINT_ENUM, "Grounded,Floating", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_motion_mode", "get_motion_mode"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "up_direction"), "set_up_direction", "get_up_direction"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "motion_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_motion_velocity", "get_motion_velocity"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_ceiling"), "set_slide_on_ceiling_enabled", "is_slide_on_ceiling_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_max_slides", "get_max_slides"); - - ADD_GROUP("Free Mode", "free_mode_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "free_mode_min_slide_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians", PROPERTY_USAGE_DEFAULT), "set_free_mode_min_slide_angle", "get_free_mode_min_slide_angle"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wall_min_slide_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians", PROPERTY_USAGE_DEFAULT), "set_wall_min_slide_angle", "get_wall_min_slide_angle"); ADD_GROUP("Floor", "floor_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_stop_on_slope"), "set_floor_stop_on_slope_enabled", "is_floor_stop_on_slope_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_constant_speed"), "set_floor_constant_speed_enabled", "is_floor_constant_speed_enabled"); @@ -1775,7 +1767,7 @@ void CharacterBody2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001"), "set_safe_margin", "get_safe_margin"); BIND_ENUM_CONSTANT(MOTION_MODE_GROUNDED); - BIND_ENUM_CONSTANT(MOTION_MODE_FREE); + BIND_ENUM_CONSTANT(MOTION_MODE_FLOATING); BIND_ENUM_CONSTANT(PLATFORM_VEL_ON_LEAVE_ALWAYS); BIND_ENUM_CONSTANT(PLATFORM_VEL_ON_LEAVE_UPWARD_ONLY); @@ -1783,12 +1775,12 @@ void CharacterBody2D::_bind_methods() { } void CharacterBody2D::_validate_property(PropertyInfo &property) const { - if (motion_mode == MOTION_MODE_FREE) { + if (motion_mode == MOTION_MODE_FLOATING) { if (property.name.begins_with("floor_") || property.name == "up_direction" || property.name == "slide_on_ceiling") { property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL; } } else { - if (property.name == "free_mode_min_slide_angle") { + if (property.name == "wall_min_slide_angle") { property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL; } } diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index 649d67d75918..cfaa2570fb6e 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -47,11 +47,11 @@ class PhysicsBody2D : public CollisionObject2D { Ref motion_cache; - Ref _move(const Vector2 &p_linear_velocity, bool p_test_only = false, real_t p_margin = 0.08); + Ref _move(const Vector2 &p_distance, bool p_test_only = false, real_t p_margin = 0.08); public: bool move_and_collide(const PhysicsServer2D::MotionParameters &p_parameters, PhysicsServer2D::MotionResult &r_result, bool p_test_only = false, bool p_cancel_sliding = true); - bool test_move(const Transform2D &p_from, const Vector2 &p_linear_velocity, const Ref &r_collision = Ref(), real_t p_margin = 0.08); + bool test_move(const Transform2D &p_from, const Vector2 &p_distance, const Ref &r_collision = Ref(), real_t p_margin = 0.08); TypedArray get_collision_exceptions(); void add_collision_exception_with(Node *p_node); //must be physicsbody @@ -328,7 +328,7 @@ class CharacterBody2D : public PhysicsBody2D { public: enum MotionMode { MOTION_MODE_GROUNDED, - MOTION_MODE_FREE, + MOTION_MODE_FLOATING, }; enum MovingPlatformApplyVelocityOnLeave { PLATFORM_VEL_ON_LEAVE_ALWAYS, @@ -374,7 +374,7 @@ class CharacterBody2D : public PhysicsBody2D { int platform_layer = 0; real_t floor_max_angle = Math::deg2rad((real_t)45.0); real_t floor_snap_length = 1; - real_t free_mode_min_slide_angle = Math::deg2rad((real_t)15.0); + real_t wall_min_slide_angle = Math::deg2rad((real_t)15.0); Vector2 up_direction = Vector2(0.0, -1.0); uint32_t moving_platform_floor_layers = UINT32_MAX; uint32_t moving_platform_wall_layers = 0; @@ -420,8 +420,8 @@ class CharacterBody2D : public PhysicsBody2D { real_t get_floor_snap_length(); void set_floor_snap_length(real_t p_floor_snap_length); - real_t get_free_mode_min_slide_angle() const; - void set_free_mode_min_slide_angle(real_t p_radians); + real_t get_wall_min_slide_angle() const; + void set_wall_min_slide_angle(real_t p_radians); uint32_t get_moving_platform_floor_layers() const; void set_moving_platform_floor_layers(const uint32_t p_exclude_layer); @@ -435,7 +435,7 @@ class CharacterBody2D : public PhysicsBody2D { void set_moving_platform_apply_velocity_on_leave(MovingPlatformApplyVelocityOnLeave p_on_leave_velocity); MovingPlatformApplyVelocityOnLeave get_moving_platform_apply_velocity_on_leave() const; - void _move_and_slide_free(double p_delta); + void _move_and_slide_floating(double p_delta); void _move_and_slide_grounded(double p_delta, bool p_was_on_floor); Ref _get_slide_collision(int p_bounce); diff --git a/scene/2d/ray_cast_2d.cpp b/scene/2d/ray_cast_2d.cpp index 1fdd8b05a620..51b3e676f95e 100644 --- a/scene/2d/ray_cast_2d.cpp +++ b/scene/2d/ray_cast_2d.cpp @@ -263,30 +263,29 @@ void RayCast2D::add_exception_rid(const RID &p_rid) { exclude.insert(p_rid); } -void RayCast2D::add_exception(const Object *p_object) { - ERR_FAIL_NULL(p_object); - const CollisionObject2D *co = Object::cast_to(p_object); - if (!co) { - return; - } - add_exception_rid(co->get_rid()); +void RayCast2D::add_exception(const CollisionObject2D *p_node) { + ERR_FAIL_NULL_MSG(p_node, "The passed Node must be an instance of CollisionObject2D."); + add_exception_rid(p_node->get_rid()); } void RayCast2D::remove_exception_rid(const RID &p_rid) { exclude.erase(p_rid); } -void RayCast2D::remove_exception(const Object *p_object) { - ERR_FAIL_NULL(p_object); - const CollisionObject2D *co = Object::cast_to(p_object); - if (!co) { - return; - } - remove_exception_rid(co->get_rid()); +void RayCast2D::remove_exception(const CollisionObject2D *p_node) { + ERR_FAIL_NULL_MSG(p_node, "The passed Node must be an instance of CollisionObject2D."); + remove_exception_rid(p_node->get_rid()); } void RayCast2D::clear_exceptions() { exclude.clear(); + + if (exclude_parent_body && is_inside_tree()) { + CollisionObject2D *parent = Object::cast_to(get_parent()); + if (parent) { + exclude.insert(parent->get_rid()); + } + } } void RayCast2D::set_collide_with_areas(bool p_enabled) { diff --git a/scene/2d/ray_cast_2d.h b/scene/2d/ray_cast_2d.h index a1015c6ce063..2c6f2d5c0000 100644 --- a/scene/2d/ray_cast_2d.h +++ b/scene/2d/ray_cast_2d.h @@ -33,6 +33,8 @@ #include "scene/2d/node_2d.h" +class CollisionObject2D; + class RayCast2D : public Node2D { GDCLASS(RayCast2D, Node2D); @@ -94,9 +96,9 @@ class RayCast2D : public Node2D { Vector2 get_collision_normal() const; void add_exception_rid(const RID &p_rid); - void add_exception(const Object *p_object); + void add_exception(const CollisionObject2D *p_node); void remove_exception_rid(const RID &p_rid); - void remove_exception(const Object *p_object); + void remove_exception(const CollisionObject2D *p_node); void clear_exceptions(); RayCast2D(); diff --git a/scene/2d/shape_cast_2d.cpp b/scene/2d/shape_cast_2d.cpp index 10194861b462..24199c96b5f2 100644 --- a/scene/2d/shape_cast_2d.cpp +++ b/scene/2d/shape_cast_2d.cpp @@ -322,26 +322,18 @@ void ShapeCast2D::add_exception_rid(const RID &p_rid) { exclude.insert(p_rid); } -void ShapeCast2D::add_exception(const Object *p_object) { - ERR_FAIL_NULL(p_object); - const CollisionObject2D *co = Object::cast_to(p_object); - if (!co) { - return; - } - add_exception_rid(co->get_rid()); +void ShapeCast2D::add_exception(const CollisionObject2D *p_node) { + ERR_FAIL_NULL_MSG(p_node, "The passed Node must be an instance of CollisionObject2D."); + add_exception_rid(p_node->get_rid()); } void ShapeCast2D::remove_exception_rid(const RID &p_rid) { exclude.erase(p_rid); } -void ShapeCast2D::remove_exception(const Object *p_object) { - ERR_FAIL_NULL(p_object); - const CollisionObject2D *co = Object::cast_to(p_object); - if (!co) { - return; - } - remove_exception_rid(co->get_rid()); +void ShapeCast2D::remove_exception(const CollisionObject2D *p_node) { + ERR_FAIL_NULL_MSG(p_node, "The passed Node must be an instance of CollisionObject2D."); + remove_exception_rid(p_node->get_rid()); } void ShapeCast2D::clear_exceptions() { diff --git a/scene/2d/shape_cast_2d.h b/scene/2d/shape_cast_2d.h index 7e1ebeb31552..ea36b25068b5 100644 --- a/scene/2d/shape_cast_2d.h +++ b/scene/2d/shape_cast_2d.h @@ -34,6 +34,8 @@ #include "scene/2d/node_2d.h" #include "scene/resources/shape_2d.h" +class CollisionObject2D; + class ShapeCast2D : public Node2D { GDCLASS(ShapeCast2D, Node2D); @@ -109,9 +111,9 @@ class ShapeCast2D : public Node2D { real_t get_closest_collision_unsafe_fraction() const; void add_exception_rid(const RID &p_rid); - void add_exception(const Object *p_object); + void add_exception(const CollisionObject2D *p_node); void remove_exception_rid(const RID &p_rid); - void remove_exception(const Object *p_object); + void remove_exception(const CollisionObject2D *p_node); void clear_exceptions(); TypedArray get_configuration_warnings() const override; diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 5857b6710f9b..02ca1ba2aaa5 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -1119,7 +1119,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList::List if (q.runtime_tile_data_cache.has(E_cell.value)) { tile_data = q.runtime_tile_data_cache[E_cell.value]; } else { - tile_data = Object::cast_to(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); } Ref mat = tile_data->get_material(); @@ -1311,7 +1311,7 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile)); + const TileData *tile_data = p_tile_data_override ? p_tile_data_override : atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile); // Get the tile modulation. Color modulate = tile_data->get_modulate() * p_modulation; @@ -1474,7 +1474,7 @@ void TileMap::_physics_update_dirty_quadrants(SelfList::List &r if (q.runtime_tile_data_cache.has(E_cell->get())) { tile_data = q.runtime_tile_data_cache[E_cell->get()]; } else { - tile_data = Object::cast_to(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); } for (int tile_set_physics_layer = 0; tile_set_physics_layer < tile_set->get_physics_layers_count(); tile_set_physics_layer++) { Ref physics_material = tile_set->get_physics_layer_physics_material(tile_set_physics_layer); @@ -1671,7 +1671,7 @@ void TileMap::_navigation_update_dirty_quadrants(SelfList::List if (q.runtime_tile_data_cache.has(E_cell->get())) { tile_data = q.runtime_tile_data_cache[E_cell->get()]; } else { - tile_data = Object::cast_to(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); } q.navigation_regions[E_cell->get()].resize(tile_set->get_navigation_layers_count()); @@ -1760,7 +1760,7 @@ void TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { if (p_quadrant->runtime_tile_data_cache.has(E_cell->get())) { tile_data = p_quadrant->runtime_tile_data_cache[E_cell->get()]; } else { - tile_data = Object::cast_to(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); } Transform2D xform; @@ -2204,7 +2204,7 @@ Set TileMap::get_terrain_constraints_from_removed_ce Ref source = tile_set->get_source(neighbor_cell.source_id); Ref atlas_source = source; if (atlas_source.is_valid()) { - TileData *tile_data = Object::cast_to(atlas_source->get_tile_data(neighbor_cell.get_atlas_coords(), neighbor_cell.alternative_tile)); + TileData *tile_data = atlas_source->get_tile_data(neighbor_cell.get_atlas_coords(), neighbor_cell.alternative_tile); if (tile_data && tile_data->get_terrain_set() == p_terrain_set) { neighbor_tile_data = tile_data; } @@ -2580,7 +2580,7 @@ void TileMap::_build_runtime_update_tile_data(SelfList::List &r if (atlas_source) { bool ret = false; if (GDVIRTUAL_CALL(_use_tile_data_runtime_update, q.layer, E_cell.value, ret) && ret) { - TileData *tile_data = Object::cast_to(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + TileData *tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); // Create the runtime TileData. TileData *tile_data_runtime_use = tile_data->duplicate(); @@ -2725,7 +2725,7 @@ void TileMap::_get_property_list(List *p_list) const { } Vector2 TileMap::map_to_world(const Vector2i &p_pos) const { - // SHOULD RETURN THE CENTER OF THE TILE + // SHOULD RETURN THE CENTER OF THE CELL ERR_FAIL_COND_V(!tile_set.is_valid(), Vector2()); Vector2 ret = p_pos; @@ -3648,9 +3648,6 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_dirty_quadrants"), &TileMap::_update_dirty_quadrants); - ClassDB::bind_method(D_METHOD("_set_tile_data", "layer", "data"), &TileMap::_set_tile_data); - ClassDB::bind_method(D_METHOD("_get_tile_data", "layer"), &TileMap::_get_tile_data); - ClassDB::bind_method(D_METHOD("_tile_set_changed_deferred_update"), &TileMap::_tile_set_changed_deferred_update); GDVIRTUAL_BIND(_use_tile_data_runtime_update, "layer", "coords"); diff --git a/scene/3d/collision_object_3d.cpp b/scene/3d/collision_object_3d.cpp index df7c044f9ec9..3ab09550fab1 100644 --- a/scene/3d/collision_object_3d.cpp +++ b/scene/3d/collision_object_3d.cpp @@ -484,7 +484,7 @@ uint32_t CollisionObject3D::create_shape_owner(Object *p_owner) { id = shapes.back()->key() + 1; } - sd.owner = p_owner; + sd.owner_id = p_owner ? p_owner->get_instance_id() : ObjectID(); shapes[id] = sd; @@ -563,7 +563,7 @@ Transform3D CollisionObject3D::shape_owner_get_transform(uint32_t p_owner) const Object *CollisionObject3D::shape_owner_get_owner(uint32_t p_owner) const { ERR_FAIL_COND_V(!shapes.has(p_owner), nullptr); - return shapes[p_owner].owner; + return ObjectDB::get_instance(shapes[p_owner].owner_id); } void CollisionObject3D::shape_owner_add_shape(uint32_t p_owner, const Ref &p_shape) { diff --git a/scene/3d/collision_object_3d.h b/scene/3d/collision_object_3d.h index f56075354363..e92843d7848d 100644 --- a/scene/3d/collision_object_3d.h +++ b/scene/3d/collision_object_3d.h @@ -57,7 +57,7 @@ class CollisionObject3D : public Node3D { PhysicsServer3D::BodyMode body_mode = PhysicsServer3D::BODY_MODE_STATIC; struct ShapeData { - Object *owner = nullptr; + ObjectID owner_id; Transform3D xform; struct ShapeBase { RID debug_shape; diff --git a/scene/3d/navigation_obstacle_3d.cpp b/scene/3d/navigation_obstacle_3d.cpp index b1f6f0cf9189..ba6c50d98c6b 100644 --- a/scene/3d/navigation_obstacle_3d.cpp +++ b/scene/3d/navigation_obstacle_3d.cpp @@ -54,9 +54,9 @@ void NavigationObstacle3D::_validate_property(PropertyInfo &p_property) const { void NavigationObstacle3D::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_READY: { - initialize_agent(); + case NOTIFICATION_ENTER_TREE: { parent_node3d = Object::cast_to(get_parent()); + reevaluate_agent_radius(); if (parent_node3d != nullptr) { // place agent on navigation map first or else the RVO agent callback creation fails silently later NavigationServer3D::get_singleton()->agent_set_map(get_rid(), parent_node3d->get_world_3d()->get_navigation_map()); @@ -91,6 +91,7 @@ void NavigationObstacle3D::_notification(int p_what) { NavigationObstacle3D::NavigationObstacle3D() { agent = NavigationServer3D::get_singleton()->agent_create(); + initialize_agent(); } NavigationObstacle3D::~NavigationObstacle3D() { @@ -118,7 +119,7 @@ void NavigationObstacle3D::initialize_agent() { void NavigationObstacle3D::reevaluate_agent_radius() { if (!estimate_radius) { NavigationServer3D::get_singleton()->agent_set_radius(agent, radius); - } else if (parent_node3d) { + } else if (parent_node3d && parent_node3d->is_inside_tree()) { NavigationServer3D::get_singleton()->agent_set_radius(agent, estimate_agent_radius()); } } diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index a992d2aaf296..cad1201d63ec 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -300,8 +300,9 @@ Node3D *Node3D::get_parent_node_3d() const { } Transform3D Node3D::get_relative_transform(const Node *p_parent) const { - if (p_parent == this) + if (p_parent == this) { return Transform3D(); + } ERR_FAIL_COND_V(!data.parent, Transform3D()); @@ -990,7 +991,7 @@ void Node3D::_bind_methods() { ADD_GROUP("Transform", ""); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_transform", "get_transform"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "global_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_transform", "get_global_transform"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_RANGE, "-99999,99999,0,or_greater,or_lesser,noslider,suffix:m", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_greater,or_lesser,noslider,suffix:m", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians", PROPERTY_USAGE_EDITOR), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "quaternion", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_quaternion", "get_quaternion"); ADD_PROPERTY(PropertyInfo(Variant::BASIS, "basis", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_basis", "get_basis"); diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp index e0e2eae4a533..0277171922a0 100644 --- a/scene/3d/occluder_instance_3d.cpp +++ b/scene/3d/occluder_instance_3d.cpp @@ -30,41 +30,24 @@ #include "occluder_instance_3d.h" #include "core/core_string_names.h" +#include "core/math/geometry_2d.h" +#include "core/math/triangulate.h" +#include "scene/3d/importer_mesh_instance_3d.h" #include "scene/3d/mesh_instance_3d.h" +#include "scene/resources/importer_mesh.h" +#include "scene/resources/surface_tool.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_node.h" +#endif RID Occluder3D::get_rid() const { - if (!occluder.is_valid()) { - occluder = RS::get_singleton()->occluder_create(); - RS::get_singleton()->occluder_set_mesh(occluder, vertices, indices); - } return occluder; } -void Occluder3D::set_vertices(PackedVector3Array p_vertices) { - vertices = p_vertices; - if (occluder.is_valid()) { - RS::get_singleton()->occluder_set_mesh(occluder, vertices, indices); - } - _update_changes(); -} - -PackedVector3Array Occluder3D::get_vertices() const { - return vertices; -} - -void Occluder3D::set_indices(PackedInt32Array p_indices) { - indices = p_indices; - if (occluder.is_valid()) { - RS::get_singleton()->occluder_set_mesh(occluder, vertices, indices); - } - _update_changes(); -} +void Occluder3D::_update() { + _update_arrays(vertices, indices); -PackedInt32Array Occluder3D::get_indices() const { - return indices; -} - -void Occluder3D::_update_changes() { aabb = AABB(); const Vector3 *ptr = vertices.ptr(); @@ -75,9 +58,18 @@ void Occluder3D::_update_changes() { debug_lines.clear(); debug_mesh.unref(); + RS::get_singleton()->occluder_set_mesh(occluder, vertices, indices); emit_changed(); } +PackedVector3Array Occluder3D::get_vertices() const { + return vertices; +} + +PackedInt32Array Occluder3D::get_indices() const { + return indices; +} + Vector Occluder3D::get_debug_lines() const { if (!debug_lines.is_empty()) { return debug_lines; @@ -87,14 +79,18 @@ Vector Occluder3D::get_debug_lines() const { return Vector(); } + const Vector3 *vertices_ptr = vertices.ptr(); + debug_lines.resize(indices.size() / 3 * 6); + Vector3 *line_ptr = debug_lines.ptrw(); + int line_i = 0; for (int i = 0; i < indices.size() / 3; i++) { for (int j = 0; j < 3; j++) { int a = indices[i * 3 + j]; int b = indices[i * 3 + (j + 1) % 3]; ERR_FAIL_INDEX_V_MSG(a, vertices.size(), Vector(), "Occluder indices are out of range."); ERR_FAIL_INDEX_V_MSG(b, vertices.size(), Vector(), "Occluder indices are out of range."); - debug_lines.push_back(vertices[a]); - debug_lines.push_back(vertices[b]); + line_ptr[line_i++] = vertices_ptr[a]; + line_ptr[line_i++] = vertices_ptr[b]; } } return debug_lines; @@ -105,7 +101,7 @@ Ref Occluder3D::get_debug_mesh() const { return debug_mesh; } - if (indices.size() % 3 != 0) { + if (vertices.is_empty() || indices.is_empty() || indices.size() % 3 != 0) { return debug_mesh; } @@ -123,18 +119,17 @@ AABB Occluder3D::get_aabb() const { return aabb; } -void Occluder3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &Occluder3D::set_vertices); - ClassDB::bind_method(D_METHOD("get_vertices"), &Occluder3D::get_vertices); - - ClassDB::bind_method(D_METHOD("set_indices", "indices"), &Occluder3D::set_indices); - ClassDB::bind_method(D_METHOD("get_indices"), &Occluder3D::get_indices); +void Occluder3D::_notification(int p_what) { + if (p_what == NOTIFICATION_POSTINITIALIZE) { + _update(); + } +} - ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_vertices", "get_vertices"); - ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "indices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_indices", "get_indices"); +void Occluder3D::_bind_methods() { } Occluder3D::Occluder3D() { + occluder = RS::get_singleton()->occluder_create(); } Occluder3D::~Occluder3D() { @@ -142,6 +137,291 @@ Occluder3D::~Occluder3D() { RS::get_singleton()->free(occluder); } } + +///////////////////////////////////////////////// + +void ArrayOccluder3D::set_arrays(PackedVector3Array p_vertices, PackedInt32Array p_indices) { + vertices = p_vertices; + indices = p_indices; + _update(); +} + +void ArrayOccluder3D::set_vertices(PackedVector3Array p_vertices) { + vertices = p_vertices; + _update(); +} + +void ArrayOccluder3D::set_indices(PackedInt32Array p_indices) { + indices = p_indices; + _update(); +} + +void ArrayOccluder3D::_update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) { + r_vertices = vertices; + r_indices = indices; +} + +void ArrayOccluder3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_arrays", "vertices", "indices"), &ArrayOccluder3D::set_arrays); + + ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &ArrayOccluder3D::set_vertices); + ClassDB::bind_method(D_METHOD("get_vertices"), &ArrayOccluder3D::get_vertices); + + ClassDB::bind_method(D_METHOD("set_indices", "indices"), &ArrayOccluder3D::set_indices); + ClassDB::bind_method(D_METHOD("get_indices"), &ArrayOccluder3D::get_indices); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_vertices", "get_vertices"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "indices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_indices", "get_indices"); +} + +ArrayOccluder3D::ArrayOccluder3D() { +} + +ArrayOccluder3D::~ArrayOccluder3D() { +} + +///////////////////////////////////////////////// + +void QuadOccluder3D::set_size(const Vector2 &p_size) { + if (size == p_size) { + return; + } + + size = p_size.max(Vector2()); + ; + _update(); +} + +Vector2 QuadOccluder3D::get_size() const { + return size; +} + +void QuadOccluder3D::_update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) { + Vector2 _size = Vector2(size.x / 2.0f, size.y / 2.0f); + + r_vertices = { + Vector3(-_size.x, -_size.y, 0), + Vector3(-_size.x, _size.y, 0), + Vector3(_size.x, _size.y, 0), + Vector3(_size.x, -_size.y, 0), + }; + + r_indices = { + 0, 1, 2, + 0, 2, 3 + }; +} + +void QuadOccluder3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_size", "size"), &QuadOccluder3D::set_size); + ClassDB::bind_method(D_METHOD("get_size"), &QuadOccluder3D::get_size); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size"); +} + +QuadOccluder3D::QuadOccluder3D() { +} + +QuadOccluder3D::~QuadOccluder3D() { +} + +///////////////////////////////////////////////// + +void BoxOccluder3D::set_size(const Vector3 &p_size) { + if (size == p_size) { + return; + } + + size = Vector3(MAX(p_size.x, 0.0f), MAX(p_size.y, 0.0f), MAX(p_size.z, 0.0f)); + ; + _update(); +} + +Vector3 BoxOccluder3D::get_size() const { + return size; +} + +void BoxOccluder3D::_update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) { + Vector3 _size = Vector3(size.x / 2.0f, size.y / 2.0f, size.z / 2.0f); + + r_vertices = { + // front + Vector3(-_size.x, -_size.y, _size.z), + Vector3(_size.x, -_size.y, _size.z), + Vector3(_size.x, _size.y, _size.z), + Vector3(-_size.x, _size.y, _size.z), + // back + Vector3(-_size.x, -_size.y, -_size.z), + Vector3(_size.x, -_size.y, -_size.z), + Vector3(_size.x, _size.y, -_size.z), + Vector3(-_size.x, _size.y, -_size.z), + }; + + r_indices = { + // front + 0, 1, 2, + 2, 3, 0, + // right + 1, 5, 6, + 6, 2, 1, + // back + 7, 6, 5, + 5, 4, 7, + // left + 4, 0, 3, + 3, 7, 4, + // bottom + 4, 5, 1, + 1, 0, 4, + // top + 3, 2, 6, + 6, 7, 3 + }; +} + +void BoxOccluder3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_size", "size"), &BoxOccluder3D::set_size); + ClassDB::bind_method(D_METHOD("get_size"), &BoxOccluder3D::get_size); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size"), "set_size", "get_size"); +} + +BoxOccluder3D::BoxOccluder3D() { +} + +BoxOccluder3D::~BoxOccluder3D() { +} + +///////////////////////////////////////////////// + +void SphereOccluder3D::set_radius(float p_radius) { + if (radius == p_radius) { + return; + } + + radius = MAX(p_radius, 0.0f); + _update(); +} + +float SphereOccluder3D::get_radius() const { + return radius; +} + +void SphereOccluder3D::_update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) { + r_vertices.resize((RINGS + 2) * (RADIAL_SEGMENTS + 1)); + int vertex_i = 0; + Vector3 *vertex_ptr = r_vertices.ptrw(); + + r_indices.resize((RINGS + 1) * RADIAL_SEGMENTS * 6); + int idx_i = 0; + int *idx_ptr = r_indices.ptrw(); + + int current_row = 0; + int previous_row = 0; + int point = 0; + for (int j = 0; j <= (RINGS + 1); j++) { + float v = j / float(RINGS + 1); + float w = Math::sin(Math_PI * v); + float y = Math::cos(Math_PI * v); + for (int i = 0; i <= RADIAL_SEGMENTS; i++) { + float u = i / float(RADIAL_SEGMENTS); + + float x = Math::cos(u * Math_TAU); + float z = Math::sin(u * Math_TAU); + vertex_ptr[vertex_i++] = Vector3(x * w, y, z * w) * radius; + + if (i > 0 && j > 0) { + idx_ptr[idx_i++] = previous_row + i - 1; + idx_ptr[idx_i++] = previous_row + i; + idx_ptr[idx_i++] = current_row + i - 1; + + idx_ptr[idx_i++] = previous_row + i; + idx_ptr[idx_i++] = current_row + i; + idx_ptr[idx_i++] = current_row + i - 1; + } + + point++; + } + + previous_row = current_row; + current_row = point; + } +} + +void SphereOccluder3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &SphereOccluder3D::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &SphereOccluder3D::get_radius); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius"), "set_radius", "get_radius"); +} + +SphereOccluder3D::SphereOccluder3D() { +} + +SphereOccluder3D::~SphereOccluder3D() { +} + +///////////////////////////////////////////////// + +void PolygonOccluder3D::set_polygon(const Vector &p_polygon) { + polygon = p_polygon; + _update(); +} + +Vector PolygonOccluder3D::get_polygon() const { + return polygon; +} + +void PolygonOccluder3D::_update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) { + if (polygon.size() < 3) { + r_vertices.clear(); + r_indices.clear(); + return; + } + + Vector occluder_polygon = polygon; + if (Triangulate::get_area(occluder_polygon) > 0) { + occluder_polygon.reverse(); + } + + Vector occluder_indices = Geometry2D::triangulate_polygon(occluder_polygon); + + if (occluder_indices.size() < 3) { + r_vertices.clear(); + r_indices.clear(); + ERR_FAIL_MSG("Failed to triangulate PolygonOccluder3D. Make sure the polygon doesn't have any intersecting edges."); + } + + r_vertices.resize(occluder_polygon.size()); + Vector3 *vertex_ptr = r_vertices.ptrw(); + const Vector2 *polygon_ptr = occluder_polygon.ptr(); + for (int i = 0; i < occluder_polygon.size(); i++) { + vertex_ptr[i] = Vector3(polygon_ptr[i].x, polygon_ptr[i].y, 0.0); + } + + r_indices.resize(occluder_indices.size()); + memcpy(r_indices.ptrw(), occluder_indices.ptr(), occluder_indices.size() * sizeof(int)); +} + +bool PolygonOccluder3D::_has_editable_3d_polygon_no_depth() const { + return false; +} + +void PolygonOccluder3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &PolygonOccluder3D::set_polygon); + ClassDB::bind_method(D_METHOD("get_polygon"), &PolygonOccluder3D::get_polygon); + + ClassDB::bind_method(D_METHOD("_has_editable_3d_polygon_no_depth"), &PolygonOccluder3D::_has_editable_3d_polygon_no_depth); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon"); +} + +PolygonOccluder3D::PolygonOccluder3D() { +} + +PolygonOccluder3D::~PolygonOccluder3D() { +} + ///////////////////////////////////////////////// AABB OccluderInstance3D::get_aabb() const { @@ -175,6 +455,13 @@ void OccluderInstance3D::set_occluder(const Ref &p_occluder) { update_gizmos(); update_configuration_warnings(); + +#ifdef TOOLS_ENABLED + // PolygonOccluder3D is edited via an editor plugin, this ensures the plugin is shown/hidden when necessary + if (Engine::get_singleton()->is_editor_hint()) { + EditorNode::get_singleton()->call_deferred(SNAME("edit_current")); + } +#endif } void OccluderInstance3D::_occluder_changed() { @@ -195,6 +482,14 @@ uint32_t OccluderInstance3D::get_bake_mask() const { return bake_mask; } +void OccluderInstance3D::set_bake_simplification_distance(float p_dist) { + bake_simplification_dist = MAX(p_dist, 0.0f); +} + +float OccluderInstance3D::get_bake_simplification_distance() const { + return bake_simplification_dist; +} + void OccluderInstance3D::set_bake_mask_value(int p_layer_number, bool p_value) { ERR_FAIL_COND_MSG(p_layer_number < 1, "Render layer number must be between 1 and 20 inclusive."); ERR_FAIL_COND_MSG(p_layer_number > 20, "Render layer number must be between 1 and 20 inclusive."); @@ -221,6 +516,47 @@ bool OccluderInstance3D::_bake_material_check(Ref p_material) { return true; } +void OccluderInstance3D::_bake_surface(const Transform3D &p_transform, Array p_surface_arrays, Ref p_material, float p_simplification_dist, PackedVector3Array &r_vertices, PackedInt32Array &r_indices) { + if (!_bake_material_check(p_material)) { + return; + } + ERR_FAIL_COND_MSG(p_surface_arrays.size() != Mesh::ARRAY_MAX, "Invalid surface array."); + + PackedVector3Array vertices = p_surface_arrays[Mesh::ARRAY_VERTEX]; + PackedInt32Array indices = p_surface_arrays[Mesh::ARRAY_INDEX]; + + if (vertices.size() == 0 || indices.size() == 0) { + return; + } + + Vector3 *vertices_ptr = vertices.ptrw(); + for (int j = 0; j < vertices.size(); j++) { + vertices_ptr[j] = p_transform.xform(vertices_ptr[j]); + } + + if (!Math::is_zero_approx(p_simplification_dist) && SurfaceTool::simplify_func) { + float error_scale = SurfaceTool::simplify_scale_func((float *)vertices.ptr(), vertices.size(), sizeof(Vector3)); + float target_error = p_simplification_dist / error_scale; + float error = -1.0f; + int target_index_count = MIN(indices.size(), 36); + uint32_t index_count = SurfaceTool::simplify_func((unsigned int *)indices.ptrw(), (unsigned int *)indices.ptr(), indices.size(), (float *)vertices.ptr(), vertices.size(), sizeof(Vector3), target_index_count, target_error, &error); + indices.resize(index_count); + } + + SurfaceTool::strip_mesh_arrays(vertices, indices); + + int vertex_offset = r_vertices.size(); + r_vertices.resize(vertex_offset + vertices.size()); + memcpy(r_vertices.ptrw() + vertex_offset, vertices.ptr(), vertices.size() * sizeof(Vector3)); + + int index_offset = r_indices.size(); + r_indices.resize(index_offset + indices.size()); + int *idx_ptr = r_indices.ptrw(); + for (int j = 0; j < indices.size(); j++) { + idx_ptr[index_offset + j] = vertex_offset + indices[j]; + } +} + void OccluderInstance3D::_bake_node(Node *p_node, PackedVector3Array &r_vertices, PackedInt32Array &r_indices) { MeshInstance3D *mi = Object::cast_to(p_node); if (mi && mi->is_visible_in_tree()) { @@ -243,58 +579,76 @@ void OccluderInstance3D::_bake_node(Node *p_node, PackedVector3Array &r_vertices Transform3D global_to_local = get_global_transform().affine_inverse() * mi->get_global_transform(); for (int i = 0; i < mesh->get_surface_count(); i++) { - if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { - continue; - } + _bake_surface(global_to_local, mesh->surface_get_arrays(i), mi->get_active_material(i), bake_simplification_dist, r_vertices, r_indices); + } + } + } - if (mi->get_surface_override_material(i).is_valid()) { - if (!_bake_material_check(mi->get_surface_override_material(i))) { - continue; - } - } else { - if (!_bake_material_check(mesh->surface_get_material(i))) { - continue; - } - } + for (int i = 0; i < p_node->get_child_count(); i++) { + Node *child = p_node->get_child(i); + if (!child->get_owner()) { + continue; // may be a helper + } - Array arrays = mesh->surface_get_arrays(i); + _bake_node(child, r_vertices, r_indices); + } +} - int vertex_offset = r_vertices.size(); - PackedVector3Array vertices = arrays[Mesh::ARRAY_VERTEX]; - r_vertices.resize(r_vertices.size() + vertices.size()); +void OccluderInstance3D::bake_single_node(const Node3D *p_node, float p_simplification_distance, PackedVector3Array &r_vertices, PackedInt32Array &r_indices) { + ERR_FAIL_COND(!p_node); - Vector3 *vtx_ptr = r_vertices.ptrw(); - for (int j = 0; j < vertices.size(); j++) { - vtx_ptr[vertex_offset + j] = global_to_local.xform(vertices[j]); - } + Transform3D xform = p_node->is_inside_tree() ? p_node->get_global_transform() : p_node->get_transform(); - int index_offset = r_indices.size(); - PackedInt32Array indices = arrays[Mesh::ARRAY_INDEX]; - r_indices.resize(r_indices.size() + indices.size()); + const MeshInstance3D *mi = Object::cast_to(p_node); + if (mi) { + Ref mesh = mi->get_mesh(); + bool valid = true; - int *idx_ptr = r_indices.ptrw(); - for (int j = 0; j < indices.size(); j++) { - idx_ptr[index_offset + j] = vertex_offset + indices[j]; - } + if (mesh.is_null()) { + valid = false; + } + + if (valid && !_bake_material_check(mi->get_material_override())) { + valid = false; + } + + if (valid) { + for (int i = 0; i < mesh->get_surface_count(); i++) { + _bake_surface(xform, mesh->surface_get_arrays(i), mi->get_active_material(i), p_simplification_distance, r_vertices, r_indices); } } } - for (int i = 0; i < p_node->get_child_count(); i++) { - Node *child = p_node->get_child(i); - if (!child->get_owner()) { - continue; //maybe a helper + const ImporterMeshInstance3D *imi = Object::cast_to(p_node); + if (imi) { + Ref mesh = imi->get_mesh(); + bool valid = true; + + if (mesh.is_null()) { + valid = false; } - _bake_node(child, r_vertices, r_indices); + if (valid) { + for (int i = 0; i < mesh->get_surface_count(); i++) { + Ref material = imi->get_surface_material(i); + if (material.is_null()) { + material = mesh->get_surface_material(i); + } + _bake_surface(xform, mesh->get_surface_arrays(i), material, p_simplification_distance, r_vertices, r_indices); + } + } } } -OccluderInstance3D::BakeError OccluderInstance3D::bake(Node *p_from_node, String p_occluder_path) { +OccluderInstance3D::BakeError OccluderInstance3D::bake_scene(Node *p_from_node, String p_occluder_path) { if (p_occluder_path.is_empty()) { if (get_occluder().is_null()) { return BAKE_ERROR_NO_SAVE_PATH; } + p_occluder_path = get_occluder()->get_path(); + if (!p_occluder_path.is_resource_file()) { + return BAKE_ERROR_NO_SAVE_PATH; + } } PackedVector3Array vertices; @@ -306,16 +660,25 @@ OccluderInstance3D::BakeError OccluderInstance3D::bake(Node *p_from_node, String return BAKE_ERROR_NO_MESHES; } - Ref occ; + Ref occ; if (get_occluder().is_valid()) { occ = get_occluder(); - } else { + set_occluder(Ref()); // clear + } + + if (occ.is_null()) { occ.instantiate(); - occ->set_path(p_occluder_path); } - occ->set_vertices(vertices); - occ->set_indices(indices); + occ->set_arrays(vertices, indices); + + Error err = ResourceSaver::save(p_occluder_path, occ); + + if (err != OK) { + return BAKE_ERROR_CANT_SAVE; + } + + occ->set_path(p_occluder_path); set_occluder(occ); return BAKE_ERROR_OK; @@ -333,28 +696,49 @@ TypedArray OccluderInstance3D::get_configuration_warnings() const { } if (occluder.is_null()) { - warnings.push_back(TTR("No occluder mesh is defined in the Occluder property, so no occlusion culling will be performed using this OccluderInstance3D.\nTo resolve this, select the OccluderInstance3D then use the Bake Occluders button at the top of the 3D editor viewport.")); - } else if (occluder->get_vertices().size() < 3) { - // Using the "New Occluder" dropdown button won't result in a correct occluder, - // so warn the user about this. - warnings.push_back(TTR("The occluder mesh has less than 3 vertices, so no occlusion culling will be performed using this OccluderInstance3D.\nTo generate a proper occluder mesh, select the OccluderInstance3D then use the Bake Occluders button at the top of the 3D editor viewport.")); + warnings.push_back(TTR("No occluder mesh is defined in the Occluder property, so no occlusion culling will be performed using this OccluderInstance3D.\nTo resolve this, set the Occluder property to one of the primitive occluder types or bake the scene meshes by selecting the OccluderInstance3D and pressing the Bake Occluders button at the top of the 3D editor viewport.")); + } else { + Ref arr_occluder = occluder; + if (arr_occluder.is_valid() && arr_occluder->get_indices().size() < 3) { + // Setting a new ArrayOccluder3D from the inspector will create an empty occluder, + // so warn the user about this. + warnings.push_back(TTR("The occluder mesh has less than 3 vertices, so no occlusion culling will be performed using this OccluderInstance3D.\nTo generate a proper occluder mesh, select the OccluderInstance3D then use the Bake Occluders button at the top of the 3D editor viewport.")); + } + Ref poly_occluder = occluder; + if (poly_occluder.is_valid() && poly_occluder->get_polygon().size() < 3) { + warnings.push_back(TTR("The polygon occluder has less than 3 vertices, so no occlusion culling will be performed using this OccluderInstance3D.\nVertices can be added in the inspector or using the polygon editing tools at the top of the 3D editor viewport.")); + } } return warnings; } +bool OccluderInstance3D::_is_editable_3d_polygon() const { + return Ref(occluder).is_valid(); +} + +Ref OccluderInstance3D::_get_editable_3d_polygon_resource() const { + return occluder; +} + void OccluderInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bake_mask", "mask"), &OccluderInstance3D::set_bake_mask); ClassDB::bind_method(D_METHOD("get_bake_mask"), &OccluderInstance3D::get_bake_mask); ClassDB::bind_method(D_METHOD("set_bake_mask_value", "layer_number", "value"), &OccluderInstance3D::set_bake_mask_value); ClassDB::bind_method(D_METHOD("get_bake_mask_value", "layer_number"), &OccluderInstance3D::get_bake_mask_value); + ClassDB::bind_method(D_METHOD("set_bake_simplification_distance", "simplification_distance"), &OccluderInstance3D::set_bake_simplification_distance); + ClassDB::bind_method(D_METHOD("get_bake_simplification_distance"), &OccluderInstance3D::get_bake_simplification_distance); ClassDB::bind_method(D_METHOD("set_occluder", "occluder"), &OccluderInstance3D::set_occluder); ClassDB::bind_method(D_METHOD("get_occluder"), &OccluderInstance3D::get_occluder); + ClassDB::bind_method(D_METHOD("_is_editable_3d_polygon"), &OccluderInstance3D::_is_editable_3d_polygon); + ClassDB::bind_method(D_METHOD("_get_editable_3d_polygon_resource"), &OccluderInstance3D::_get_editable_3d_polygon_resource); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "occluder", PROPERTY_HINT_RESOURCE_TYPE, "Occluder3D"), "set_occluder", "get_occluder"); ADD_GROUP("Bake", "bake_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_bake_mask", "get_bake_mask"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_simplification_distance", PROPERTY_HINT_RANGE, "0.0,2.0,0.01"), "set_bake_simplification_distance", "get_bake_simplification_distance"); } OccluderInstance3D::OccluderInstance3D() { diff --git a/scene/3d/occluder_instance_3d.h b/scene/3d/occluder_instance_3d.h index 8a8d4c9af441..669ddba7751b 100644 --- a/scene/3d/occluder_instance_3d.h +++ b/scene/3d/occluder_instance_3d.h @@ -37,24 +37,23 @@ class Occluder3D : public Resource { GDCLASS(Occluder3D, Resource); RES_BASE_EXTENSION("occ"); - mutable RID occluder; - mutable Ref debug_mesh; - mutable Vector debug_lines; - AABB aabb; - + RID occluder; PackedVector3Array vertices; PackedInt32Array indices; + AABB aabb; - void _update_changes(); + mutable Ref debug_mesh; + mutable Vector debug_lines; protected: + void _update(); + virtual void _update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) = 0; + static void _bind_methods(); + void _notification(int p_what); public: - void set_vertices(PackedVector3Array p_vertices); PackedVector3Array get_vertices() const; - - void set_indices(PackedInt32Array p_indices); PackedInt32Array get_indices() const; Vector get_debug_lines() const; @@ -63,7 +62,102 @@ class Occluder3D : public Resource { virtual RID get_rid() const override; Occluder3D(); - ~Occluder3D(); + virtual ~Occluder3D(); +}; + +class ArrayOccluder3D : public Occluder3D { + GDCLASS(ArrayOccluder3D, Occluder3D); + + PackedVector3Array vertices; + PackedInt32Array indices; + +protected: + virtual void _update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) override; + static void _bind_methods(); + +public: + void set_arrays(PackedVector3Array p_vertices, PackedInt32Array p_indices); + void set_vertices(PackedVector3Array p_vertices); + void set_indices(PackedInt32Array p_indices); + + ArrayOccluder3D(); + ~ArrayOccluder3D(); +}; + +class QuadOccluder3D : public Occluder3D { + GDCLASS(QuadOccluder3D, Occluder3D); + +private: + Vector2 size = Vector2(1.0f, 1.0f); + +protected: + virtual void _update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) override; + static void _bind_methods(); + +public: + Vector2 get_size() const; + void set_size(const Vector2 &p_size); + + QuadOccluder3D(); + ~QuadOccluder3D(); +}; + +class BoxOccluder3D : public Occluder3D { + GDCLASS(BoxOccluder3D, Occluder3D); + +private: + Vector3 size = Vector3(1.0f, 1.0f, 1.0f); + +protected: + virtual void _update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) override; + static void _bind_methods(); + +public: + Vector3 get_size() const; + void set_size(const Vector3 &p_size); + + BoxOccluder3D(); + ~BoxOccluder3D(); +}; + +class SphereOccluder3D : public Occluder3D { + GDCLASS(SphereOccluder3D, Occluder3D); + +private: + static constexpr int RINGS = 7; + static constexpr int RADIAL_SEGMENTS = 7; + float radius = 1.0f; + +protected: + virtual void _update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) override; + static void _bind_methods(); + +public: + float get_radius() const; + void set_radius(float p_radius); + + SphereOccluder3D(); + ~SphereOccluder3D(); +}; + +class PolygonOccluder3D : public Occluder3D { + GDCLASS(PolygonOccluder3D, Occluder3D); + +private: + Vector polygon; + + bool _has_editable_3d_polygon_no_depth() const; + +protected: + virtual void _update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) override; + static void _bind_methods(); + +public: + void set_polygon(const Vector &p_polygon); + Vector get_polygon() const; + + PolygonOccluder3D(); + ~PolygonOccluder3D(); }; class OccluderInstance3D : public VisualInstance3D { @@ -72,12 +166,17 @@ class OccluderInstance3D : public VisualInstance3D { private: Ref occluder; uint32_t bake_mask = 0xFFFFFFFF; + float bake_simplification_dist = 0.1f; void _occluder_changed(); - bool _bake_material_check(Ref p_material); + static bool _bake_material_check(Ref p_material); + static void _bake_surface(const Transform3D &p_transform, Array p_surface_arrays, Ref p_material, float p_simplification_dist, PackedVector3Array &r_vertices, PackedInt32Array &r_indices); void _bake_node(Node *p_node, PackedVector3Array &r_vertices, PackedInt32Array &r_indices); + bool _is_editable_3d_polygon() const; + Ref _get_editable_3d_polygon_resource() const; + protected: static void _bind_methods(); @@ -88,6 +187,7 @@ class OccluderInstance3D : public VisualInstance3D { BAKE_ERROR_OK, BAKE_ERROR_NO_SAVE_PATH, BAKE_ERROR_NO_MESHES, + BAKE_ERROR_CANT_SAVE, }; void set_occluder(const Ref &p_occluder); @@ -99,10 +199,14 @@ class OccluderInstance3D : public VisualInstance3D { void set_bake_mask(uint32_t p_mask); uint32_t get_bake_mask() const; + void set_bake_simplification_distance(float p_dist); + float get_bake_simplification_distance() const; + void set_bake_mask_value(int p_layer_number, bool p_enable); bool get_bake_mask_value(int p_layer_number) const; - BakeError bake(Node *p_from_node, String p_occluder_path = ""); + BakeError bake_scene(Node *p_from_node, String p_occluder_path = ""); + static void bake_single_node(const Node3D *p_node, float p_simplification_distance, PackedVector3Array &r_vertices, PackedInt32Array &r_indices); OccluderInstance3D(); ~OccluderInstance3D(); diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index b3192a5bb59a..13a38f3b9f70 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -34,8 +34,8 @@ #include "scene/scene_string_names.h" void PhysicsBody3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("move_and_collide", "linear_velocity", "test_only", "safe_margin", "max_collisions"), &PhysicsBody3D::_move, DEFVAL(false), DEFVAL(0.001), DEFVAL(1)); - ClassDB::bind_method(D_METHOD("test_move", "from", "linear_velocity", "collision", "safe_margin", "max_collisions"), &PhysicsBody3D::test_move, DEFVAL(Variant()), DEFVAL(0.001), DEFVAL(1)); + ClassDB::bind_method(D_METHOD("move_and_collide", "distance", "test_only", "safe_margin", "max_collisions"), &PhysicsBody3D::_move, DEFVAL(false), DEFVAL(0.001), DEFVAL(1)); + ClassDB::bind_method(D_METHOD("test_move", "from", "distance", "collision", "safe_margin", "max_collisions"), &PhysicsBody3D::test_move, DEFVAL(Variant()), DEFVAL(0.001), DEFVAL(1)); ClassDB::bind_method(D_METHOD("set_axis_lock", "axis", "lock"), &PhysicsBody3D::set_axis_lock); ClassDB::bind_method(D_METHOD("get_axis_lock", "axis"), &PhysicsBody3D::get_axis_lock); @@ -91,11 +91,8 @@ void PhysicsBody3D::remove_collision_exception_with(Node *p_node) { PhysicsServer3D::get_singleton()->body_remove_collision_exception(get_rid(), collision_object->get_rid()); } -Ref PhysicsBody3D::_move(const Vector3 &p_linear_velocity, bool p_test_only, real_t p_margin, int p_max_collisions) { - // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky - double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); - - PhysicsServer3D::MotionParameters parameters(get_global_transform(), p_linear_velocity * delta, p_margin); +Ref PhysicsBody3D::_move(const Vector3 &p_distance, bool p_test_only, real_t p_margin, int p_max_collisions) { + PhysicsServer3D::MotionParameters parameters(get_global_transform(), p_distance, p_margin); parameters.max_collisions = p_max_collisions; PhysicsServer3D::MotionResult result; @@ -170,7 +167,7 @@ bool PhysicsBody3D::move_and_collide(const PhysicsServer3D::MotionParameters &p_ return colliding; } -bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_linear_velocity, const Ref &r_collision, real_t p_margin, int p_max_collisions) { +bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_distance, const Ref &r_collision, real_t p_margin, int p_max_collisions) { ERR_FAIL_COND_V(!is_inside_tree(), false); PhysicsServer3D::MotionResult *r = nullptr; @@ -182,10 +179,7 @@ bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_linear r = &temp_result; } - // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky - double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); - - PhysicsServer3D::MotionParameters parameters(p_from, p_linear_velocity * delta, p_margin); + PhysicsServer3D::MotionParameters parameters(p_from, p_distance, p_margin); bool colliding = PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), parameters, r); @@ -1233,7 +1227,7 @@ bool CharacterBody3D::move_and_slide() { if (motion_mode == MOTION_MODE_GROUNDED) { _move_and_slide_grounded(delta, was_on_floor); } else { - _move_and_slide_free(delta); + _move_and_slide_floating(delta); } // Compute real velocity. @@ -1512,7 +1506,7 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo } } -void CharacterBody3D::_move_and_slide_free(double p_delta) { +void CharacterBody3D::_move_and_slide_floating(double p_delta) { Vector3 motion = motion_velocity * p_delta; platform_rid = RID(); @@ -1929,7 +1923,7 @@ const Vector3 &CharacterBody3D::get_up_direction() const { } void CharacterBody3D::set_up_direction(const Vector3 &p_up_direction) { - ERR_FAIL_COND_MSG(p_up_direction == Vector3(), "up_direction can't be equal to Vector3.ZERO, consider using Free motion mode instead."); + ERR_FAIL_COND_MSG(p_up_direction == Vector3(), "up_direction can't be equal to Vector3.ZERO, consider using Floating motion mode instead."); up_direction = p_up_direction.normalized(); } @@ -2000,12 +1994,11 @@ void CharacterBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_slide_collision", "slide_idx"), &CharacterBody3D::_get_slide_collision); ClassDB::bind_method(D_METHOD("get_last_slide_collision"), &CharacterBody3D::_get_last_slide_collision); - ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_mode", PROPERTY_HINT_ENUM, "Grounded,Free", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_motion_mode", "get_motion_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_mode", PROPERTY_HINT_ENUM, "Grounded,Floating", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_motion_mode", "get_motion_mode"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "up_direction"), "set_up_direction", "get_up_direction"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_ceiling"), "set_slide_on_ceiling_enabled", "is_slide_on_ceiling_enabled"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "motion_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_motion_velocity", "get_motion_velocity"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_max_slides", "get_max_slides"); - ADD_GROUP("Free Mode", "free_mode_"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wall_min_slide_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians", PROPERTY_USAGE_DEFAULT), "set_wall_min_slide_angle", "get_wall_min_slide_angle"); ADD_GROUP("Floor", "floor_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_stop_on_slope"), "set_floor_stop_on_slope_enabled", "is_floor_stop_on_slope_enabled"); @@ -2020,7 +2013,7 @@ void CharacterBody3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001"), "set_safe_margin", "get_safe_margin"); BIND_ENUM_CONSTANT(MOTION_MODE_GROUNDED); - BIND_ENUM_CONSTANT(MOTION_MODE_FREE); + BIND_ENUM_CONSTANT(MOTION_MODE_FLOATING); BIND_ENUM_CONSTANT(PLATFORM_VEL_ON_LEAVE_ALWAYS); BIND_ENUM_CONSTANT(PLATFORM_VEL_ON_LEAVE_UPWARD_ONLY); @@ -2028,7 +2021,7 @@ void CharacterBody3D::_bind_methods() { } void CharacterBody3D::_validate_property(PropertyInfo &property) const { - if (motion_mode == MOTION_MODE_FREE) { + if (motion_mode == MOTION_MODE_FLOATING) { if (property.name.begins_with("floor_") || property.name == "up_direction" || property.name == "slide_on_ceiling") { property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL; } diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h index e37b841117df..67dc7382c36e 100644 --- a/scene/3d/physics_body_3d.h +++ b/scene/3d/physics_body_3d.h @@ -50,11 +50,11 @@ class PhysicsBody3D : public CollisionObject3D { uint16_t locked_axis = 0; - Ref _move(const Vector3 &p_linear_velocity, bool p_test_only = false, real_t p_margin = 0.001, int p_max_collisions = 1); + Ref _move(const Vector3 &p_distance, bool p_test_only = false, real_t p_margin = 0.001, int p_max_collisions = 1); public: bool move_and_collide(const PhysicsServer3D::MotionParameters &p_parameters, PhysicsServer3D::MotionResult &r_result, bool p_test_only = false, bool p_cancel_sliding = true); - bool test_move(const Transform3D &p_from, const Vector3 &p_linear_velocity, const Ref &r_collision = Ref(), real_t p_margin = 0.001, int p_max_collisions = 1); + bool test_move(const Transform3D &p_from, const Vector3 &p_distance, const Ref &r_collision = Ref(), real_t p_margin = 0.001, int p_max_collisions = 1); void set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock); bool get_axis_lock(PhysicsServer3D::BodyAxis p_axis) const; @@ -345,7 +345,7 @@ class CharacterBody3D : public PhysicsBody3D { public: enum MotionMode { MOTION_MODE_GROUNDED, - MOTION_MODE_FREE, + MOTION_MODE_FLOATING, }; enum MovingPlatformApplyVelocityOnLeave { PLATFORM_VEL_ON_LEAVE_ALWAYS, @@ -468,7 +468,7 @@ class CharacterBody3D : public PhysicsBody3D { void set_moving_platform_apply_velocity_on_leave(MovingPlatformApplyVelocityOnLeave p_on_leave_velocity); MovingPlatformApplyVelocityOnLeave get_moving_platform_apply_velocity_on_leave() const; - void _move_and_slide_free(double p_delta); + void _move_and_slide_floating(double p_delta); void _move_and_slide_grounded(double p_delta, bool p_was_on_floor); Ref _get_slide_collision(int p_bounce); diff --git a/scene/3d/ray_cast_3d.cpp b/scene/3d/ray_cast_3d.cpp index 3bb65d07a0ed..b251aa38baa8 100644 --- a/scene/3d/ray_cast_3d.cpp +++ b/scene/3d/ray_cast_3d.cpp @@ -243,30 +243,29 @@ void RayCast3D::add_exception_rid(const RID &p_rid) { exclude.insert(p_rid); } -void RayCast3D::add_exception(const Object *p_object) { - ERR_FAIL_NULL(p_object); - const CollisionObject3D *co = Object::cast_to(p_object); - if (!co) { - return; - } - add_exception_rid(co->get_rid()); +void RayCast3D::add_exception(const CollisionObject3D *p_node) { + ERR_FAIL_NULL_MSG(p_node, "The passed Node must be an instance of CollisionObject3D."); + add_exception_rid(p_node->get_rid()); } void RayCast3D::remove_exception_rid(const RID &p_rid) { exclude.erase(p_rid); } -void RayCast3D::remove_exception(const Object *p_object) { - ERR_FAIL_NULL(p_object); - const CollisionObject3D *co = Object::cast_to(p_object); - if (!co) { - return; - } - remove_exception_rid(co->get_rid()); +void RayCast3D::remove_exception(const CollisionObject3D *p_node) { + ERR_FAIL_NULL_MSG(p_node, "The passed Node must be an instance of CollisionObject3D."); + remove_exception_rid(p_node->get_rid()); } void RayCast3D::clear_exceptions() { exclude.clear(); + + if (exclude_parent_body && is_inside_tree()) { + CollisionObject3D *parent = Object::cast_to(get_parent()); + if (parent) { + exclude.insert(parent->get_rid()); + } + } } void RayCast3D::set_collide_with_areas(bool p_enabled) { diff --git a/scene/3d/ray_cast_3d.h b/scene/3d/ray_cast_3d.h index a53e2c83fcd5..ad85001591b8 100644 --- a/scene/3d/ray_cast_3d.h +++ b/scene/3d/ray_cast_3d.h @@ -33,6 +33,8 @@ #include "scene/3d/node_3d.h" +class CollisionObject3D; + class RayCast3D : public Node3D { GDCLASS(RayCast3D, Node3D); @@ -116,9 +118,9 @@ class RayCast3D : public Node3D { Vector3 get_collision_normal() const; void add_exception_rid(const RID &p_rid); - void add_exception(const Object *p_object); + void add_exception(const CollisionObject3D *p_node); void remove_exception_rid(const RID &p_rid); - void remove_exception(const Object *p_object); + void remove_exception(const CollisionObject3D *p_node); void clear_exceptions(); RayCast3D(); diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index 3957a1653f3a..b6b5920f6958 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -506,7 +506,7 @@ int Skeleton3D::get_bone_axis_forward_enum(int p_bone) { // Skeleton creation api void Skeleton3D::add_bone(const String &p_name) { - ERR_FAIL_COND(p_name.is_empty() || p_name.find(":") != -1 || p_name.find("/") != -1); + ERR_FAIL_COND(p_name.is_empty() || p_name.contains(":") || p_name.contains("/")); for (int i = 0; i < bones.size(); i++) { ERR_FAIL_COND(bones[i].name == p_name); diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index a37fce946980..331d58927bbd 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -1088,8 +1088,9 @@ void AnimatedSprite3D::set_frame(int p_frame) { if (frames->has_animation(animation)) { int limit = frames->get_frame_count(animation); - if (p_frame >= limit) + if (p_frame >= limit) { p_frame = limit - 1; + } } if (p_frame < 0) { diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp index a054f35d2ee0..66d1b9705608 100644 --- a/scene/3d/xr_nodes.cpp +++ b/scene/3d/xr_nodes.cpp @@ -482,22 +482,22 @@ void XRController3D::_unbind_tracker() { void XRController3D::_button_pressed(const String &p_name) { // just pass it on... - emit_signal("button_pressed", p_name); + emit_signal(SNAME("button_pressed"), p_name); } void XRController3D::_button_released(const String &p_name) { // just pass it on... - emit_signal("button_released", p_name); + emit_signal(SNAME("button_released"), p_name); } void XRController3D::_input_value_changed(const String &p_name, float p_value) { // just pass it on... - emit_signal("input_value_changed", p_name, p_value); + emit_signal(SNAME("input_value_changed"), p_name, p_value); } void XRController3D::_input_axis_changed(const String &p_name, Vector2 p_value) { // just pass it on... - emit_signal("input_axis_changed", p_name, p_value); + emit_signal(SNAME("input_axis_changed"), p_name, p_value); } bool XRController3D::is_button_pressed(const StringName &p_name) const { diff --git a/scene/SCsub b/scene/SCsub index 92288211bbc6..a7b23af598a7 100644 --- a/scene/SCsub +++ b/scene/SCsub @@ -9,6 +9,7 @@ env.add_source_files(env.scene_sources, "*.cpp") # Chain load SCsubs SConscript("main/SCsub") +SConscript("multiplayer/SCsub") SConscript("gui/SCsub") if not env["disable_3d"]: SConscript("3d/SCsub") diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp index 33939d4cd674..849316c56838 100644 --- a/scene/animation/animation_blend_space_1d.cpp +++ b/scene/animation/animation_blend_space_1d.cpp @@ -292,10 +292,10 @@ double AnimationNodeBlendSpace1D::process(double p_time, bool p_seek) { // actually blend the animations now - float max_time_remaining = 0.0; + double max_time_remaining = 0.0; for (int i = 0; i < blend_points_used; i++) { - float remaining = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, weights[i], FILTER_IGNORE, false); + double remaining = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, weights[i], FILTER_IGNORE, false); max_time_remaining = MAX(max_time_remaining, remaining); } diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp index f169e79751cb..a3aa3f6cc8d5 100644 --- a/scene/animation/animation_blend_space_2d.cpp +++ b/scene/animation/animation_blend_space_2d.cpp @@ -438,7 +438,7 @@ double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek) { Vector2 blend_pos = get_parameter(blend_position); int closest = get_parameter(this->closest); double length_internal = get_parameter(this->length_internal); - float mind = 0.0; //time of min distance point + double mind = 0.0; //time of min distance point if (blend_mode == BLEND_MODE_INTERPOLATED) { if (triangles.size() == 0) { @@ -502,7 +502,7 @@ double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek) { for (int j = 0; j < 3; j++) { if (i == triangle_points[j]) { //blend with the given weight - float t = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, blend_weights[j], FILTER_IGNORE, false); + double t = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, blend_weights[j], FILTER_IGNORE, false); if (first || t < mind) { mind = t; first = false; @@ -530,7 +530,7 @@ double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek) { } if (new_closest != closest && new_closest != -1) { - float from = 0.0; + double from = 0.0; if (blend_mode == BLEND_MODE_DISCRETE_CARRY && closest != -1) { //for ping-pong loop Ref na_c = static_cast>(blend_points[closest].node); diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index 2740103a4aa6..3d0ac291b8a5 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -371,6 +371,8 @@ void AnimationNodeOneShot::_bind_methods() { ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeOneShot::set_use_sync); ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeOneShot::is_using_sync); + ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_mode", PROPERTY_HINT_ENUM, "Blend,Add"), "set_mix_mode", "get_mix_mode"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fadein_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_fadein_time", "get_fadein_time"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fadeout_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_fadeout_time", "get_fadeout_time"); @@ -748,7 +750,7 @@ double AnimationNodeTransition::process(double p_time, bool p_seek) { return 0; } - float rem = 0.0; + double rem = 0.0; if (prev < 0) { // process current animation, check for transition @@ -855,7 +857,7 @@ void AnimationNodeBlendTree::add_node(const StringName &p_name, Refoutput); - ERR_FAIL_COND(String(p_name).find("/") != -1); + ERR_FAIL_COND(String(p_name).contains("/")); Node n; n.node = p_node; diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index a23e1b689cc4..a68f7e037d1a 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -50,7 +50,7 @@ bool AnimationNodeStateMachineTransition::has_auto_advance() const { void AnimationNodeStateMachineTransition::set_advance_condition(const StringName &p_condition) { String cs = p_condition; - ERR_FAIL_COND(cs.find("/") != -1 || cs.find(":") != -1); + ERR_FAIL_COND(cs.contains("/") || cs.contains(":")); advance_condition = p_condition; if (!cs.is_empty()) { advance_condition_name = "conditions/" + cs; @@ -536,7 +536,7 @@ Variant AnimationNodeStateMachine::get_parameter_default_value(const StringName void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref p_node, const Vector2 &p_position) { ERR_FAIL_COND(states.has(p_name)); ERR_FAIL_COND(p_node.is_null()); - ERR_FAIL_COND(String(p_name).find("/") != -1); + ERR_FAIL_COND(String(p_name).contains("/")); State state; state.node = p_node; @@ -553,7 +553,7 @@ void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref p_node) { ERR_FAIL_COND(states.has(p_name) == false); ERR_FAIL_COND(p_node.is_null()); - ERR_FAIL_COND(String(p_name).find("/") != -1); + ERR_FAIL_COND(String(p_name).contains("/")); { Ref node = states[p_name].node; diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 128c6cae8b8e..4a3fd7208080 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -1171,7 +1171,7 @@ void AnimationPlayer::_animation_process(double p_delta) { Error AnimationPlayer::add_animation(const StringName &p_name, const Ref &p_animation) { #ifdef DEBUG_ENABLED - ERR_FAIL_COND_V_MSG(String(p_name).find("/") != -1 || String(p_name).find(":") != -1 || String(p_name).find(",") != -1 || String(p_name).find("[") != -1, ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + "."); + ERR_FAIL_COND_V_MSG(String(p_name).contains("/") || String(p_name).contains(":") || String(p_name).contains(",") || String(p_name).contains("["), ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + "."); #endif ERR_FAIL_COND_V(p_animation.is_null(), ERR_INVALID_PARAMETER); @@ -1213,7 +1213,7 @@ void AnimationPlayer::_unref_anim(const Ref &p_anim) { void AnimationPlayer::rename_animation(const StringName &p_name, const StringName &p_new_name) { ERR_FAIL_COND(!animation_set.has(p_name)); - ERR_FAIL_COND(String(p_new_name).find("/") != -1 || String(p_new_name).find(":") != -1); + ERR_FAIL_COND(String(p_new_name).contains("/") || String(p_new_name).contains(":")); ERR_FAIL_COND(animation_set.has(p_new_name)); stop(); diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index a55141777812..f2f69fc43957 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -88,7 +88,7 @@ void AnimationNode::get_child_nodes(List *r_child_nodes) { } } -void AnimationNode::blend_animation(const StringName &p_animation, real_t p_time, real_t p_delta, bool p_seeked, real_t p_blend, int p_pingponged) { +void AnimationNode::blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, real_t p_blend, int p_pingponged) { ERR_FAIL_COND(!state); ERR_FAIL_COND(!state->player->has_animation(p_animation)); @@ -119,13 +119,13 @@ void AnimationNode::blend_animation(const StringName &p_animation, real_t p_time state->animation_states.push_back(anim_state); } -real_t AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, real_t p_time, bool p_seek, const Vector &p_connections) { +double AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, const Vector &p_connections) { base_path = p_base_path; parent = p_parent; connections = p_connections; state = p_state; - real_t t = process(p_time, p_seek); + double t = process(p_time, p_seek); state = nullptr; parent = nullptr; @@ -144,7 +144,7 @@ void AnimationNode::make_invalid(const String &p_reason) { state->invalid_reasons += String::utf8("• ") + p_reason; } -real_t AnimationNode::blend_input(int p_input, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter, bool p_optimize) { +double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, real_t p_blend, FilterAction p_filter, bool p_optimize) { ERR_FAIL_INDEX_V(p_input, inputs.size(), 0); ERR_FAIL_COND_V(!state, 0); @@ -163,7 +163,7 @@ real_t AnimationNode::blend_input(int p_input, real_t p_time, bool p_seek, real_ //inputs.write[p_input].last_pass = state->last_pass; real_t activity = 0.0; - real_t ret = _blend_node(node_name, blend_tree->get_node_connection_array(node_name), nullptr, node, p_time, p_seek, p_blend, p_filter, p_optimize, &activity); + double ret = _blend_node(node_name, blend_tree->get_node_connection_array(node_name), nullptr, node, p_time, p_seek, p_blend, p_filter, p_optimize, &activity); Vector *activity_ptr = state->tree->input_activity_map.getptr(base_path); @@ -174,11 +174,11 @@ real_t AnimationNode::blend_input(int p_input, real_t p_time, bool p_seek, real_ return ret; } -real_t AnimationNode::blend_node(const StringName &p_sub_path, Ref p_node, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter, bool p_optimize) { +double AnimationNode::blend_node(const StringName &p_sub_path, Ref p_node, double p_time, bool p_seek, real_t p_blend, FilterAction p_filter, bool p_optimize) { return _blend_node(p_sub_path, Vector(), this, p_node, p_time, p_seek, p_blend, p_filter, p_optimize); } -real_t AnimationNode::_blend_node(const StringName &p_subpath, const Vector &p_connections, AnimationNode *p_new_parent, Ref p_node, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter, bool p_optimize, real_t *r_max) { +double AnimationNode::_blend_node(const StringName &p_subpath, const Vector &p_connections, AnimationNode *p_new_parent, Ref p_node, double p_time, bool p_seek, real_t p_blend, FilterAction p_filter, bool p_optimize, real_t *r_max) { ERR_FAIL_COND_V(!p_node.is_valid(), 0); ERR_FAIL_COND_V(!state, 0); @@ -314,7 +314,7 @@ void AnimationNode::add_input(const String &p_name) { //root nodes can't add inputs ERR_FAIL_COND(Object::cast_to(this) != nullptr); Input input; - ERR_FAIL_COND(p_name.find(".") != -1 || p_name.find("/") != -1); + ERR_FAIL_COND(p_name.contains(".") || p_name.contains("/")); input.name = p_name; inputs.push_back(input); emit_changed(); @@ -322,7 +322,7 @@ void AnimationNode::add_input(const String &p_name) { void AnimationNode::set_input_name(int p_input, const String &p_name) { ERR_FAIL_INDEX(p_input, inputs.size()); - ERR_FAIL_COND(p_name.find(".") != -1 || p_name.find("/") != -1); + ERR_FAIL_COND(p_name.contains(".") || p_name.contains("/")); inputs.write[p_input].name = p_name; emit_changed(); } @@ -796,7 +796,7 @@ void AnimationTree::_clear_caches() { cache_valid = false; } -void AnimationTree::_process_graph(real_t p_delta) { +void AnimationTree::_process_graph(double p_delta) { _update_properties(); //if properties need updating, update them //check all tracks, see if they need modification @@ -1335,10 +1335,10 @@ void AnimationTree::_process_graph(real_t p_delta) { t->playing = false; playing_caches.erase(t); } else { - real_t start_ofs = a->audio_track_get_key_start_offset(i, idx); + double start_ofs = a->audio_track_get_key_start_offset(i, idx); start_ofs += time - a->track_get_key_time(i, idx); - real_t end_ofs = a->audio_track_get_key_end_offset(i, idx); - real_t len = stream->get_length(); + double end_ofs = a->audio_track_get_key_end_offset(i, idx); + double len = stream->get_length(); if (start_ofs > len - end_ofs) { t->object->call("stop"); @@ -1374,9 +1374,9 @@ void AnimationTree::_process_graph(real_t p_delta) { t->playing = false; playing_caches.erase(t); } else { - real_t start_ofs = a->audio_track_get_key_start_offset(i, idx); - real_t end_ofs = a->audio_track_get_key_end_offset(i, idx); - real_t len = stream->get_length(); + double start_ofs = a->audio_track_get_key_start_offset(i, idx); + double end_ofs = a->audio_track_get_key_end_offset(i, idx); + double len = stream->get_length(); t->object->call("set_stream", stream); t->object->call("play", start_ofs); @@ -1407,7 +1407,7 @@ void AnimationTree::_process_graph(real_t p_delta) { } } } else if (t->len > 0) { - real_t len = t->start > time ? (a->get_length() - t->start) + time : time - t->start; + double len = t->start > time ? (a->get_length() - t->start) + time : time - t->start; if (len > t->len) { stop = true; @@ -1455,7 +1455,7 @@ void AnimationTree::_process_graph(real_t p_delta) { Ref anim = player2->get_animation(anim_name); - real_t at_anim_pos = 0.0; + double at_anim_pos = 0.0; switch (anim->get_loop_mode()) { case Animation::LoopMode::LOOP_NONE: { diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 11c9bcd4ef44..705ee91c7629 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -83,7 +83,7 @@ class AnimationNode : public Resource { Vector blends; State *state = nullptr; - real_t _pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, real_t p_time, bool p_seek, const Vector &p_connections); + double _pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, const Vector &p_connections); //all this is temporary StringName base_path; @@ -96,12 +96,12 @@ class AnimationNode : public Resource { Array _get_filters() const; void _set_filters(const Array &p_filters); friend class AnimationNodeBlendTree; - real_t _blend_node(const StringName &p_subpath, const Vector &p_connections, AnimationNode *p_new_parent, Ref p_node, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true, real_t *r_max = nullptr); + double _blend_node(const StringName &p_subpath, const Vector &p_connections, AnimationNode *p_new_parent, Ref p_node, double p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true, real_t *r_max = nullptr); protected: - void blend_animation(const StringName &p_animation, real_t p_time, real_t p_delta, bool p_seeked, real_t p_blend, int p_pingponged = 0); - real_t blend_node(const StringName &p_sub_path, Ref p_node, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true); - real_t blend_input(int p_input, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true); + void blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, real_t p_blend, int p_pingponged = 0); + double blend_node(const StringName &p_sub_path, Ref p_node, double p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true); + double blend_input(int p_input, double p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true); void make_invalid(const String &p_reason); @@ -234,8 +234,8 @@ class AnimationTree : public Node { struct TrackCacheAudio : public TrackCache { bool playing = false; - real_t start = 0.0; - real_t len = 0.0; + double start = 0.0; + double len = 0.0; TrackCacheAudio() { type = Animation::TYPE_AUDIO; @@ -265,7 +265,7 @@ class AnimationTree : public Node { void _clear_caches(); bool _update_caches(AnimationPlayer *player); - void _process_graph(real_t p_delta); + void _process_graph(double p_delta); uint64_t setup_pass = 1; uint64_t process_pass = 1; diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index a37c6f535545..a2fed718bee1 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -249,8 +249,6 @@ bool Tween::custom_step(float p_delta) { } bool Tween::step(float p_delta) { - ERR_FAIL_COND_V_MSG(tweeners.is_empty(), false, "Tween started, but has no Tweeners."); - if (dead) { return false; } @@ -271,6 +269,7 @@ bool Tween::step(float p_delta) { } if (!started) { + ERR_FAIL_COND_V_MSG(tweeners.is_empty(), false, "Tween started, but has no Tweeners."); current_step = 0; loops_done = 0; start_tweeners(); @@ -284,6 +283,10 @@ bool Tween::step(float p_delta) { float step_delta = rem_delta; step_active = false; +#ifdef DEBUG_ENABLED + float prev_delta = rem_delta; +#endif + for (Ref &tweener : tweeners.write[current_step]) { // Modified inside Tweener.step(). float temp_delta = rem_delta; @@ -313,6 +316,12 @@ bool Tween::step(float p_delta) { start_tweeners(); } } + +#ifdef DEBUG_ENABLED + if (Math::is_equal_approx(rem_delta, prev_delta) && running && loops <= 0) { + ERR_FAIL_V_MSG(false, "Infinite loop detected. Check set_loops() description for more info."); + } +#endif } return true; diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h index 432317a4238e..c8298391bb51 100644 --- a/scene/debugger/scene_debugger.h +++ b/scene/debugger/scene_debugger.h @@ -37,6 +37,7 @@ #include "core/variant/array.h" class Script; +class Node; class SceneDebugger { public: diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index bcb2b0c50eec..da2ef6c5ec2a 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -382,8 +382,11 @@ Ref BaseButton::get_button_group() const { } void BaseButton::set_shortcut_context(Node *p_node) { - ERR_FAIL_NULL_MSG(p_node, "Shortcut context node can't be null."); - shortcut_context = p_node->get_instance_id(); + if (p_node != nullptr) { + shortcut_context = p_node->get_instance_id(); + } else { + shortcut_context = ObjectID(); + } } Node *BaseButton::get_shortcut_context() const { @@ -400,7 +403,7 @@ bool BaseButton::_is_focus_owner_in_shorcut_context() const { } Node *ctx_node = get_shortcut_context(); - Control *vp_focus = get_focus_owner(); + Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr; // If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it. return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus)); @@ -441,6 +444,7 @@ void BaseButton::_bind_methods() { ADD_SIGNAL(MethodInfo("button_up")); ADD_SIGNAL(MethodInfo("button_down")); ADD_SIGNAL(MethodInfo("toggled", PropertyInfo(Variant::BOOL, "button_pressed"))); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "toggle_mode"), "set_toggle_mode", "is_toggle_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_in_tooltip"), "set_shortcut_in_tooltip", "is_shortcut_in_tooltip_enabled"); @@ -500,7 +504,8 @@ BaseButton *ButtonGroup::get_pressed_button() { void ButtonGroup::_bind_methods() { ClassDB::bind_method(D_METHOD("get_pressed_button"), &ButtonGroup::get_pressed_button); ClassDB::bind_method(D_METHOD("get_buttons"), &ButtonGroup::_get_buttons); - ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::OBJECT, "button"))); + + ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::OBJECT, "button", PROPERTY_HINT_RESOURCE_TYPE, "BaseButton"))); } ButtonGroup::ButtonGroup() { diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index 552dd28513c7..3ed1b873afe3 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -197,6 +197,8 @@ void Button::_notification(int p_what) { color = get_theme_color(SNAME("font_disabled_color")); if (has_theme_color(SNAME("icon_disabled_color"))) { color_icon = get_theme_color(SNAME("icon_disabled_color")); + } else { + color_icon.a = 0.4; } } break; @@ -232,9 +234,6 @@ void Button::_notification(int p_what) { } if (!_icon.is_null()) { int valign = size.height - style->get_minimum_size().y; - if (is_disabled()) { - color_icon.a = 0.4; - } float icon_ofs_region = 0.0; Point2 style_offset; diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 8da7264b028c..8cb8a78e8d82 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -34,14 +34,6 @@ #include "core/string/string_builder.h" #include "core/string/ustring.h" -static bool _is_whitespace(char32_t c) { - return c == '\t' || c == ' '; -} - -static bool _is_char(char32_t c) { - return !is_symbol(c); -} - void CodeEdit::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: @@ -232,7 +224,7 @@ void CodeEdit::_notification(int p_what) { int begin = 0; int end = 0; - if (line.find(String::chr(0xFFFF)) != -1) { + if (line.contains(String::chr(0xFFFF))) { begin = font->get_string_size(line.substr(0, line.find(String::chr(0xFFFF))), font_size).x; end = font->get_string_size(line.substr(0, line.rfind(String::chr(0xFFFF))), font_size).x; } @@ -571,6 +563,8 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { // Overridable actions void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { bool had_selection = has_selection(); + String selection_text = (had_selection ? get_selected_text() : ""); + if (had_selection) { begin_complex_operation(); delete_selection(); @@ -591,27 +585,38 @@ void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { if (auto_brace_completion_enabled) { int cl = get_caret_line(); int cc = get_caret_column(); - int caret_move_offset = 1; - - int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1; - if (has_string_delimiter(chr) && cc > 0 && _is_char(get_line(cl)[cc - 1]) && post_brace_pair == -1) { - insert_text_at_caret(chr); - } else if (cc < get_line(cl).length() && _is_char(get_line(cl)[cc])) { - insert_text_at_caret(chr); - } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) { - caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length(); - } else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) { + if (had_selection) { insert_text_at_caret(chr); + + String close_key = get_auto_brace_completion_close_key(chr); + if (!close_key.is_empty()) { + insert_text_at_caret(selection_text + close_key); + set_caret_column(get_caret_column() - 1); + } } else { - insert_text_at_caret(chr); + int caret_move_offset = 1; + + int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1; + + if (has_string_delimiter(chr) && cc > 0 && !is_symbol(get_line(cl)[cc - 1]) && post_brace_pair == -1) { + insert_text_at_caret(chr); + } else if (cc < get_line(cl).length() && !is_symbol(get_line(cl)[cc])) { + insert_text_at_caret(chr); + } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) { + caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length(); + } else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) { + insert_text_at_caret(chr); + } else { + insert_text_at_caret(chr); - int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1); - if (pre_brace_pair != -1) { - insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key); + int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1); + if (pre_brace_pair != -1) { + insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key); + } } + set_caret_column(cc + caret_move_offset); } - set_caret_column(cc + caret_move_offset); } else { insert_text_at_caret(chr); } @@ -937,8 +942,10 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { return; } - const int cc = get_caret_column(); + /* When not splitting the line, we need to factor in indentation from the end of the current line. */ + const int cc = p_split_current_line ? get_caret_column() : get_line(get_caret_line()).length(); const int cl = get_caret_line(); + const String line = get_line(cl); String ins = "\n"; @@ -986,7 +993,7 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { } /* Make sure this is the last char, trailing whitespace or comments are okay. */ - if (should_indent && (!_is_whitespace(c) && is_in_comment(cl, cc) == -1)) { + if (should_indent && (!is_whitespace(c) && is_in_comment(cl, cc) == -1)) { should_indent = false; } } @@ -1012,6 +1019,8 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { bool first_line = false; if (!p_split_current_line) { + deselect(); + if (p_above) { if (cl > 0) { set_caret_line(cl - 1, false); @@ -1800,7 +1809,7 @@ void CodeEdit::request_code_completion(bool p_force) { String line = get_line(get_caret_line()); int ofs = CLAMP(get_caret_column(), 0, line.length()); - if (ofs > 0 && (is_in_string(get_caret_line(), ofs) != -1 || _is_char(line[ofs - 1]) || code_completion_prefixes.has(line[ofs - 1]))) { + if (ofs > 0 && (is_in_string(get_caret_line(), ofs) != -1 || !is_symbol(line[ofs - 1]) || code_completion_prefixes.has(line[ofs - 1]))) { emit_signal(SNAME("code_completion_requested")); } else if (ofs > 1 && line[ofs - 1] == ' ' && code_completion_prefixes.has(line[ofs - 2])) { emit_signal(SNAME("code_completion_requested")); @@ -1909,7 +1918,7 @@ void CodeEdit::confirm_code_completion(bool p_replace) { if (merge_text) { for (; caret_col < line.length(); caret_col++) { - if (!_is_char(line[caret_col])) { + if (is_symbol(line[caret_col])) { break; } } @@ -2545,7 +2554,7 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c region = E->value(); in_region = true; for (int i = E->key() - 2; i >= 0; i--) { - if (!_is_whitespace(line[i])) { + if (!is_whitespace(line[i])) { return -1; } } @@ -2564,7 +2573,7 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c } for (int i = end_col; i < line.length(); i++) { - if (!_is_whitespace(line[i])) { + if (!is_whitespace(line[i])) { return -1; } } @@ -2780,11 +2789,11 @@ void CodeEdit::_filter_code_completion_candidates_impl() { while (ofs > 0 && line[ofs] == ' ') { ofs--; } - prev_is_word = _is_char(line[ofs]); + prev_is_word = !is_symbol(line[ofs]); /* Otherwise get current word and set cofs to the start. */ } else { int start_cofs = cofs; - while (cofs > 0 && line[cofs - 1] > 32 && (line[cofs - 1] == '/' || _is_char(line[cofs - 1]))) { + while (cofs > 0 && line[cofs - 1] > 32 && (line[cofs - 1] == '/' || !is_symbol(line[cofs - 1]))) { cofs--; } string_to_complete = line.substr(cofs, start_cofs - cofs); @@ -2853,7 +2862,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { completion_options_casei.push_back(option); } else if (s.is_subsequence_of(option.display)) { completion_options_subseq.push_back(option); - } else if (s.is_subsequence_ofi(option.display)) { + } else if (s.is_subsequence_ofn(option.display)) { completion_options_subseq_casei.push_back(option); } diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 562ea8a8f764..fdae8e2f1fd1 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -576,6 +576,11 @@ void Control::_update_canvas_item_transform() { Transform2D xform = _get_internal_transform(); xform[2] += get_position(); + // We use a little workaround to avoid flickering when moving the pivot with _edit_set_pivot() + if (is_inside_tree() && Math::abs(Math::sin(data.rotation * 4.0f)) < 0.00001f && get_viewport()->is_snap_controls_to_pixels_enabled()) { + xform[2] = xform[2].round(); + } + RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), xform); } @@ -2191,8 +2196,7 @@ void Control::release_focus() { return; } - get_viewport()->_gui_remove_focus(); - update(); + get_viewport()->gui_release_focus(); } bool Control::is_top_level_control() const { @@ -2595,11 +2599,6 @@ Control::MouseFilter Control::get_mouse_filter() const { return data.mouse_filter; } -Control *Control::get_focus_owner() const { - ERR_FAIL_COND_V(!is_inside_tree(), nullptr); - return get_viewport()->_gui_get_focus_owner(); -} - void Control::warp_mouse(const Point2 &p_to_pos) { ERR_FAIL_COND(!is_inside_tree()); get_viewport()->warp_mouse(get_global_transform().xform(p_to_pos)); @@ -2889,7 +2888,6 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("release_focus"), &Control::release_focus); ClassDB::bind_method(D_METHOD("find_prev_valid_focus"), &Control::find_prev_valid_focus); ClassDB::bind_method(D_METHOD("find_next_valid_focus"), &Control::find_next_valid_focus); - ClassDB::bind_method(D_METHOD("get_focus_owner"), &Control::get_focus_owner); ClassDB::bind_method(D_METHOD("set_h_size_flags", "flags"), &Control::set_h_size_flags); ClassDB::bind_method(D_METHOD("get_h_size_flags"), &Control::get_h_size_flags); diff --git a/scene/gui/control.h b/scene/gui/control.h index bf79f790e787..962135280f14 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -462,8 +462,6 @@ class Control : public CanvasItem { void set_focus_previous(const NodePath &p_prev); NodePath get_focus_previous() const; - Control *get_focus_owner() const; - void set_mouse_filter(MouseFilter p_filter); MouseFilter get_mouse_filter() const; diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index e5bd6f4882d6..dad84461f4b0 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -169,7 +169,14 @@ void FileDialog::update_dir() { dir->set_text(dir_access->get_current_dir(false)); if (drives->is_visible()) { - drives->select(dir_access->get_current_drive()); + if (dir_access->get_current_dir().is_network_share_path()) { + _update_drives(false); + drives->add_item(RTR("Network")); + drives->set_item_disabled(drives->get_item_count() - 1, true); + drives->select(drives->get_item_count() - 1); + } else { + drives->select(dir_access->get_current_drive()); + } } // Deselect any item, to make "Select Current Folder" button text by default. @@ -846,7 +853,7 @@ void FileDialog::_select_drive(int p_idx) { _push_history(); } -void FileDialog::_update_drives() { +void FileDialog::_update_drives(bool p_select) { int dc = dir_access->get_drive_count(); if (dc == 0 || access != ACCESS_FILESYSTEM) { drives->hide(); @@ -864,7 +871,9 @@ void FileDialog::_update_drives() { drives->add_item(dir_access->get_drive(i)); } - drives->select(dir_access->get_current_drive()); + if (p_select) { + drives->select(dir_access->get_current_drive()); + } } } diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 9f8bc02b2a2d..36a6b262b092 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -132,7 +132,7 @@ class FileDialog : public ConfirmationDialog { void _go_back(); void _go_forward(); - void _update_drives(); + void _update_drives(bool p_select = true); virtual void unhandled_input(const Ref &p_event) override; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 2be76473b045..95575a8226d9 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -1692,8 +1692,9 @@ int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set &r_u, switch (p_operation) { case GraphEdit::IS_EQUAL: { for (Set::Element *E = r_u.front(); E; E = E->next()) { - if (!r_v.has(E->get())) + if (!r_v.has(E->get())) { return 0; + } } return r_u.size() == r_v.size(); } break; @@ -1702,8 +1703,9 @@ int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set &r_u, return 1; } for (Set::Element *E = r_u.front(); E; E = E->next()) { - if (!r_v.has(E->get())) + if (!r_v.has(E->get())) { return 0; + } } return 1; } break; diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index fab420d5939c..852aaaab24bd 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -82,9 +82,11 @@ void Label::_shape() { Ref style = get_theme_stylebox(SNAME("normal"), SNAME("Label")); int width = (get_size().width - style->get_minimum_size().width); - if (dirty) { + if (dirty || font_dirty) { String lang = (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale(); - TS->shaped_text_clear(text_rid); + if (dirty) { + TS->shaped_text_clear(text_rid); + } if (text_direction == Control::TEXT_DIRECTION_INHERITED) { TS->shaped_text_set_direction(text_rid, is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); } else { @@ -97,9 +99,17 @@ void Label::_shape() { if (visible_chars >= 0 && visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) { text = text.substr(0, visible_chars); } - TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, opentype_features, lang); + if (dirty) { + TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, opentype_features, lang); + } else { + int spans = TS->shaped_get_span_count(text_rid); + for (int i = 0; i < spans; i++) { + TS->shaped_set_span_update_font(text_rid, i, font->get_rids(), font_size, opentype_features); + } + } TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, text)); dirty = false; + font_dirty = false; lines_dirty = true; } @@ -276,7 +286,7 @@ void Label::_notification(int p_what) { RenderingServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true); } - if (dirty || lines_dirty) { + if (dirty || font_dirty || lines_dirty) { _shape(); } @@ -521,7 +531,7 @@ void Label::_notification(int p_what) { } if (p_what == NOTIFICATION_THEME_CHANGED) { - dirty = true; + font_dirty = true; update(); } if (p_what == NOTIFICATION_RESIZED) { @@ -531,7 +541,7 @@ void Label::_notification(int p_what) { Size2 Label::get_minimum_size() const { // don't want to mutable everything - if (dirty || lines_dirty) { + if (dirty || font_dirty || lines_dirty) { const_cast