273 Commits

Author SHA1 Message Date
7e969649ae _macdrag precleanup
Some checks failed
Build Android / Game (Android, Release ARM64) (push) Has been cancelled
Build iOS / Game (iOS, Release ARM64) (push) Has been cancelled
Build Linux / Editor (Linux, Development x64) (push) Has been cancelled
Build Linux / Game (Linux, Release x64) (push) Has been cancelled
Build macOS / Editor (Mac, Development ARM64) (push) Has been cancelled
Build macOS / Game (Mac, Release ARM64) (push) Has been cancelled
Build Windows / Editor (Windows, Development x64) (push) Has been cancelled
Build Windows / Game (Windows, Release x64) (push) Has been cancelled
Cooker / Cook (Mac) (push) Has been cancelled
Tests / Tests (Linux) (push) Has been cancelled
Tests / Tests (Windows) (push) Has been cancelled
2026-04-04 19:37:15 +03:00
3288fe4006 Fix mouse warping when controlling camera in Editor viewport on macOS 2026-04-04 19:17:26 +03:00
fd334769b2 Handle external application close events 2026-04-04 18:52:49 +03:00
576130f74f Fix destroyed windows removing keyboard focus on macOS 2026-04-04 18:36:51 +03:00
b99c8ddda7 Fix missing window icon on macOS 2026-04-04 16:45:19 +03:00
Wojtek Figat
2fc95bdf57 Merge remote-tracking branch 'origin/master' into 1.12
# Conflicts:
#	Content/Editor/MaterialTemplates/Deformable.shader
#	Flax.flaxproj
#	Source/Engine/Content/Content.h
#	Source/Engine/Serialization/JsonTools.cpp
2026-04-01 17:14:21 +02:00
Wojtek Figat
9a7abd751f Fix crash on invalid unpack node usage in shader graph
#4030
2026-04-01 16:55:28 +02:00
Wojtek Figat
cabae8142d Merge branch 'Zode-visject-larger-inputs' 2026-04-01 16:26:53 +02:00
Wojtek Figat
2d0da37907 Remove ComparisonNode from #3540 that is impl in #3866 2026-04-01 16:26:47 +02:00
Wojtek Figat
42ec33bd9a Fix auto-sizing nodes with hidden default value fields (eg, comparison node)
#3540
2026-04-01 16:25:33 +02:00
Wojtek Figat
806027408a Merge branch 'visject-larger-inputs' of https://github.com/Zode/FlaxEngine into Zode-visject-larger-inputs 2026-04-01 16:02:10 +02:00
Wojtek Figat
dc6d53de7c Try to fix compile error 2026-04-01 16:01:01 +02:00
Wojtek Figat
70f2eee172 Merge branch 'xxSeys1-VISjectIsNowMoreVISuallyAppealingAndLegible' 2026-04-01 15:58:41 +02:00
Wojtek Figat
7737dbc77f Various minor fixes
#3866
2026-04-01 15:58:31 +02:00
Wojtek Figat
9d9d582598 Add undo to preserve connections when changing parameter in param node 2026-04-01 09:50:02 +02:00
Wojtek Figat
f1796dc8c8 Fix and improve layout of various Visject nodes and elements
#3866
2026-04-01 09:49:29 +02:00
Wojtek Figat
ebb4ff1dc2 Move UseFixedSize property from node archetype to flags and optimize unnecessary layouts during node loading
#3866
2026-03-31 22:47:10 +02:00
Wojtek Figat
ea5e7f1416 Add hover highlight to Visject surface boxes
#3866
2026-03-31 21:46:21 +02:00
Wojtek Figat
917e62621d Restore textures background back to Visject surface
#3866
2026-03-31 21:45:29 +02:00
Wojtek Figat
a88e3265cd Merge branch 'VISjectIsNowMoreVISuallyAppealingAndLegible' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-VISjectIsNowMoreVISuallyAppealingAndLegible 2026-03-31 15:16:55 +02:00
Wojtek Figat
b2d1d0f1d9 Fix sending replication message to newly connected clients for objects that were not spawned
#4029
2026-03-31 13:13:33 +02:00
Wojtek Figat
c6204fc274 Fix binary asset dependencies tracking when dependent asset gets loaded later on
#3951

Adds additional cache for dependencies tracking to update them when other asset gets loaded later on.
Remove critical-section from `LoadAssetTask` that was causing some deadlocks (data access is already atomic there).
2026-03-31 12:03:18 +02:00
Wojtek Figat
8b383d4dbe Fix regression on Large Worlds build and bunch of warnings 2026-03-30 23:50:03 +02:00
Wojtek Figat
7b695bf6bf Fix compilation regression 2026-03-30 22:44:38 +02:00
Wojtek Figat
327547a895 Rename FlaxStorage::AllowDataModifications to IsReadOnly 2026-03-30 22:40:37 +02:00
Wojtek Figat
e2fbd83086 Optimize VariantType name allocs to use static type when possible 2026-03-30 21:56:01 +02:00
Wojtek Figat
d96e1297f7 Optimize MMethod::GetParameterIsOut to cache all parameters in a single bit-flag 2026-03-30 19:56:32 +02:00
Wojtek Figat
2748216190 Fix using Dictionary as virtual method parameter in scripting bindings 2026-03-30 19:55:44 +02:00
Wojtek Figat
b2457b6ee6 Fix Variant array element typename 2026-03-30 19:55:13 +02:00
Wojtek Figat
bf20f5d2bf Fix scripting bindings in searching virtual methods to invoke when there is a name and parameter count collision
#3649
2026-03-30 19:54:47 +02:00
Wojtek Figat
767854a2af Fix shader error when using Position Offset in deformable material
#4028
2026-03-30 10:18:15 +02:00
Wojtek Figat
243173df3c Fix pasted ghost prefab objects when paste target is not defined
#3945
2026-03-30 10:14:50 +02:00
Wojtek Figat
408c6d96b1 Minor imporvements 2026-03-30 10:14:25 +02:00
Wojtek Figat
039407b6ee Fix broken prefab linkage when duplicating nested prefab instance root
#3945
2026-03-30 10:14:07 +02:00
Wojtek Figat
71ff0c6362 Fix root linkage for prefab instances copy pasted in Editor
#3945
2026-03-30 10:13:25 +02:00
Wojtek Figat
b3aeab777f Fix invalid CanRenderer when canvas actor is gone
#3945
2026-03-30 00:13:49 +02:00
Wojtek Figat
b8110e9db3 Add = to text box separators 2026-03-30 00:13:24 +02:00
Wojtek Figat
f500fcd6fd Optimize actors copy/paste data to use a single JSON for all objects
#3945
2026-03-28 23:39:43 +01:00
Saas
bda0dee371 fix curve node 2026-03-28 22:32:55 +01:00
Saas
ec756fe626 fix custom code node size 2026-03-28 22:16:31 +01:00
Saas
b78ff57f6a fix custom code nodes 2026-03-28 22:04:41 +01:00
Saas
9fa9e75e08 fix decorator size when surface is loaded 2026-03-28 21:28:09 +01:00
Wojtek Figat
095f7277e2 Minor fixes 2026-03-28 15:13:52 +01:00
Saas
f26dbf530c fix skeleton blend mask node to resize when asset reference is (dis-) connected 2026-03-28 13:47:14 +01:00
Wojtek Figat
76378156f7 Fix crash when applying prefab changes
#3755
2026-03-28 00:31:46 +01:00
Wojtek Figat
c12948c6cc Fix crash when applying prefab changes using default instance (invalid)
#3754
2026-03-28 00:04:03 +01:00
Wojtek Figat
e99dc8dce4 Fix missing doc warning 2026-03-27 23:54:43 +01:00
Wojtek Figat
258ba233d5 Attempt to fix time precision issue in long game run
#3865
2026-03-27 23:52:11 +01:00
Wojtek Figat
2b538e4864 Fix direction gizmo size on different FOV and fix event
#4025
2026-03-27 23:19:27 +01:00
Wojtek Figat
a0dc300a7b Merge branch 'Tryibion-add-content-tree-view' 2026-03-27 22:20:19 +01:00
Wojtek Figat
9f54bca831 Fix regression in HDR screen space reflections 2026-03-27 18:23:17 +01:00
Wojtek Figat
bb2ee7260f Optimize code in draw calls batching for shadow 2026-03-27 17:59:25 +01:00
Wojtek Figat
1bef4d00ad Fix deprecated compilation errors on the latest macOS SDK 2026-03-27 17:26:05 +01:00
Wojtek Figat
81176c98a1 Rebuild tint for macOS with SPIRV reader 2026-03-27 17:25:36 +01:00
Chandler Cox
6c4fc44163 Fix arrow rect issue. 2026-03-27 10:59:06 -05:00
Chandler Cox
a41f2e9260 Merge branch 'master' into add-content-tree-view 2026-03-27 10:48:08 -05:00
Wojtek Figat
cc9e41fe44 Dont build tint by default 2026-03-27 11:29:16 +01:00
Wojtek Figat
4d77ced41d Go back with min .NET version 2026-03-27 11:24:39 +01:00
Wojtek Figat
54202eeb8a Merge remote-tracking branch 'origin/1.12' into 1.12 2026-03-27 11:22:32 +01:00
Wojtek Figat
0e1c5cd6f8 Bump up build number 2026-03-27 11:16:25 +01:00
Wojtek Figat
8f50d9faec Fix terrain collision geometry order to match heights buffer
#3844
2026-03-27 11:06:11 +01:00
Wojtek Figat
5249864af4 Merge branch 'xxSeys1-DirectionGizmoPolish' 2026-03-27 10:40:01 +01:00
Wojtek Figat
3e5ceb1e1e Merge branch 'DirectionGizmoPolish' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-DirectionGizmoPolish 2026-03-27 10:39:11 +01:00
Wojtek Figat
ed46025a11 Merge branch 'xxSeys1-112InIssueTemplate' 2026-03-27 10:35:14 +01:00
Wojtek Figat
706b24e1c8 Merge branch '112InIssueTemplate' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-112InIssueTemplate 2026-03-27 10:35:09 +01:00
Wojtek Figat
f5651de725 Add NDK 27 as minimum for Android to fix 16kb page alignment issue on libc++_shared.so 2026-03-27 10:16:32 +01:00
Wojtek Figat
51ff2969e1 Ignore signal SIG34 in Linux tests 2026-03-27 08:52:11 +01:00
Wojtek Figat
5773dc47f4 Restore no cursor clipping in Locked mode on non-SDL input to avoid issues 2026-03-26 23:52:24 +01:00
Wojtek Figat
c30c56e8eb Restore windows lock during input events processing in non-SDL verison for now 2026-03-26 23:49:30 +01:00
Wojtek Figat
eab7794e3b Merge remote-tracking branch 'origin/master' into 1.12
# Conflicts:
#	Source/Engine/Content/Storage/FlaxStorage.cpp
#	global.json
2026-03-26 23:44:33 +01:00
Wojtek Figat
b347cd2f54 Disable SDL on Mac and Windows for now 2026-03-26 23:32:51 +01:00
Wojtek Figat
78052fbaec Refactor engine configuration to support platform-specific namespaces in config files 2026-03-26 23:27:51 +01:00
Wojtek Figat
557dc9b649 Add transparency support to dragging windows without SDL 2026-03-26 22:08:41 +01:00
Wojtek Figat
9fbcce13b3 Add tint shader compiler dependency to manually build it for Mac with SPIR-V reader
Official binary doesn't include it, only WGSL reader which blocks usage in Flax.
2026-03-26 21:43:51 +01:00
Wojtek Figat
6d433d4ef7 Add normalization to Reflections debug view to be usable in HDR scenes 2026-03-26 19:16:16 +01:00
Wojtek Figat
7531b1fdbd Fix rendering crash when D24_8Stencil format is not supported (lol) 2026-03-26 19:12:58 +01:00
Wojtek Figat
58586ca4f0 Add small improvement to terrain normals 2026-03-26 17:17:13 +01:00
Wojtek Figat
09a0573932 Fix MeshAccessor.Stream count for items not aligned to buffer start 2026-03-26 17:14:14 +01:00
Wojtek Figat
b6789ee523 Add various improvements to MeshAccessor such as ComputeNormals and ComputeTangents
https://forum.flaxengine.com/t/how-to-create-procedural-generated-geometry-using-meshaccessor/2420
2026-03-26 17:13:46 +01:00
Wojtek Figat
f1b6c13ba9 Fix PixelFormatSampler.Write` in C# to actually work 2026-03-26 15:33:55 +01:00
Wojtek Figat
ccc8d209da Fix MeshAccessor to validate buffer existence in C# API 2026-03-26 15:33:14 +01:00
Wojtek Figat
e624e1ba69 Fix missing Custom Global Code code generation in Defines stage 2026-03-26 14:14:15 +01:00
Wojtek Figat
d0cc88f82a Try to fix random hdiutil failure in Continuous Delivery on Github Action 2026-03-26 14:08:03 +01:00
Wojtek Figat
a48ce78733 Fix crash when loading CollisionData before physics init
#3971
2026-03-26 12:28:31 +01:00
Wojtek Figat
d1f6440a76 Fix stack overflow on mac when cooking game with too small stack size 2026-03-26 10:55:11 +01:00
Wojtek Figat
5ccc33719e Add basis universal and Web build support for macOS 2026-03-26 10:47:52 +01:00
Wojtek Figat
0f09cad1cf Update PhysX with new slim Build
Rebuild for win64 and android only
2026-03-26 09:33:56 +01:00
Wojtek Figat
f92ec30e1b Fix crash when using convex mesh collider with negative scale
#3853
2026-03-26 09:26:42 +01:00
Wojtek Figat
a9fbbaa88e Fix spawned in Editor actor RigidBody to never be static 2026-03-26 09:21:54 +01:00
Saas
3db3eb58a3 add Flax 1.12 to bug report template and tweak wording for some things 2026-03-25 23:16:27 +01:00
Saas
e455c874c6 tweak Z axis color and add brightness option
https://discord.com/channels/437989205315158016/509056735844106251/1486480204242096369
2026-03-25 22:57:17 +01:00
Wojtek Figat
32af903a2d Fix crash when exporting terrain that has missing patch data 2026-03-25 22:31:53 +01:00
Wojtek Figat
c12553812c Update Terrain scripting API 2026-03-25 22:31:36 +01:00
Saas
c9df03d293 fix viewport orientation when clicking on axis 2026-03-25 22:15:38 +01:00
Saas
c71e1d78e0 polish direction gizmo
- slightly change appearance to be more functional and more pleasant on the eyes
- add options to customize appearance in Viewport editor options
2026-03-25 22:12:16 +01:00
Wojtek Figat
a84109df2c Add UnusedStorageLifetime for asset file TTL
#3931
2026-03-25 21:30:07 +01:00
Wojtek Figat
9d0e4e9768 Fix potential stack overflow inside CustomEditor.RebuildLayout
#3720
2026-03-25 18:51:12 +01:00
Wojtek Figat
3f2e6d82c9 Fix regression from 21e2c830e5 when reimport Animation
#3937
2026-03-25 18:44:56 +01:00
Wojtek Figat
56b208ad85 Merge branch 'Tryibion-direction-gizmo' 2026-03-25 18:11:40 +01:00
Wojtek Figat
842b25f80f Fix Direction Gizmo axes placement in ortho view
#3857
2026-03-25 18:11:31 +01:00
Wojtek Figat
27a1db617c Add hover color highlight to match transform gizmo in direction gizmo
Don't snap shapes to pixels to smooth movement during view rotation

#3857
2026-03-25 17:47:56 +01:00
Wojtek Figat
8356009526 Add button to hide direction gizmo
#3857
2026-03-25 17:41:29 +01:00
Wojtek Figat
0cbb2f0525 Add smooth viewport orientation movement to Direction Gizmo
#3857
2026-03-25 17:39:03 +01:00
Wojtek Figat
0ac5e85cd9 Code cleanup #3857 2026-03-25 17:29:25 +01:00
Wojtek Figat
9b5dbe858c Merge branch 'direction-gizmo' of https://github.com/Tryibion/FlaxEngine into Tryibion-direction-gizmo 2026-03-25 17:24:57 +01:00
Wojtek Figat
914e0869b1 Fix warning when building C# lib with newer .NET than base version used on deps 2026-03-25 17:23:11 +01:00
Wojtek Figat
2c742bee41 Fix typo from #3914 2026-03-25 17:22:38 +01:00
Wojtek Figat
a9ac4d427f Merge branch 'xxSeys1-RadialMenuImprovements' 2026-03-25 17:21:24 +01:00
Wojtek Figat
0f332e331d Merge branch 'RadialMenuImprovements' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-RadialMenuImprovements 2026-03-25 17:21:20 +01:00
Wojtek Figat
be69453bfd Merge branch 'xxSeys1-OnlyCreateMaterialSlots' 2026-03-25 17:16:06 +01:00
Wojtek Figat
5e6723aa22 Fix missing init and serialization #2977 2026-03-25 17:16:01 +01:00
Wojtek Figat
c7449c8f95 Merge branch 'OnlyCreateMaterialSlots' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-OnlyCreateMaterialSlots
# Conflicts:
#	Source/Engine/Tools/ModelTool/ModelTool.h
2026-03-25 17:15:47 +01:00
Wojtek Figat
e23cbd1de9 Merge branch 'alsed-prefab_skinned' 2026-03-25 17:12:35 +01:00
Wojtek Figat
0fe297c390 Minor fixes to #3669
Don't change imported model type, only Prefab can do it conditionally.
Add type of `PrefabObject` to extend it in future with Cameras, Lights, etc.
2026-03-25 16:57:38 +01:00
Wojtek Figat
b10303cd8d Merge branch 'prefab_skinned' of https://github.com/alsed/FlaxEngine into alsed-prefab_skinned 2026-03-25 16:22:05 +01:00
Wojtek Figat
9e9715a2ea Merge branch 'xxSeys1-RipAppartAndConnectConnectionsVisject' 2026-03-25 15:52:19 +01:00
Wojtek Figat
a04995174f Merge branch 'RipAppartAndConnectConnectionsVisject' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-RipAppartAndConnectConnectionsVisject
# Conflicts:
#	Source/Editor/Surface/VisjectSurface.Input.cs
2026-03-25 15:49:46 +01:00
Wojtek Figat
e4a97258f3 Merge branch 'xxSeys1-NoClearSearchboxesWhenStuffHappens' 2026-03-25 15:47:31 +01:00
Wojtek Figat
912f5fb4f3 Merge branch 'NoClearSearchboxesWhenStuffHappens' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-NoClearSearchboxesWhenStuffHappens 2026-03-25 15:46:26 +01:00
Wojtek Figat
4d9898770a Attempt to fix Github Action issues 2026-03-25 15:45:51 +01:00
Wojtek Figat
508ccb714c Attempt to fix Github Action issues 2026-03-25 15:18:02 +01:00
Wojtek Figat
0c1da8e13b Attempt to fix Github Action issues 2026-03-25 15:02:58 +01:00
Wojtek Figat
d40573df5b Merge branch 'xxSeys1-FocusSelectedVJControls' 2026-03-25 15:00:00 +01:00
Wojtek Figat
707d19284d Merge branch 'FocusSelectedVJControls' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-FocusSelectedVJControls 2026-03-25 14:56:27 +01:00
Wojtek Figat
5a6b07e4c5 Merge branch 'xxSeys1-PackUnpackAlternativeNodeTitles' 2026-03-25 14:47:10 +01:00
Wojtek Figat
935645d553 Merge branch 'PackUnpackAlternativeNodeTitles' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-PackUnpackAlternativeNodeTitles 2026-03-25 14:44:32 +01:00
Wojtek Figat
d9f2931b9e Merge branch 'xxSeys1-VisjectFixes' 2026-03-25 14:43:31 +01:00
Wojtek Figat
0770f4a216 Merge branch 'VisjectFixes' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-VisjectFixes 2026-03-25 14:43:27 +01:00
Wojtek Figat
ecc9406968 Attempt to fix Github Action issues 2026-03-25 13:54:14 +01:00
Wojtek Figat
d06be3d1d6 Fix crash on invalid prefab data size
#3979
2026-03-25 13:45:21 +01:00
Wojtek Figat
aeecadffb0 Attempt to fix Github Action issues 2026-03-25 13:32:21 +01:00
Wojtek Figat
f3e23b6420 Attempt to fix Github Action issues 2026-03-25 12:45:06 +01:00
Wojtek Figat
7c5b1942e6 Fix MaterialBase::GetParameterValue when parameter is not overridden
#3965
2026-03-25 12:44:42 +01:00
Wojtek Figat
9850761b5c Fix crash when cloth sim is still active 2026-03-25 12:27:50 +01:00
Wojtek Figat
1e1f7ce56d Attempt to fix Github Action issues 2026-03-25 12:16:41 +01:00
Wojtek Figat
64005c2774 Attempt to fix Github Action issues 2026-03-25 09:49:38 +01:00
Wojtek Figat
22ad2d1fab Attempt to fix Github Action issues 2026-03-25 09:34:50 +01:00
Wojtek Figat
9d478b570f Attempt to fix Github Action issues 2026-03-25 09:11:26 +01:00
Wojtek Figat
35fb320ff0 Merge remote-tracking branch 'origin/master' into 1.12 2026-03-24 23:59:41 +01:00
Wojtek Figat
e000a6727e Fix format support on Vulkan 2026-03-24 23:58:01 +01:00
Wojtek Figat
3a3b997e97 Fix compilation regression 2026-03-24 23:46:21 +01:00
Wojtek Figat
f3c957481c Merge remote-tracking branch 'origin/master' into 1.12
# Conflicts:
#	Source/Editor/GUI/Dialogs/ColorPickerDialog.cs
#	Source/Editor/GUI/Dialogs/ColorSelector.cs
2026-03-24 23:41:58 +01:00
Wojtek Figat
e8134803c4 Fix invalid index buffer format returned by MeshAccessor when model is not yet loaded
#4017
2026-03-24 23:13:50 +01:00
Wojtek Figat
7b3cfd989f Add memory profiler category for Cloth 2026-03-24 23:01:09 +01:00
Wojtek Figat
13f5222ec7 Fix MeshAccessor to properly reference model asset data during the usage 2026-03-24 23:00:51 +01:00
Wojtek Figat
29abfbcdc9 Fix Cloth with models that use compressed vertex buffer
#4017
2026-03-24 22:59:21 +01:00
Wojtek Figat
a63e05d444 Fix Cloth to snap to the parent actor on spawn in Editor 2026-03-24 20:10:20 +01:00
Wojtek Figat
f0f1c57ff1 Add scrolling scene tree hierarchy to the newly spawned actor after drag and drop 2026-03-24 19:43:37 +01:00
Wojtek Figat
d2ef0671e3 Fix incorrect terrain debug buffers disposing 2026-03-24 19:32:17 +01:00
Wojtek Figat
5383de10b8 Merge branch 'xxSeys1-NewColorPicker' 2026-03-24 18:58:34 +01:00
Wojtek Figat
1318dde3ca Add new HSV wheel material, maintain Editor icons atlas for now
#3987
2026-03-24 18:58:32 +01:00
Wojtek Figat
90e96678a6 Merge branch 'NewColorPicker' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-NewColorPicker 2026-03-24 18:50:06 +01:00
Wojtek Figat
2e13c6043d Merge branch 'xxSeys1-FixedTimlineZoomAndAutoZoom' 2026-03-24 18:40:13 +01:00
Wojtek Figat
32d0241f54 Merge branch 'FixedTimlineZoomAndAutoZoom' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-FixedTimlineZoomAndAutoZoom 2026-03-24 18:35:23 +01:00
Wojtek Figat
e8fe1cd044 Merge branch 'xxSeys1-PhysicsSphereCullFix' 2026-03-24 18:34:33 +01:00
Wojtek Figat
956a9b8fab Merge branch 'PhysicsSphereCullFix' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-PhysicsSphereCullFix 2026-03-24 18:34:30 +01:00
Wojtek Figat
5732290c29 Merge branch 'ThePhantomMask-Fix-Dropdown' 2026-03-24 18:34:05 +01:00
Wojtek Figat
601b40dd86 Merge branch 'Fix-Dropdown' of https://github.com/ThePhantomMask/FlaxEngine into ThePhantomMask-Fix-Dropdown 2026-03-24 18:33:56 +01:00
Wojtek Figat
7f1add4bdd Fix missing Length in Vector4 2026-03-24 18:26:45 +01:00
Wojtek Figat
fa17d83501 Fix showing internal surface parameters (eg. Base Model in Anim Graph)
#3936
2026-03-24 18:25:40 +01:00
Wojtek Figat
99d3da467e Fix deadlock when hot-reloading scripts in Editor while Animation asset gets auto-saved
#3942
2026-03-24 17:45:06 +01:00
Wojtek Figat
b54794255a Fix .pch files rebuilds after MSVC toolchain updates 2026-03-24 17:14:30 +01:00
Wojtek Figat
ce5dd6bc16 Fix render image output flags when UAV is not supported 2026-03-24 17:00:26 +01:00
Wojtek Figat
45feda8f07 Fix Firefox and Safari bug with missing bind group layout 2026-03-24 16:59:49 +01:00
Wojtek Figat
bd4c20d9ce Add auto-converting unsupported GPU texture format at runtime (excluding compressed textures) 2026-03-24 15:57:50 +01:00
Wojtek Figat
cc89a1e1ca Add check for minimum browser version when running on Web 2026-03-24 15:23:33 +01:00
Wojtek Figat
df6b550c3d Add Web to engine build distro 2026-03-24 01:01:46 +01:00
Wojtek Figat
0cb5e3421a Revert "Optimize Visject native graph connections loading with less loops"
This reverts commit 1e44bb1832.
2026-03-24 00:31:22 +01:00
Wojtek Figat
4d5bf4ec0c Fix SSR with HiZ on WebGPU on Android device when R32_Float cannnot be sampled 2026-03-24 00:26:47 +01:00
Wojtek Figat
88fe9ba186 Fix WebGPU crashes when resizing canvas 2026-03-23 18:37:58 +01:00
Wojtek Figat
b756c16018 Add renaming underlying GPU resource (eg. for pooled render targets) 2026-03-23 18:37:18 +01:00
Wojtek Figat
1e44bb1832 Optimize Visject native graph connections loading with less loops 2026-03-23 18:29:01 +01:00
Wojtek Figat
3baec506e2 Optimize various initial pre-allocs to happen within memory tracing 2026-03-23 18:25:05 +01:00
Wojtek Figat
90a0cc0e03 Add warning about Web platform being not finished yet 2026-03-23 18:19:53 +01:00
Saas
a346e258d3 make "Flow" group archetype color more legible 2026-03-21 12:53:10 +01:00
Saas
c673e9f52d minor fixes 2026-03-20 20:18:12 +01:00
Saas
1bd86ca28b temporary fix for "Copy Node" node size 2026-03-20 20:17:10 +01:00
Saas
4594a84753 disable vertex snapping for Blend Points of Animation Blend 1D and 2D 2026-03-20 19:07:11 +01:00
Saas
a1af870874 fix surface elements resizing the node 2026-03-20 19:01:52 +01:00
Wojtek Figat
b8d5ea595e Optimize Web app a bit more 2026-03-18 23:22:45 +01:00
Wojtek Figat
5404dff5c2 Rebuild PhysX Vehicle lib for Web without dummy symbol export 2026-03-18 23:09:44 +01:00
Wojtek Figat
a5ec8565e4 Refactor WebGPU ASYNCIFY to use JSPI
Smaller build size and better performance. Also, link time goes down a lot
2026-03-18 23:08:39 +01:00
Saas
e5d526c9af show whole timeline when animation loads 2026-03-18 16:55:45 +01:00
Saas
cb9e09c21b fix Show whole timeline button 2026-03-18 16:55:35 +01:00
Wojtek Figat
750fd1f941 Optimize Web export size 2026-03-17 23:31:34 +01:00
Saas
1308465280 fix sphere with negative radius getting culled to early in physics collider debug draw 2026-03-17 22:02:36 +01:00
Wojtek Figat
a38633c453 Add basic WebThread impl 2026-03-16 23:03:02 +01:00
Wojtek Figat
3949cba83c Add Emscripten heap memory size and max limit stats to profiler 2026-03-16 22:29:19 +01:00
Wojtek Figat
e10d784e83 Add custom HTML shell template to Web export 2026-03-16 22:09:21 +01:00
Wojtek Figat
0f5c5dcf3e Fix memory issues on Web 2026-03-16 16:42:30 +01:00
Wojtek Figat
ed3a827b5f Implement GPUDeviceWebGPU::WaitForGPU 2026-03-16 16:41:39 +01:00
Wojtek Figat
7f49cae9af Optimize LOG() macro to use stack-allocated buffer for shorter texts 2026-03-16 16:41:16 +01:00
Wojtek Figat
427f4647fc Fix various issues in WebGPU backend 2026-03-16 16:39:18 +01:00
Saas
d5d10aa329 fix handling controls during resizing 2026-03-15 19:53:09 +01:00
Saas
5b2b1930d2 fix Particle Emitter node to not auto resize 2026-03-15 17:03:01 +01:00
Saas
5655cc8f42 account for surface node elements when auto resizing 2026-03-15 16:59:09 +01:00
Saas
8bad080d59 Merge remote-tracking branch 'upstream/master' into VISjectIsNowMoreVISuallyAppealingAndLegible 2026-03-15 14:43:11 +01:00
Saas
0237235dcb draw close button using Render2D to make it look good even when zoomed in a lot 2026-03-15 14:41:50 +01:00
Wojtek Figat
a5bbf0dbde Add option for experimental use of thread in Web
Might not run well in certain browsers, requires rebuilding all dependencies for Web with `pthread` enabled.
Disabled by default (for now).
2026-03-14 22:11:36 +01:00
Saas
5ec018b904 fix particle module changing size after certain user actions 2026-03-14 19:05:35 +01:00
Saas
f2a13b64d4 fix reroute node output box moving on connection change 2026-03-14 19:00:12 +01:00
Saas
44ecac7ffc make debug text more legible 2026-03-14 16:52:56 +01:00
Saas
60076d48c7 fix box alignment for nodes with fixed size and adjust some node sizes 2026-03-14 16:38:56 +01:00
Saas
2fbeac6077 disable decorator node shadows 2026-03-14 16:29:43 +01:00
Phantom
57355a741e Merge branch 'master' into Fix-Dropdown 2026-03-14 15:31:38 +01:00
Phantom
14d6273a2f fix #2 on Dropdown 2026-03-14 15:29:31 +01:00
Saas
0cec65f35f disable vertex snapping while drawing box to fix snapping artefacts when zooming surface 2026-03-14 14:46:10 +01:00
Saas
fcdd05dede fix straight connection sprite rotation offset at some angles 2026-03-14 14:20:30 +01:00
Saas
e008c7e2b4 fix behavior tree nodes 2026-03-14 14:20:00 +01:00
Wojtek Figat
f42a9a760a Adjust new Z Axis color in Transform 2026-03-13 23:27:21 +01:00
Wojtek Figat
80767d65ae Fix camera preview placement in editor preview when resizing window 2026-03-13 23:27:03 +01:00
Saas
d3891688f0 fix boxes positioning and remove unused constants 2026-03-13 21:59:53 +01:00
Phantom
f876068086 Fix Text Color Highlighted 2026-03-13 20:09:57 +01:00
Saas
d163064f95 fix hv wheel selector 2026-03-05 20:33:18 +01:00
Saas
64c2d64d84 increase distante for slot into connection feature 2026-03-05 20:14:48 +01:00
Saas
a9c510c296 tweak some group archetype colors 2026-03-05 19:49:29 +01:00
Saas
23c3edcab9 small cleanup 2026-03-04 23:37:33 +01:00
Saas
2c98d80506 fix node auto resizing 2026-03-04 23:15:09 +01:00
Saas
93ca9f1cb0 Merge remote-tracking branch 'upstream/master' into VISjectIsNowMoreVISuallyAppealingAndLegible 2026-03-04 19:12:11 +01:00
Saas
c62b3f7624 improve color picker layout and accept color behaviour
- Color will accept on close dialog or focus loss
- Color will cancel on ESC press
- Increased saved colors count from 8 to 10
- Moved RGB and HSV into tabbed interface
- Moved eyedropper icon
- Removed "Auto Accept Color Picker Change" option since there are no "Cancel" or "OK" button anymore
- Made sure to reset cursor type when the color picker is closed
2026-03-04 17:53:27 +01:00
Saas
66b4c64f98 polish sliders and wheel
- Hide mouse cursor when clicking on wheel or sliders
- Move mouse position to knob position when letting go of slider or wheel
- Show mouse again when letting go of slider or wheel (obviously)
- Provide some visual feedback when the clicks on the wheel or sliders
- Make sliders wider
- Add alpha grid background to alpha slider
2026-03-04 15:17:47 +01:00
Saas
b9b11b3c2a loop Hue value box 2026-03-03 23:49:16 +01:00
Saas
feca1c7994 replace HW with material instead of sprite 2026-03-03 23:44:24 +01:00
Chandler Cox
24a11ac2a8 Add tree view mode for content window. 2026-02-28 12:38:07 -06:00
Saas
2a21141dd4 focus whole graph if no selection 2026-02-09 15:56:37 +01:00
Saas
eac9f67bc1 Merge remote-tracking branch 'upstream/master' into FocusSelectedVJControls 2026-02-09 15:52:27 +01:00
Saas
b19611e3d2 steal right fix from Tryibions PR 2026-02-07 19:58:26 +01:00
Chandler Cox
37e776e407 Add HideInEditor attribute to DirectionGizmo 2026-02-06 21:56:25 -06:00
Saas
3adda3629e don't try to auto connect to comments and particle emitter nodes 2026-02-04 23:29:41 +01:00
Saas
a57fe6c04d add lazy connect feature to visject 2026-02-04 21:31:03 +01:00
Saas
29b043342a don't fall into the trap of moving nodes with NoMove flag again 2026-02-04 14:11:23 +01:00
Saas
8d8bf87c69 move nodes if needed and add undo support 2026-02-04 13:52:37 +01:00
Saas
ab88a6339a add ripping apart connection with reconnect and auto connect when node is dropped over existing connection 2026-02-03 21:36:32 +01:00
Saas
b27e556363 expose material instance 2026-02-02 21:35:40 +01:00
Saas
b0a0c5b66c change selectedSegment to int 2026-02-02 12:52:54 +01:00
Saas
f1d5c0257f improve radial menu control
- Add AllowChangeSelectionWhenOutside to allow changing the selection from the outside the menu
- Expose UpdateAngle() to get *some* kind of controller support
- Expose `SelectedSegment`
- Improve various doc comments
2026-02-02 12:50:24 +01:00
Saas
3279016067 tweak some node spacing 2025-12-20 20:39:54 +01:00
Saas
a92d2b6ca2 add slight background grid 2025-12-20 16:58:35 +01:00
Saas
a98a76f6e5 fix box spacing and (auto calculate) node height
- Work in progress but enough progress to commit because it basically works, it's just a bit ugly
- Node height is now recalculated every time a new element is added to the node
- Introduced `Archetype.UseFixedSize` for special nodes like *Color Gradient* or *Curve* that must rely on hardcoded size for now because the auto sizing does not take elements like the gradient editor or curve editor into account (ideally in the future it will)
- Fixed input and output box spacing (still some 1px offsets that I'm unsure where they are from, could be placebo though)
2025-12-20 00:02:11 +01:00
Saas
21d46dad07 change how connected boxes are drawn 2025-12-18 22:55:56 +01:00
Saas
cfda18ea9d propagate disabled node boxes across reroute nodes 2025-12-18 22:41:55 +01:00
Saas
31b1ceb9f0 increase selection outline width 2025-12-18 19:57:58 +01:00
Saas
d3d67fddcc add node shadow
- rectangular shadow (can be disabled...)
- ... for example for nodes with more complex shapes like reroute
2025-12-18 19:54:31 +01:00
Saas
e272870803 increase max zoom 2025-12-18 19:51:17 +01:00
Saas
b714668f4c improve visject graph readability
- Separate size for boxes (the circle thingie to connect to) and box rows
- Smaller boxes (circle thingie)
- Smaller footer
- Smaller and color header
- Left align header text
- Replace background with solid color
- Adjust node `ArchetypeColor`s (former `FooterColor`) to keep header title readable over colored header
2025-12-17 18:54:37 +01:00
Chandler Cox
1b9f6ed20f Fix large world build. 2025-12-16 18:16:58 -06:00
Chandler Cox
2c5cc7fcc0 Fix double drawing. 2025-12-16 17:32:35 -06:00
Chandler Cox
b9f177d1ab Add ensure capacity to lists. 2025-12-16 17:12:54 -06:00
Chandler Cox
9708601d3b Small cleanup. 2025-12-15 21:46:03 -06:00
Chandler Cox
d671f57952 Change to container control for anchoring. 2025-12-15 21:41:45 -06:00
Chandler Cox
56e6df261d Add hovering and selecting. 2025-12-15 21:22:11 -06:00
Chandler Cox
88b02105b3 Add direction gizmo. 2025-12-15 20:36:35 -06:00
Saas
b5b1d84b3f fix silent crash sometimes occurring when dragging and dropping connections 2025-12-12 22:44:13 +01:00
Saas
206b948ee1 add "Split" for Unpack nodes 2025-11-02 13:36:21 +01:00
Saas
fbad6ba75c add alternative node titles to pack/unpack nodes
Now every pack/ unpack node can be found by searching:
- Make
- Construct
- Compose

-  Break
- Deconstruct
- Decompose
2025-11-02 00:50:07 +01:00
Saas
bf3403449d disable format nodes option when either a) all selected nodes have NoMove or b) clicked on node has NoMove 2025-11-01 18:18:18 +01:00
Saas
d13b98b205 fix Auto Format Nodes to only produce one undo step 2025-11-01 17:55:21 +01:00
Saas
1188150163 prevent node formatting tools from moving nodes with NoMove flag 2025-11-01 17:24:10 +01:00
Saas
e78191cc82 fix error when clearing vj context menu 2025-11-01 17:04:58 +01:00
Saas
ec08a6ca72 Reapply "Merge branch 'NoClearSearchboxesWhenStuffHappens' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-NoClearSearchboxesWhenStuffHappens"
This reverts commit 537d8b57ca.
2025-10-03 20:44:35 +02:00
Saas
7b7d7aaf3d Merge remote-tracking branch 'upstream/master' into NoClearSearchboxesWhenStuffHappens 2025-10-03 20:44:04 +02:00
Saas
2d4843b1f4 fix reloading scripts expanding scene tree
#3701
2025-10-03 20:41:52 +02:00
Saas
7ba01a413f Revert "fix reloading scripts expands all folders"
This reverts commit 2516820e4a.
2025-10-03 18:50:41 +02:00
Saas
2516820e4a fix reloading scripts expands all folders 2025-10-03 18:46:18 +02:00
Saas
1dd96ae9cb add undo to MoveSelectedNodes (do TODO) 2025-09-19 23:04:43 +02:00
Saas
07c1dfc613 add focus selected visject controls functionality 2025-09-19 20:23:47 +02:00
alsed
05f08db66e Fixed a crash when model with no Bones was imported as Skinned Model 2025-09-08 01:28:49 -03:00
alsed
52ee8b3953 better comment for animated prefab and remove unused code in remap blendshapes 2025-09-07 20:40:28 -03:00
alsed
a8768f918e Add more conditions for skeleton import and add blendshapes for prefab recognition 2025-09-07 13:45:21 -03:00
alsed
9a363e2882 Implement prefab detection of skinned models 2025-08-30 08:51:21 -04:00
Zode
4bacceb19f Make comparisons automatically expand if needed so it contains the inputs 2025-06-09 22:49:30 +03:00
Zode
846065007b Make visject inputboxes uniform size of 50 pixels 2025-06-09 22:49:13 +03:00
xxSeys1
d2dba124df add model import option to only create material slots
but not import actual materials
2024-10-09 13:12:48 +02:00
351 changed files with 6939 additions and 2713 deletions

View File

@@ -5,7 +5,7 @@ body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! Please attach any minimal reproduction projects!
Thanks for taking the time to fill out this bug report! Please attach a minimal reproduction project if available!
- type: textarea
id: description-area
attributes:
@@ -17,19 +17,19 @@ body:
id: steps-area
attributes:
label: Steps to reproduce
description: Please provide reproduction steps if possible.
description: Please provide reproduction steps if available.
validations:
required: true
- type: dropdown
id: version
attributes:
label: Version
description: What version of Flax are you running?
description: What version of Flax did you experience the bug in?
options:
- '1.8'
- '1.9'
- '1.10'
- '1.11'
- '1.12'
- master branch
default: 3
validations:

View File

@@ -5,7 +5,7 @@ body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out a feature request!
Thank you for taking the time to submit this feature request!
- type: textarea
id: description-area
attributes:
@@ -17,6 +17,6 @@ body:
id: benefits-area
attributes:
label: Benefits
description: Please provide what benefits this feature would provide to the engine!
description: Please list what benefits this feature would provide to the engine!
validations:
required: true

1
.github/data/bt.sh vendored
View File

@@ -9,6 +9,7 @@ gdb -q --batch \
-ex 'handle SIGUSR1 nostop pass' \
-ex 'handle SIGUSR2 nostop pass' \
-ex 'handle SIGCHLD nostop pass' \
-ex 'handle SIG34 nostop pass' \
-ex 'set print thread-events off' \
-return-child-result \
-ex 'run' \

View File

@@ -4,6 +4,7 @@ on: [push, pull_request]
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: false
DOTNET_ROLL_FORWARD: 'minor'
jobs:
@@ -19,7 +20,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
dotnet-version: 8.0.419
- name: Setup .NET Workload
run: |
dotnet workload install android
@@ -33,4 +34,7 @@ jobs:
git lfs pull
- name: Build
run: |
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=8 -arch=ARM64 -platform=Android -configuration=Release -buildtargets=FlaxGame

View File

@@ -4,6 +4,7 @@ on: [push, pull_request]
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: false
DOTNET_ROLL_FORWARD: 'minor'
jobs:
@@ -19,7 +20,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
dotnet-version: 8.0.419
- name: Print .NET info
run: |
dotnet --info
@@ -30,6 +31,9 @@ jobs:
git lfs pull
- name: Build
run: |
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxEditor
# Game
@@ -44,7 +48,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
dotnet-version: 8.0.419
- name: Print .NET info
run: |
dotnet --info
@@ -55,4 +59,7 @@ jobs:
git lfs pull
- name: Build
run: |
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Windows -configuration=Release -buildtargets=FlaxGame

View File

@@ -7,6 +7,7 @@ on:
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: false
DOTNET_ROLL_FORWARD: 'minor'
GIT_LFS_PULL_OPTIONS: '-c lfs.concurrenttransfers=1 -c lfs.transfer.maxretries=2 -c http.version="HTTP/1.1" -c lfs.activitytimeout=60'
jobs:
@@ -27,13 +28,16 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
dotnet-version: 8.0.419
- name: Print .NET info
run: |
dotnet --info
dotnet workload --info
- name: Build
run: |
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
.\PackageEditor.bat -arch=x64 -platform=Windows -deployOutput=Output -dotnet=8
- name: Upload
uses: actions/upload-artifact@v4
@@ -60,13 +64,16 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
dotnet-version: 8.0.419
- name: Print .NET info
run: |
dotnet --info
dotnet workload --info
- name: Build
run: |
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
.\PackagePlatforms.bat -arch=x64 -platform=Windows -deployOutput=Output -dotnet=8
- name: Upload
uses: actions/upload-artifact@v4

View File

@@ -4,6 +4,7 @@ on: [push, pull_request]
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: false
DOTNET_ROLL_FORWARD: 'minor'
jobs:
@@ -56,7 +57,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
dotnet-version: 8.0.419
- name: Print .NET info
run: |
dotnet --info
@@ -67,6 +68,9 @@ jobs:
git lfs pull
- name: Build
run: |
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
.\GenerateProjectFiles.bat -vs2022 -log -verbose -printSDKs -dotnet=8
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -dotnet=8 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxTestsTarget
dotnet msbuild Source\Tools\Flax.Build.Tests\Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo

View File

@@ -1,4 +1,4 @@
# Redirect to our own Git LFS server
[lfs]
#url="https://gitlab.flaxengine.com/flax/flaxengine.git/info/lfs"
url="https://gitlab.flaxengine.com/flax/flaxengine.git/info/lfs"
locksverify = false

BIN
Content/Editor/HSWheel.flax LFS Normal file

Binary file not shown.

View File

@@ -337,7 +337,6 @@ VertexOutput VS_SplineModel(ModelInput input)
// Apply world position offset per-vertex
#if USE_POSITION_OFFSET
output.Geometry.WorldPosition += material.PositionOffset;
output.Geometry.PrevWorldPosition += material.PositionOffset;
output.Position = PROJECT_POINT(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix);
#endif

Binary file not shown.

View File

@@ -4,7 +4,7 @@
"Major": 1,
"Minor": 12,
"Revision": 0,
"Build": 6908
"Build": 6910
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.",
@@ -14,6 +14,14 @@
"UseCSharp": true,
"UseLargeWorlds": false,
"UseDotNet": true,
"UseSDL": true
"Windows": {
"UseSDL": false,
},
"Mac": {
"UseSDL": false,
},
"Linux": {
"UseSDL": true,
},
}
}
}

View File

@@ -19,7 +19,7 @@ namespace FlaxEditor.Content.GUI
/// <summary>
/// Gets the target node.
/// </summary>
public ContentTreeNode TargetNode { get; }
public ContentFolderTreeNode TargetNode { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ContentNavigationButton"/> class.
@@ -28,7 +28,7 @@ namespace FlaxEditor.Content.GUI
/// <param name="x">The x position.</param>
/// <param name="y">The y position.</param>
/// <param name="height">The height.</param>
public ContentNavigationButton(ContentTreeNode targetNode, float x, float y, float height)
public ContentNavigationButton(ContentFolderTreeNode targetNode, float x, float y, float height)
: base(x, y, height)
{
TargetNode = targetNode;
@@ -147,7 +147,7 @@ namespace FlaxEditor.Content.GUI
ClearItems();
foreach (var child in Target.TargetNode.Children)
{
if (child is ContentTreeNode node)
if (child is ContentFolderTreeNode node)
{
if (node.Folder.VisibleInHierarchy) // Respect the filter set by ContentFilterConfig.Filter(...)
AddItem(node.Folder.ShortName);
@@ -180,7 +180,7 @@ namespace FlaxEditor.Content.GUI
var item = _items[index];
foreach (var child in Target.TargetNode.Children)
{
if (child is ContentTreeNode node && node.Folder.ShortName == item)
if (child is ContentFolderTreeNode node && node.Folder.ShortName == item)
{
Editor.Instance.Windows.ContentWin.Navigate(node);
return;

View File

@@ -59,7 +59,7 @@ namespace FlaxEditor.Content
/// <summary>
/// Gets the content node.
/// </summary>
public ContentTreeNode Node { get; }
public ContentFolderTreeNode Node { get; }
/// <summary>
/// The subitems of this folder.
@@ -72,7 +72,7 @@ namespace FlaxEditor.Content
/// <param name="type">The folder type.</param>
/// <param name="path">The path to the item.</param>
/// <param name="node">The folder parent node.</param>
internal ContentFolder(ContentFolderType type, string path, ContentTreeNode node)
internal ContentFolder(ContentFolderType type, string path, ContentFolderTreeNode node)
: base(path)
{
FolderType = type;
@@ -118,7 +118,7 @@ namespace FlaxEditor.Content
get
{
var hasParentFolder = ParentFolder != null;
var isContentFolder = Node is MainContentTreeNode;
var isContentFolder = Node is MainContentFolderTreeNode;
return hasParentFolder && !isContentFolder;
}
}

View File

@@ -0,0 +1,433 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using FlaxEditor.GUI;
using FlaxEditor.GUI.Drag;
using FlaxEditor.GUI.Tree;
using FlaxEditor.SceneGraph;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Content;
/// <summary>
/// Content folder tree node.
/// </summary>
/// <seealso cref="TreeNode" />
public class ContentFolderTreeNode : TreeNode
{
private DragItems _dragOverItems;
private DragActors _dragActors;
private List<Rectangle> _highlights;
/// <summary>
/// The folder.
/// </summary>
protected ContentFolder _folder;
/// <summary>
/// Whether this node can be deleted.
/// </summary>
public virtual bool CanDelete => true;
/// <summary>
/// Whether this node can be duplicated.
/// </summary>
public virtual bool CanDuplicate => true;
/// <summary>
/// Gets the content folder item.
/// </summary>
public ContentFolder Folder => _folder;
/// <summary>
/// Gets the type of the folder.
/// </summary>
public ContentFolderType FolderType => _folder.FolderType;
/// <summary>
/// Returns true if that folder can import/manage scripts.
/// </summary>
public bool CanHaveScripts => _folder.CanHaveScripts;
/// <summary>
/// Returns true if that folder can import/manage assets.
/// </summary>
/// <returns>True if can contain assets for project, otherwise false</returns>
public bool CanHaveAssets => _folder.CanHaveAssets;
/// <summary>
/// Gets the parent node.
/// </summary>
public ContentFolderTreeNode ParentNode => Parent as ContentFolderTreeNode;
/// <summary>
/// Gets the folder path.
/// </summary>
public string Path => _folder.Path;
/// <summary>
/// Gets the navigation button label.
/// </summary>
public virtual string NavButtonLabel => _folder.ShortName;
/// <summary>
/// Initializes a new instance of the <see cref="ContentFolderTreeNode"/> class.
/// </summary>
/// <param name="parent">The parent node.</param>
/// <param name="path">The folder path.</param>
public ContentFolderTreeNode(ContentFolderTreeNode parent, string path)
: this(parent, parent?.FolderType ?? ContentFolderType.Other, path)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ContentFolderTreeNode"/> class.
/// </summary>
/// <param name="parent">The parent node.</param>
/// <param name="type">The folder type.</param>
/// <param name="path">The folder path.</param>
protected ContentFolderTreeNode(ContentFolderTreeNode parent, ContentFolderType type, string path)
: base(false, Editor.Instance.Icons.FolderClosed32, Editor.Instance.Icons.FolderOpen32)
{
_folder = new ContentFolder(type, path, this);
Text = _folder.ShortName;
if (parent != null)
{
Folder.ParentFolder = parent.Folder;
Parent = parent;
}
IconColor = Color.Transparent; // Hide default icon, we draw scaled icon manually
UpdateCustomArrowRect();
Editor.Instance?.Windows?.ContentWin?.TryAutoExpandContentNode(this);
}
/// <summary>
/// Updates the custom arrow rectangle so it stays aligned with the current layout.
/// </summary>
private void UpdateCustomArrowRect()
{
var contentWindow = Editor.Instance?.Windows?.ContentWin;
var scale = contentWindow != null && contentWindow.IsTreeOnlyMode ? contentWindow.View.ViewScale : 1.0f;
var arrowSize = Mathf.Clamp(12.0f * scale, 10.0f, 20.0f);
var iconSize = Mathf.Clamp(16.0f * scale, 12.0f, 28.0f);
// Use the current text layout, not just cached values.
var textRect = TextRect;
var iconLeft = textRect.Left - iconSize - 2.0f;
var x = Mathf.Max(iconLeft - arrowSize - 2.0f, 0.0f);
var y = Mathf.Max((HeaderHeight - arrowSize) * 0.5f, 0.0f);
CustomArrowRect = new Rectangle(x, y, arrowSize, arrowSize);
}
/// <inheritdoc />
public override void PerformLayout(bool force = false)
{
base.PerformLayout(force);
UpdateCustomArrowRect();
}
/// <summary>
/// Shows the rename popup for the item.
/// </summary>
public void StartRenaming()
{
if (!_folder.CanRename)
return;
// Start renaming the folder
Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(false);
var dialog = RenamePopup.Show(this, TextRect, _folder.ShortName, false);
dialog.Tag = _folder;
dialog.Renamed += popup =>
{
Editor.Instance.Windows.ContentWin.Rename((ContentFolder)popup.Tag, popup.Text);
Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(true);
};
dialog.Closed += popup => { Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(true); };
}
/// <summary>
/// Updates the query search filter.
/// </summary>
/// <param name="filterText">The filter text.</param>
public void UpdateFilter(string filterText)
{
bool noFilter = string.IsNullOrWhiteSpace(filterText);
// Update itself
bool isThisVisible;
if (noFilter)
{
// Clear filter
_highlights?.Clear();
isThisVisible = true;
}
else
{
var text = Text;
if (QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges))
{
// Update highlights
if (_highlights == null)
_highlights = new List<Rectangle>(ranges.Length);
else
_highlights.Clear();
var style = Style.Current;
var font = style.FontSmall;
var textRect = TextRect;
for (int i = 0; i < ranges.Length; i++)
{
var start = font.GetCharPosition(text, ranges[i].StartIndex);
var end = font.GetCharPosition(text, ranges[i].EndIndex);
_highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height));
}
isThisVisible = true;
}
else
{
// Hide
_highlights?.Clear();
isThisVisible = false;
}
}
// Update children
bool isAnyChildVisible = false;
for (int i = 0; i < _children.Count; i++)
{
if (_children[i] is ContentFolderTreeNode child)
{
child.UpdateFilter(filterText);
isAnyChildVisible |= child.Visible;
}
else if (_children[i] is ContentItemTreeNode itemNode)
{
itemNode.UpdateFilter(filterText);
isAnyChildVisible |= itemNode.Visible;
}
}
if (!noFilter)
{
bool isExpanded = isAnyChildVisible;
if (isExpanded)
Expand(true);
else
Collapse(true);
}
Visible = isThisVisible | isAnyChildVisible;
}
/// <inheritdoc />
public override int Compare(Control other)
{
if (other is ContentItemTreeNode)
return -1;
if (other is ContentFolderTreeNode otherNode)
return string.Compare(Text, otherNode.Text, StringComparison.Ordinal);
return base.Compare(other);
}
/// <inheritdoc />
public override void Draw()
{
base.Draw();
// Draw all highlights
if (_highlights != null)
{
var style = Style.Current;
var color = style.ProgressNormal * 0.6f;
for (int i = 0; i < _highlights.Count; i++)
Render2D.FillRectangle(_highlights[i], color);
}
var contentWindow = Editor.Instance.Windows.ContentWin;
var scale = contentWindow != null && contentWindow.IsTreeOnlyMode ? contentWindow.View.ViewScale : 1.0f;
var icon = IsExpanded ? Editor.Instance.Icons.FolderOpen32 : Editor.Instance.Icons.FolderClosed32;
var iconSize = Mathf.Clamp(16.0f * scale, 12.0f, 28.0f);
var iconRect = new Rectangle(TextRect.Left - iconSize - 2.0f, (HeaderHeight - iconSize) * 0.5f, iconSize, iconSize);
Render2D.DrawSprite(icon, iconRect);
}
/// <inheritdoc />
public override void OnDestroy()
{
// Delete folder item
_folder.Dispose();
base.OnDestroy();
}
/// <inheritdoc />
protected override void OnExpandedChanged()
{
base.OnExpandedChanged();
Editor.Instance?.Windows?.ContentWin?.OnContentTreeNodeExpandedChanged(this, IsExpanded);
}
private DragDropEffect GetDragEffect(DragData data)
{
if (_dragActors != null && _dragActors.HasValidDrag)
return DragDropEffect.Move;
if (data is DragDataFiles)
{
if (_folder.CanHaveAssets)
return DragDropEffect.Copy;
}
else
{
if (_dragOverItems != null && _dragOverItems.HasValidDrag)
return DragDropEffect.Move;
}
return DragDropEffect.None;
}
private bool ValidateDragItem(ContentItem item)
{
// Reject itself and any parent
return item != _folder && !item.Find(_folder);
}
private bool ValidateDragActors(ActorNode actor)
{
return actor.CanCreatePrefab && _folder.CanHaveAssets;
}
private void ImportActors(DragActors actors)
{
Select();
foreach (var actorNode in actors.Objects)
{
var actor = actorNode.Actor;
if (actors.Objects.Contains(actorNode.ParentNode as ActorNode))
continue;
Editor.Instance.Prefabs.CreatePrefab(actor, false);
}
Editor.Instance.Windows.ContentWin.RefreshView();
}
/// <inheritdoc />
protected override DragDropEffect OnDragEnterHeader(DragData data)
{
if (data is DragDataFiles)
return _folder.CanHaveAssets ? DragDropEffect.Copy : DragDropEffect.None;
if (_dragActors == null)
_dragActors = new DragActors(ValidateDragActors);
if (_dragActors.OnDragEnter(data))
return DragDropEffect.Move;
if (_dragOverItems == null)
_dragOverItems = new DragItems(ValidateDragItem);
_dragOverItems.OnDragEnter(data);
return GetDragEffect(data);
}
/// <inheritdoc />
protected override DragDropEffect OnDragMoveHeader(DragData data)
{
if (data is DragDataFiles)
return _folder.CanHaveAssets ? DragDropEffect.Copy : DragDropEffect.None;
if (_dragActors != null && _dragActors.HasValidDrag)
return DragDropEffect.Move;
return GetDragEffect(data);
}
/// <inheritdoc />
protected override void OnDragLeaveHeader()
{
_dragOverItems?.OnDragLeave();
_dragActors?.OnDragLeave();
base.OnDragLeaveHeader();
}
/// <inheritdoc />
protected override DragDropEffect OnDragDropHeader(DragData data)
{
var result = DragDropEffect.None;
// Check if drop element or files
if (data is DragDataFiles files)
{
// Import files
Editor.Instance.ContentImporting.Import(files.Files, _folder);
result = DragDropEffect.Copy;
Expand();
}
else if (_dragActors != null && _dragActors.HasValidDrag)
{
ImportActors(_dragActors);
_dragActors.OnDragDrop();
result = DragDropEffect.Move;
Expand();
}
else if (_dragOverItems != null && _dragOverItems.HasValidDrag)
{
// Move items
Editor.Instance.ContentDatabase.Move(_dragOverItems.Objects, _folder);
result = DragDropEffect.Move;
Expand();
}
_dragOverItems?.OnDragDrop();
return result;
}
/// <inheritdoc />
protected override void DoDragDrop()
{
DoDragDrop(DragItems.GetDragData(_folder));
}
/// <inheritdoc />
protected override void OnLongPress()
{
Select();
StartRenaming();
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
if (IsFocused)
{
switch (key)
{
case KeyboardKeys.F2:
StartRenaming();
return true;
case KeyboardKeys.Delete:
if (Folder.Exists && CanDelete)
Editor.Instance.Windows.ContentWin.Delete(Folder);
return true;
}
if (RootWindow.GetKey(KeyboardKeys.Control))
{
switch (key)
{
case KeyboardKeys.D:
if (Folder.Exists && CanDuplicate)
Editor.Instance.Windows.ContentWin.Duplicate(Folder);
return true;
}
}
}
return base.OnKeyDown(key);
}
}

View File

@@ -0,0 +1,224 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using FlaxEditor.Content.GUI;
using FlaxEditor.GUI.Drag;
using FlaxEditor.GUI.Tree;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Content;
/// <summary>
/// Tree node for non-folder content items.
/// </summary>
public sealed class ContentItemTreeNode : TreeNode, IContentItemOwner
{
private List<Rectangle> _highlights;
/// <summary>
/// The content item.
/// </summary>
public ContentItem Item { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ContentItemTreeNode"/> class.
/// </summary>
/// <param name="item">The content item.</param>
public ContentItemTreeNode(ContentItem item)
: base(false, Editor.Instance.Icons.Document128, Editor.Instance.Icons.Document128)
{
Item = item ?? throw new ArgumentNullException(nameof(item));
UpdateDisplayedName();
IconColor = Color.Transparent; // Reserve icon space but draw custom thumbnail.
Item.AddReference(this);
}
private static SpriteHandle GetIcon(ContentItem item)
{
if (item == null)
return SpriteHandle.Invalid;
var icon = item.Thumbnail;
if (!icon.IsValid)
icon = item.DefaultThumbnail;
if (!icon.IsValid)
icon = Editor.Instance.Icons.Document128;
return icon;
}
/// <summary>
/// Updates the query search filter.
/// </summary>
/// <param name="filterText">The filter text.</param>
public void UpdateFilter(string filterText)
{
bool noFilter = string.IsNullOrWhiteSpace(filterText);
bool isVisible;
if (noFilter)
{
_highlights?.Clear();
isVisible = true;
}
else
{
var text = Text;
if (QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges))
{
if (_highlights == null)
_highlights = new List<Rectangle>(ranges.Length);
else
_highlights.Clear();
var style = Style.Current;
var font = style.FontSmall;
var textRect = TextRect;
for (int i = 0; i < ranges.Length; i++)
{
var start = font.GetCharPosition(text, ranges[i].StartIndex);
var end = font.GetCharPosition(text, ranges[i].EndIndex);
_highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height));
}
isVisible = true;
}
else
{
_highlights?.Clear();
isVisible = false;
}
}
Visible = isVisible;
}
/// <inheritdoc />
public override void Draw()
{
base.Draw();
var icon = GetIcon(Item);
if (icon.IsValid)
{
var contentWindow = Editor.Instance.Windows.ContentWin;
var scale = contentWindow != null && contentWindow.IsTreeOnlyMode ? contentWindow.View.ViewScale : 1.0f;
var iconSize = Mathf.Clamp(16.0f * scale, 12.0f, 28.0f);
var textRect = TextRect;
var iconRect = new Rectangle(textRect.Left - iconSize - 2.0f, (HeaderHeight - iconSize) * 0.5f, iconSize, iconSize);
Render2D.DrawSprite(icon, iconRect);
}
if (_highlights != null)
{
var style = Style.Current;
var color = style.ProgressNormal * 0.6f;
for (int i = 0; i < _highlights.Count; i++)
Render2D.FillRectangle(_highlights[i], color);
}
}
/// <inheritdoc />
protected override bool OnMouseDoubleClickHeader(ref Float2 location, MouseButton button)
{
if (button == MouseButton.Left)
{
Editor.Instance.Windows.ContentWin.Open(Item);
return true;
}
return base.OnMouseDoubleClickHeader(ref location, button);
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
if (IsFocused)
{
switch (key)
{
case KeyboardKeys.Return:
Editor.Instance.Windows.ContentWin.Open(Item);
return true;
case KeyboardKeys.F2:
Editor.Instance.Windows.ContentWin.Rename(Item);
return true;
case KeyboardKeys.Delete:
Editor.Instance.Windows.ContentWin.Delete(Item);
return true;
}
}
return base.OnKeyDown(key);
}
/// <inheritdoc />
protected override void DoDragDrop()
{
DoDragDrop(DragItems.GetDragData(Item));
}
/// <inheritdoc />
public override bool OnShowTooltip(out string text, out Float2 location, out Rectangle area)
{
Item.UpdateTooltipText();
TooltipText = Item.TooltipText;
return base.OnShowTooltip(out text, out location, out area);
}
/// <inheritdoc />
void IContentItemOwner.OnItemDeleted(ContentItem item)
{
}
/// <inheritdoc />
void IContentItemOwner.OnItemRenamed(ContentItem item)
{
UpdateDisplayedName();
}
/// <inheritdoc />
void IContentItemOwner.OnItemReimported(ContentItem item)
{
}
/// <inheritdoc />
void IContentItemOwner.OnItemDispose(ContentItem item)
{
}
/// <inheritdoc />
public override int Compare(Control other)
{
if (other is ContentFolderTreeNode)
return 1;
if (other is ContentItemTreeNode otherItem)
return ApplySortOrder(string.Compare(Text, otherItem.Text, StringComparison.InvariantCulture));
return base.Compare(other);
}
/// <inheritdoc />
public override void OnDestroy()
{
Item.RemoveReference(this);
base.OnDestroy();
}
/// <summary>
/// Updates the text of the node.
/// </summary>
public void UpdateDisplayedName()
{
var contentWindow = Editor.Instance?.Windows?.ContentWin;
var showExtensions = contentWindow?.View?.ShowFileExtensions ?? true;
Text = Item.ShowFileExtension || showExtensions ? Item.FileName : Item.ShortName;
}
private static SortType GetSortType()
{
return Editor.Instance?.Windows?.ContentWin?.CurrentSortType ?? SortType.AlphabeticOrder;
}
private static int ApplySortOrder(int result)
{
return GetSortType() == SortType.AlphabeticReverse ? -result : result;
}
}

View File

@@ -1,333 +0,0 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using FlaxEditor.GUI;
using FlaxEditor.GUI.Drag;
using FlaxEditor.GUI.Tree;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Content
{
/// <summary>
/// Content folder tree node.
/// </summary>
/// <seealso cref="TreeNode" />
public class ContentTreeNode : TreeNode
{
private DragItems _dragOverItems;
private List<Rectangle> _highlights;
/// <summary>
/// The folder.
/// </summary>
protected ContentFolder _folder;
/// <summary>
/// Whether this node can be deleted.
/// </summary>
public virtual bool CanDelete => true;
/// <summary>
/// Whether this node can be duplicated.
/// </summary>
public virtual bool CanDuplicate => true;
/// <summary>
/// Gets the content folder item.
/// </summary>
public ContentFolder Folder => _folder;
/// <summary>
/// Gets the type of the folder.
/// </summary>
public ContentFolderType FolderType => _folder.FolderType;
/// <summary>
/// Returns true if that folder can import/manage scripts.
/// </summary>
public bool CanHaveScripts => _folder.CanHaveScripts;
/// <summary>
/// Returns true if that folder can import/manage assets.
/// </summary>
/// <returns>True if can contain assets for project, otherwise false</returns>
public bool CanHaveAssets => _folder.CanHaveAssets;
/// <summary>
/// Gets the parent node.
/// </summary>
public ContentTreeNode ParentNode => Parent as ContentTreeNode;
/// <summary>
/// Gets the folder path.
/// </summary>
public string Path => _folder.Path;
/// <summary>
/// Gets the navigation button label.
/// </summary>
public virtual string NavButtonLabel => _folder.ShortName;
/// <summary>
/// Initializes a new instance of the <see cref="ContentTreeNode"/> class.
/// </summary>
/// <param name="parent">The parent node.</param>
/// <param name="path">The folder path.</param>
public ContentTreeNode(ContentTreeNode parent, string path)
: this(parent, parent?.FolderType ?? ContentFolderType.Other, path)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ContentTreeNode"/> class.
/// </summary>
/// <param name="parent">The parent node.</param>
/// <param name="type">The folder type.</param>
/// <param name="path">The folder path.</param>
protected ContentTreeNode(ContentTreeNode parent, ContentFolderType type, string path)
: base(false, Editor.Instance.Icons.FolderClosed32, Editor.Instance.Icons.FolderOpen32)
{
_folder = new ContentFolder(type, path, this);
Text = _folder.ShortName;
if (parent != null)
{
Folder.ParentFolder = parent.Folder;
Parent = parent;
}
IconColor = Style.Current.Foreground;
}
/// <summary>
/// Shows the rename popup for the item.
/// </summary>
public void StartRenaming()
{
if (!_folder.CanRename)
return;
// Start renaming the folder
Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(false);
var dialog = RenamePopup.Show(this, TextRect, _folder.ShortName, false);
dialog.Tag = _folder;
dialog.Renamed += popup =>
{
Editor.Instance.Windows.ContentWin.Rename((ContentFolder)popup.Tag, popup.Text);
Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(true);
};
dialog.Closed += popup => { Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(true); };
}
/// <summary>
/// Updates the query search filter.
/// </summary>
/// <param name="filterText">The filter text.</param>
public void UpdateFilter(string filterText)
{
bool noFilter = string.IsNullOrWhiteSpace(filterText);
// Update itself
bool isThisVisible;
if (noFilter)
{
// Clear filter
_highlights?.Clear();
isThisVisible = true;
}
else
{
var text = Text;
if (QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges))
{
// Update highlights
if (_highlights == null)
_highlights = new List<Rectangle>(ranges.Length);
else
_highlights.Clear();
var style = Style.Current;
var font = style.FontSmall;
var textRect = TextRect;
for (int i = 0; i < ranges.Length; i++)
{
var start = font.GetCharPosition(text, ranges[i].StartIndex);
var end = font.GetCharPosition(text, ranges[i].EndIndex);
_highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height));
}
isThisVisible = true;
}
else
{
// Hide
_highlights?.Clear();
isThisVisible = false;
}
}
// Update children
bool isAnyChildVisible = false;
for (int i = 0; i < _children.Count; i++)
{
if (_children[i] is ContentTreeNode child)
{
child.UpdateFilter(filterText);
isAnyChildVisible |= child.Visible;
}
}
bool isExpanded = isAnyChildVisible;
if (isExpanded)
{
Expand(true);
}
else
{
Collapse(true);
}
Visible = isThisVisible | isAnyChildVisible;
}
/// <inheritdoc />
public override void Draw()
{
base.Draw();
// Draw all highlights
if (_highlights != null)
{
var style = Style.Current;
var color = style.ProgressNormal * 0.6f;
for (int i = 0; i < _highlights.Count; i++)
Render2D.FillRectangle(_highlights[i], color);
}
}
/// <inheritdoc />
public override void OnDestroy()
{
// Delete folder item
_folder.Dispose();
base.OnDestroy();
}
private DragDropEffect GetDragEffect(DragData data)
{
if (data is DragDataFiles)
{
if (_folder.CanHaveAssets)
return DragDropEffect.Copy;
}
else
{
if (_dragOverItems.HasValidDrag)
return DragDropEffect.Move;
}
return DragDropEffect.None;
}
private bool ValidateDragItem(ContentItem item)
{
// Reject itself and any parent
return item != _folder && !item.Find(_folder);
}
/// <inheritdoc />
protected override DragDropEffect OnDragEnterHeader(DragData data)
{
if (_dragOverItems == null)
_dragOverItems = new DragItems(ValidateDragItem);
_dragOverItems.OnDragEnter(data);
return GetDragEffect(data);
}
/// <inheritdoc />
protected override DragDropEffect OnDragMoveHeader(DragData data)
{
return GetDragEffect(data);
}
/// <inheritdoc />
protected override void OnDragLeaveHeader()
{
_dragOverItems.OnDragLeave();
base.OnDragLeaveHeader();
}
/// <inheritdoc />
protected override DragDropEffect OnDragDropHeader(DragData data)
{
var result = DragDropEffect.None;
// Check if drop element or files
if (data is DragDataFiles files)
{
// Import files
Editor.Instance.ContentImporting.Import(files.Files, _folder);
result = DragDropEffect.Copy;
Expand();
}
else if (_dragOverItems.HasValidDrag)
{
// Move items
Editor.Instance.ContentDatabase.Move(_dragOverItems.Objects, _folder);
result = DragDropEffect.Move;
Expand();
}
_dragOverItems.OnDragDrop();
return result;
}
/// <inheritdoc />
protected override void DoDragDrop()
{
DoDragDrop(DragItems.GetDragData(_folder));
}
/// <inheritdoc />
protected override void OnLongPress()
{
Select();
StartRenaming();
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
if (IsFocused)
{
switch (key)
{
case KeyboardKeys.F2:
StartRenaming();
return true;
case KeyboardKeys.Delete:
if (Folder.Exists && CanDelete)
Editor.Instance.Windows.ContentWin.Delete(Folder);
return true;
}
if (RootWindow.GetKey(KeyboardKeys.Control))
{
switch (key)
{
case KeyboardKeys.D:
if (Folder.Exists && CanDuplicate)
Editor.Instance.Windows.ContentWin.Duplicate(Folder);
return true;
}
}
}
return base.OnKeyDown(key);
}
}
}

View File

@@ -7,8 +7,8 @@ namespace FlaxEditor.Content
/// <summary>
/// Content tree node used for main directories.
/// </summary>
/// <seealso cref="FlaxEditor.Content.ContentTreeNode" />
public class MainContentTreeNode : ContentTreeNode
/// <seealso cref="ContentFolderTreeNode" />
public class MainContentFolderTreeNode : ContentFolderTreeNode
{
private FileSystemWatcher _watcher;
@@ -19,12 +19,12 @@ namespace FlaxEditor.Content
public override bool CanDuplicate => false;
/// <summary>
/// Initializes a new instance of the <see cref="MainContentTreeNode"/> class.
/// Initializes a new instance of the <see cref="MainContentFolderTreeNode"/> class.
/// </summary>
/// <param name="parent">The parent project.</param>
/// <param name="type">The folder type.</param>
/// <param name="path">The folder path.</param>
public MainContentTreeNode(ProjectTreeNode parent, ContentFolderType type, string path)
public MainContentFolderTreeNode(ProjectFolderTreeNode parent, ContentFolderType type, string path)
: base(parent, type, path)
{
_watcher = new FileSystemWatcher(path)

View File

@@ -1,5 +1,6 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using FlaxEngine.GUI;
namespace FlaxEditor.Content
@@ -7,8 +8,8 @@ namespace FlaxEditor.Content
/// <summary>
/// Root tree node for the project workspace.
/// </summary>
/// <seealso cref="FlaxEditor.Content.ContentTreeNode" />
public sealed class ProjectTreeNode : ContentTreeNode
/// <seealso cref="ContentFolderTreeNode" />
public sealed class ProjectFolderTreeNode : ContentFolderTreeNode
{
/// <summary>
/// The project/
@@ -18,18 +19,18 @@ namespace FlaxEditor.Content
/// <summary>
/// The project content directory.
/// </summary>
public MainContentTreeNode Content;
public MainContentFolderTreeNode Content;
/// <summary>
/// The project source code directory.
/// </summary>
public MainContentTreeNode Source;
public MainContentFolderTreeNode Source;
/// <summary>
/// Initializes a new instance of the <see cref="ProjectTreeNode"/> class.
/// Initializes a new instance of the <see cref="ProjectFolderTreeNode"/> class.
/// </summary>
/// <param name="project">The project.</param>
public ProjectTreeNode(ProjectInfo project)
public ProjectFolderTreeNode(ProjectInfo project)
: base(null, project.ProjectFolderPath)
{
Project = project;
@@ -48,9 +49,29 @@ namespace FlaxEditor.Content
/// <inheritdoc />
public override int Compare(Control other)
{
// Move the main game project to the top
if (Project.Name == Editor.Instance.GameProject.Name)
return -1;
if (other is ProjectFolderTreeNode otherProject)
{
var gameProject = Editor.Instance.GameProject;
var engineProject = Editor.Instance.EngineProject;
bool isGame = Project == gameProject;
bool isEngine = Project == engineProject;
bool otherIsGame = otherProject.Project == gameProject;
bool otherIsEngine = otherProject.Project == engineProject;
// Main game project at the top
if (isGame && !otherIsGame)
return -1;
if (!isGame && otherIsGame)
return 1;
// Engine project at the bottom (when distinct)
if (isEngine && !otherIsEngine)
return 1;
if (!isEngine && otherIsEngine)
return -1;
return string.CompareOrdinal(Project.Name, otherProject.Project.Name);
}
return base.Compare(other);
}
}

View File

@@ -5,13 +5,13 @@ namespace FlaxEditor.Content
/// <summary>
/// Root tree node for the content workspace.
/// </summary>
/// <seealso cref="FlaxEditor.Content.ContentTreeNode" />
public sealed class RootContentTreeNode : ContentTreeNode
/// <seealso cref="ContentFolderTreeNode" />
public sealed class RootContentFolderTreeNode : ContentFolderTreeNode
{
/// <summary>
/// Initializes a new instance of the <see cref="RootContentTreeNode"/> class.
/// Initializes a new instance of the <see cref="RootContentFolderTreeNode"/> class.
/// </summary>
public RootContentTreeNode()
public RootContentFolderTreeNode()
: base(null, string.Empty)
{
}

View File

@@ -535,7 +535,8 @@ bool GameCooker::Build(BuildPlatform platform, BuildConfiguration configuration,
{
Function<int32()> f;
f.Bind(ThreadFunction);
const auto thread = ThreadSpawner::Start(f, GameCookerServiceInstance.Name, ThreadPriority::Highest);
uint32 stackSize = 4 * 1024 * 1024; // Larger stack
const auto thread = ThreadSpawner::Start(f, GameCookerServiceInstance.Name, ThreadPriority::Highest, stackSize);
if (thread == nullptr)
{
GameCookerImpl::IsRunning = false;

View File

@@ -49,7 +49,7 @@ ArchitectureType WebPlatformTools::GetArchitecture() const
DotNetAOTModes WebPlatformTools::UseAOT() const
{
return DotNetAOTModes::MonoAOTDynamic;//MonoAOTStatic;// DotNetAOTModes::None;//DotNetAOTModes::MonoAOTStatic;//DotNetAOTModes::NoDotnet;
return DotNetAOTModes::NoDotnet;
}
PixelFormat WebPlatformTools::GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format)
@@ -166,9 +166,8 @@ bool WebPlatformTools::OnPostProcess(CookingData& data)
// Move .wasm assemblies into the data files in order for dlopen to work (blocking)
{
Array<String> files, files2;
Array<String> files;
FileSystem::DirectoryGetFiles(files, data.OriginalOutputPath, TEXT("*.wasm"), DirectorySearchOption::AllDirectories);
FileSystem::DirectoryGetFiles(files, data.OriginalOutputPath, TEXT("*.so"), DirectorySearchOption::AllDirectories);
StringView gameWasm = StringUtils::GetFileNameWithoutExtension(gameJs);
for (const String& file : files)
{
@@ -176,12 +175,6 @@ bool WebPlatformTools::OnPostProcess(CookingData& data)
continue; // Skip the main game module
FileSystem::MoveFile(data.DataOutputPath / StringUtils::GetFileName(file), file, true);
}
for (const String& file : files2)
{
if (StringUtils::GetFileNameWithoutExtension(file) == gameWasm)
continue; // Skip the main game module
FileSystem::MoveFile(data.DataOutputPath / StringUtils::GetFileName(file), file, true);
}
}
// Pack data files into a single file using Emscripten's file_packager tool
@@ -212,7 +205,15 @@ bool WebPlatformTools::OnPostProcess(CookingData& data)
FileSystem::CopyFile(dstIcon, platformDataPath / TEXT("favicon.ico"));
}
// TODO: customizable HTML templates
// Copy custom HTMl template
auto customHtml = platformSettings->CustomHtml.TrimTrailing();
if (customHtml.HasChars())
{
FileSystem::CopyFile(data.OriginalOutputPath / TEXT("FlaxGame.html"), customHtml);
}
// Rename game website main HTML file to match the most common name used by web servers (index.html)
FileSystem::MoveFile(data.OriginalOutputPath / TEXT("index.html"), data.OriginalOutputPath / TEXT("FlaxGame.html"), true);
// Insert packaged file system with game data
{
@@ -261,8 +262,6 @@ bool WebPlatformTools::OnPostProcess(CookingData& data)
return false;
GameCooker::PackageFiles();
// TODO: minify/compress output JS files (in Release builds)
LOG(Info, "Output website size: {0} MB", FileSystem::GetDirectorySize(data.OriginalOutputPath) / 1024 / 1024);
return false;

View File

@@ -37,8 +37,8 @@ void PrecompileAssembliesStep::OnBuildStarted(CookingData& data)
if (cachedData != aotModeCacheValue)
{
LOG(Info, "AOT cache invalidation");
//FileSystem::DeleteDirectory(data.ManagedCodeOutputPath); // Remove AOT cache
//FileSystem::DeleteDirectory(data.DataOutputPath / TEXT("Dotnet")); // Remove deployed Dotnet libs (be sure to remove any leftovers from previous build)
FileSystem::DeleteDirectory(data.ManagedCodeOutputPath); // Remove AOT cache
FileSystem::DeleteDirectory(data.DataOutputPath / TEXT("Dotnet")); // Remove deployed Dotnet libs (be sure to remove any leftovers from previous build)
}
}
if (!FileSystem::DirectoryExists(data.ManagedCodeOutputPath))

View File

@@ -52,6 +52,7 @@ namespace FlaxEditor.CustomEditors
private readonly List<CustomEditor> _children = new List<CustomEditor>();
private ValueContainer _values;
private bool _isSetBlocked;
private bool _isRebuilding;
private bool _skipChildrenRefresh;
private bool _hasValueDirty;
private bool _rebuildOnRefresh;
@@ -178,7 +179,7 @@ namespace FlaxEditor.CustomEditors
public void RebuildLayout()
{
// Skip rebuilding during init
if (CurrentCustomEditor == this)
if (CurrentCustomEditor == this || _isRebuilding)
return;
// Special case for root objects to run normal layout build
@@ -197,6 +198,7 @@ namespace FlaxEditor.CustomEditors
_parent?.RebuildLayout();
return;
}
_isRebuilding = true;
var control = layout.ContainerControl;
var parent = _parent;
var parentScrollV = (_presenter?.Panel.Parent as Panel)?.VScrollBar?.Value ?? -1;
@@ -216,6 +218,7 @@ namespace FlaxEditor.CustomEditors
// Restore scroll value
if (parentScrollV > -1 && _presenter != null && _presenter.Panel.Parent is Panel panel && panel.VScrollBar != null)
panel.VScrollBar.Value = parentScrollV;
_isRebuilding = false;
}
/// <summary>

View File

@@ -55,9 +55,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
// TODO: consider editing more than one instance of the same prefab asset at once
var prefab = FlaxEngine.Content.LoadAsync<Prefab>(actor.PrefabID);
// TODO: don't stall here?
if (prefab && !prefab.WaitForLoaded())
var prefab = FlaxEngine.Content.Load<Prefab>(actor.PrefabID);
if (prefab)
{
var prefabObjectId = actor.PrefabObjectID;
var prefabInstance = prefab.GetDefaultInstance(ref prefabObjectId);
@@ -203,7 +202,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
//Presenter.BuildLayoutOnUpdate();
// Better way is to just update the reference value using the new default instance of the prefab, created after changes apply
if (Values != null && prefab && !prefab.WaitForLoaded())
if (Values != null && (Actor)Values[0] && prefab && !prefab.WaitForLoaded())
{
var actor = (Actor)Values[0];
var prefabObjectId = actor.PrefabObjectID;

View File

@@ -370,7 +370,7 @@ namespace FlaxEditor.CustomEditors.Editors
SetValue(Editor.Instance.ContentDatabase.Find(Utilities.Utils.ToPathAbsolute(path)));
else if (value is Asset)
SetValue(FlaxEngine.Content.LoadAsync(path));
else if (value is string)
else if (value is string || Values.Type == typeof(string))
SetValue(path);
}

View File

@@ -61,6 +61,7 @@ public class Editor : EditorModule
options.PrivateDependencies.Add("Renderer");
options.PrivateDependencies.Add("TextureTool");
options.PrivateDependencies.Add("Particles");
options.PrivateDependencies.Add("Terrain");
var platformToolsRoot = Path.Combine(FolderPath, "Cooker", "Platform");
var platformToolsRootExternal = Path.Combine(Globals.EngineRoot, "Source", "Platforms");

View File

@@ -654,7 +654,7 @@ Window* Editor::CreateMainWindow()
PROFILE_MEM(Editor);
Window* window = Managed->GetMainWindow();
#if PLATFORM_LINUX
#if PLATFORM_LINUX || PLATFORM_MAC
// Set window icon
const String iconPath = Globals::BinariesFolder / TEXT("Logo.png");
if (FileSystem::FileExists(iconPath))

View File

@@ -69,6 +69,11 @@ namespace FlaxEditor
/// </summary>
public static string WindowIcon = "Editor/EditorIcon";
/// <summary>
/// The material used for the HS color wheel.
/// </summary>
public static string HSWheelMaterial = "Editor/HSWheel";
/// <summary>
/// The window icons font.
/// </summary>

View File

@@ -1,10 +1,11 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using FlaxEditor.GUI.Input;
using FlaxEditor.GUI.Tabs;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
using System.Collections.Generic;
namespace FlaxEditor.GUI.Dialogs
{
@@ -25,15 +26,16 @@ namespace FlaxEditor.GUI.Dialogs
/// <seealso cref="FlaxEditor.GUI.Dialogs.Dialog" />
public class ColorPickerDialog : Dialog, IColorPickerDialog
{
private const float ButtonsWidth = 60.0f;
private const float PickerMargin = 6.0f;
private const float EyedropperMargin = 8.0f;
private const float RGBAMargin = 12.0f;
private const float HSVMargin = 0.0f;
private const float ChannelsMargin = 4.0f;
private const float ChannelTextWidth = 12.0f;
private const float SavedColorButtonWidth = 20.0f;
private const float SavedColorButtonHeight = 20.0f;
private const float TabHeight = 20;
private const float ValueBoxesWidth = 100.0f;
private const float HSVRGBTextWidth = 15.0f;
private const float ColorPreviewHeight = 50.0f;
private const int SavedColorsAmount = 10;
private Color _initialValue;
private Color _value;
@@ -46,16 +48,19 @@ namespace FlaxEditor.GUI.Dialogs
private ColorValueBox.ColorPickerClosedEvent _onClosed;
private ColorSelectorWithSliders _cSelector;
private Tabs.Tabs _hsvRGBTabs;
private Tab _RGBTab;
private Panel _rgbPanel;
private FloatValueBox _cRed;
private FloatValueBox _cGreen;
private FloatValueBox _cBlue;
private FloatValueBox _cAlpha;
private Tab _hsvTab;
private Panel _hsvPanel;
private FloatValueBox _cHue;
private FloatValueBox _cSaturation;
private FloatValueBox _cValue;
private TextBox _cHex;
private Button _cCancel;
private Button _cOK;
private FloatValueBox _cAlpha;
private Button _cEyedropper;
private Button _cLinearSRGB;
@@ -127,97 +132,110 @@ namespace FlaxEditor.GUI.Dialogs
_savedColors = JsonSerializer.Deserialize<List<Color>>(savedColors);
// Selector
_cSelector = new ColorSelectorWithSliders(180, 18)
_cSelector = new ColorSelectorWithSliders(180, 21)
{
Location = new Float2(PickerMargin, PickerMargin),
Parent = this
};
_cSelector.ColorChanged += x => SelectedColor = x;
// Red
_cRed = new FloatValueBox(0, _cSelector.Right + PickerMargin + RGBAMargin + ChannelTextWidth, PickerMargin, 100, 0, float.MaxValue, 0.001f)
_hsvRGBTabs = new Tabs.Tabs
{
Parent = this
Location = new Float2(_cSelector.Right + 30.0f, PickerMargin),
TabsTextHorizontalAlignment = TextAlignment.Center,
Width = ValueBoxesWidth + HSVRGBTextWidth * 2.0f + ChannelsMargin * 2.0f,
Height = (FloatValueBox.DefaultHeight + ChannelsMargin) * 4 + ChannelsMargin,
Parent = this,
};
_hsvRGBTabs.TabsSize = new Float2(_hsvRGBTabs.Width * 0.5f, TabHeight);
_hsvRGBTabs.SelectedTabChanged += SelectedTabChanged;
// RGB Tab
_RGBTab = _hsvRGBTabs.AddTab(new Tab("RGB"));
_rgbPanel = new Panel(ScrollBars.Vertical)
{
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
Parent = _RGBTab,
};
// HSV Tab
_hsvTab = _hsvRGBTabs.AddTab(new Tab("HSV"));
_hsvPanel = new Panel(ScrollBars.Vertical)
{
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
Parent = _hsvTab,
};
// Red
_cRed = new FloatValueBox(0, HSVRGBTextWidth + ChannelsMargin, PickerMargin, ValueBoxesWidth, 0, float.MaxValue, 0.001f)
{
Parent = _rgbPanel,
};
_cRed.ValueChanged += OnRGBAChanged;
// Green
_cGreen = new FloatValueBox(0, _cRed.X, _cRed.Bottom + ChannelsMargin, _cRed.Width, 0, float.MaxValue, 0.001f)
{
Parent = this
Parent = _rgbPanel,
};
_cGreen.ValueChanged += OnRGBAChanged;
// Blue
_cBlue = new FloatValueBox(0, _cRed.X, _cGreen.Bottom + ChannelsMargin, _cRed.Width, 0, float.MaxValue, 0.001f)
{
Parent = this
Parent = _rgbPanel,
};
_cBlue.ValueChanged += OnRGBAChanged;
// Alpha
_cAlpha = new FloatValueBox(0, _cRed.X, _cBlue.Bottom + ChannelsMargin, _cRed.Width, 0, float.MaxValue, 0.001f)
{
Parent = this
};
_cAlpha.ValueChanged += OnRGBAChanged;
// Hue
_cHue = new FloatValueBox(0, PickerMargin + HSVMargin + ChannelTextWidth, _cSelector.Bottom + PickerMargin, 100, 0, 360)
_cHue = new FloatValueBox(0, HSVRGBTextWidth + ChannelsMargin, PickerMargin, ValueBoxesWidth)
{
Parent = this
Parent = _hsvPanel,
Category = Utils.ValueCategory.Angle,
};
_cHue.ValueChanged += OnHSVChanged;
// Saturation
_cSaturation = new FloatValueBox(0, _cHue.X, _cHue.Bottom + ChannelsMargin, _cHue.Width, 0, 100.0f, 0.1f)
{
Parent = this
Parent = _hsvPanel,
};
_cSaturation.ValueChanged += OnHSVChanged;
// Value
_cValue = new FloatValueBox(0, _cHue.X, _cSaturation.Bottom + ChannelsMargin, _cHue.Width, 0, float.MaxValue, 0.1f)
{
Parent = this
Parent = _hsvPanel,
};
_cValue.ValueChanged += OnHSVChanged;
// Set valid dialog size based on UI content
_dialogSize = Size = new Float2(_cRed.Right + PickerMargin, 300);
// Alpha
_cAlpha = new FloatValueBox(0, _hsvRGBTabs.Left + HSVRGBTextWidth + ChannelsMargin, _hsvRGBTabs.Bottom + ChannelsMargin * 4.0f, ValueBoxesWidth, 0, float.MaxValue, 0.001f)
{
Parent = this,
};
_cAlpha.ValueChanged += OnRGBAChanged;
// Hex
const float hexTextBoxWidth = 80;
_cHex = new TextBox(false, Width - hexTextBoxWidth - PickerMargin, _cSelector.Bottom + PickerMargin, hexTextBoxWidth)
_cHex = new TextBox(false, _hsvRGBTabs.Left + HSVRGBTextWidth + ChannelsMargin, _cAlpha.Bottom + ChannelsMargin * 2.0f, ValueBoxesWidth)
{
Parent = this
Parent = this,
};
_cHex.EditEnd += OnHexChanged;
// Cancel
_cCancel = new Button(Width - ButtonsWidth - PickerMargin, Height - Button.DefaultHeight - PickerMargin, ButtonsWidth)
{
Text = "Cancel",
Parent = this
};
_cCancel.Clicked += OnCancel;
// OK
_cOK = new Button(_cCancel.Left - ButtonsWidth - PickerMargin, _cCancel.Y, ButtonsWidth)
{
Text = "Ok",
Parent = this
};
_cOK.Clicked += OnSubmit;
// Set valid dialog size based on UI content
_dialogSize = Size = new Float2(_hsvRGBTabs.Right + PickerMargin, _cHex.Bottom + 40.0f + ColorPreviewHeight + PickerMargin);
// Create saved color buttons
CreateAllSaveButtons();
CreateAllSavedColorsButtons();
// Eyedropper button
var style = Style.Current;
_cEyedropper = new Button(_cOK.X - EyedropperMargin, _cHex.Bottom + PickerMargin)
_cEyedropper = new Button(_cSelector.BottomLeft.X, _cSelector.BottomLeft.Y - 25.0f, 25.0f, 25.0f)
{
TooltipText = "Eyedropper tool to pick a color directly from the screen",
TooltipText = "Eyedropper tool to pick a color directly from the screen.",
BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Search32),
BackgroundColor = style.Foreground,
BackgroundColorHighlighted = style.Foreground.RGBMultiplied(0.9f),
@@ -226,14 +244,11 @@ namespace FlaxEditor.GUI.Dialogs
Parent = this,
};
_cEyedropper.Clicked += OnEyedropStart;
_cEyedropper.Height = (_cValue.Bottom - _cEyedropper.Y) * 0.5f;
_cEyedropper.Width = _cEyedropper.Height;
_cEyedropper.X -= _cEyedropper.Width;
// Linear/sRGB toggle button
_cLinearSRGB = new Button(_cOK.X - EyedropperMargin, _cHex.Bottom + PickerMargin)
_cLinearSRGB = new Button(_cSelector.X, _cHex.Bottom + PickerMargin)
{
TooltipText = "Toggles between color preview in Linear and sRGB",
TooltipText = "Toggles between color preview in Linear and sRGB.",
BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.SplineAligned64),
BackgroundColor = _cEyedropper.BackgroundColor,
BackgroundColorHighlighted = _cEyedropper.BackgroundColorHighlighted,
@@ -261,12 +276,10 @@ namespace FlaxEditor.GUI.Dialogs
foreach (var color in _savedColors)
{
if (color == _value)
{
return;
}
}
// Set color of button to current value;
// Set color of button to current value
button.BackgroundColor = _value;
button.BackgroundColorHighlighted = _value;
button.BackgroundColorSelected = _value.RGBMultiplied(0.8f);
@@ -278,10 +291,10 @@ namespace FlaxEditor.GUI.Dialogs
var savedColors = JsonSerializer.Serialize(_savedColors, typeof(List<Color>));
Editor.Instance.ProjectCache.SetCustomData("ColorPickerSavedColors", savedColors);
// create new + button
if (_savedColorButtons.Count < 8)
// Create new + button
if (_savedColorButtons.Count < SavedColorsAmount)
{
var savedColorButton = new Button(PickerMargin * (_savedColorButtons.Count + 1) + SavedColorButtonWidth * _savedColorButtons.Count, Height - SavedColorButtonHeight - PickerMargin, SavedColorButtonWidth, SavedColorButtonHeight)
var savedColorButton = new Button(PickerMargin * (_savedColorButtons.Count + 1) + SavedColorButtonWidth * _savedColorButtons.Count, _cHex.Bottom + 40.0f + ColorPreviewHeight * 0.5f, SavedColorButtonWidth, SavedColorButtonHeight)
{
Text = "+",
Parent = this,
@@ -298,11 +311,32 @@ namespace FlaxEditor.GUI.Dialogs
}
}
private void SelectedTabChanged(Tabs.Tabs tabs)
{
if (_rgbPanel == null || _hsvPanel == null)
return;
switch (tabs.SelectedTabIndex)
{
// RGB
case 0:
_rgbPanel.Visible = true;
_hsvPanel.Visible = false;
break;
// HSV
case 1:
_rgbPanel.Visible = false;
_hsvPanel.Visible = true;
break;
}
}
private void OnColorPicked(Color32 colorPicked)
{
if (_activeEyedropper)
{
_activeEyedropper = false;
_cEyedropper.BackgroundColor = _cEyedropper.BackgroundColorHighlighted = Style.Current.Foreground;
if (colorPicked != Color.Transparent)
{
Color color = colorPicked;
@@ -317,6 +351,7 @@ namespace FlaxEditor.GUI.Dialogs
private void OnEyedropStart()
{
_activeEyedropper = true;
_cEyedropper.BackgroundColor = _cEyedropper.BackgroundColorHighlighted = Style.Current.BackgroundHighlighted;
Platform.PickScreenColor();
Platform.PickScreenColorDone += OnColorPicked;
}
@@ -334,6 +369,7 @@ namespace FlaxEditor.GUI.Dialogs
if (_disableEvents)
return;
_cHue.Value = Mathf.Wrap(_cHue.Value, 0f, 360f);
SelectedColor = Color.FromHSV(_cHue.Value, _cSaturation.Value / 100.0f, _cValue.Value / 100.0f, _cAlpha.Value);
}
@@ -374,64 +410,76 @@ namespace FlaxEditor.GUI.Dialogs
base.Draw();
// RGBA
var rgbaR = new Rectangle(_cRed.Left - ChannelTextWidth, _cRed.Y, 10000, _cRed.Height);
Render2D.DrawText(style.FontMedium, "R", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center);
rgbaR.Location.Y = _cGreen.Y;
Render2D.DrawText(style.FontMedium, "G", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center);
rgbaR.Location.Y = _cBlue.Y;
Render2D.DrawText(style.FontMedium, "B", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center);
rgbaR.Location.Y = _cAlpha.Y;
Render2D.DrawText(style.FontMedium, "A", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center);
switch (_hsvRGBTabs.SelectedTabIndex)
{
// RGB
case 0:
var rgbRect = new Rectangle(_hsvRGBTabs.Left + PickerMargin, _hsvRGBTabs.Top + TabHeight + PickerMargin, 10000, _cRed.Height);
Render2D.DrawText(style.FontMedium, "R", rgbRect, textColor, TextAlignment.Near, TextAlignment.Center);
rgbRect.Location.Y += _cRed.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "G", rgbRect, textColor, TextAlignment.Near, TextAlignment.Center);
rgbRect.Location.Y += _cRed.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "B", rgbRect, textColor, TextAlignment.Near, TextAlignment.Center);
break;
// HSV
case 1:
// Left
var hsvLeftRect = new Rectangle(_hsvRGBTabs.Left + PickerMargin, _hsvRGBTabs.Top + TabHeight + PickerMargin, 10000, _cHue.Height);
Render2D.DrawText(style.FontMedium, "H", hsvLeftRect, textColor, TextAlignment.Near, TextAlignment.Center);
hsvLeftRect.Location.Y += _cHue.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "S", hsvLeftRect, textColor, TextAlignment.Near, TextAlignment.Center);
hsvLeftRect.Location.Y += _cHue.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "V", hsvLeftRect, textColor, TextAlignment.Near, TextAlignment.Center);
// HSV left
var hsvHl = new Rectangle(_cHue.Left - ChannelTextWidth, _cHue.Y, 10000, _cHue.Height);
Render2D.DrawText(style.FontMedium, "H", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center);
hsvHl.Location.Y = _cSaturation.Y;
Render2D.DrawText(style.FontMedium, "S", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center);
hsvHl.Location.Y = _cValue.Y;
Render2D.DrawText(style.FontMedium, "V", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center);
// Right
var hsvRightRect = new Rectangle(_hsvRGBTabs.Right - HSVRGBTextWidth, _hsvRGBTabs.Top + TabHeight + PickerMargin, ChannelTextWidth, _cHue.Height);
Render2D.DrawText(style.FontMedium, "°", hsvRightRect, textColor, TextAlignment.Near, TextAlignment.Center);
hsvRightRect.Location.Y += _cHue.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "%", hsvRightRect, textColor, TextAlignment.Near, TextAlignment.Center);
hsvRightRect.Location.Y += _cHue.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "%", hsvRightRect, textColor, TextAlignment.Near, TextAlignment.Center);
break;
}
// HSV right
var hsvHr = new Rectangle(_cHue.Right + 2, _cHue.Y, 10000, _cHue.Height);
Render2D.DrawText(style.FontMedium, "°", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center);
hsvHr.Location.Y = _cSaturation.Y;
Render2D.DrawText(style.FontMedium, "%", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center);
hsvHr.Location.Y = _cValue.Y;
Render2D.DrawText(style.FontMedium, "%", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center);
// A
var alphaHexRect = new Rectangle(_hsvRGBTabs.Left + PickerMargin, _cAlpha.Top, ChannelTextWidth, _cAlpha.Height);
Render2D.DrawText(style.FontMedium, "A", alphaHexRect, textColor, TextAlignment.Near, TextAlignment.Center);
// Hex
var hex = new Rectangle(_cHex.Left - 26, _cHex.Y, 10000, _cHex.Height);
Render2D.DrawText(style.FontMedium, "Hex", hex, textColor, TextAlignment.Near, TextAlignment.Center);
alphaHexRect.Y += _cAlpha.Height + ChannelsMargin * 2.0f;
alphaHexRect.X -= 5.0f; // "Hex" is two characters wider than the other labels so we need to adjust for that
Render2D.DrawText(style.FontMedium, "Hex", alphaHexRect, textColor, TextAlignment.Far, TextAlignment.Center);
// Color difference
var newRect = new Rectangle(_cOK.X - 3, _cHex.Bottom + PickerMargin, 130, 0);
newRect.Size.Y = 50;
Render2D.FillRectangle(newRect, Color.White);
var smallRectSize = 10;
var numHor = Mathf.FloorToInt(newRect.Width / smallRectSize);
var numVer = Mathf.FloorToInt(newRect.Height / smallRectSize);
var differenceRect = new Rectangle(_hsvRGBTabs.Left, _cHex.Bottom + 40.0f, _hsvRGBTabs.Width, ColorPreviewHeight);
// Draw checkerboard for background of color to help with transparency
Render2D.FillRectangle(differenceRect, Color.White);
var smallRectSize = 10;
var numHor = Mathf.CeilToInt(differenceRect.Width / smallRectSize);
var numVer = Mathf.CeilToInt(differenceRect.Height / smallRectSize);
Render2D.PushClip(differenceRect);
for (int i = 0; i < numHor; i++)
{
for (int j = 0; j < numVer; j++)
{
if ((i + j) % 2 == 0)
{
var rect = new Rectangle(newRect.X + smallRectSize * i, newRect.Y + smallRectSize * j, new Float2(smallRectSize));
var rect = new Rectangle(differenceRect.X + smallRectSize * i, differenceRect.Y + smallRectSize * j, new Float2(smallRectSize));
Render2D.FillRectangle(rect, Color.Gray);
}
}
}
Render2D.FillRectangle(newRect, _linear ? _value.ToSRgb() : _value);
Render2D.PopClip();
Render2D.FillRectangle(differenceRect, _linear ? _value.ToSRgb() : _value);
}
/// <inheritdoc />
protected override void OnShow()
{
// Auto cancel on lost focus
// Apply changes on lost focus
#if !PLATFORM_LINUX
((WindowRootControl)Root).Window.LostFocus += OnWindowLostFocus;
((WindowRootControl)Root).Window.LostFocus += OnSubmit;
#endif
base.OnShow();
@@ -444,6 +492,7 @@ namespace FlaxEditor.GUI.Dialogs
{
// Cancel eye dropping
_activeEyedropper = false;
_cEyedropper.BackgroundColor = _cEyedropper.BackgroundColorHighlighted = Style.Current.Foreground;
Platform.PickScreenColorDone -= OnColorPicked;
return true;
}
@@ -455,9 +504,7 @@ namespace FlaxEditor.GUI.Dialogs
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (base.OnMouseUp(location, button))
{
return true;
}
var child = GetChildAtRecursive(location);
if (button == MouseButton.Right && child is Button b && b.Tag is Color c)
@@ -519,20 +566,20 @@ namespace FlaxEditor.GUI.Dialogs
}
_savedColorButtons.Clear();
CreateAllSaveButtons();
CreateAllSavedColorsButtons();
// Save new colors
var savedColors = JsonSerializer.Serialize(_savedColors, typeof(List<Color>));
Editor.Instance.ProjectCache.SetCustomData("ColorPickerSavedColors", savedColors);
}
private void CreateAllSaveButtons()
private void CreateAllSavedColorsButtons()
{
// Create saved color buttons
for (int i = 0; i < _savedColors.Count; i++)
{
var savedColor = _savedColors[i];
var savedColorButton = new Button(PickerMargin * (i + 1) + SavedColorButtonWidth * i, Height - SavedColorButtonHeight - PickerMargin, SavedColorButtonWidth, SavedColorButtonHeight)
var savedColorButton = new Button(PickerMargin * (i + 1) + SavedColorButtonWidth * i, _cHex.Bottom + 40.0f + ColorPreviewHeight * 0.5f, SavedColorButtonWidth, SavedColorButtonHeight)
{
Parent = this,
Tag = savedColor,
@@ -543,9 +590,9 @@ namespace FlaxEditor.GUI.Dialogs
savedColorButton.ButtonClicked += OnSavedColorButtonClicked;
_savedColorButtons.Add(savedColorButton);
}
if (_savedColors.Count < 8)
if (_savedColors.Count < SavedColorsAmount)
{
var savedColorButton = new Button(PickerMargin * (_savedColors.Count + 1) + SavedColorButtonWidth * _savedColors.Count, Height - SavedColorButtonHeight - PickerMargin, SavedColorButtonWidth, SavedColorButtonHeight)
var savedColorButton = new Button(PickerMargin * (_savedColors.Count + 1) + SavedColorButtonWidth * _savedColors.Count, _cHex.Bottom + 40.0f + ColorPreviewHeight * 0.5f, SavedColorButtonWidth, SavedColorButtonHeight)
{
Text = "+",
Parent = this,
@@ -557,19 +604,6 @@ namespace FlaxEditor.GUI.Dialogs
}
}
private void OnWindowLostFocus()
{
// Auto apply color on defocus
var autoAcceptColorPickerChange = Editor.Instance.Options.Options.Interface.AutoAcceptColorPickerChange;
if (_useDynamicEditing && _initialValue != _value && _canPassLastChangeEvent && autoAcceptColorPickerChange)
{
_canPassLastChangeEvent = false;
_onChanged?.Invoke(_value, false);
}
OnCancel();
}
/// <inheritdoc />
public override void OnSubmit()
{
@@ -577,6 +611,9 @@ namespace FlaxEditor.GUI.Dialogs
return;
_disableEvents = true;
// Ensure the cursor is restored
Cursor = CursorType.Default;
// Send color event if modified
if (_value != _initialValue)
{
@@ -593,6 +630,9 @@ namespace FlaxEditor.GUI.Dialogs
return;
_disableEvents = true;
// Ensure the cursor is restored
Cursor = CursorType.Default;
// Restore color if modified
if (_useDynamicEditing && _initialValue != _value && _canPassLastChangeEvent)
{

View File

@@ -12,6 +12,13 @@ namespace FlaxEditor.GUI.Dialogs
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
public class ColorSelector : ContainerControl
{
private const String GrayedOutParamName = "GrayedOut";
/// <summary>
/// Offset value applied to mouse cursor position when the user lets go of wheel or sliders.
/// </summary>
protected const float MouseCursorOffset = 6.0f;
/// <summary>
/// The color.
/// </summary>
@@ -22,9 +29,22 @@ namespace FlaxEditor.GUI.Dialogs
/// </summary>
protected Rectangle _wheelRect;
private readonly SpriteHandle _colorWheelSprite;
private readonly MaterialBase _hsWheelMaterial;
private bool _isMouseDownWheel;
private Rectangle wheelDragRect
{
get
{
var hsv = _color.ToHSV();
float hAngle = hsv.X * Mathf.DegreesToRadians;
float hRadius = hsv.Y * _wheelRect.Width * 0.5f;
var hsPos = new Float2(hRadius * Mathf.Cos(hAngle), -hRadius * Mathf.Sin(hAngle));
float wheelBoxSize = IsSliding ? 9.0f : 5.0f;
return new Rectangle(hsPos - (wheelBoxSize * 0.5f) + _wheelRect.Center, new Float2(wheelBoxSize));
}
}
/// <summary>
/// Occurs when selected color gets changed.
/// </summary>
@@ -78,7 +98,8 @@ namespace FlaxEditor.GUI.Dialogs
{
AutoFocus = true;
_colorWheelSprite = Editor.Instance.Icons.ColorWheel128;
_hsWheelMaterial = FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.HSWheelMaterial);
_hsWheelMaterial = _hsWheelMaterial.CreateVirtualInstance();
_wheelRect = new Rectangle(0, 0, wheelSize, wheelSize);
}
@@ -165,17 +186,19 @@ namespace FlaxEditor.GUI.Dialogs
{
base.Draw();
var hsv = _color.ToHSV();
bool enabled = EnabledInHierarchy;
_hsWheelMaterial.SetParameterValue(GrayedOutParamName, enabled ? 1.0f : 0.5f);
Render2D.DrawMaterial(_hsWheelMaterial, _wheelRect, enabled ? Color.White : Color.Gray);
// Wheel
float boxExpand = (2.0f * 4.0f / 128.0f) * _wheelRect.Width;
Render2D.DrawSprite(_colorWheelSprite, _wheelRect.MakeExpanded(boxExpand), enabled ? Color.White : Color.Gray);
float hAngle = hsv.X * Mathf.DegreesToRadians;
float hRadius = hsv.Y * _wheelRect.Width * 0.5f;
var hsPos = new Float2(hRadius * Mathf.Cos(hAngle), -hRadius * Mathf.Sin(hAngle));
const float wheelBoxSize = 4.0f;
Render2D.DrawRectangle(new Rectangle(hsPos - (wheelBoxSize * 0.5f) + _wheelRect.Center, new Float2(wheelBoxSize)), _isMouseDownWheel ? Color.Gray : Color.Black);
Render2D.DrawMaterial(_hsWheelMaterial, _wheelRect, enabled ? Color.White : Color.Gray);
Float3 hsv = _color.ToHSV();
Color hsColor = Color.FromHSV(new Float3(hsv.X, hsv.Y, 1));
Rectangle wheelRect = wheelDragRect;
Render2D.FillRectangle(wheelRect, hsColor);
Render2D.DrawRectangle(wheelRect, Color.Black, _isMouseDownWheel ? 2.0f : 1.0f);
}
/// <inheritdoc />
@@ -194,6 +217,15 @@ namespace FlaxEditor.GUI.Dialogs
base.OnMouseMove(location);
}
/// <inheritdoc />
public override void OnMouseMoveRelative(Float2 motion)
{
var location = PointFromScreen(FlaxEngine.Input.MouseScreenPosition);
UpdateMouse(ref location);
base.OnMouseMoveRelative(motion);
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
@@ -202,6 +234,7 @@ namespace FlaxEditor.GUI.Dialogs
if (!_isMouseDownWheel)
{
_isMouseDownWheel = true;
Cursor = CursorType.Hidden;
StartMouseCapture();
SlidingStart?.Invoke();
}
@@ -218,6 +251,10 @@ namespace FlaxEditor.GUI.Dialogs
if (button == MouseButton.Left && _isMouseDownWheel)
{
EndMouseCapture();
// Make the cursor appear where the user expects it to be (position of selection rectangle)
Rectangle dragRect = wheelDragRect;
Root.MousePosition = dragRect.Center + MouseCursorOffset;
Cursor = CursorType.Default;
EndSliding();
return true;
}
@@ -246,10 +283,10 @@ namespace FlaxEditor.GUI.Dialogs
/// <seealso cref="ColorSelector" />
public class ColorSelectorWithSliders : ColorSelector
{
private Rectangle _slider1Rect;
private Rectangle _slider2Rect;
private bool _isMouseDownSlider1;
private bool _isMouseDownSlider2;
private Rectangle _valueSliderRect;
private Rectangle _alphaSliderRect;
private bool _isMouseDownValueSlider;
private bool _isMouseDownAlphaSlider;
/// <summary>
/// Initializes a new instance of the <see cref="ColorSelectorWithSliders"/> class.
@@ -260,26 +297,26 @@ namespace FlaxEditor.GUI.Dialogs
: base(wheelSize)
{
// Setup dimensions
const float slidersMargin = 8.0f;
_slider1Rect = new Rectangle(wheelSize + slidersMargin, 0, slidersThickness, wheelSize);
_slider2Rect = new Rectangle(_slider1Rect.Right + slidersMargin, _slider1Rect.Y, slidersThickness, _slider1Rect.Height);
Size = new Float2(_slider2Rect.Right, wheelSize);
const float slidersMargin = 10.0f;
_valueSliderRect = new Rectangle(wheelSize + slidersMargin, 0, slidersThickness, wheelSize);
_alphaSliderRect = new Rectangle(_valueSliderRect.Right + slidersMargin * 1.5f, _valueSliderRect.Y, slidersThickness, _valueSliderRect.Height);
Size = new Float2(_alphaSliderRect.Right, wheelSize);
}
/// <inheritdoc />
protected override void UpdateMouse(ref Float2 location)
{
if (_isMouseDownSlider1)
if (_isMouseDownValueSlider)
{
var hsv = _color.ToHSV();
hsv.Z = 1.0f - Mathf.Saturate((location.Y - _slider1Rect.Y) / _slider1Rect.Height);
hsv.Z = 1.0f - Mathf.Saturate((location.Y - _valueSliderRect.Y) / _valueSliderRect.Height);
Color = Color.FromHSV(hsv, _color.A);
}
else if (_isMouseDownSlider2)
else if (_isMouseDownAlphaSlider)
{
var color = _color;
color.A = 1.0f - Mathf.Saturate((location.Y - _slider2Rect.Y) / _slider2Rect.Height);
color.A = 1.0f - Mathf.Saturate((location.Y - _alphaSliderRect.Y) / _alphaSliderRect.Height);
Color = color;
}
@@ -296,56 +333,69 @@ namespace FlaxEditor.GUI.Dialogs
// Cache data
var style = Style.Current;
var features = Render2D.Features;
Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping;
var hsv = _color.ToHSV();
var hs = hsv;
hs.Z = 1.0f;
Color hsC = Color.FromHSV(hs);
const float slidersOffset = 3.0f;
const float slidersThickness = 4.0f;
// Value
float valueY = _slider2Rect.Height * (1 - hsv.Z);
var valueR = new Rectangle(_slider1Rect.X - slidersOffset, _slider1Rect.Y + valueY - slidersThickness / 2, _slider1Rect.Width + slidersOffset * 2, slidersThickness);
Render2D.FillRectangle(_slider1Rect, hsC, hsC, Color.Black, Color.Black);
Render2D.DrawRectangle(_slider1Rect, _isMouseDownSlider1 ? style.BackgroundSelected : Color.Black);
Render2D.DrawRectangle(valueR, _isMouseDownSlider1 ? Color.White : Color.Gray);
// Value slider
float valueKnobExpand = _isMouseDownValueSlider ? 10.0f : 4.0f;
float valueY = _valueSliderRect.Height * (1 - hsv.Z);
float valueKnobWidth = _valueSliderRect.Width + valueKnobExpand;
float valueKnobHeight = _isMouseDownValueSlider ? 7.0f : 4.0f;
float valueKnobX = _valueSliderRect.X - valueKnobExpand * 0.5f;
float valueKnobY = _valueSliderRect.Y + valueY - valueKnobHeight * 0.5f;
Rectangle valueKnobRect = new Rectangle(valueKnobX, valueKnobY, valueKnobWidth, valueKnobHeight);
Render2D.FillRectangle(_valueSliderRect, hsC, hsC, Color.Black, Color.Black);
// Draw one black and one white border to make the knob visible at any saturation level
Render2D.DrawRectangle(valueKnobRect, Color.White, _isMouseDownValueSlider ? 3.0f : 2.0f);
Render2D.DrawRectangle(valueKnobRect, Color.Black, _isMouseDownValueSlider ? 2.0f : 1.0f);
// Draw checkerboard pattern to part of the alpha slider background
var alphaRect = _slider2Rect;
Render2D.FillRectangle(alphaRect, Color.White);
var smallRectSize = alphaRect.Width * 0.5f;
var numHor = Mathf.CeilToInt(alphaRect.Width / smallRectSize);
var numVer = Mathf.CeilToInt(alphaRect.Height / smallRectSize);
// Draw checkerboard pattern as background of alpha slider
Render2D.FillRectangle(_alphaSliderRect, Color.White);
var smallRectSize = _alphaSliderRect.Width / 2.0f;
var numHor = Mathf.CeilToInt(_alphaSliderRect.Width / smallRectSize);
var numVer = Mathf.CeilToInt(_alphaSliderRect.Height / smallRectSize);
Render2D.PushClip(_alphaSliderRect);
for (int i = 0; i < numHor; i++)
{
for (int j = 0; j < numVer; j++)
{
if ((i + j) % 2 == 0)
{
var rect = new Rectangle(alphaRect.X + smallRectSize * i, alphaRect.Y + smallRectSize * j, new Float2(smallRectSize));
Render2D.PushClip(alphaRect);
var rect = new Rectangle(_alphaSliderRect.X + smallRectSize * i, _alphaSliderRect.Y + smallRectSize * j, new Float2(smallRectSize));
Render2D.FillRectangle(rect, Color.Gray);
Render2D.PopClip();
}
}
}
Render2D.PopClip();
// Alpha
float alphaY = _slider2Rect.Height * (1 - _color.A);
var alphaR = new Rectangle(_slider2Rect.X - slidersOffset, _slider2Rect.Y + alphaY - slidersThickness / 2, _slider2Rect.Width + slidersOffset * 2, slidersThickness);
// Alpha slider
float alphaKnobExpand = _isMouseDownAlphaSlider ? 10.0f : 4.0f;
float alphaY = _alphaSliderRect.Height * (1 - _color.A);
float alphaKnobWidth = _alphaSliderRect.Width + alphaKnobExpand;
float alphaKnobHeight = _isMouseDownAlphaSlider ? 7.0f : 4.0f;
float alphaKnobX = _alphaSliderRect.X - alphaKnobExpand * 0.5f;
float alphaKnobY = _alphaSliderRect.Y + alphaY - alphaKnobExpand * 0.5f;
Rectangle alphaKnobRect = new Rectangle(alphaKnobX, alphaKnobY, alphaKnobWidth, alphaKnobHeight);
var color = _color;
color.A = 1; // Keep slider 2 fill rect from changing color alpha while selecting.
Render2D.FillRectangle(_slider2Rect, color, color, Color.Transparent, Color.Transparent);
Render2D.DrawRectangle(_slider2Rect, _isMouseDownSlider2 ? style.BackgroundSelected : Color.Black);
Render2D.DrawRectangle(alphaR, _isMouseDownSlider2 ? Color.White : Color.Gray);
color.A = 1; // Prevent alpha slider fill from becoming transparent
Render2D.FillRectangle(_alphaSliderRect, color, color, Color.Transparent, Color.Transparent);
// Draw one black and one white border to make the knob visible at any saturation level
Render2D.DrawRectangle(alphaKnobRect, Color.White, _isMouseDownAlphaSlider ? 3.0f : 2.0f);
Render2D.DrawRectangle(alphaKnobRect, Color.Black, _isMouseDownAlphaSlider ? 2.0f : 1.0f);
Render2D.Features = features;
}
/// <inheritdoc />
public override void OnLostFocus()
{
// Clear flags
_isMouseDownSlider1 = false;
_isMouseDownSlider2 = false;
_isMouseDownValueSlider = false;
_isMouseDownAlphaSlider = false;
base.OnLostFocus();
}
@@ -353,15 +403,17 @@ namespace FlaxEditor.GUI.Dialogs
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _slider1Rect.Contains(location))
if (button == MouseButton.Left && _valueSliderRect.Contains(location))
{
_isMouseDownSlider1 = true;
_isMouseDownValueSlider = true;
Cursor = CursorType.Hidden;
StartMouseCapture();
UpdateMouse(ref location);
}
if (button == MouseButton.Left && _slider2Rect.Contains(location))
if (button == MouseButton.Left && _alphaSliderRect.Contains(location))
{
_isMouseDownSlider2 = true;
_isMouseDownAlphaSlider = true;
Cursor = CursorType.Hidden;
StartMouseCapture();
UpdateMouse(ref location);
}
@@ -372,10 +424,16 @@ namespace FlaxEditor.GUI.Dialogs
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && (_isMouseDownSlider1 || _isMouseDownSlider2))
if (button == MouseButton.Left && (_isMouseDownValueSlider || _isMouseDownAlphaSlider))
{
_isMouseDownSlider1 = false;
_isMouseDownSlider2 = false;
// Make the cursor appear where the user expects it to be (center of slider horizontally and slider knob position vertically)
float sliderCenter = _isMouseDownValueSlider ? _valueSliderRect.Center.X : _alphaSliderRect.Center.X;
// Calculate y position based on the slider knob to avoid incrementing value by a small amount when the user repeatedly clicks the slider (f.e. when moving in small steps)
float mouseSliderPosition = _isMouseDownValueSlider ? _valueSliderRect.Y + _valueSliderRect.Height * (1 - _color.ToHSV().Z) + MouseCursorOffset : _alphaSliderRect.Y + _alphaSliderRect.Height * (1 - _color.A) + MouseCursorOffset;
Root.MousePosition = new Float2(sliderCenter + MouseCursorOffset, Mathf.Clamp(mouseSliderPosition, _valueSliderRect.Top, _valueSliderRect.Bottom));
Cursor = CursorType.Default;
_isMouseDownValueSlider = false;
_isMouseDownAlphaSlider = false;
EndMouseCapture();
return true;
}
@@ -387,8 +445,8 @@ namespace FlaxEditor.GUI.Dialogs
public override void OnEndMouseCapture()
{
// Clear flags
_isMouseDownSlider1 = false;
_isMouseDownSlider2 = false;
_isMouseDownValueSlider = false;
_isMouseDownAlphaSlider = false;
base.OnEndMouseCapture();
}

View File

@@ -135,11 +135,7 @@ namespace FlaxEditor.GUI.Docking
settings.MaximumSize = Float2.Zero; // Unlimited size
settings.Fullscreen = false;
settings.HasBorder = true;
#if PLATFORM_SDL
settings.SupportsTransparency = true;
#else
settings.SupportsTransparency = false;
#endif
settings.ActivateWhenFirstShown = true;
settings.AllowInput = true;
settings.AllowMinimize = true;
@@ -211,6 +207,7 @@ namespace FlaxEditor.GUI.Docking
{
if (ChildPanelsCount > 0)
return;
// Close window
_window?.Close();
}

View File

@@ -269,8 +269,9 @@ namespace FlaxEditor.GUI.Docking
if (_toDock == null)
return;
if (_toDock.RootWindow.Window != _dragSourceWindow)
_toDock.RootWindow.Window.MouseUp -= OnMouseUp;
var window = _toDock.RootWindow?.Window;
if (window != null && window != _dragSourceWindow)
window.MouseUp -= OnMouseUp;
_dockHintDown?.Parent.RemoveChild(_dockHintDown);
_dockHintUp?.Parent.RemoveChild(_dockHintUp);
@@ -327,10 +328,10 @@ namespace FlaxEditor.GUI.Docking
_toDock?.RootWindow.Window.BringToFront();
//_toDock?.RootWindow.Window.Focus();
#if PLATFORM_SDL
// Make the dragged window transparent when dock hints are visible
_toMove.Window.Window.Opacity = _toDock == null ? 1.0f : DragWindowOpacity;
#else
#if !PLATFORM_SDL
// Bring the drop source always to the top
if (_dragSourceWindow != null)
_dragSourceWindow.BringToFront();

View File

@@ -76,9 +76,23 @@ namespace FlaxEditor.GUI
toolstrip.IsLayoutLocked = toolstripLocked;
toolstrip.ItemsMargin = toolstripMargin;
var lastToolstripButton = toolstrip.LastButton;
var margin = toolstrip.ItemsMargin;
float xOffset = margin.Left;
bool hadChild = false;
for (int i = 0; i < toolstrip.ChildrenCount; i++)
{
var child = toolstrip.GetChild(i);
if (child == this || !child.Visible)
continue;
hadChild = true;
xOffset += child.Width + margin.Width;
}
var right = hadChild ? xOffset - margin.Width : margin.Left;
var parentSize = Parent.Size;
Bounds = new Rectangle(lastToolstripButton.Right + 8.0f, 0, parentSize.X - X - 8.0f, toolstrip.Height);
var x = right + 8.0f;
var width = Mathf.Max(parentSize.X - x - 8.0f, 0.0f);
Bounds = new Rectangle(x, 0, width, toolstrip.Height);
}
/// <inheritdoc />

View File

@@ -121,7 +121,7 @@ namespace FlaxEditor.GUI
}
// Select the first platform
_selected = PlatformType.Web;
_selected = platforms[0].PlatformType;
((Image)Children[0]).Color = _selectedColor;
((Image)Children[0]).MouseOverColor = _selectedColor;
}

View File

@@ -2148,10 +2148,9 @@ namespace FlaxEditor.GUI.Timeline
/// </summary>
public void ShowWholeTimeline()
{
var viewWidth = Width;
var timelineWidth = Duration * UnitsPerSecond * Zoom + 8 * StartOffset;
_backgroundArea.ViewOffset = Float2.Zero;
Zoom = viewWidth / timelineWidth;
const float padding = 40f;
Zoom = (_backgroundArea.Width - padding * 2f) / (Duration * UnitsPerSecond);
_backgroundArea.ViewOffset = new Float2(-_leftEdge.X + padding, _backgroundArea.ViewOffset.Y);
}
/// <summary>

View File

@@ -0,0 +1,300 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using FlaxEditor.Options;
using FlaxEditor.Viewport;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Gizmo;
[HideInEditor]
internal class DirectionGizmo : ContainerControl
{
public const float DefaultGizmoSize = 112.5f;
private const float AxisLength = 71.25f;
private const float SpriteRadius = 10.85f;
private IGizmoOwner _owner;
private ViewportProjection _viewportProjection;
private EditorViewport _viewport;
private Vector3 _gizmoCenter;
private float _gizmoBrightness;
private float _gizmoOpacity;
private float _backgroundOpacity;
private float _axisLength;
private float _spriteRadius;
private AxisData _xAxisData;
private AxisData _yAxisData;
private AxisData _zAxisData;
private AxisData _negXAxisData;
private AxisData _negYAxisData;
private AxisData _negZAxisData;
private List<AxisData> _axisData = new List<AxisData>();
private int _hoveredAxisIndex = -1;
private SpriteHandle _posHandle;
private SpriteHandle _negHandle;
private FontReference _fontReference;
// Store sprite positions for hover detection
private List<(Float2 position, AxisDirection direction)> _spritePositions = new List<(Float2, AxisDirection)>();
private struct ViewportProjection
{
private Matrix _viewProjection;
private BoundingFrustum _frustum;
private FlaxEngine.Viewport _viewport;
private Vector3 _origin;
public void Init(EditorViewport editorViewport)
{
// Inline EditorViewport.ProjectPoint to save on calculation for large set of points
_viewport = new FlaxEngine.Viewport(0, 0, editorViewport.Width, editorViewport.Height);
_frustum = editorViewport.ViewFrustum;
_viewProjection = _frustum.Matrix;
_origin = editorViewport.Task.View.Origin;
}
public void ProjectPoint(Vector3 worldSpaceLocation, out Float2 viewportSpaceLocation)
{
worldSpaceLocation -= _origin;
_viewport.Project(ref worldSpaceLocation, ref _viewProjection, out var projected);
viewportSpaceLocation = new Float2((float)projected.X, (float)projected.Y);
}
}
private struct AxisData
{
public Float2 Delta;
public float Distance;
public string Label;
public Color AxisColor;
public bool Negative;
public AxisDirection Direction;
}
private enum AxisDirection
{
PosX,
PosY,
PosZ,
NegX,
NegY,
NegZ
}
/// <summary>
/// Constructor of the Direction Gizmo
/// </summary>
/// <param name="owner">The owner of this object.</param>
public DirectionGizmo(IGizmoOwner owner)
{
_owner = owner;
_viewport = owner.Viewport;
_viewportProjection.Init(owner.Viewport);
_xAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "X", AxisColor = new Color(1.0f, 0.0f, 0.02745f, 1.0f), Negative = false, Direction = AxisDirection.PosX };
_yAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "Y", AxisColor = new Color(0.239215f, 1.0f, 0.047058f, 1.0f), Negative = false, Direction = AxisDirection.PosY };
_zAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "Z", AxisColor = new Color(0.0f, 0.3607f, 0.9f, 1.0f), Negative = false, Direction = AxisDirection.PosZ };
_negXAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "-X", AxisColor = new Color(1.0f, 0.0f, 0.02745f, 1.0f), Negative = true, Direction = AxisDirection.NegX };
_negYAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "-Y", AxisColor = new Color(0.239215f, 1.0f, 0.047058f, 1.0f), Negative = true, Direction = AxisDirection.NegY };
_negZAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "-Z", AxisColor = new Color(0.0f, 0.3607f, 0.9f, 1.0f), Negative = true, Direction = AxisDirection.NegZ };
_axisData.EnsureCapacity(6);
_spritePositions.EnsureCapacity(6);
var editor = Editor.Instance;
_posHandle = editor.Icons.VisjectBoxClosed32;
_negHandle = editor.Icons.VisjectBoxOpen32;
_fontReference = new FontReference(Style.Current.FontSmall);
editor.Options.OptionsChanged += OnEditorOptionsChanged;
OnEditorOptionsChanged(editor.Options.Options);
}
private void OnEditorOptionsChanged(EditorOptions options)
{
float gizmoScale = options.Viewport.DirectionGizmoScale;
_axisLength = AxisLength * gizmoScale;
_spriteRadius = SpriteRadius * gizmoScale;
_gizmoBrightness = options.Viewport.DirectionGizmoBrightness;
_gizmoOpacity = options.Viewport.DirectionGizmoOpacity;
_backgroundOpacity = options.Viewport.DirectionGizmoBackgroundOpacity;
_fontReference.Size = 8.25f * gizmoScale;
}
private bool IsPointInSprite(Float2 point, Float2 spriteCenter)
{
Float2 delta = point - spriteCenter;
float distanceSq = delta.LengthSquared;
float radiusSq = _spriteRadius * _spriteRadius;
return distanceSq <= radiusSq;
}
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
_hoveredAxisIndex = -1;
// Check which axis is being hovered - check from closest to farthest for proper layering
for (int i = _spritePositions.Count - 1; i >= 0; i--)
{
if (IsPointInSprite(location, _spritePositions[i].position))
{
_hoveredAxisIndex = i;
break;
}
}
base.OnMouseMove(location);
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (base.OnMouseUp(location, button))
return true;
// Check which axis is being clicked - check from closest to farthest for proper layering
for (int i = _spritePositions.Count - 1; i >= 0; i--)
{
if (IsPointInSprite(location, _spritePositions[i].position))
{
OrientViewToAxis(_spritePositions[i].direction);
return true;
}
}
return false;
}
private void OrientViewToAxis(AxisDirection direction)
{
Quaternion orientation = direction switch
{
AxisDirection.PosX => Quaternion.Euler(0, -90, 0),
AxisDirection.NegX => Quaternion.Euler(0, 90, 0),
AxisDirection.PosY => Quaternion.Euler(90, 0, 0),
AxisDirection.NegY => Quaternion.Euler(-90, 0, 0),
AxisDirection.PosZ => Quaternion.Euler(0, 180, 0),
AxisDirection.NegZ => Quaternion.Euler(0, 0, 0),
_ => Quaternion.Identity
};
_viewport.OrientViewport(ref orientation);
}
/// <summary>
/// Used to Draw the gizmo.
/// </summary>
public override void DrawSelf()
{
base.DrawSelf();
var features = Render2D.Features;
Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping;
_viewportProjection.Init(_owner.Viewport);
_gizmoCenter = _viewport.Task.View.WorldPosition + _viewport.Task.View.Direction * 1500;
_viewportProjection.ProjectPoint(_gizmoCenter, out var gizmoCenterScreen);
var relativeCenter = Size * 0.5f;
// Project unit vectors
_viewportProjection.ProjectPoint(_gizmoCenter + Vector3.Right, out var xProjected);
_viewportProjection.ProjectPoint(_gizmoCenter + Vector3.Up, out var yProjected);
_viewportProjection.ProjectPoint(_gizmoCenter + Vector3.Forward, out var zProjected);
_viewportProjection.ProjectPoint(_gizmoCenter - Vector3.Right, out var negXProjected);
_viewportProjection.ProjectPoint(_gizmoCenter - Vector3.Up, out var negYProjected);
_viewportProjection.ProjectPoint(_gizmoCenter - Vector3.Forward, out var negZProjected);
// Normalize by viewport height to keep size independent of FOV and viewport dimensions
float heightNormalization = _viewport.Height / 720.0f; // 720 = reference height
// Fix in axes distance no matter FOV/OrthoScale to keep consistent size regardless of zoom level
if (_owner.Viewport.UseOrthographicProjection)
heightNormalization /= _owner.Viewport.OrthographicScale * 0.5f;
else
{
// This could be some actual math expression, not that hack
var fov = _owner.Viewport.FieldOfView / 60.0f;
float scaleAt30 = 0.1f, scaleAt60 = 1.0f, scaleAt120 = 1.5f, scaleAt180 = 3.0f;
heightNormalization /= Mathf.Lerp(scaleAt30, scaleAt60, fov);
heightNormalization /= Mathf.Lerp(scaleAt60, scaleAt120, Mathf.Saturate(fov - 1));
heightNormalization /= Mathf.Lerp(scaleAt60, scaleAt180, Mathf.Saturate(fov - 2));
}
Float2 xDelta = (xProjected - gizmoCenterScreen) / heightNormalization;
Float2 yDelta = (yProjected - gizmoCenterScreen) / heightNormalization;
Float2 zDelta = (zProjected - gizmoCenterScreen) / heightNormalization;
Float2 negXDelta = (negXProjected - gizmoCenterScreen) / heightNormalization;
Float2 negYDelta = (negYProjected - gizmoCenterScreen) / heightNormalization;
Float2 negZDelta = (negZProjected - gizmoCenterScreen) / heightNormalization;
// Calculate distances from camera to determine draw order
Vector3 cameraPosition = _viewport.Task.View.Position;
float xDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter + Vector3.Right);
float yDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter + Vector3.Up);
float zDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter + Vector3.Forward);
float negXDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter - Vector3.Right);
float negYDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter - Vector3.Up);
float negZDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter - Vector3.Forward);
_xAxisData.Delta = xDelta;
_xAxisData.Distance = xDistance;
_yAxisData.Delta = yDelta;
_yAxisData.Distance = yDistance;
_zAxisData.Delta = zDelta;
_zAxisData.Distance = zDistance;
_negXAxisData.Delta = negXDelta;
_negXAxisData.Distance = negXDistance;
_negYAxisData.Delta = negYDelta;
_negYAxisData.Distance = negYDistance;
_negZAxisData.Delta = negZDelta;
_negZAxisData.Distance = negZDistance;
// Sort for correct draw order.
_axisData.Clear();
_axisData.AddRange([_xAxisData, _yAxisData, _zAxisData, _negXAxisData, _negYAxisData, _negZAxisData]);
_axisData.Sort((a, b) => -a.Distance.CompareTo(b.Distance));
// Rebuild sprite positions list for hover detection
_spritePositions.Clear();
Render2D.DrawSprite(_posHandle, new Rectangle(0, 0, Size), Color.Black.AlphaMultiplied(_backgroundOpacity));
// Draw in order from farthest to closest
for (int i = 0; i < _axisData.Count; i++)
{
var axis = _axisData[i];
Float2 tipScreen = relativeCenter + axis.Delta * _axisLength;
bool isHovered = _hoveredAxisIndex == i;
// Store sprite position for hover detection
_spritePositions.Add((tipScreen, axis.Direction));
var axisColor = isHovered ? new Color(1.0f, 0.8980392f, 0.039215688f) : axis.AxisColor;
axisColor = axisColor.RGBMultiplied(_gizmoBrightness).AlphaMultiplied(_gizmoOpacity);
var font = _fontReference.GetFont();
if (!axis.Negative)
{
Render2D.DrawLine(relativeCenter, tipScreen, axisColor, 1.5f);
Render2D.DrawSprite(_posHandle, new Rectangle(tipScreen - new Float2(_spriteRadius), new Float2(_spriteRadius * 2)), axisColor);
Render2D.DrawText(font, axis.Label, Color.Black, tipScreen - font.MeasureText(axis.Label) * 0.5f);
}
else
{
Render2D.DrawSprite(_posHandle, new Rectangle(tipScreen - new Float2(_spriteRadius), new Float2(_spriteRadius * 2)), axisColor.RGBMultiplied(0.85f).AlphaMultiplied(0.8f));
if (isHovered)
Render2D.DrawText(font, axis.Label, Color.Black, tipScreen - font.MeasureText(axis.Label) * 0.5f);
}
}
Render2D.Features = features;
}
}

View File

@@ -57,7 +57,7 @@ namespace FlaxEditor.Gizmo
var height = output.Height;
var desc = GPUTextureDescription.New2D(width, height, format, GPUTextureFlags.RenderTarget | GPUTextureFlags.ShaderResource, 1, 1, msaaLevel);
var target = RenderTargetPool.Get(ref desc);
desc = GPUTextureDescription.New2D(width, height, PixelFormat.D24_UNorm_S8_UInt, GPUTextureFlags.DepthStencil, 1, 1, msaaLevel);
desc = GPUTextureDescription.New2D(width, height, renderContext.Buffers.DepthBuffer.Format, GPUTextureFlags.DepthStencil, 1, 1, msaaLevel);
var targetDepth = RenderTargetPool.Get(ref desc);
// Copy frame and clear depth

View File

@@ -529,7 +529,7 @@ namespace FlaxEditor
if (EnableBackground && _view != null)
{
// Draw background
Surface.VisjectSurface.DrawBackgroundDefault(Editor.Instance.UI.VisjectSurfaceBackground, Width, Height);
Surface.VisjectSurface.DrawBackgroundDefault(Editor.Instance.UI.VisjectSurfaceBackground, Size, _view.Location);
if (ShowGrid)
{

View File

@@ -24,22 +24,22 @@ namespace FlaxEditor.Modules
private bool _rebuildInitFlag;
private int _itemsCreated;
private int _itemsDeleted;
private readonly HashSet<MainContentTreeNode> _dirtyNodes = new HashSet<MainContentTreeNode>();
private readonly HashSet<MainContentFolderTreeNode> _dirtyNodes = new HashSet<MainContentFolderTreeNode>();
/// <summary>
/// The project directory.
/// </summary>
public ProjectTreeNode Game { get; private set; }
public ProjectFolderTreeNode Game { get; private set; }
/// <summary>
/// The engine directory.
/// </summary>
public ProjectTreeNode Engine { get; private set; }
public ProjectFolderTreeNode Engine { get; private set; }
/// <summary>
/// The list of all projects workspace directories (including game, engine and plugins projects).
/// </summary>
public readonly List<ProjectTreeNode> Projects = new List<ProjectTreeNode>();
public readonly List<ProjectFolderTreeNode> Projects = new List<ProjectFolderTreeNode>();
/// <summary>
/// The list with all content items proxy objects. Use <see cref="AddProxy"/> and <see cref="RemoveProxy"/> to modify this or <see cref="Rebuild"/> to refresh database when adding new item proxy types.
@@ -116,7 +116,7 @@ namespace FlaxEditor.Modules
/// </summary>
/// <param name="project">The project.</param>
/// <returns>The project workspace or null if not loaded into database.</returns>
public ProjectTreeNode GetProjectWorkspace(ProjectInfo project)
public ProjectFolderTreeNode GetProjectWorkspace(ProjectInfo project)
{
return Projects.FirstOrDefault(x => x.Project == project);
}
@@ -878,7 +878,7 @@ namespace FlaxEditor.Modules
}
}
private void LoadFolder(ContentTreeNode node, bool checkSubDirs)
private void LoadFolder(ContentFolderTreeNode node, bool checkSubDirs)
{
if (node == null)
return;
@@ -957,7 +957,7 @@ namespace FlaxEditor.Modules
if (childFolderNode == null)
{
// Create node
ContentTreeNode n = new ContentTreeNode(node, childPath);
ContentFolderTreeNode n = new ContentFolderTreeNode(node, childPath);
if (!_isDuringFastSetup)
sortChildren = true;
@@ -982,7 +982,7 @@ namespace FlaxEditor.Modules
node.SortChildren();
// Ignore some special folders
if (node is MainContentTreeNode mainNode && mainNode.Folder.ShortName == "Source")
if (node is MainContentFolderTreeNode mainNode && mainNode.Folder.ShortName == "Source")
{
var mainNodeChild = mainNode.Folder.Find(StringUtils.CombinePaths(mainNode.Path, "obj")) as ContentFolder;
if (mainNodeChild != null)
@@ -999,7 +999,7 @@ namespace FlaxEditor.Modules
}
}
private void LoadScripts(ContentTreeNode parent, string[] files)
private void LoadScripts(ContentFolderTreeNode parent, string[] files)
{
for (int i = 0; i < files.Length; i++)
{
@@ -1045,7 +1045,7 @@ namespace FlaxEditor.Modules
}
}
private void LoadAssets(ContentTreeNode parent, string[] files)
private void LoadAssets(ContentFolderTreeNode parent, string[] files)
{
for (int i = 0; i < files.Length; i++)
{
@@ -1097,20 +1097,20 @@ namespace FlaxEditor.Modules
var workspace = GetProjectWorkspace(project);
if (workspace == null)
{
workspace = new ProjectTreeNode(project);
workspace = new ProjectFolderTreeNode(project);
Projects.Add(workspace);
var contentFolder = StringUtils.CombinePaths(project.ProjectFolderPath, "Content");
if (Directory.Exists(contentFolder))
{
workspace.Content = new MainContentTreeNode(workspace, ContentFolderType.Content, contentFolder);
workspace.Content = new MainContentFolderTreeNode(workspace, ContentFolderType.Content, contentFolder);
workspace.Content.Folder.ParentFolder = workspace.Folder;
}
var sourceFolder = StringUtils.CombinePaths(project.ProjectFolderPath, "Source");
if (Directory.Exists(sourceFolder))
{
workspace.Source = new MainContentTreeNode(workspace, ContentFolderType.Source, sourceFolder);
workspace.Source = new MainContentFolderTreeNode(workspace, ContentFolderType.Source, sourceFolder);
workspace.Source.Folder.ParentFolder = workspace.Folder;
}
}
@@ -1218,16 +1218,16 @@ namespace FlaxEditor.Modules
Proxy.Add(new GenericJsonAssetProxy());
// Create content folders nodes
Engine = new ProjectTreeNode(Editor.EngineProject)
Engine = new ProjectFolderTreeNode(Editor.EngineProject)
{
Content = new MainContentTreeNode(Engine, ContentFolderType.Content, Globals.EngineContentFolder),
Content = new MainContentFolderTreeNode(Engine, ContentFolderType.Content, Globals.EngineContentFolder),
};
if (Editor.GameProject != Editor.EngineProject)
{
Game = new ProjectTreeNode(Editor.GameProject)
Game = new ProjectFolderTreeNode(Editor.GameProject)
{
Content = new MainContentTreeNode(Game, ContentFolderType.Content, Globals.ProjectContentFolder),
Source = new MainContentTreeNode(Game, ContentFolderType.Source, Globals.ProjectSourceFolder),
Content = new MainContentFolderTreeNode(Game, ContentFolderType.Content, Globals.ProjectContentFolder),
Source = new MainContentFolderTreeNode(Game, ContentFolderType.Source, Globals.ProjectSourceFolder),
};
// TODO: why it's required? the code above should work for linking the nodes hierarchy
Game.Content.Folder.ParentFolder = Game.Folder;
@@ -1307,7 +1307,7 @@ namespace FlaxEditor.Modules
}
}
internal void OnDirectoryEvent(MainContentTreeNode node, FileSystemEventArgs e)
internal void OnDirectoryEvent(MainContentFolderTreeNode node, FileSystemEventArgs e)
{
// Ensure to be ready for external events
if (_isDuringFastSetup)

View File

@@ -710,6 +710,10 @@ namespace FlaxEditor.Options
[EditorDisplay("Node Editors"), EditorOrder(4580)]
public InputBinding NodesDistributeVertical = new InputBinding(KeyboardKeys.A, KeyboardKeys.Alt);
[DefaultValue(typeof(InputBinding), "Shift+F")]
[EditorDisplay("Node Editors"), EditorOrder(4590)]
public InputBinding FocusSelectedNodes = new InputBinding(KeyboardKeys.F, KeyboardKeys.Shift);
#endregion
}
}

View File

@@ -253,13 +253,6 @@ namespace FlaxEditor.Options
[EditorDisplay("Interface"), EditorOrder(280), Tooltip("Editor content window orientation.")]
public FlaxEngine.GUI.Orientation ContentWindowOrientation { get; set; } = FlaxEngine.GUI.Orientation.Horizontal;
/// <summary>
/// If checked, color pickers will always modify the color unless 'Cancel' if pressed, otherwise color won't change unless 'Ok' is pressed.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Interface"), EditorOrder(290)]
public bool AutoAcceptColorPickerChange { get; set; } = true;
/// <summary>
/// Gets or sets the formatting option for numeric values in the editor.
/// </summary>

View File

@@ -171,5 +171,40 @@ namespace FlaxEditor.Options
[DefaultValue(1000.0f), Limit(0.0f, 20000.0f, 5.0f)]
[EditorDisplay("Viewport Icons"), EditorOrder(410)]
public float MaxSizeDistance { get; set; } = 1000.0f;
/// <summary>
/// Gets or sets a value that indicates whether the main viewports <see cref="Gizmo.DirectionGizmo"/> is visible.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Direction Gizmo"), EditorOrder(500), Tooltip("Sets the visibility of the direction gizmo in the main editor viewport.")]
public bool ShowDirectionGizmo { get; set; } = true;
/// <summary>
/// Gets or sets a value by which the main viewports <see cref="Gizmo.DirectionGizmo"/> size is multiplied with.
/// </summary>
[DefaultValue(1f), Limit(0.0f, 2.0f)]
[EditorDisplay("Direction Gizmo"), EditorOrder(501), Tooltip("The scale of the direction gizmo in the main viewport.")]
public float DirectionGizmoScale { get; set; } = 1f;
/// <summary>
/// Gets or sets a value for the opacity of the main viewports <see cref="Gizmo.DirectionGizmo"/> background.
/// </summary>
[DefaultValue(0.1f), Limit(0.0f, 1.0f)]
[EditorDisplay("Direction Gizmo"), EditorOrder(502), Tooltip("The background opacity of the of the direction gizmo in the main viewport.")]
public float DirectionGizmoBackgroundOpacity { get; set; } = 0.1f;
/// <summary>
/// Gets or sets a value for the opacity of the main viewports <see cref="Gizmo.DirectionGizmo"/>.
/// </summary>
[DefaultValue(0.6f), Limit(0.0f, 1.0f)]
[EditorDisplay("Direction Gizmo"), EditorOrder(503), Tooltip("The opacity of the of the direction gizmo in the main viewport.")]
public float DirectionGizmoOpacity { get; set; } = 0.6f;
/// <summary>
/// Gets or sets a value for the opacity of the main viewports <see cref="Gizmo.DirectionGizmo"/>.
/// </summary>
[DefaultValue(1f), Limit(0.0f, 2.0f)]
[EditorDisplay("Direction Gizmo"), EditorOrder(504), Tooltip("The brightness of the of the direction gizmo in the main viewport.")]
public float DirectionGizmoBrightness{ get; set; } = 1f;
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine;
namespace FlaxEditor.SceneGraph.Actors
{
/// <summary>
/// Scene tree node for <see cref="Cloth"/> actor type.
/// </summary>
[HideInEditor]
public sealed class ClothNode : ActorNode
{
/// <inheritdoc />
public ClothNode(Actor actor)
: base(actor)
{
}
/// <inheritdoc />
public override void PostSpawn()
{
base.PostSpawn();
// Snap to the parent
if (!(ParentNode is SceneNode))
Actor.LocalTransform = Transform.Identity;
}
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine;
namespace FlaxEditor.SceneGraph.Actors
{
/// <summary>
/// Scene tree node for <see cref="RigidBody"/> actor type.
/// </summary>
[HideInEditor]
public sealed class RigidBodyNode : ActorNode
{
/// <inheritdoc />
public RigidBodyNode(Actor actor)
: base(actor)
{
}
/// <inheritdoc />
public override void PostSpawn()
{
base.PostSpawn();
if (HasPrefabLink)
return;
Actor.StaticFlags = StaticFlags.None;
}
}
}

View File

@@ -324,13 +324,12 @@ namespace FlaxEditor.SceneGraph.GUI
isExpanded = Editor.Instance.ProjectCache.IsExpandedActor(ref id);
}
if (isExpanded)
if (!noFilter)
{
Expand(true);
}
else
{
Collapse(true);
if (isExpanded)
Expand(true);
else
Collapse(true);
}
Visible = isThisVisible | isAnyChildVisible;

View File

@@ -51,6 +51,7 @@ namespace FlaxEditor.SceneGraph
CustomNodesTypes.Add(typeof(AudioSource), typeof(AudioSourceNode));
CustomNodesTypes.Add(typeof(BoneSocket), typeof(BoneSocketNode));
CustomNodesTypes.Add(typeof(Decal), typeof(DecalNode));
CustomNodesTypes.Add(typeof(Cloth), typeof(ClothNode));
CustomNodesTypes.Add(typeof(BoxCollider), typeof(BoxColliderNode));
CustomNodesTypes.Add(typeof(SphereCollider), typeof(ColliderNode));
CustomNodesTypes.Add(typeof(CapsuleCollider), typeof(ColliderNode));
@@ -73,6 +74,7 @@ namespace FlaxEditor.SceneGraph
CustomNodesTypes.Add(typeof(NavMesh), typeof(ActorNode));
CustomNodesTypes.Add(typeof(SpriteRender), typeof(SpriteRenderNode));
CustomNodesTypes.Add(typeof(Joint), typeof(JointNode));
CustomNodesTypes.Add(typeof(RigidBody), typeof(RigidBodyNode));
}
/// <summary>

View File

@@ -102,8 +102,14 @@ namespace FlaxEditor.Surface.Archetypes
outline = style.BorderHighlighted;
else if (_editor._node.SelectedAnimationIndex == _index)
outline = style.BackgroundSelected;
var features = Render2D.Features;
Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping;
Render2D.DrawSprite(icon, rect.MakeExpanded(4.0f), outline);
Render2D.DrawSprite(icon, rect, style.Foreground);
Render2D.Features = features;
}
/// <inheritdoc />
@@ -563,6 +569,9 @@ namespace FlaxEditor.Surface.Archetypes
// Grid
_node.DrawEditorGrid(ref rect);
var features = Render2D.Features;
Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping;
base.Draw();
// Draw debug position
@@ -578,6 +587,8 @@ namespace FlaxEditor.Surface.Archetypes
Render2D.DrawSprite(icon, debugRect, style.ProgressNormal);
}
Render2D.Features = features;
// Frame
var frameColor = containsFocus ? style.BackgroundSelected : (IsMouseOver ? style.ForegroundGrey : style.ForegroundDisabled);
Render2D.DrawRectangle(new Rectangle(1, 1, rect.Width - 2, rect.Height - 2), frameColor);

View File

@@ -79,7 +79,7 @@ namespace FlaxEditor.Surface.Archetypes
: base(id, context, nodeArch, groupArch)
{
var marginX = FlaxEditor.Surface.Constants.NodeMarginX;
var uiStartPosY = FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize;
var uiStartPosY = FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight;
var editButton = new Button(marginX, uiStartPosY, 246, 20)
{

View File

@@ -70,6 +70,9 @@ namespace FlaxEditor.Surface.Archetypes
if (box.ID != _assetBox.ID)
return;
_assetSelect.Visible = !box.HasAnyConnection;
if (!Archetype.Flags.HasFlag(NodeFlags.FixedSize))
ResizeAuto();
}
}
@@ -243,8 +246,8 @@ namespace FlaxEditor.Surface.Archetypes
{
Type = NodeElementType.Input,
Position = new Float2(
FlaxEditor.Surface.Constants.NodeMarginX - FlaxEditor.Surface.Constants.BoxOffsetX,
FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize + ylevel * FlaxEditor.Surface.Constants.LayoutOffsetY),
FlaxEditor.Surface.Constants.NodeMarginX,
FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight + ylevel * FlaxEditor.Surface.Constants.LayoutOffsetY),
Text = "Pose " + _blendPoses.Count,
Single = true,
ValueIndex = -1,
@@ -263,7 +266,7 @@ namespace FlaxEditor.Surface.Archetypes
private void UpdateHeight()
{
float nodeHeight = 10 + (Mathf.Max(_blendPoses.Count, 1) + 3) * FlaxEditor.Surface.Constants.LayoutOffsetY;
Height = nodeHeight + FlaxEditor.Surface.Constants.NodeMarginY * 2 + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.NodeFooterSize;
Height = nodeHeight + FlaxEditor.Surface.Constants.NodeMarginY * 2 + FlaxEditor.Surface.Constants.NodeHeaderHeight + FlaxEditor.Surface.Constants.NodeFooterSize;
}
/// <inheritdoc />
@@ -621,8 +624,8 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new MultiBlend1D(id, context, arch, groupArch),
Title = "Multi Blend 1D",
Description = "Animation blending in 1D",
Flags = NodeFlags.AnimGraph | NodeFlags.VariableValuesSize,
Size = new Float2(420, 300),
Flags = NodeFlags.AnimGraph | NodeFlags.VariableValuesSize | NodeFlags.FixedSize,
Size = new Float2(420, 320),
DefaultValues = new object[]
{
// Node data
@@ -646,9 +649,9 @@ namespace FlaxEditor.Surface.Archetypes
// Axis X
NodeElementArchetype.Factory.Input(3, "X", true, typeof(float), 4),
NodeElementArchetype.Factory.Text(30, 3 * Surface.Constants.LayoutOffsetY + 2, "(min: max: )"),
NodeElementArchetype.Factory.Vector_X(60, 3 * Surface.Constants.LayoutOffsetY + 2, 0),
NodeElementArchetype.Factory.Vector_Y(145, 3 * Surface.Constants.LayoutOffsetY + 2, 0),
NodeElementArchetype.Factory.Text(30, 3 * Surface.Constants.LayoutOffsetY, "(min: max: )"),
NodeElementArchetype.Factory.Vector_X(60, 3 * Surface.Constants.LayoutOffsetY, 0),
NodeElementArchetype.Factory.Vector_Y(145, 3 * Surface.Constants.LayoutOffsetY, 0),
}
},
new NodeArchetype
@@ -657,8 +660,8 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new MultiBlend2D(id, context, arch, groupArch),
Title = "Multi Blend 2D",
Description = "Animation blending in 2D",
Flags = NodeFlags.AnimGraph | NodeFlags.VariableValuesSize,
Size = new Float2(420, 620),
Flags = NodeFlags.AnimGraph | NodeFlags.VariableValuesSize | NodeFlags.FixedSize,
Size = new Float2(420, 640),
DefaultValues = new object[]
{
// Node data
@@ -682,15 +685,15 @@ namespace FlaxEditor.Surface.Archetypes
// Axis X
NodeElementArchetype.Factory.Input(3, "X", true, typeof(float), 4),
NodeElementArchetype.Factory.Text(30, 3 * Surface.Constants.LayoutOffsetY + 2, "(min: max: )"),
NodeElementArchetype.Factory.Vector_X(60, 3 * Surface.Constants.LayoutOffsetY + 2, 0),
NodeElementArchetype.Factory.Vector_Y(145, 3 * Surface.Constants.LayoutOffsetY + 2, 0),
NodeElementArchetype.Factory.Text(30, 3 * Surface.Constants.LayoutOffsetY, "(min: max: )"),
NodeElementArchetype.Factory.Vector_X(60, 3 * Surface.Constants.LayoutOffsetY, 0),
NodeElementArchetype.Factory.Vector_Y(145, 3 * Surface.Constants.LayoutOffsetY, 0),
// Axis Y
NodeElementArchetype.Factory.Input(4, "Y", true, typeof(float), 5),
NodeElementArchetype.Factory.Text(30, 4 * Surface.Constants.LayoutOffsetY + 2, "(min: max: )"),
NodeElementArchetype.Factory.Vector_Z(60, 4 * Surface.Constants.LayoutOffsetY + 2, 0),
NodeElementArchetype.Factory.Vector_W(145, 4 * Surface.Constants.LayoutOffsetY + 2, 0),
NodeElementArchetype.Factory.Text(30, 4 * Surface.Constants.LayoutOffsetY, "(min: max: )"),
NodeElementArchetype.Factory.Vector_Z(60, 4 * Surface.Constants.LayoutOffsetY, 0),
NodeElementArchetype.Factory.Vector_W(145, 4 * Surface.Constants.LayoutOffsetY, 0),
}
},
new NodeArchetype
@@ -931,7 +934,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 27,
Title = "Copy Node",
Description = "Copies the skeleton node transformation data (in local space)",
Flags = NodeFlags.AnimGraph,
Flags = NodeFlags.AnimGraph | NodeFlags.FixedSize,
Size = new Float2(260, 140),
DefaultValues = new object[]
{

View File

@@ -189,13 +189,69 @@ namespace FlaxEditor.Surface.Archetypes
public override void Draw()
{
base.Draw();
var style = Style.Current;
var backgroundRect = new Rectangle(Float2.Zero, Size);
// Shadow
if (DrawBasicShadow)
{
var shadowRect = backgroundRect.MakeOffsetted(ShadowOffset);
Render2D.FillRectangle(shadowRect, Color.Black.AlphaMultiplied(0.125f));
}
// Background
Render2D.FillRectangle(backgroundRect, BackgroundColor);
// Breakpoint hit
if (Breakpoint.Hit)
{
var colorTop = Color.OrangeRed;
var colorBottom = Color.Red;
var time = DateTime.Now - Engine.StartupTime;
Render2D.DrawRectangle(backgroundRect.MakeExpanded(Mathf.Lerp(3.0f, 12.0f, Mathf.Sin((float)time.TotalSeconds * 10.0f) * 0.5f + 0.5f)), colorTop, colorTop, colorBottom, colorBottom, 2.0f);
}
// Header
var headerColor = style.BackgroundHighlighted;
if (_headerRect.Contains(ref _mousePosition) && !Surface.IsConnecting && !Surface.IsSelecting)
headerColor *= 1.07f;
Render2D.FillRectangle(_headerRect, style.BackgroundHighlighted);
Render2D.DrawText(style.FontLarge, Title, _headerTextRect, style.Foreground, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1f, FlaxEditor.Surface.Constants.NodeHeaderTextScale);
// Close button
if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0 && Surface.CanEdit)
{
bool highlightClose = _closeButtonRect.Contains(_mousePosition) && !Surface.IsConnecting && !Surface.IsSelecting;
DrawCloseButton(_closeButtonRect, highlightClose ? style.Foreground : style.ForegroundGrey);
}
DrawChildren();
// Selection outline
if (_isSelected)
{
var colorTop = Color.Orange;
var colorBottom = Color.OrangeRed;
Render2D.DrawRectangle(backgroundRect, colorTop, colorTop, colorBottom, colorBottom, 2.5f);
}
// Breakpoint dot
if (Breakpoint.Set)
{
var icon = Breakpoint.Enabled ? Surface.Style.Icons.BoxClose : Surface.Style.Icons.BoxOpen;
Render2D.DrawSprite(icon, new Rectangle(-7, -7, 16, 16), new Color(0.9f, 0.9f, 0.9f));
Render2D.DrawSprite(icon, new Rectangle(-6, -6, 14, 14), new Color(0.894117647f, 0.0784313725f, 0.0f));
}
if (highlightBox != null)
Render2D.DrawRectangle(highlightBox.Bounds, style.BorderHighlighted, 2f);
// Debug Info
if (!string.IsNullOrEmpty(_debugInfo))
{
var style = Style.Current;
Render2D.DrawText(style.FontSmall, _debugInfo, new Rectangle(4, _headerRect.Bottom + 4, _debugInfoSize), style.Foreground);
// Draw an extra background to cover the archetype color colored node background and make text more legible
Render2D.FillRectangle(new Rectangle(0, _headerRect.Bottom + 4, Width, Height - _headerRect.Bottom - 4), style.BackgroundHighlighted);
Render2D.DrawText(style.FontSmall, _debugInfo, new Rectangle(4, _headerRect.Bottom + 7, _debugInfoSize), style.Foreground, scale: 0.8f);
}
// Debug relevancy outline
@@ -203,7 +259,7 @@ namespace FlaxEditor.Surface.Archetypes
{
var colorTop = Color.LightYellow;
var colorBottom = Color.Yellow;
var backgroundRect = new Rectangle(Float2.One, Size - new Float2(2.0f));
backgroundRect = new Rectangle(Float2.One, Size - new Float2(2.0f));
Render2D.DrawRectangle(backgroundRect, colorTop, colorTop, colorBottom, colorBottom);
}
}
@@ -415,8 +471,8 @@ namespace FlaxEditor.Surface.Archetypes
// Setup boxes
_input = (InputBox)GetBox(0);
_output = (OutputBox)GetBox(1);
_input.ConnectionOffset = new Float2(0, FlaxEditor.Surface.Constants.BoxSize * -0.5f);
_output.ConnectionOffset = new Float2(0, FlaxEditor.Surface.Constants.BoxSize * 0.5f);
_input.ConnectionOffset = new Float2(0, FlaxEditor.Surface.Constants.BoxRowHeight * -0.5f);
_output.ConnectionOffset = new Float2(0, FlaxEditor.Surface.Constants.BoxRowHeight * 0.5f);
// Setup node type and data
var flagsRoot = NodeFlags.NoRemove | NodeFlags.NoCloseButton | NodeFlags.NoSpawnViaPaste;
@@ -515,7 +571,7 @@ namespace FlaxEditor.Surface.Archetypes
height += decorator.Height + DecoratorsMarginY;
width = Mathf.Max(width, decorator.Width - FlaxEditor.Surface.Constants.NodeCloseButtonSize - 2 * DecoratorsMarginX);
}
Size = new Float2(width + FlaxEditor.Surface.Constants.NodeMarginX * 2 + FlaxEditor.Surface.Constants.NodeCloseButtonSize, height + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.NodeFooterSize);
Size = new Float2(width + FlaxEditor.Surface.Constants.NodeMarginX * 2 + FlaxEditor.Surface.Constants.NodeCloseButtonSize, height + FlaxEditor.Surface.Constants.NodeHeaderHeight);
UpdateRectangles();
}
@@ -536,13 +592,13 @@ namespace FlaxEditor.Surface.Archetypes
if (decorator.IndexInParent < indexInParent)
decorator.IndexInParent = indexInParent + 1; // Push elements above the node
}
const float footerSize = FlaxEditor.Surface.Constants.NodeFooterSize;
const float headerSize = FlaxEditor.Surface.Constants.NodeHeaderSize;
const float headerHeight = FlaxEditor.Surface.Constants.NodeHeaderHeight;
const float closeButtonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
const float closeButtonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
_headerRect = new Rectangle(0, bounds.Y - Y, bounds.Width, headerSize);
float closeButtonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize * 0.75f;
_headerRect = new Rectangle(0, bounds.Y - Y, bounds.Width, headerHeight);
_headerTextRect = _headerRect with { X = 5f, Width = Width - closeButtonSize - closeButtonMargin * 4f };
_closeButtonRect = new Rectangle(bounds.Width - closeButtonSize - closeButtonMargin, _headerRect.Y + closeButtonMargin, closeButtonSize, closeButtonSize);
_footerRect = new Rectangle(0, bounds.Height - footerSize, bounds.Width, footerSize);
if (_output != null && _output.Visible)
{
_footerRect.Y -= ConnectionAreaHeight;
@@ -648,6 +704,8 @@ namespace FlaxEditor.Surface.Archetypes
private DragDecorator _dragDecorator;
private float _dragLocation = -1;
internal override bool DrawBasicShadow => false;
internal Decorator(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
@@ -667,7 +725,7 @@ namespace FlaxEditor.Surface.Archetypes
}
}
protected override Color FooterColor => Color.Transparent;
protected override Color ArchetypeColor => Color.Transparent;
protected override Float2 CalculateNodeSize(float width, float height)
{
@@ -676,7 +734,7 @@ namespace FlaxEditor.Surface.Archetypes
width = Mathf.Max(width, _debugInfoSize.X + 8.0f);
height += _debugInfoSize.Y + 8.0f;
}
return new Float2(width + FlaxEditor.Surface.Constants.NodeCloseButtonSize * 2 + DecoratorsMarginX * 2, height + FlaxEditor.Surface.Constants.NodeHeaderSize);
return new Float2(width + FlaxEditor.Surface.Constants.NodeCloseButtonSize * 2 + DecoratorsMarginX * 2, height + FlaxEditor.Surface.Constants.NodeHeaderHeight);
}
protected override void UpdateRectangles()
@@ -684,8 +742,14 @@ namespace FlaxEditor.Surface.Archetypes
base.UpdateRectangles();
_footerRect = Rectangle.Empty;
const float closeButtonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
float closeButtonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize * 0.75f;
_closeButtonRect = new Rectangle(Bounds.Width - closeButtonSize - closeButtonMargin, _headerRect.Y + closeButtonMargin, closeButtonSize, closeButtonSize);
if (_dragIcon != null)
_dragIcon.Bounds = new Rectangle(_closeButtonRect.X - _closeButtonRect.Width, _closeButtonRect.Y, _closeButtonRect.Size);
{
var dragIconRect = _closeButtonRect.MakeExpanded(5f);
_dragIcon.Bounds = new Rectangle(dragIconRect.X - dragIconRect.Width, dragIconRect.Y, dragIconRect.Size);
}
}
protected override void UpdateTitle()
@@ -754,16 +818,21 @@ namespace FlaxEditor.Surface.Archetypes
{
base.OnSurfaceLoaded(action);
var node = Node;
if (action == SurfaceNodeActions.Undo)
{
// Update parent node layout when restoring decorator from undo
var node = Node;
if (node != null)
{
node._decorators = null;
node.ResizeAuto();
}
}
else
{
// Correctly size decorators when surface is loaded
node?.ResizeAuto();
}
}
/// <inheritdoc />

View File

@@ -173,10 +173,10 @@ namespace FlaxEditor.Surface.Archetypes
{
Op(1, "==", "Determines whether two values are equal", new[] { "equals" }),
Op(2, "!=", "Determines whether two values are not equal", new[] { "not equals" }),
Op(3, ">", "Determines whether the first value is greater than the other", new[] { "greater than", "larger than", "bigger than" }),
Op(4, "<", "Determines whether the first value is less than the other", new[] { "less than", "smaller than" }),
Op(5, "<=", "Determines whether the first value is less or equal to the other", new[] { "less equals than", "smaller equals than" }),
Op(6, ">=", "Determines whether the first value is greater or equal to the other", new[] { "greater equals than", "larger equals than", "bigger equals than" }),
Op(3, ">", "Determines whether the first value is greater than the other", new[] { "greater than", "larger than", "bigger than", "more than" }),
Op(4, "<", "Determines whether the first value is less than the other", new[] { "less than", "smaller than", "tinier than" }),
Op(5, "<=", "Determines whether the first value is less or equal to the other", new[] { "less equals than", "smaller equals than", "tinier equals than" }),
Op(6, ">=", "Determines whether the first value is greater or equal to the other", new[] { "greater equals than", "larger equals than", "bigger equals than", "more equals than" }),
new NodeArchetype
{
TypeID = 7,

View File

@@ -166,7 +166,7 @@ namespace FlaxEditor.Surface.Archetypes
_picker = new EnumComboBox(type)
{
EnumTypeValue = Values[0],
Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize, 160, 16),
Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight, 160, FlaxEditor.Surface.Constants.BoxRowHeight),
Parent = this,
};
_picker.ValueChanged += () => SetValue(0, _picker.EnumTypeValue);
@@ -218,7 +218,7 @@ namespace FlaxEditor.Surface.Archetypes
_output = (OutputBox)Elements[0];
_typePicker = new TypePickerControl
{
Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize, 160, 16),
Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight, 160, 16),
Parent = this,
};
_typePicker.ValueChanged += () => Set(3);
@@ -362,7 +362,7 @@ namespace FlaxEditor.Surface.Archetypes
_output = (OutputBox)Elements[0];
_keyTypePicker = new TypePickerControl
{
Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize, 160, 16),
Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight, 160, 16),
Parent = this,
};
_keyTypePicker.ValueChanged += OnKeyTypeChanged;
@@ -482,7 +482,7 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(bool))),
Description = "Constant boolean value",
Flags = NodeFlags.AllGraphs,
Size = new Float2(110, 20),
Size = new Float2(90, 20),
DefaultValues = new object[]
{
false
@@ -515,7 +515,7 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(int))),
Description = "Constant integer value",
Flags = NodeFlags.AllGraphs,
Size = new Float2(110, 20),
Size = new Float2(120, 20),
DefaultValues = new object[]
{
0
@@ -543,7 +543,7 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(float))),
Description = "Constant floating point",
Flags = NodeFlags.AllGraphs,
Size = new Float2(110, 20),
Size = new Float2(120, 20),
DefaultValues = new object[]
{
0.0f
@@ -750,7 +750,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = "PI",
Description = "A value specifying the approximation of π which is 180 degrees",
Flags = NodeFlags.AllGraphs,
Size = new Float2(50, 20),
Size = new Float2(45, 20),
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, "π", typeof(float), 0),
@@ -782,7 +782,7 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(uint))),
Description = "Constant unsigned integer value",
Flags = NodeFlags.AllGraphs,
Size = new Float2(170, 20),
Size = new Float2(130, 20),
DefaultValues = new object[]
{
0u
@@ -824,7 +824,7 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(double))),
Description = "Constant floating point",
Flags = NodeFlags.AllGraphs,
Size = new Float2(110, 20),
Size = new Float2(120, 20),
DefaultValues = new object[]
{
0.0d

View File

@@ -267,7 +267,7 @@ namespace FlaxEditor.Surface.Archetypes
{
_nameField = new Label
{
Width = 140.0f,
Size = new Float2(140, FlaxEditor.Surface.Constants.BoxRowHeight),
TextColorHighlighted = Style.Current.ForegroundGrey,
HorizontalAlignment = TextAlignment.Near,
Parent = this,
@@ -386,8 +386,7 @@ namespace FlaxEditor.Surface.Archetypes
_types = surface.FunctionTypes;
_typePicker = new ComboBox
{
Location = new Float2(4, 32),
Width = 80.0f,
Bounds = new Rectangle(4, 34, 80, FlaxEditor.Surface.Constants.BoxRowHeight),
Parent = this,
};
for (int i = 0; i < _types.Length; i++)
@@ -455,8 +454,7 @@ namespace FlaxEditor.Surface.Archetypes
_types = surface.FunctionTypes;
_typePicker = new ComboBox
{
Location = new Float2(24, 32),
Width = 80.0f,
Bounds = new Rectangle(24, 34, 80, FlaxEditor.Surface.Constants.BoxRowHeight),
Parent = this,
};
for (int i = 0; i < _types.Length; i++)
@@ -631,7 +629,7 @@ namespace FlaxEditor.Surface.Archetypes
}
/// <inheritdoc />
protected override Color FooterColor => new Color(200, 11, 112);
protected override Color ArchetypeColor => new Color(200, 11, 112);
/// <inheritdoc />
public override void OnLoaded(SurfaceNodeActions action)

View File

@@ -52,6 +52,7 @@ namespace FlaxEditor.Surface.Archetypes
},
new NodeArchetype
{
// [Deprecated]
TypeID = 3,
Title = "Pack Material Layer",
Description = "Pack material properties",
@@ -75,6 +76,7 @@ namespace FlaxEditor.Surface.Archetypes
},
new NodeArchetype
{
// [Deprecated]
TypeID = 4,
Title = "Unpack Material Layer",
Description = "Unpack material properties",
@@ -120,6 +122,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 6,
Title = "Pack Material Layer",
Description = "Pack material properties",
AlternativeTitles = new[] { "Make Material Layer", "Construct Material Layer", "Compose Material Layer" },
Flags = NodeFlags.MaterialGraph,
Size = new Float2(200, 280),
Elements = new[]
@@ -146,6 +149,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 7,
Title = "Unpack Material Layer",
Description = "Unpack material properties",
AlternativeTitles = new[] { "Break Material Layer", "Deconstruct Material Layer", "Decompose Material Layer", "Split Material Layer" },
Flags = NodeFlags.MaterialGraph,
Size = new Float2(210, 280),
Elements = new[]

View File

@@ -312,16 +312,17 @@ namespace FlaxEditor.Surface.Archetypes
: base(id, context, nodeArch, groupArch)
{
_sizeValueIndex = Archetype.TypeID == 8 ? 1 : 3; // Index of the Size stored in Values array
Float2 pos = new Float2(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize), size;
Float2 pos = new Float2(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight), size;
if (nodeArch.TypeID == 8)
{
pos += new Float2(60, 0);
size = new Float2(172, 200);
pos += new Float2(65, 0);
size = new Float2(160, 185);
_sizeMin = new Float2(240, 185);
}
else
{
pos += new Float2(0, 40);
size = new Float2(300, 200);
pos += new Float2(0, 40 + FlaxEditor.Utilities.Constants.UIMargin * 2f);
size = new Float2(300, 180);
}
_textBox = new CustomCodeTextBox
{
@@ -506,8 +507,8 @@ namespace FlaxEditor.Surface.Archetypes
Size = new Float2(300, 200),
DefaultValues = new object[]
{
"// Here you can add HLSL code\nOutput0 = Input0;",
new Float2(300, 200),
"// You can add HLSL code here\nOutput0 = Input0;",
new Float2(350, 200),
},
Elements = new[]
{
@@ -931,7 +932,7 @@ namespace FlaxEditor.Surface.Archetypes
new NodeArchetype
{
TypeID = 36,
Title = "HSVToRGB",
Title = "HSV To RGB",
Description = "Converts a HSV value to linear RGB [X = 0/360, Y = 0/1, Z = 0/1]",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(160, 25),
@@ -948,7 +949,7 @@ namespace FlaxEditor.Surface.Archetypes
new NodeArchetype
{
TypeID = 37,
Title = "RGBToHSV",
Title = "RGB To HSV",
Description = "Converts a linear RGB value to HSV [X = 0/360, Y = 0/1, Z = 0/1]",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(160, 25),
@@ -972,7 +973,7 @@ namespace FlaxEditor.Surface.Archetypes
Size = new Float2(300, 240),
DefaultValues = new object[]
{
"// Here you can add HLSL code\nfloat4 GetCustomColor()\n{\n\treturn float4(1, 0, 0, 1);\n}",
"// You can add HLSL code here\nfloat4 GetCustomColor()\n{\n\treturn float4(1, 0, 0, 1);\n}",
true,
(int)MaterialTemplateInputsMapping.Utilities,
new Float2(300, 240),

View File

@@ -213,7 +213,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 30,
Title = "Vector Transform",
Description = "Transform vector from source space to destination space",
Flags = NodeFlags.MaterialGraph,
Flags = NodeFlags.MaterialGraph | NodeFlags.FixedSize,
Size = new Float2(170, 40),
DefaultValues = new object[]
{

View File

@@ -342,6 +342,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 20,
Title = "Pack Float2",
Description = "Pack components to Float2",
AlternativeTitles = new[] { "Make Float2", "Construct Float2", "Compose Float2" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 40),
DefaultValues = new object[]
@@ -361,6 +362,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 21,
Title = "Pack Float3",
Description = "Pack components to Float3",
AlternativeTitles = new[] { "Make Float3", "Construct Float3", "Compose Float3" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 60),
DefaultValues = new object[]
@@ -382,6 +384,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 22,
Title = "Pack Float4",
Description = "Pack components to Float4",
AlternativeTitles = new[] { "Make Float4", "Construct Float4", "Compose Float4" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 80),
DefaultValues = new object[]
@@ -405,6 +408,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 23,
Title = "Pack Rotation",
Description = "Pack components to Rotation",
AlternativeTitles = new[] { "Make Rotation", "Construct Rotation", "Compose Rotation" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 60),
DefaultValues = new object[]
@@ -426,6 +430,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 24,
Title = "Pack Transform",
Description = "Pack components to Transform",
AlternativeTitles = new[] { "Make Transform", "Construct Transform", "Compose Transform" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 80),
Elements = new[]
@@ -441,6 +446,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 25,
Title = "Pack Box",
Description = "Pack components to BoundingBox",
AlternativeTitles = new[] { "Make Box", "Construct Box", "Compose Box" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 40),
Elements = new[]
@@ -454,6 +460,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 26,
Title = "Pack Structure",
AlternativeTitles = new[] { "Make Structure", "Construct Structure", "Compose Structure" },
Create = (id, context, arch, groupArch) => new PackStructureNode(id, context, arch, groupArch),
IsInputCompatible = PackStructureNode.IsInputCompatible,
IsOutputCompatible = PackStructureNode.IsOutputCompatible,
@@ -479,6 +486,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 30,
Title = "Unpack Float2",
Description = "Unpack components from Float2",
AlternativeTitles = new[] { "Break Float2", "Deconstruct Float2", "Decompose Float2", "Split Float2" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 40),
Elements = new[]
@@ -493,6 +501,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 31,
Title = "Unpack Float3",
Description = "Unpack components from Float3",
AlternativeTitles = new[] { "Break Float3", "Deconstruct Float3", "Decompose Float3", "Split Float3" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 60),
Elements = new[]
@@ -508,6 +517,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 32,
Title = "Unpack Float4",
Description = "Unpack components from Float4",
AlternativeTitles = new[] { "Break Float4", "Deconstruct Float4", "Decompose Float4", "Split Float4" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 80),
Elements = new[]
@@ -524,6 +534,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 33,
Title = "Unpack Rotation",
Description = "Unpack components from Rotation",
AlternativeTitles = new[] { "Break Rotation", "Deconstruct Rotation", "Decompose Rotation", "Split Rotation" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(170, 60),
Elements = new[]
@@ -539,6 +550,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 34,
Title = "Unpack Transform",
Description = "Unpack components from Transform",
AlternativeTitles = new[] { "Break Transform", "Deconstruct Transform", "Decompose Transform", "Split Transform" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(170, 60),
Elements = new[]
@@ -554,6 +566,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 35,
Title = "Unpack Box",
Description = "Unpack components from BoundingBox",
AlternativeTitles = new[] { "Break BoundingBox", "Deconstruct BoundingBox", "Decompose BoundingBox", "Split BoundingBox" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(170, 40),
Elements = new[]
@@ -572,6 +585,7 @@ namespace FlaxEditor.Surface.Archetypes
IsOutputCompatible = UnpackStructureNode.IsOutputCompatible,
GetInputOutputDescription = UnpackStructureNode.GetInputOutputDescription,
Description = "Breaks the structure data to allow extracting components from it.",
AlternativeTitles = new[] { "Break Structure", "Deconstruct Structure", "Decompose Structure", "Split Structure" },
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI,
Size = new Float2(180, 20),
DefaultValues = new object[]

View File

@@ -13,6 +13,7 @@ using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEngine;
using FlaxEngine.Utilities;
using FlaxEditor.Surface.Undo;
namespace FlaxEditor.Surface.Archetypes
{
@@ -22,15 +23,107 @@ namespace FlaxEditor.Surface.Archetypes
[HideInEditor]
public static class Parameters
{
/// <summary>
/// Surface node type for parameters group Get/Set nodes.
/// </summary>
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" />
public abstract class SurfaceNodeParamsBase : SurfaceNode
{
/// <summary>
/// The combobox for picking parameter.
/// </summary>
protected ComboBoxElement _combobox;
/// <summary>
/// Fag used to block layout updated when updating node.
/// </summary>
protected bool _isUpdateLocked;
/// <inheritdoc />
protected SurfaceNodeParamsBase(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
}
/// <summary>
/// Gets the selected parameter.
/// </summary>
/// <returns>Surface parameter object or null if nothing selected or cannot find it.</returns>
protected SurfaceParameter GetSelected()
{
if (Surface != null)
return Surface.GetParameter(_combobox.SelectedItem);
return Context.GetParameter((Guid)Values[0]);
}
/// <summary>
/// Updates the combo box.
/// </summary>
protected void UpdateCombo()
{
if (_isUpdateLocked)
return;
_isUpdateLocked = true;
if (_combobox == null)
{
_combobox = GetChild<ComboBoxElement>();
_combobox.SelectedIndexChanged += OnSelectedChanged;
}
string toSelect = null;
Guid loadedSelected = (Guid)Values[0];
_combobox.ClearItems();
var parameters = Surface.Parameters;
for (int i = 0; i < parameters.Count; i++)
{
var param = parameters[i];
if (!param.IsPublic && !Surface.CanShowPrivateParameters)
continue;
_combobox.AddItem(param.Name);
if (param.ID == loadedSelected)
toSelect = param.Name;
}
_combobox.SelectedItem = toSelect;
_isUpdateLocked = false;
}
private void OnSelectedChanged(ComboBox cb)
{
if (_isUpdateLocked)
return;
var selected = GetSelected();
var selectedID = selected?.ID ?? Guid.Empty;
if (selectedID != (Guid)Values[0])
{
if (Surface.Undo != null && Surface.Undo.Enabled)
{
// Capture node connections to support undo
var action = new EditNodeConnections(Context, this);
RemoveConnections();
action.End();
Surface.AddBatchedUndoAction(action);
}
Set(selected, ref selectedID);
}
}
/// <summary>
/// Sets the selected parameter.
/// </summary>
/// <param name="selected">Parameter.</param>
/// <param name="selectedID">Parameter identifier.</param>
protected virtual void Set(SurfaceParameter selected, ref Guid selectedID)
{
SetValue(0, selectedID);
}
}
/// <summary>
/// Surface node type for parameters group Get node.
/// </summary>
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" />
public class SurfaceNodeParamsGet : SurfaceNode, IParametersDependantNode
public class SurfaceNodeParamsGet : SurfaceNodeParamsBase, IParametersDependantNode
{
private ComboBoxElement _combobox;
private readonly List<ISurfaceNodeElement> _dynamicChildren = new List<ISurfaceNodeElement>();
private bool _isUpdateLocked;
private ScriptType _layoutType;
private NodeElementArchetype[] _layoutElements;
@@ -306,49 +399,6 @@ namespace FlaxEditor.Surface.Archetypes
UpdateTitle();
}
private void UpdateCombo()
{
if (_isUpdateLocked)
return;
_isUpdateLocked = true;
if (_combobox == null)
{
_combobox = (ComboBoxElement)_children[0];
_combobox.SelectedIndexChanged += OnSelectedChanged;
}
int toSelect = -1;
Guid loadedSelected = (Guid)Values[0];
_combobox.ClearItems();
for (int i = 0; i < Surface.Parameters.Count; i++)
{
var param = Surface.Parameters[i];
_combobox.AddItem(param.Name);
if (param.ID == loadedSelected)
toSelect = i;
}
_combobox.SelectedIndex = toSelect;
_isUpdateLocked = false;
}
private void OnSelectedChanged(ComboBox cb)
{
if (_isUpdateLocked)
return;
var selected = GetSelected();
var selectedID = selected?.ID ?? Guid.Empty;
SetValue(0, selectedID);
}
private SurfaceParameter GetSelected()
{
if (Surface != null)
{
var selectedIndex = _combobox.SelectedIndex;
return selectedIndex >= 0 && selectedIndex < Surface.Parameters.Count ? Surface.Parameters[selectedIndex] : null;
}
return Context.GetParameter((Guid)Values[0]);
}
private void ClearDynamicElements()
{
for (int i = 0; i < _dynamicChildren.Count; i++)
@@ -463,15 +513,19 @@ namespace FlaxEditor.Surface.Archetypes
else if (!_isUpdateLocked)
{
_isUpdateLocked = true;
int toSelect = -1;
string toSelect = null;
Guid loadedSelected = (Guid)Values[0];
for (int i = 0; i < Surface.Parameters.Count; i++)
var parameters = Surface.Parameters;
for (int i = 0; i < parameters.Count; i++)
{
var param = Surface.Parameters[i];
var param = parameters[i];
if (param.ID == loadedSelected)
toSelect = i;
{
toSelect = param.Name;
break;
}
}
_combobox.SelectedIndex = toSelect;
_combobox.SelectedItem = toSelect;
_isUpdateLocked = false;
}
UpdateLayout();
@@ -817,66 +871,23 @@ namespace FlaxEditor.Surface.Archetypes
/// Surface node type for parameters group Set node.
/// </summary>
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" />
public class SurfaceNodeParamsSet : SurfaceNode, IParametersDependantNode
public class SurfaceNodeParamsSet : SurfaceNodeParamsBase, IParametersDependantNode
{
private ComboBoxElement _combobox;
private bool _isUpdateLocked;
/// <inheritdoc />
public SurfaceNodeParamsSet(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
}
private void UpdateCombo()
/// <inheritdoc />
protected override void Set(SurfaceParameter selected, ref Guid selectedID)
{
if (_isUpdateLocked)
return;
_isUpdateLocked = true;
if (_combobox == null)
SetValues(new[]
{
_combobox = GetChild<ComboBoxElement>();
_combobox.SelectedIndexChanged += OnSelectedChanged;
}
int toSelect = -1;
Guid loadedSelected = (Guid)Values[0];
_combobox.ClearItems();
for (int i = 0; i < Surface.Parameters.Count; i++)
{
var param = Surface.Parameters[i];
_combobox.AddItem(param.Name);
if (param.ID == loadedSelected)
toSelect = i;
}
_combobox.SelectedIndex = toSelect;
_isUpdateLocked = false;
}
private void OnSelectedChanged(ComboBox cb)
{
if (_isUpdateLocked)
return;
var selected = GetSelected();
var selectedID = selected?.ID ?? Guid.Empty;
if (selectedID != (Guid)Values[0])
{
SetValues(new[]
{
selectedID,
selected != null ? TypeUtils.GetDefaultValue(selected.Type) : null,
});
UpdateUI();
}
}
private SurfaceParameter GetSelected()
{
if (Surface != null)
{
var selectedIndex = _combobox.SelectedIndex;
return selectedIndex >= 0 && selectedIndex < Surface.Parameters.Count ? Surface.Parameters[selectedIndex] : null;
}
return Context.GetParameter((Guid)Values[0]);
selectedID,
selected != null ? TypeUtils.GetDefaultValue(selected.Type) : null,
});
UpdateUI();
}
/// <inheritdoc />

View File

@@ -121,7 +121,7 @@ namespace FlaxEditor.Surface.Archetypes
Render2D.DrawRectangle(new Rectangle(1, 0, Width - 2, Height - 1), Colors[idx]);
// Close button
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
DrawCloseButton(_closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
// Arrange button
var dragBarColor = _arrangeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey;
@@ -138,6 +138,12 @@ namespace FlaxEditor.Surface.Archetypes
}
}
/// <inheritdoc />
public override void Resize(float width, float height)
{
// Do nothing so module does not change size
}
private bool ArrangeAreaCheck(out int index, out Rectangle rect)
{
var barSidesExtend = 20.0f;
@@ -261,9 +267,9 @@ namespace FlaxEditor.Surface.Archetypes
const float closeButtonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
const float closeButtonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
_headerRect = new Rectangle(0, 0, Width, headerSize);
_closeButtonRect = new Rectangle(Width - closeButtonSize - closeButtonMargin, closeButtonMargin, closeButtonSize, closeButtonSize);
_closeButtonRect = new Rectangle(Width - closeButtonSize * 0.75f - closeButtonMargin, closeButtonMargin + 0.25f, closeButtonSize * 0.75f, closeButtonSize * 0.75f);
_footerRect = Rectangle.Empty;
_enabled.Location = new Float2(_closeButtonRect.X - _enabled.Width - 2, _closeButtonRect.Y);
_enabled.Location = new Float2(_closeButtonRect.X - _enabled.Width - 2, _closeButtonRect.Y - 0.25f);
_arrangeButtonRect = new Rectangle(_enabled.X - closeButtonSize - closeButtonMargin, closeButtonMargin, closeButtonSize, closeButtonSize);
}
@@ -461,7 +467,7 @@ namespace FlaxEditor.Surface.Archetypes
/// <summary>
/// The particle module node elements offset applied to controls to reduce default surface node header thickness.
/// </summary>
private const float NodeElementsOffset = 16.0f - Surface.Constants.NodeHeaderSize;
private const float NodeElementsOffset = 16.0f - Surface.Constants.NodeHeaderHeight;
private const NodeFlags DefaultModuleFlags = NodeFlags.ParticleEmitterGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoMove;

View File

@@ -74,7 +74,7 @@ namespace FlaxEditor.Surface.Archetypes
/// <summary>
/// The header height.
/// </summary>
public const float HeaderHeight = FlaxEditor.Surface.Constants.NodeHeaderSize;
public const float HeaderHeight = FlaxEditor.Surface.Constants.NodeHeaderHeight;
/// <summary>
/// Gets the type of the module.
@@ -199,7 +199,7 @@ namespace FlaxEditor.Surface.Archetypes
DrawChildren();
// Options border
var optionsAreaStart = FlaxEditor.Surface.Constants.NodeHeaderSize + 3.0f;
var optionsAreaStart = FlaxEditor.Surface.Constants.NodeHeaderHeight + 3.0f;
var optionsAreaHeight = 7 * FlaxEditor.Surface.Constants.LayoutOffsetY + 6.0f;
Render2D.DrawRectangle(new Rectangle(1, optionsAreaStart, Width - 2, optionsAreaHeight), style.BackgroundSelected);
@@ -340,7 +340,7 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new ParticleEmitterNode(id, context, arch, groupArch),
Title = "Particle Emitter",
Description = "Main particle emitter node. Contains a set of modules per emitter context. Modules are executed in order from top to bottom of the stack.",
Flags = NodeFlags.ParticleEmitterGraph | NodeFlags.NoRemove | NodeFlags.NoSpawnViaGUI | NodeFlags.NoSpawnViaPaste | NodeFlags.NoCloseButton,
Flags = NodeFlags.ParticleEmitterGraph | NodeFlags.NoRemove | NodeFlags.NoSpawnViaGUI | NodeFlags.NoSpawnViaPaste | NodeFlags.NoCloseButton | NodeFlags.FixedSize,
Size = new Float2(300, 600),
DefaultValues = new object[]
{

View File

@@ -57,7 +57,7 @@ namespace FlaxEditor.Surface.Archetypes
{
_textureGroupPicker = new ComboBox
{
Location = new Float2(FlaxEditor.Surface.Constants.NodeMarginX + 50, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.LayoutOffsetY * _level),
Location = new Float2(FlaxEditor.Surface.Constants.NodeMarginX + 50, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight + FlaxEditor.Surface.Constants.LayoutOffsetY * _level),
Width = 100,
Parent = this,
};
@@ -133,8 +133,8 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Texture",
Create = (id, context, arch, groupArch) => new Constants.ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Texture))),
Description = "Two dimensional texture object",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(140, 120),
Flags = NodeFlags.MaterialGraph | NodeFlags.FixedSize,
Size = new Float2(140, 140),
DefaultValues = new object[]
{
Guid.Empty
@@ -158,7 +158,7 @@ namespace FlaxEditor.Surface.Archetypes
AlternativeTitles = new string[] { "UV", "UVs" },
Description = "Texture coordinates",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(150, 30),
Size = new Float2(160, 20),
DefaultValues = new object[]
{
0u
@@ -176,8 +176,8 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Cube Texture",
Create = (id, context, arch, groupArch) => new Constants.ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(CubeTexture))),
Description = "Set of 6 textures arranged in a cube",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(140, 120),
Flags = NodeFlags.MaterialGraph | NodeFlags.FixedSize,
Size = new Float2(140, 140),
DefaultValues = new object[]
{
Guid.Empty
@@ -239,7 +239,7 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(3, "Max Steps", true, typeof(float), 3, 2),
NodeElementArchetype.Factory.Input(4, "Heightmap Texture", true, typeof(FlaxEngine.Object), 4),
NodeElementArchetype.Factory.Output(0, "Parallax UVs", typeof(Float2), 5),
NodeElementArchetype.Factory.Text(Surface.Constants.BoxSize + 4, 5 * Surface.Constants.LayoutOffsetY, "Channel"),
NodeElementArchetype.Factory.Text(Surface.Constants.BoxRowHeight + 4, 5 * Surface.Constants.LayoutOffsetY, "Channel"),
NodeElementArchetype.Factory.ComboBox(70, 5 * Surface.Constants.LayoutOffsetY, 50, 3, new[]
{
"R",
@@ -493,7 +493,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 18,
Title = "Lightmap UV",
AlternativeTitles = new string[] { "Lightmap TexCoord" },
AlternativeTitles = new string[] { "Lightmap TexCoord" },
Description = "Lightmap UVs",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(110, 20),

View File

@@ -467,7 +467,7 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new CurveNode<T>(id, context, arch, groupArch),
Description = "An animation spline represented by a set of keyframes, each representing an endpoint of a Bezier curve.",
Flags = NodeFlags.AllGraphs,
Size = new Float2(400, 180),
Size = new Float2(400, 180.0f),
DefaultValues = new object[]
{
// Keyframes count
@@ -485,7 +485,7 @@ namespace FlaxEditor.Surface.Archetypes
zero, // Tangent In
zero, // Tangent Out
// Empty keys zero-6
// Empty keys 0-6
0.0f, zero, zero, zero,
0.0f, zero, zero, zero,
0.0f, zero, zero, zero,
@@ -515,13 +515,12 @@ namespace FlaxEditor.Surface.Archetypes
base.OnLoaded(action);
// Create curve editor
var upperLeft = GetBox(0).BottomLeft;
var upperRight = GetBox(1).BottomRight;
float curveMargin = 20.0f;
var upperLeft = GetBox(0).BottomRight;
var upperRight = GetBox(1).BottomLeft;
_curve = new BezierCurveEditor<T>
{
MaxKeyframes = 7,
Bounds = new Rectangle(upperLeft + new Float2(curveMargin, 10.0f), upperRight.X - upperLeft.X - curveMargin * 2.0f, 140.0f),
Bounds = new Rectangle(upperLeft + new Float2(0f, 10.0f), upperRight.X - upperLeft.X - 8.0f, 135.0f),
Parent = this,
AnchorMax = Float2.One,
};
@@ -841,7 +840,7 @@ namespace FlaxEditor.Surface.Archetypes
_picker = new TypePickerControl
{
Type = ScriptType.FlaxObject,
Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX + 20, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize, 160, 16),
Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX + 20, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight, 160, 16),
Parent = this,
};
_picker.ValueChanged += () => SetValue(0, _picker.ValueTypeName);
@@ -910,7 +909,7 @@ namespace FlaxEditor.Surface.Archetypes
_picker = new TypePickerControl
{
Type = ScriptType.Object,
Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize, 140, 16),
Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight, 140, 16),
Parent = this,
};
_picker.ValueChanged += () => SetValue(0, _picker.ValueTypeName);
@@ -961,7 +960,7 @@ namespace FlaxEditor.Surface.Archetypes
_picker = new TypePickerControl
{
Type = ScriptType.FlaxObject,
Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX + 20, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize, 160, 16),
Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX + 20, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight, 160, 16),
Parent = this,
};
_picker.ValueChanged += () => SetValue(0, _picker.ValueTypeName);
@@ -1012,7 +1011,7 @@ namespace FlaxEditor.Surface.Archetypes
_picker = new TypePickerControl
{
Type = type,
Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX + 20, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize, 160, 16),
Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX + 20, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight, 160, 16),
Parent = this,
};
_picker.ValueChanged += () => SetValue(0, _picker.ValueTypeName);
@@ -1071,7 +1070,11 @@ namespace FlaxEditor.Surface.Archetypes
internal class RerouteNode : SurfaceNode, IConnectionInstigator
{
internal static readonly Float2 DefaultSize = new Float2(FlaxEditor.Surface.Constants.BoxSize);
internal static readonly Float2 DefaultSize = new Float2(FlaxEditor.Surface.Constants.BoxRowHeight);
internal bool DrawDisabled => _input.AllConnectionsDisabled || _output.AllConnectionsDisabled;
internal override bool DrawBasicShadow => false;
private Rectangle _localBounds;
private InputBox _input;
private OutputBox _output;
@@ -1174,9 +1177,18 @@ namespace FlaxEditor.Surface.Archetypes
_footerRect = Rectangle.Empty;
}
/// <inheritdoc />
public override void Resize(float width, float height)
{
// Do nothing so the input and output boxes do not change position
}
/// <inheritdoc />
public override void Draw()
{
// Update active state of input
_input.IsActive = !_output.AllConnectionsDisabled;
var style = Surface.Style;
var connectionColor = style.Colors.Default;
var type = ScriptType.Null;
@@ -1190,6 +1202,10 @@ namespace FlaxEditor.Surface.Archetypes
Surface.Style.GetConnectionColor(type, hints, out connectionColor);
}
// Draw the box as disabled if needed
if (DrawDisabled)
connectionColor = connectionColor * 0.6f;
if (!_input.HasAnyConnection)
Render2D.FillRectangle(new Rectangle(-barHorizontalOffset - barHeight * 2, (DefaultSize.Y - barHeight) / 2, barHeight * 2, barHeight), connectionColor);
if (!_output.HasAnyConnection)
@@ -1200,6 +1216,11 @@ namespace FlaxEditor.Surface.Archetypes
icon = type.IsVoid ? style.Icons.ArrowClose : style.Icons.BoxClose;
else
icon = type.IsVoid ? style.Icons.ArrowOpen : style.Icons.BoxOpen;
// Shadow
var shadowRect = _localBounds.MakeOffsetted(ShadowOffset);
Render2D.DrawSprite(icon, shadowRect, Color.Black.AlphaMultiplied(0.125f));
Render2D.DrawSprite(icon, _localBounds, connectionColor);
base.Draw();
@@ -1444,8 +1465,8 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 6,
Title = "Panner",
Description = "Animates UVs over time",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(170, 80),
Flags = NodeFlags.MaterialGraph | NodeFlags.FixedSize,
Size = new Float2(170, 96),
DefaultValues = new object[]
{
false
@@ -1455,7 +1476,7 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
NodeElementArchetype.Factory.Input(1, "Time", true, typeof(float), 1),
NodeElementArchetype.Factory.Input(2, "Speed", true, typeof(Float2), 2),
NodeElementArchetype.Factory.Text(18, Surface.Constants.LayoutOffsetY * 3 + 5, "Fractional Part"),
NodeElementArchetype.Factory.Text(20, Surface.Constants.LayoutOffsetY * 3 + 5, "Fractional Part"),
NodeElementArchetype.Factory.Bool(0, Surface.Constants.LayoutOffsetY * 3 + 5, 0),
NodeElementArchetype.Factory.Output(0, "", typeof(Float2), 3)
}
@@ -1505,7 +1526,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Color Gradient",
Create = (id, context, arch, groupArch) => new ColorGradientNode(id, context, arch, groupArch),
Description = "Linear color gradient sampler",
Flags = NodeFlags.AllGraphs,
Flags = NodeFlags.AllGraphs | NodeFlags.FixedSize,
Size = new Float2(400, 150.0f),
DefaultValues = new object[]
{
@@ -1825,7 +1846,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Reroute",
Create = (id, context, arch, groupArch) => new RerouteNode(id, context, arch, groupArch),
Description = "Reroute a connection.",
Flags = NodeFlags.NoCloseButton | NodeFlags.NoSpawnViaGUI | NodeFlags.AllGraphs,
Flags = NodeFlags.NoCloseButton | NodeFlags.NoSpawnViaGUI | NodeFlags.AllGraphs | NodeFlags.FixedSize,
Size = RerouteNode.DefaultSize,
ConnectionsHints = ConnectionsHint.All,
IndependentBoxes = new int[] { 0 },

View File

@@ -13,46 +13,61 @@ namespace FlaxEditor.Surface
/// <summary>
/// The node close button size.
/// </summary>
public const float NodeCloseButtonSize = 12.0f;
public const float NodeCloseButtonSize = 10.0f;
/// <summary>
/// The node close button margin from the edges.
/// </summary>
public const float NodeCloseButtonMargin = 2.0f;
public const float NodeCloseButtonMargin = 5.0f;
/// <summary>
/// The node header height.
/// </summary>
public const float NodeHeaderSize = 28.0f;
public const float NodeHeaderHeight = 25.0f;
/// <summary>
/// The scale of the header text.
/// </summary>
public const float NodeHeaderTextScale = 0.8f;
/// <summary>
/// The node footer height.
/// </summary>
public const float NodeFooterSize = 4.0f;
public const float NodeFooterSize = 2.0f;
/// <summary>
/// The node left margin.
/// The horizontal node margin.
/// </summary>
public const float NodeMarginX = 5.0f;
public const float NodeMarginX = 6.0f;
/// <summary>
/// The node right margin.
/// The vertical node right margin.
/// </summary>
public const float NodeMarginY = 5.0f;
public const float NodeMarginY = 8.0f;
/// <summary>
/// The box position offset on the x axis.
/// The size of the row that is started by a box.
/// </summary>
public const float BoxOffsetX = 2.0f;
public const float BoxRowHeight = 19.0f;
/// <summary>
/// The box size (with and height).
/// </summary>
public const float BoxSize = 20.0f;
public const float BoxSize = 15.0f;
/// <summary>
/// The node layout offset on the y axis (height of the boxes rows, etc.). It's used to make the design more consistent.
/// </summary>
public const float LayoutOffsetY = 20.0f;
public const float LayoutOffsetY = 24.0f;
/// <summary>
/// The offset between the box text and the box
/// </summary>
public const float BoxTextOffset = 1.65f;
/// <summary>
/// The width of the rectangle used to draw the box text.
/// </summary>
public const float BoxTextRectWidth = 500.0f;
}
}

View File

@@ -585,7 +585,7 @@ namespace FlaxEditor.Surface.ContextMenu
private void UpdateFilters()
{
if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBoxes[0] == null)
if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBoxes.Count == 0)
{
ResetView();
Profiler.EndEvent();

View File

@@ -33,7 +33,7 @@ namespace FlaxEditor.Surface.Elements
/// <inheritdoc />
public BoolValue(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(parentNode, archetype, archetype.ActualPosition, new Float2(16), true)
: base(parentNode, archetype, archetype.ActualPosition, new Float2(Constants.BoxRowHeight), true)
{
}

View File

@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Undo;
using FlaxEngine;
@@ -194,9 +195,19 @@ namespace FlaxEditor.Surface.Elements
set => _isActive = value;
}
/// <summary>
/// Gets if the box is disabled (user can still connect, but connections will be ignored).
/// </summary>
public bool IsDisabled => !(Enabled && IsActive);
/// <summary>
/// Gets a value indicating whether all connections are disabled.
/// </summary>
public bool AllConnectionsDisabled => Connections.All(c => c.IsDisabled);
/// <inheritdoc />
protected Box(SurfaceNode parentNode, NodeElementArchetype archetype, Float2 location)
: base(parentNode, archetype, location, new Float2(Constants.BoxSize), false)
: base(parentNode, archetype, location, new Float2(Constants.BoxRowHeight), false)
{
_currentType = DefaultType;
_isSingle = Archetype.Single;
@@ -352,10 +363,10 @@ namespace FlaxEditor.Surface.Elements
Assert.AreEqual(r1, r2);
// Update
ConnectionTick();
box.ConnectionTick();
OnConnectionsChanged();
box.OnConnectionsChanged();
ConnectionTick();
box.ConnectionTick();
Surface?.OnNodesDisconnected(this, box);
}
@@ -379,10 +390,10 @@ namespace FlaxEditor.Surface.Elements
Assert.IsTrue(AreConnected(box));
// Update
ConnectionTick();
box.ConnectionTick();
OnConnectionsChanged();
box.OnConnectionsChanged();
ConnectionTick();
box.ConnectionTick();
Surface?.OnNodesConnected(this, box);
}

View File

@@ -28,6 +28,7 @@ namespace FlaxEditor.Surface.Elements
public ColorValue(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(Get(parentNode, archetype), archetype.Position.X, archetype.Position.Y)
{
Height = Constants.BoxRowHeight;
ParentNode = parentNode;
Archetype = archetype;
UseDynamicEditing = false;

View File

@@ -33,6 +33,7 @@ namespace FlaxEditor.Surface.Elements
public ComboBoxElement(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(archetype.ActualPositionX, archetype.ActualPositionY, archetype.Size.X)
{
Height = Constants.BoxRowHeight;
ParentNode = parentNode;
Archetype = archetype;

View File

@@ -2,6 +2,7 @@
using System;
using FlaxEditor.GUI;
using FlaxEngine;
using FlaxEngine.Utilities;
namespace FlaxEditor.Surface.Elements
@@ -29,9 +30,7 @@ namespace FlaxEditor.Surface.Elements
public EnumValue(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(TypeUtils.GetType(archetype.Text).Type)
{
X = archetype.ActualPositionX;
Y = archetype.ActualPositionY;
Width = archetype.Size.X;
Bounds = new Rectangle(archetype.ActualPositionX, archetype.ActualPositionY, archetype.Size.X, Constants.BoxRowHeight);
ParentNode = parentNode;
Archetype = archetype;
Value = Convert.ToInt32(ParentNode.Values[Archetype.ValueIndex]);

View File

@@ -29,6 +29,7 @@ namespace FlaxEditor.Surface.Elements
public FloatValue(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(Get(parentNode, archetype), archetype.Position.X, archetype.Position.Y, 50, archetype.ValueMin, archetype.ValueMax, 0.01f)
{
Height = Constants.BoxRowHeight;
ParentNode = parentNode;
Archetype = archetype;

View File

@@ -120,7 +120,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = IntegerValue.Get(box.ParentNode, box.Archetype, box.Value);
var width = 40;
var width = 50;
var control = new IntValueBox(value, bounds.X, bounds.Y, width + 12, int.MinValue, int.MaxValue, 0.01f)
{
Height = bounds.Height,
@@ -166,7 +166,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = UnsignedIntegerValue.Get(box.ParentNode, box.Archetype, box.Value);
var width = 40;
var width = 50;
var control = new UIntValueBox(value, bounds.X, bounds.Y, width + 12, uint.MinValue, uint.MaxValue, 0.01f)
{
Height = bounds.Height,
@@ -212,7 +212,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = FloatValue.Get(box.ParentNode, box.Archetype, box.Value);
var width = 40;
var width = 50;
var control = new FloatValueBox(value, bounds.X, bounds.Y, width + 12, float.MinValue, float.MaxValue, 0.01f)
{
Height = bounds.Height,
@@ -303,7 +303,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
var width = 30;
var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 2 - 2, bounds.Height)
{
ClipChildren = false,
@@ -377,7 +377,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
var width = 30;
var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 3 - 2, bounds.Height)
{
ClipChildren = false,
@@ -460,7 +460,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
var width = 20;
var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 4 - 2, bounds.Height)
{
ClipChildren = false,
@@ -553,7 +553,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
var width = 30;
var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 2 - 2, bounds.Height)
{
ClipChildren = false,
@@ -627,7 +627,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
var width = 30;
var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 3 - 2, bounds.Height)
{
ClipChildren = false,
@@ -710,7 +710,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
var width = 20;
var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 4 - 2, bounds.Height)
{
ClipChildren = false,
@@ -803,7 +803,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
var width = 30;
var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 2 - 2, bounds.Height)
{
ClipChildren = false,
@@ -877,7 +877,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
var width = 30;
var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 3 - 2, bounds.Height)
{
ClipChildren = false,
@@ -960,7 +960,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
var width = 20;
var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 4 - 2, bounds.Height)
{
ClipChildren = false,
@@ -1053,7 +1053,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box).EulerAngles;
var width = 20;
var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 3 - 2, bounds.Height)
{
ClipChildren = false,
@@ -1442,8 +1442,8 @@ namespace FlaxEditor.Surface.Elements
// Draw text
var style = Style.Current;
var rect = new Rectangle(Width + 4, 0, 1410, Height);
Render2D.DrawText(style.FontSmall, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center);
var rect = new Rectangle(Width + Constants.BoxTextOffset, 0, Constants.BoxTextRectWidth, Height);
Render2D.DrawText(style.FontMedium, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center);
}
/// <inheritdoc />

View File

@@ -32,6 +32,7 @@ namespace FlaxEditor.Surface.Elements
public IntegerValue(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(Get(parentNode, archetype), archetype.Position.X, archetype.Position.Y, 50, (int)archetype.ValueMin, (int)archetype.ValueMax, 0.05f)
{
Height = Constants.BoxRowHeight;
ParentNode = parentNode;
Archetype = archetype;

View File

@@ -34,11 +34,6 @@ namespace FlaxEditor.Surface.Elements
/// </summary>
public const float DefaultConnectionOffset = 24f;
/// <summary>
/// Distance for the mouse to be considered above the connection
/// </summary>
public float MouseOverConnectionDistance => 100f / Surface.ViewScale;
/// <inheritdoc />
public OutputBox(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(parentNode, archetype, archetype.Position + new Float2(parentNode.Archetype.Size.X, 0))
@@ -109,12 +104,13 @@ namespace FlaxEditor.Surface.Elements
/// </summary>
/// <param name="targetBox">The other box.</param>
/// <param name="mousePosition">The mouse position</param>
public bool IntersectsConnection(Box targetBox, ref Float2 mousePosition)
/// <param name="distance">Distance at which its an intersection</param>
public bool IntersectsConnection(Box targetBox, ref Float2 mousePosition, float distance)
{
float connectionOffset = Mathf.Max(0f, DefaultConnectionOffset * (1 - Editor.Instance.Options.Options.Interface.ConnectionCurvature));
Float2 start = new Float2(ConnectionOrigin.X + connectionOffset, ConnectionOrigin.Y);
Float2 end = new Float2(targetBox.ConnectionOrigin.X - connectionOffset, targetBox.ConnectionOrigin.Y);
return IntersectsConnection(ref start, ref end, ref mousePosition, MouseOverConnectionDistance);
return IntersectsConnection(ref start, ref end, ref mousePosition, distance);
}
/// <summary>
@@ -182,7 +178,7 @@ namespace FlaxEditor.Surface.Elements
{
// Draw all the connections
var style = Surface.Style;
var mouseOverDistance = MouseOverConnectionDistance;
var mouseOverDistance = Surface.MouseOverConnectionDistance;
var startPos = ConnectionOrigin;
var startHighlight = ConnectionsHighlightIntensity;
for (int i = 0; i < Connections.Count; i++)
@@ -190,7 +186,7 @@ namespace FlaxEditor.Surface.Elements
Box targetBox = Connections[i];
var endPos = targetBox.ConnectionOrigin;
var highlight = DefaultConnectionThickness + Mathf.Max(startHighlight, targetBox.ConnectionsHighlightIntensity);
var alpha = targetBox.Enabled && targetBox.IsActive ? 1.0f : 0.6f;
var alpha = targetBox.IsDisabled ? 0.6f : 1.0f;
// We have to calculate an offset here to preserve the original color for when the default connection thickness is larger than 1
var highlightOffset = (highlight - (DefaultConnectionThickness - 1));
@@ -216,7 +212,7 @@ namespace FlaxEditor.Surface.Elements
// Draw all the connections
var startPos = ConnectionOrigin;
var endPos = targetBox.ConnectionOrigin;
var alpha = targetBox.Enabled && targetBox.IsActive ? 1.0f : 0.6f;
var alpha = targetBox.IsDisabled ? 0.6f : 1.0f;
var color = _currentTypeColor * alpha;
DrawConnection(Surface.Style, ref startPos, ref endPos, ref color, SelectedConnectionThickness);
}
@@ -234,8 +230,8 @@ namespace FlaxEditor.Surface.Elements
// Draw text
var style = Style.Current;
var rect = new Rectangle(-100, 0, 100 - 2, Height);
Render2D.DrawText(style.FontSmall, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Far, TextAlignment.Center);
var rect = new Rectangle(-Constants.BoxTextRectWidth - Constants.BoxTextOffset * 2f, 0f, Constants.BoxTextRectWidth, Height);
Render2D.DrawText(style.FontMedium, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Far, TextAlignment.Center);
}
}
}

View File

@@ -24,6 +24,7 @@ namespace FlaxEditor.Surface.Elements
public UnsignedIntegerValue(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(Get(parentNode, archetype), archetype.Position.X, archetype.Position.Y, 50, (uint)archetype.ValueMin, (uint)archetype.ValueMax, 0.05f)
{
Height = Constants.BoxRowHeight;
ParentNode = parentNode;
Archetype = archetype;

View File

@@ -3,8 +3,9 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using FlaxEditor.CustomEditors;
using FlaxEditor.GUI.Input;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEngine;
namespace FlaxEditor.Surface
@@ -78,12 +79,12 @@ namespace FlaxEditor.Surface
/// <summary>
/// Gets the actual element position on the y axis.
/// </summary>
public float ActualPositionY => Position.Y + Constants.NodeMarginY + Constants.NodeHeaderSize;
public float ActualPositionY => Position.Y + Constants.NodeMarginY + Constants.NodeHeaderHeight;
/// <summary>
/// Gets the actual element position.
/// </summary>
public Float2 ActualPosition => new Float2(Position.X + Constants.NodeMarginX, Position.Y + Constants.NodeMarginY + Constants.NodeHeaderSize);
public Float2 ActualPosition => new Float2(Position.X + Constants.NodeMarginX, Position.Y + Constants.NodeMarginY + Constants.NodeHeaderHeight);
/// <summary>
/// Node element archetypes factory object. Helps to build surface nodes archetypes.
@@ -106,8 +107,8 @@ namespace FlaxEditor.Surface
{
Type = NodeElementType.Input,
Position = new Float2(
Constants.NodeMarginX - Constants.BoxOffsetX,
Constants.NodeMarginY + Constants.NodeHeaderSize + yLevel * Constants.LayoutOffsetY),
Constants.NodeMarginX,
Constants.NodeMarginY + Constants.NodeHeaderHeight + yLevel * Constants.LayoutOffsetY),
Text = text,
Single = single,
ValueIndex = valueIndex,
@@ -132,8 +133,8 @@ namespace FlaxEditor.Surface
{
Type = NodeElementType.Input,
Position = new Float2(
Constants.NodeMarginX - Constants.BoxOffsetX,
Constants.NodeMarginY + Constants.NodeHeaderSize + yLevel * Constants.LayoutOffsetY),
Constants.NodeMarginX,
Constants.NodeMarginY + Constants.NodeHeaderHeight + yLevel * Constants.LayoutOffsetY),
Text = text,
Single = single,
ValueIndex = valueIndex,
@@ -157,8 +158,8 @@ namespace FlaxEditor.Surface
{
Type = NodeElementType.Output,
Position = new Float2(
Constants.NodeMarginX - Constants.BoxSize + Constants.BoxOffsetX,
Constants.NodeMarginY + Constants.NodeHeaderSize + yLevel * Constants.LayoutOffsetY),
-Constants.NodeMarginX,
Constants.NodeMarginY + Constants.NodeHeaderHeight + yLevel * Constants.LayoutOffsetY),
Text = text,
Single = single,
ValueIndex = -1,
@@ -182,8 +183,8 @@ namespace FlaxEditor.Surface
{
Type = NodeElementType.Output,
Position = new Float2(
Constants.NodeMarginX - Constants.BoxSize + Constants.BoxOffsetX,
Constants.NodeMarginY + Constants.NodeHeaderSize + yLevel * Constants.LayoutOffsetY),
-Constants.NodeMarginX,
Constants.NodeMarginY + Constants.NodeHeaderHeight + yLevel * Constants.LayoutOffsetY),
Text = text,
Single = single,
ValueIndex = -1,
@@ -205,6 +206,7 @@ namespace FlaxEditor.Surface
{
Type = NodeElementType.BoolValue,
Position = new Float2(x, y),
Size = new Float2(16f),
Text = null,
Single = false,
ValueIndex = valueIndex,
@@ -228,7 +230,8 @@ namespace FlaxEditor.Surface
return new NodeElementArchetype
{
Type = NodeElementType.IntegerValue,
Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderSize + y),
Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderHeight + y),
Size = new Float2(50f, IntegerValue.DefaultHeight),
Text = null,
Single = false,
ValueIndex = valueIndex,
@@ -254,7 +257,8 @@ namespace FlaxEditor.Surface
return new NodeElementArchetype
{
Type = NodeElementType.UnsignedIntegerValue,
Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderSize + y),
Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderHeight + y),
Size = new Float2(50f, UnsignedIntegerValue.DefaultHeight),
Text = null,
Single = false,
ValueIndex = valueIndex,
@@ -280,7 +284,8 @@ namespace FlaxEditor.Surface
return new NodeElementArchetype
{
Type = NodeElementType.FloatValue,
Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderSize + y),
Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderHeight + y),
Size = new Float2(50f, FloatValueBox.DefaultHeight),
Text = null,
Single = false,
ValueIndex = valueIndex,
@@ -359,7 +364,8 @@ namespace FlaxEditor.Surface
return new NodeElementArchetype
{
Type = NodeElementType.ColorValue,
Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderSize + y),
Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderHeight + y),
Size = new Float2(32, 18),
Text = null,
Single = false,
ValueIndex = valueIndex,
@@ -382,6 +388,7 @@ namespace FlaxEditor.Surface
{
Type = NodeElementType.Asset,
Position = new Float2(x, y),
Size = new Float2(78f, 90f),
Text = type.FullName,
Single = false,
ValueIndex = valueIndex,
@@ -499,7 +506,7 @@ namespace FlaxEditor.Surface
/// <param name="height">The control height.</param>
/// <param name="tooltip">The control tooltip text.</param>
/// <returns>The archetype.</returns>
public static NodeElementArchetype Text(float x, float y, string text, float width = 100.0f, float height = 16.0f, string tooltip = null)
public static NodeElementArchetype Text(float x, float y, string text, float width = 100.0f, float height = 19.0f, string tooltip = null)
{
return new NodeElementArchetype
{
@@ -530,7 +537,7 @@ namespace FlaxEditor.Surface
return new NodeElementArchetype
{
Type = NodeElementType.TextBox,
Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderSize + y),
Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderHeight + y),
Size = new Float2(width, height),
Single = false,
ValueIndex = valueIndex,
@@ -595,7 +602,7 @@ namespace FlaxEditor.Surface
return new NodeElementArchetype
{
Type = NodeElementType.BoxValue,
Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderSize + y),
Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderHeight + y),
Text = null,
Single = false,
ValueIndex = valueIndex,

View File

@@ -60,84 +60,84 @@ namespace FlaxEditor.Surface
{
GroupID = 1,
Name = "Material",
Color = new Color(231, 76, 60),
Color = new Color(181, 89, 49),
Archetypes = Archetypes.Material.Nodes
},
new GroupArchetype
{
GroupID = 2,
Name = "Constants",
Color = new Color(243, 156, 18),
Color = new Color(163, 106, 21),
Archetypes = Archetypes.Constants.Nodes
},
new GroupArchetype
{
GroupID = 3,
Name = "Math",
Color = new Color(52, 152, 219),
Color = new Color(45, 126, 181),
Archetypes = Archetypes.Math.Nodes
},
new GroupArchetype
{
GroupID = 4,
Name = "Packing",
Color = new Color(155, 89, 182),
Color = new Color(124, 66, 143),
Archetypes = Archetypes.Packing.Nodes
},
new GroupArchetype
{
GroupID = 5,
Name = "Textures",
Color = new Color(46, 204, 113),
Color = new Color(43, 130, 83),
Archetypes = Archetypes.Textures.Nodes
},
new GroupArchetype
{
GroupID = 6,
Name = "Parameters",
Color = new Color(52, 73, 94),
Color = new Color(55, 78, 99),
Archetypes = Archetypes.Parameters.Nodes
},
new GroupArchetype
{
GroupID = 7,
Name = "Tools",
Color = new Color(149, 165, 166),
Color = new Color(88, 96, 97),
Archetypes = Archetypes.Tools.Nodes
},
new GroupArchetype
{
GroupID = 8,
Name = "Layers",
Color = new Color(249, 105, 116),
Color = new Color(189, 75, 81),
Archetypes = Archetypes.Layers.Nodes
},
new GroupArchetype
{
GroupID = 9,
Name = "Animations",
Color = new Color(105, 179, 160),
Color = new Color(72, 125, 107),
Archetypes = Archetypes.Animation.Nodes
},
new GroupArchetype
{
GroupID = 10,
Name = "Boolean",
Color = new Color(237, 28, 36),
Color = new Color(166, 27, 32),
Archetypes = Archetypes.Boolean.Nodes
},
new GroupArchetype
{
GroupID = 11,
Name = "Bitwise",
Color = new Color(181, 230, 29),
Color = new Color(96, 125, 34),
Archetypes = Archetypes.Bitwise.Nodes
},
new GroupArchetype
{
GroupID = 12,
Name = "Comparisons",
Color = new Color(148, 30, 34),
Color = new Color(166, 33, 57),
Archetypes = Archetypes.Comparisons.Nodes
},
// GroupID = 13 -> Custom Nodes provided externally
@@ -145,7 +145,7 @@ namespace FlaxEditor.Surface
{
GroupID = 14,
Name = "Particles",
Color = new Color(121, 210, 176),
Color = new Color(72, 125, 107),
Archetypes = Archetypes.Particles.Nodes
},
new GroupArchetype
@@ -166,7 +166,7 @@ namespace FlaxEditor.Surface
{
GroupID = 17,
Name = "Flow",
Color = new Color(237, 136, 64),
Color = new Color(181, 91, 33),
Archetypes = Archetypes.Flow.Nodes
},
new GroupArchetype

View File

@@ -78,6 +78,11 @@ namespace FlaxEditor.Surface
/// </summary>
VariableValuesSize = 2048,
/// <summary>
/// Node has fixed size defined and should not use automatic layout.
/// </summary>
FixedSize = 4096,
/// <summary>
/// Node can be used in the all visual graphs.
/// </summary>

View File

@@ -59,7 +59,7 @@ namespace FlaxEditor.Surface
var width = _rootNode.Width;
var rootPos = _rootNode.Location;
var pos = rootPos;
pos.Y += Constants.NodeHeaderSize + 1.0f + 7 * Constants.LayoutOffsetY + 6.0f + 4.0f;
pos.Y += Constants.NodeHeaderHeight + 1.0f + 7 * Constants.LayoutOffsetY + 6.0f + 4.0f;
for (int i = 0; i < _rootNode.Headers.Length; i++)
{
@@ -67,7 +67,7 @@ namespace FlaxEditor.Surface
var modulesStart = pos - rootPos;
var modules = modulesGroups.FirstOrDefault(x => x.Key == header.ModuleType);
pos.Y += Constants.NodeHeaderSize + 2.0f;
pos.Y += Constants.NodeHeaderHeight + 2.0f;
if (modules != null)
{
foreach (var module in modules)

View File

@@ -74,6 +74,12 @@ namespace FlaxEditor.Surface
Resize(size.X, size.Y);
}
/// <inheritdoc />
public override void ResizeAuto()
{
// Do nothing, we want to put full control of node size into the users hands
}
/// <inheritdoc />
public override void Draw()
{

View File

@@ -56,10 +56,10 @@ namespace FlaxEditor.Surface
: base(id, context, nodeArch, groupArch)
{
_sizeValueIndex = 2; // Index of the Size stored in Values array
_sizeMin = new Float2(140.0f, Constants.NodeHeaderSize);
_sizeMin = new Float2(140.0f, Constants.NodeHeaderHeight);
_renameTextBox = new TextBox(false, 0, 0, Width)
{
Height = Constants.NodeHeaderSize,
Height = Constants.NodeHeaderHeight,
Visible = false,
Parent = this,
EndEditOnClick = false, // We have to handle this ourselves, otherwise the textbox instantly loses focus when double-clicking the header
@@ -124,11 +124,11 @@ namespace FlaxEditor.Surface
{
base.UpdateRectangles();
const float headerSize = Constants.NodeHeaderSize;
const float headerSize = Constants.NodeHeaderHeight;
const float buttonMargin = Constants.NodeCloseButtonMargin;
const float buttonSize = Constants.NodeCloseButtonSize;
_headerRect = new Rectangle(0, 0, Width, headerSize);
_closeButtonRect = new Rectangle(Width - buttonSize - buttonMargin, buttonMargin, buttonSize, buttonSize);
_closeButtonRect = new Rectangle(Width - buttonSize * 0.75f - buttonMargin, buttonMargin, buttonSize * 0.75f, buttonSize * 0.75f);
_colorButtonRect = new Rectangle(_closeButtonRect.Left - buttonSize - buttonMargin, buttonMargin, buttonSize, buttonSize);
_resizeButtonRect = new Rectangle(_closeButtonRect.Left, Height - buttonSize - buttonMargin, buttonSize, buttonSize);
_renameTextBox.Width = Width;
@@ -183,7 +183,7 @@ namespace FlaxEditor.Surface
if (Surface.CanEdit)
{
// Close button
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
DrawCloseButton(_closeButtonRect, _closeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
// Color button
Render2D.DrawSprite(style.Settings, _colorButtonRect, _colorButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);

View File

@@ -40,6 +40,13 @@ namespace FlaxEditor.Surface
[HideInEditor]
public class SurfaceNode : SurfaceControl
{
internal const float ShadowOffset = 2.25f;
/// <summary>
/// If true, draws a basic rectangle shadow behind the node. Disable to hide shadow or if the node is drawing a custom shadow.
/// </summary>
internal virtual bool DrawBasicShadow => true;
/// <summary>
/// The box to draw a highlight around. Drawing will be skipped if null.
/// </summary>
@@ -55,6 +62,11 @@ namespace FlaxEditor.Surface
/// </summary>
protected Rectangle _headerRect;
/// <summary>
/// The header text rectangle (local space).
/// </summary>
protected Rectangle _headerTextRect;
/// <summary>
/// The close button rectangle (local space).
/// </summary>
@@ -123,7 +135,7 @@ namespace FlaxEditor.Surface
/// <param name="nodeArch">The node archetype.</param>
/// <param name="groupArch">The group archetype.</param>
public SurfaceNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(context, nodeArch.Size.X + Constants.NodeMarginX * 2, nodeArch.Size.Y + Constants.NodeMarginY * 2 + Constants.NodeHeaderSize + Constants.NodeFooterSize)
: base(context, nodeArch.Size.X + Constants.NodeMarginX * 2, nodeArch.Size.Y + Constants.NodeMarginY * 2 + Constants.NodeHeaderHeight + Constants.NodeFooterSize)
{
Title = nodeArch.Title;
ID = id;
@@ -132,7 +144,7 @@ namespace FlaxEditor.Surface
AutoFocus = false;
TooltipText = GetTooltip();
CullChildren = false;
BackgroundColor = Style.Current.BackgroundNormal;
BackgroundColor = Color.Lerp(Style.Current.Background, Style.Current.BackgroundHighlighted, 0.55f);
if (Archetype.DefaultValues != null)
{
@@ -147,9 +159,9 @@ namespace FlaxEditor.Surface
public virtual string ContentSearchText => null;
/// <summary>
/// Gets the color of the footer of the node.
/// Gets the color of the header of the node.
/// </summary>
protected virtual Color FooterColor => GroupArchetype.Color;
protected virtual Color ArchetypeColor => GroupArchetype.Color;
private Float2 mouseDownMousePosition;
@@ -161,7 +173,7 @@ namespace FlaxEditor.Surface
/// <returns>The node control total size.</returns>
protected virtual Float2 CalculateNodeSize(float width, float height)
{
return new Float2(width + Constants.NodeMarginX * 2, height + Constants.NodeMarginY * 2 + Constants.NodeHeaderSize + Constants.NodeFooterSize);
return new Float2(width + Constants.NodeMarginX * 2, height + Constants.NodeMarginY * 2 + Constants.NodeHeaderHeight + Constants.NodeFooterSize);
}
/// <summary>
@@ -169,7 +181,7 @@ namespace FlaxEditor.Surface
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public void Resize(float width, float height)
public virtual void Resize(float width, float height)
{
if (Surface == null)
return;
@@ -187,7 +199,7 @@ namespace FlaxEditor.Surface
{
if (Elements[i] is OutputBox box)
{
box.Location = box.Archetype.Position + new Float2(width, 0);
box.Location = box.Archetype.Position + new Float2(width - Constants.NodeMarginX, 0);
}
}
@@ -215,30 +227,41 @@ namespace FlaxEditor.Surface
var child = Children[i];
if (!child.Visible)
continue;
// Input boxes
if (child is InputBox inputBox)
{
var boxWidth = boxLabelFont.MeasureText(inputBox.Text).X + 20;
if (inputBox.DefaultValueEditor != null)
var boxWidth = boxLabelFont.MeasureText(inputBox.Text).X + 25;
if (inputBox.DefaultValueEditor != null && inputBox.DefaultValueEditor.Visible)
boxWidth += inputBox.DefaultValueEditor.Width + 4;
leftWidth = Mathf.Max(leftWidth, boxWidth);
leftHeight = Mathf.Max(leftHeight, inputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f);
leftHeight = Mathf.Max(leftHeight, inputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderHeight + Constants.BoxRowHeight);
}
// Output boxes
else if (child is OutputBox outputBox)
{
rightWidth = Mathf.Max(rightWidth, boxLabelFont.MeasureText(outputBox.Text).X + 20);
rightHeight = Mathf.Max(rightHeight, outputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f);
rightWidth = Mathf.Max(rightWidth, boxLabelFont.MeasureText(outputBox.Text).X + 25);
rightHeight = Mathf.Max(rightHeight, outputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderHeight + Constants.BoxRowHeight);
}
// Elements (Float-, int-, uint- value boxes, asset pickers, etc.)
// These will only ever be on the left side of the node, so we only adjust left width and height
else if (child is SurfaceNodeElementControl elementControl)
{
leftWidth = Mathf.Max(leftWidth, elementControl.Width + 8f);
leftHeight = Mathf.Max(leftHeight, elementControl.Height);
}
// Other controls in the node
else if (child is Control control)
{
if (control.AnchorPreset == AnchorPresets.TopLeft)
{
width = Mathf.Max(width, control.Right + 4 - Constants.NodeMarginX);
height = Mathf.Max(height, control.Bottom + 4 - Constants.NodeMarginY - Constants.NodeHeaderSize);
width = Mathf.Max(width, control.Right + 15 + Constants.NodeMarginX);
height = Mathf.Max(height, control.Bottom - Constants.NodeMarginY - Constants.NodeHeaderHeight);
}
else if (!_headerRect.Intersects(control.Bounds))
{
width = Mathf.Max(width, control.Width + 4);
height = Mathf.Max(height, control.Height + 4);
width = Mathf.Max(width, control.Width + 15 + Constants.NodeMarginX);
height = Mathf.Max(height, control.Height);
}
}
}
@@ -325,6 +348,9 @@ namespace FlaxEditor.Surface
Elements.Add(element);
if (element is Control control)
AddChild(control);
if (!IsLayoutLocked)
UpdateSize();
}
/// <summary>
@@ -365,7 +391,7 @@ namespace FlaxEditor.Surface
// Sync properties for exiting box
box.Text = text;
box.CurrentType = type;
box.Y = Constants.NodeMarginY + Constants.NodeHeaderSize + yLevel * Constants.LayoutOffsetY;
box.Y = Constants.NodeMarginY + Constants.NodeHeaderHeight + yLevel * Constants.LayoutOffsetY;
}
// Update box
@@ -434,7 +460,7 @@ namespace FlaxEditor.Surface
private static readonly List<SurfaceNode> UpdateStack = new List<SurfaceNode>();
/// <summary>
/// Updates dependant/independent boxes types.
/// Updates dependent/independent boxes types.
/// </summary>
public void UpdateBoxesTypes()
{
@@ -776,6 +802,24 @@ namespace FlaxEditor.Surface
return output;
}
/// <summary>
/// Draws the close button inside of the <paramref name="rect"/>.
/// </summary>
/// <param name="rect">The rectangle to draw the close button in.</param>
/// <param name="color">The color of the close button.</param>
public void DrawCloseButton(Rectangle rect, Color color)
{
// Disable vertex snapping to reduce artefacts at the line ends
var features = Render2D.Features;
Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping;
rect.Expand(-2f); // Don't overshoot the rectangle because of the thickness
Render2D.DrawLine(rect.TopLeft, rect.BottomRight, color, 2f);
Render2D.DrawLine(rect.BottomLeft, rect.TopRight, color, 2f);
Render2D.Features = features;
}
/// <summary>
/// Draws all the connections between surface objects related to this node.
/// </summary>
@@ -867,6 +911,14 @@ namespace FlaxEditor.Surface
return sb.ToString();
}
private void UpdateSize()
{
if (Archetype.Flags.HasFlag(NodeFlags.FixedSize))
Resize(Archetype.Size.X, Archetype.Size.Y);
else
ResizeAuto();
}
/// <inheritdoc />
protected override bool ShowTooltip => base.ShowTooltip && _headerRect.Contains(ref _mousePosition) && !Surface.IsLeftMouseButtonDown && !Surface.IsRightMouseButtonDown && !Surface.IsPrimaryMenuOpened;
@@ -919,6 +971,8 @@ namespace FlaxEditor.Surface
if (Elements[i] is Box box)
box.OnConnectionsChanged();
}
UpdateSize();
}
/// <inheritdoc />
@@ -956,6 +1010,8 @@ namespace FlaxEditor.Surface
Surface.AddBatchedUndoAction(new EditNodeValuesAction(this, before, graphEdited));
_isDuringValuesEditing = false;
UpdateSize();
}
/// <summary>
@@ -990,6 +1046,8 @@ namespace FlaxEditor.Surface
}
_isDuringValuesEditing = false;
UpdateSize();
}
internal void SetIsDuringValuesEditing(bool value)
@@ -998,7 +1056,7 @@ namespace FlaxEditor.Surface
}
/// <summary>
/// Sets teh node values from the given pasted source. Can be overriden to perform validation or custom values processing.
/// Sets teh node values from the given pasted source. Can be overridden to perform validation or custom values processing.
/// </summary>
/// <param name="values">The input values array.</param>
public virtual void SetValuesPaste(object[] values)
@@ -1022,16 +1080,18 @@ namespace FlaxEditor.Surface
public virtual void ConnectionTick(Box box)
{
UpdateBoxesTypes();
UpdateSize();
}
/// <inheritdoc />
protected override void UpdateRectangles()
{
const float footerSize = Constants.NodeFooterSize;
const float headerSize = Constants.NodeHeaderSize;
const float headerSize = Constants.NodeHeaderHeight;
const float closeButtonMargin = Constants.NodeCloseButtonMargin;
const float closeButtonSize = Constants.NodeCloseButtonSize;
_headerRect = new Rectangle(0, 0, Width, headerSize);
_headerTextRect = _headerRect with { X = 5f, Width = Width - closeButtonSize - closeButtonMargin * 4f };
_closeButtonRect = new Rectangle(Width - closeButtonSize - closeButtonMargin, closeButtonMargin, closeButtonSize, closeButtonSize);
_footerRect = new Rectangle(0, Height - footerSize, Width, footerSize);
}
@@ -1040,9 +1100,16 @@ namespace FlaxEditor.Surface
public override void Draw()
{
var style = Style.Current;
var backgroundRect = new Rectangle(Float2.Zero, Size);
// Shadow
if (DrawBasicShadow)
{
var shadowRect = backgroundRect.MakeOffsetted(ShadowOffset);
Render2D.FillRectangle(shadowRect, Color.Black.AlphaMultiplied(0.125f));
}
// Background
var backgroundRect = new Rectangle(Float2.Zero, Size);
Render2D.FillRectangle(backgroundRect, BackgroundColor);
// Breakpoint hit
@@ -1058,18 +1125,18 @@ namespace FlaxEditor.Surface
var headerColor = style.BackgroundHighlighted;
if (_headerRect.Contains(ref _mousePosition) && !Surface.IsConnecting && !Surface.IsSelecting)
headerColor *= 1.07f;
Render2D.FillRectangle(_headerRect, headerColor);
Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center);
Render2D.FillRectangle(_headerRect, ArchetypeColor);
Render2D.DrawText(style.FontLarge, Title, _headerTextRect, style.Foreground, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1f, Constants.NodeHeaderTextScale);
// Close button
if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0 && Surface.CanEdit)
{
bool highlightClose = _closeButtonRect.Contains(_mousePosition) && !Surface.IsConnecting && !Surface.IsSelecting;
Render2D.DrawSprite(style.Cross, _closeButtonRect, highlightClose ? style.Foreground : style.ForegroundGrey);
DrawCloseButton(_closeButtonRect, highlightClose ? style.Foreground : style.ForegroundGrey);
}
// Footer
Render2D.FillRectangle(_footerRect, FooterColor);
Render2D.FillRectangle(_footerRect, ArchetypeColor);
DrawChildren();
@@ -1078,7 +1145,7 @@ namespace FlaxEditor.Surface
{
var colorTop = Color.Orange;
var colorBottom = Color.OrangeRed;
Render2D.DrawRectangle(backgroundRect, colorTop, colorTop, colorBottom, colorBottom);
Render2D.DrawRectangle(backgroundRect, colorTop, colorTop, colorBottom, colorBottom, 2.5f);
}
// Breakpoint dot

View File

@@ -2,6 +2,7 @@
using System;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEngine;
using FlaxEngine.Utilities;
@@ -140,6 +141,11 @@ namespace FlaxEditor.Surface
/// </summary>
public Texture Background;
/// <summary>
/// The color used as a surface background.
/// </summary>
public Color BackgroundColor;
/// <summary>
/// Boxes drawing callback.
/// </summary>
@@ -216,19 +222,20 @@ namespace FlaxEditor.Surface
private static void DefaultDrawBox(Elements.Box box)
{
var rect = new Rectangle(Float2.Zero, box.Size);
var rect = new Rectangle(box.Width * 0.5f - Constants.BoxSize * 0.5f, box.Height * 0.5f - Constants.BoxSize * 0.5f, new Float2(Constants.BoxSize));
// Size culling
const float minBoxSize = 5.0f;
if (rect.Size.LengthSquared < minBoxSize * minBoxSize)
return;
// Debugging boxes size
// Debugging boxes size and bounds
//Render2D.DrawRectangle(rect, Color.Orange); return;
//Render2D.DrawRectangle(box.Bounds, Color.Green);
// Draw icon
bool hasConnections = box.HasAnyConnection;
float alpha = box.Enabled && box.IsActive ? 1.0f : 0.6f;
float alpha = box.IsDisabled ? 0.6f : 1.0f;
Color color = box.CurrentTypeColor * alpha;
var style = box.Surface.Style;
SpriteHandle icon;
@@ -237,20 +244,42 @@ namespace FlaxEditor.Surface
else
icon = hasConnections ? style.Icons.BoxClose : style.Icons.BoxOpen;
color *= box.ConnectionsHighlightIntensity + 1;
if (box.IsMouseOver)
{
color *= 1.3f;
rect = rect.MakeExpanded(1.0f);
}
// Disable vertex snapping to prevent position jitter/snapping artefacts for the boxes when zooming the surface
var features = Render2D.Features;
Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping;
Render2D.DrawSprite(icon, rect, color);
// Draw connected hint with color from connected output box
if (hasConnections && box.Connections[0] is OutputBox connectedOutputBox)
{
bool connectedSameColor = connectedOutputBox.CurrentTypeColor == box.CurrentTypeColor;
Color innerColor = connectedSameColor ? color.RGBMultiplied(0.4f) : connectedOutputBox.CurrentTypeColor;
innerColor = innerColor * alpha;
Render2D.DrawSprite(icon, rect.MakeExpanded(-5.0f), innerColor);
}
// Draw selection hint
if (box.IsSelected)
{
float outlineAlpha = Mathf.Sin(Time.TimeSinceStartup * 4.0f) * 0.5f + 0.5f;
float outlineWidth = Mathf.Lerp(1.5f, 4.0f, outlineAlpha);
var outlineRect = new Rectangle(rect.X - outlineWidth, rect.Y - outlineWidth, rect.Width + outlineWidth * 2, rect.Height + outlineWidth * 2);
Render2D.DrawSprite(icon, outlineRect, FlaxEngine.GUI.Style.Current.BorderSelected.RGBMultiplied(1.0f + outlineAlpha * 0.4f));
Color selectionColor = FlaxEngine.GUI.Style.Current.BorderSelected.RGBMultiplied(1.0f + outlineAlpha * 0.4f);
Render2D.DrawSprite(icon, outlineRect, selectionColor.AlphaMultiplied(0.4f));
}
Render2D.Features = features;
}
/// <summary>
/// Function used to create style for the given surface type. Can be overriden to provide some customization via user plugin.
/// Function used to create style for the given surface type. Can be overridden to provide some customization via user plugin.
/// </summary>
public static Func<Editor, SurfaceStyle> CreateStyleHandler = CreateDefault;
@@ -293,6 +322,7 @@ namespace FlaxEditor.Surface
ArrowClose = editor.Icons.VisjectArrowClosed32,
},
Background = editor.UI.VisjectSurfaceBackground,
BackgroundColor = new Color(31, 31, 31),
};
}
@@ -311,13 +341,11 @@ namespace FlaxEditor.Surface
{
var dir = sub / length;
var arrowRect = new Rectangle(0, 0, 16.0f, 16.0f);
float rotation = Float2.Dot(dir, Float2.UnitY);
if (endPos.X < startPos.X)
rotation = 2 - rotation;
float rotation = Mathf.Atan2(dir.Y, dir.X);
var sprite = Editor.Instance.Icons.VisjectArrowClosed32;
var arrowTransform =
Matrix3x3.Translation2D(-6.5f, -8) *
Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) *
Matrix3x3.RotationZ(rotation) *
Matrix3x3.Translation2D(endPos - dir * 8);
Render2D.PushTransform(ref arrowTransform);

View File

@@ -574,13 +574,13 @@ namespace FlaxEditor.Surface
var showSearch = () => editor.ContentFinding.ShowSearch(window);
// Toolstrip
saveButton = toolStrip.AddButton(editor.Icons.Save64, window.Save).LinkTooltip("Save", ref inputOptions.Save);
saveButton = toolStrip.AddButton(editor.Icons.Save64, window.Save).LinkTooltip("Save.", ref inputOptions.Save);
toolStrip.AddSeparator();
undoButton = toolStrip.AddButton(editor.Icons.Undo64, undo.PerformUndo).LinkTooltip("Undo", ref inputOptions.Undo);
redoButton = toolStrip.AddButton(editor.Icons.Redo64, undo.PerformRedo).LinkTooltip("Redo", ref inputOptions.Redo);
undoButton = toolStrip.AddButton(editor.Icons.Undo64, undo.PerformUndo).LinkTooltip("Undo.", ref inputOptions.Undo);
redoButton = toolStrip.AddButton(editor.Icons.Redo64, undo.PerformRedo).LinkTooltip("Redo.", ref inputOptions.Redo);
toolStrip.AddSeparator();
toolStrip.AddButton(editor.Icons.Search64, showSearch).LinkTooltip("Open content search tool", ref inputOptions.Search);
toolStrip.AddButton(editor.Icons.CenterView64, surface.ShowWholeGraph).LinkTooltip("Show whole graph");
toolStrip.AddButton(editor.Icons.Search64, showSearch).LinkTooltip("Open content search tool.", ref inputOptions.Search);
toolStrip.AddButton(editor.Icons.CenterView64, surface.ShowWholeGraph).LinkTooltip("Show whole graph.");
var gridSnapButton = toolStrip.AddButton(editor.Icons.Grid32, surface.ToggleGridSnapping);
gridSnapButton.LinkTooltip("Toggle grid snapping for nodes.");
gridSnapButton.AutoCheck = true;

View File

@@ -410,8 +410,11 @@ namespace FlaxEditor.Surface
}
menu.AddSeparator();
_cmFormatNodesMenu = menu.AddChildMenu("Format node(s)");
_cmFormatNodesMenu.Enabled = CanEdit && HasNodesSelection;
bool allNodesNoMove = SelectedNodes.All(n => n.Archetype.Flags.HasFlag(NodeFlags.NoMove));
bool clickedNodeNoMove = ((SelectedNodes.Count == 1 && controlUnderMouse is SurfaceNode n && n.Archetype.Flags.HasFlag(NodeFlags.NoMove)));
_cmFormatNodesMenu = menu.AddChildMenu("Format nodes");
_cmFormatNodesMenu.Enabled = CanEdit && HasNodesSelection && !(allNodesNoMove || clickedNodeNoMove);
_cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Auto format", Editor.Instance.Options.Options.Input.NodesAutoFormat, () => { FormatGraph(SelectedNodes); });
_cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Straighten connections", Editor.Instance.Options.Options.Input.NodesStraightenConnections, () => { StraightenGraphConnections(SelectedNodes); });

View File

@@ -65,31 +65,68 @@ namespace FlaxEditor.Surface
/// </summary>
protected virtual void DrawBackground()
{
DrawBackgroundDefault(Style.Background, Width, Height);
DrawBackgroundDefault(Style.Background, Size, _rootControl.Location);
//DrawBackgroundSolidColor(Style.BackgroundColor, Width, Height);
DrawGridBackground();
}
internal static void DrawBackgroundDefault(Texture background, float width, float height)
internal static void DrawBackgroundSolidColor(Color color, float width, float height)
{
Rectangle backgroundRect = new Rectangle(0f, 0f, width, height);
Render2D.FillRectangle(backgroundRect, color);
}
internal void DrawGridBackground()
{
var viewRect = GetClientArea();
var upperLeft = _rootControl.PointFromParent(viewRect.Location);
var bottomRight = _rootControl.PointFromParent(viewRect.Size);
var min = Float2.Min(upperLeft, bottomRight);
var max = Float2.Max(upperLeft, bottomRight);
var pixelRange = (max - min) * ViewScale * 2.75f;
Render2D.PushClip(ref viewRect);
DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X);
DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y);
Render2D.PopClip();
}
private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange)
{
var linesColor = Style.BackgroundColor.RGBMultiplied(1.2f);
float[] gridTickStrengths = null;
Utilities.Utils.DrawCurveTicks((decimal tick, double step, float strength) =>
{
var p = _rootControl.PointToParent(axis * (float)tick); ;
// Draw line
var lineRect = new Rectangle
(
viewRect.Location + (p - 0.5f) * axis,
Float2.Lerp(viewRect.Size, Float2.One, axis)
);
Render2D.FillRectangle(lineRect, linesColor.AlphaMultiplied(strength));
}, Utilities.Utils.CurveTickSteps, ref gridTickStrengths, min, max, pixelRange);
}
internal static void DrawBackgroundDefault(Texture background, Float2 size, Float2 offset)
{
if (background && background.ResidentMipLevels > 0)
{
var bSize = background.Size;
float bw = bSize.X;
float bh = bSize.Y;
var pos = Float2.Mod(bSize);
var pos = Float2.Mod(offset / bSize) * bSize;
var max = Float2.Ceil(size / bSize + 1.0f);
if (pos.X > 0)
pos.X -= bw;
pos.X -= bSize.X;
if (pos.Y > 0)
pos.Y -= bh;
pos.Y -= bSize.Y;
int maxI = Mathf.CeilToInt(width / bw + 1.0f);
int maxJ = Mathf.CeilToInt(height / bh + 1.0f);
for (int i = 0; i < maxI; i++)
for (int i = 0; i < max.X; i++)
{
for (int j = 0; j < maxJ; j++)
for (int j = 0; j < max.Y; j++)
{
Render2D.DrawTexture(background, new Rectangle(pos.X + i * bw, pos.Y + j * bh, bw, bh), Color.White);
Render2D.DrawTexture(background, new Rectangle(pos.X + i * bSize.X, pos.Y + j * bSize.Y, bSize), Color.White);
}
}
}
@@ -213,6 +250,44 @@ namespace FlaxEditor.Surface
}
}
/// <summary>
/// Draw connection hints for lazy connect feature.
/// </summary>
protected virtual void DrawLazyConnect()
{
var style = FlaxEngine.GUI.Style.Current;
if (_lazyConnectStartNode != null)
{
Float2 upperLeft = _rootControl.PointToParent(_lazyConnectStartNode.UpperLeft);
Rectangle startNodeOutline = new Rectangle(upperLeft + 1f, _lazyConnectStartNode.Size - 1f);
startNodeOutline.Size *= ViewScale;
Render2D.DrawRectangle(startNodeOutline.MakeExpanded(4f), style.BackgroundSelected, 4f);
}
if (_lazyConnectEndNode != null)
{
Float2 upperLeft = _rootControl.PointToParent(_lazyConnectEndNode.UpperLeft);
Rectangle startNodeOutline = new Rectangle(upperLeft + 1f, _lazyConnectEndNode.Size - 1f);
startNodeOutline.Size *= ViewScale;
Render2D.DrawRectangle(startNodeOutline.MakeExpanded(4f), style.BackgroundSelected, 4f);
}
Rectangle startRect = new Rectangle(_rightMouseDownPos - 6f, new Float2(12f));
Rectangle endRect = new Rectangle(_mousePos - 6f, new Float2(12f));
// Start and end shadows/ outlines
Render2D.FillRectangle(startRect.MakeExpanded(2.5f), Color.Black);
Render2D.FillRectangle(endRect.MakeExpanded(2.5f), Color.Black);
Render2D.DrawLine(_rightMouseDownPos, _mousePos, Color.Black, 7.5f);
Render2D.DrawLine(_rightMouseDownPos, _mousePos, style.ForegroundGrey, 5f);
// Draw start and end boxes over the lines to hide ugly artifacts at the ends
Render2D.FillRectangle(startRect, style.ForegroundGrey);
Render2D.FillRectangle(endRect, style.ForegroundGrey);
}
/// <summary>
/// Draws the contents of the surface (nodes, connections, comments, etc.).
/// </summary>
@@ -260,6 +335,9 @@ namespace FlaxEditor.Surface
DrawContents();
if (_isLazyConnecting)
DrawLazyConnect();
//Render2D.DrawText(style.FontTitle, string.Format("Scale: {0}", _rootControl.Scale), rect, Enabled ? Color.Red : Color.Black);
// Draw border

View File

@@ -39,6 +39,8 @@ namespace FlaxEditor.Surface
if (nodes.Count <= 1)
return;
List<MoveNodesAction> undoActions = new List<MoveNodesAction>();
var nodesToVisit = new HashSet<SurfaceNode>(nodes);
// While we haven't formatted every node
@@ -73,18 +75,23 @@ namespace FlaxEditor.Surface
}
}
FormatConnectedGraph(connectedNodes);
undoActions.AddRange(FormatConnectedGraph(connectedNodes));
}
Undo?.AddAction(new MultiUndoAction(undoActions, "Format nodes"));
MarkAsEdited(false);
}
/// <summary>
/// Formats a graph where all nodes are connected.
/// </summary>
/// <param name="nodes">List of connected nodes.</param>
protected void FormatConnectedGraph(List<SurfaceNode> nodes)
private List<MoveNodesAction> FormatConnectedGraph(List<SurfaceNode> nodes)
{
List<MoveNodesAction> undoActions = new List<MoveNodesAction>();
if (nodes.Count <= 1)
return;
return undoActions;
var boundingBox = GetNodesBounds(nodes);
@@ -140,7 +147,6 @@ namespace FlaxEditor.Surface
}
// Set the node positions
var undoActions = new List<MoveNodesAction>();
var topRightPosition = endNodes[0].Location;
for (int i = 0; i < nodes.Count; i++)
{
@@ -155,16 +161,18 @@ namespace FlaxEditor.Surface
}
}
MarkAsEdited(false);
Undo?.AddAction(new MultiUndoAction(undoActions, "Format nodes"));
return undoActions;
}
/// <summary>
/// Straightens every connection between nodes in <paramref name="nodes"/>.
/// </summary>
/// <param name="nodes">List of nodes.</param>
/// <returns>List of undo actions.</returns>
public void StraightenGraphConnections(List<SurfaceNode> nodes)
{
{
nodes = nodes.Where(n => !n.Archetype.Flags.HasFlag(NodeFlags.NoMove)).ToList();
if (nodes.Count <= 1)
return;
@@ -350,8 +358,10 @@ namespace FlaxEditor.Surface
/// <param name="nodes">List of nodes.</param>
/// <param name="alignmentType">Alignemnt type.</param>
public void AlignNodes(List<SurfaceNode> nodes, NodeAlignmentType alignmentType)
{
if(nodes.Count <= 1)
{
nodes = nodes.Where(n => !n.Archetype.Flags.HasFlag(NodeFlags.NoMove)).ToList();
if (nodes.Count <= 1)
return;
var undoActions = new List<MoveNodesAction>();
@@ -392,6 +402,8 @@ namespace FlaxEditor.Surface
/// <param name="vertically">If false will be done horizontally, if true will be done vertically.</param>
public void DistributeNodes(List<SurfaceNode> nodes, bool vertically)
{
nodes = nodes.Where(n => !n.Archetype.Flags.HasFlag(NodeFlags.NoMove)).ToList();
if(nodes.Count <= 1)
return;

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static FlaxEditor.Surface.Archetypes.Particles;
using FlaxEditor.Options;
using FlaxEditor.Surface.Elements;
using FlaxEditor.Surface.Undo;
@@ -23,12 +24,26 @@ namespace FlaxEditor.Surface
/// </summary>
public bool PanWithMiddleMouse = false;
/// <summary>
/// Distance for the mouse to be considered above the connection.
/// </summary>
public float MouseOverConnectionDistance => 100f / ViewScale;
/// <summary>
/// Distance of a node from which it is able to be slotted into an existing connection.
/// </summary>
public float SlotNodeIntoConnectionDistance => 250f / ViewScale;
private string _currentInputText = string.Empty;
private Float2 _movingNodesDelta;
private Float2 _gridRoundingDelta;
private HashSet<SurfaceNode> _movingNodes;
private HashSet<SurfaceNode> _temporarySelectedNodes;
private readonly Stack<InputBracket> _inputBrackets = new Stack<InputBracket>();
private bool _isLazyConnecting;
private SurfaceNode _lazyConnectStartNode;
private SurfaceNode _lazyConnectEndNode;
private InputBinding _focusSelectedNodeBinding;
private class InputBracket
{
@@ -250,8 +265,13 @@ namespace FlaxEditor.Surface
// Cache mouse location
_mousePos = location;
if (_isLazyConnecting && GetControlUnderMouse() is SurfaceNode nodeUnderMouse && !(nodeUnderMouse is SurfaceComment || nodeUnderMouse is ParticleEmitterNode))
_lazyConnectEndNode = nodeUnderMouse;
else if (_isLazyConnecting && Nodes.Count > 0)
_lazyConnectEndNode = GetClosestNodeAtLocation(location);
// Moving around surface with mouse
if (_rightMouseDown)
if (_rightMouseDown && !_isLazyConnecting)
{
// Calculate delta
var delta = location - _rightMouseDownPos;
@@ -321,6 +341,33 @@ namespace FlaxEditor.Surface
foreach (var node in _movingNodes)
{
// Allow ripping the node from its current connection
if (RootWindow.GetKey(KeyboardKeys.Alt))
{
InputBox nodeConnectedInput = null;
OutputBox nodeConnectedOuput = null;
var boxes = node.GetBoxes();
foreach (var box in boxes)
{
if (!box.IsOutput && box.Connections.Count > 0)
{
nodeConnectedInput = (InputBox)box;
continue;
}
if (box.IsOutput && box.Connections.Count > 0)
{
nodeConnectedOuput = (OutputBox)box;
continue;
}
}
if (nodeConnectedInput != null && nodeConnectedOuput != null)
TryConnect(nodeConnectedOuput.Connections[0], nodeConnectedInput.Connections[0]);
node.RemoveConnections();
}
if (gridSnap)
{
Float2 unroundedLocation = node.Location;
@@ -420,7 +467,7 @@ namespace FlaxEditor.Surface
if (!handled && CanEdit && CanUseNodeType(7, 29))
{
var mousePos = _rootControl.PointFromParent(ref _mousePos);
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox) && GetControlUnderMouse() == null)
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox, MouseOverConnectionDistance) && GetControlUnderMouse() == null)
{
if (Undo != null)
{
@@ -515,11 +562,17 @@ namespace FlaxEditor.Surface
_middleMouseDownPos = location;
}
if (root.GetKey(KeyboardKeys.Alt) && button == MouseButton.Right)
_isLazyConnecting = true;
// Check if any node is under the mouse
SurfaceControl controlUnderMouse = GetControlUnderMouse();
var cLocation = _rootControl.PointFromParent(ref location);
if (controlUnderMouse != null)
{
if (controlUnderMouse is SurfaceNode node && _isLazyConnecting && !(controlUnderMouse is SurfaceComment || controlUnderMouse is ParticleEmitterNode))
_lazyConnectStartNode = node;
// Check if mouse is over header and user is pressing mouse left button
if (_leftMouseDown && controlUnderMouse.CanSelect(ref cLocation))
{
@@ -554,6 +607,9 @@ namespace FlaxEditor.Surface
}
else
{
if (_isLazyConnecting && Nodes.Count > 0)
_lazyConnectStartNode = GetClosestNodeAtLocation(location);
// Cache flags and state
if (_leftMouseDown)
{
@@ -602,8 +658,71 @@ namespace FlaxEditor.Surface
{
if (_movingNodes != null && _movingNodes.Count > 0)
{
// Allow dropping a single node onto an existing connection and connect it
if (_movingNodes.Count == 1)
{
var mousePos = _rootControl.PointFromParent(ref _mousePos);
InputBox intersectedConnectionInputBox;
OutputBox intersectedConnectionOutputBox;
if (IntersectsConnection(mousePos, out intersectedConnectionInputBox, out intersectedConnectionOutputBox, SlotNodeIntoConnectionDistance))
{
SurfaceNode node = _movingNodes.First();
InputBox nodeInputBox = (InputBox)node.GetBoxes().First(b => !b.IsOutput);
OutputBox nodeOutputBox = (OutputBox)node.GetBoxes().First(b => b.IsOutput);
TryConnect(intersectedConnectionOutputBox, nodeInputBox);
TryConnect(nodeOutputBox, intersectedConnectionInputBox);
float intersectedConnectionNodesXDistance = intersectedConnectionInputBox.ParentNode.Left - intersectedConnectionOutputBox.ParentNode.Right;
float paddedNodeWidth = node.Width + 2f;
if (intersectedConnectionNodesXDistance < paddedNodeWidth)
{
List<SurfaceNode> visitedNodes = new List<SurfaceNode>{ node };
List<SurfaceNode> movedNodes = new List<SurfaceNode>();
Float2 locationDelta = new Float2(paddedNodeWidth, 0f);
MoveConnectedNodes(intersectedConnectionInputBox.ParentNode);
void MoveConnectedNodes(SurfaceNode node)
{
// Only move node if it is to the right of the node we have connected the moved node to
if (node.Right > intersectedConnectionInputBox.ParentNode.Left + 15f && !node.Archetype.Flags.HasFlag(NodeFlags.NoMove))
{
node.Location += locationDelta;
movedNodes.Add(node);
}
visitedNodes.Add(node);
foreach (var box in node.GetBoxes())
{
if (!box.HasAnyConnection || box == intersectedConnectionInputBox)
continue;
foreach (var connectedBox in box.Connections)
{
SurfaceNode nextNode = connectedBox.ParentNode;
if (visitedNodes.Contains(nextNode))
continue;
MoveConnectedNodes(nextNode);
}
}
}
Float2 nodeMoveOffset = new Float2(node.Width * 0.5f, 0f);
node.Location += nodeMoveOffset;
var moveNodesAction = new MoveNodesAction(Context, movedNodes.Select(n => n.ID).ToArray(), locationDelta);
var moveNodeAction = new MoveNodesAction(Context, [node.ID], nodeMoveOffset);
var multiAction = new MultiUndoAction(moveNodeAction, moveNodesAction);
AddBatchedUndoAction(multiAction);
}
}
}
if (Undo != null && !_movingNodesDelta.IsZero && CanEdit)
Undo.AddAction(new MoveNodesAction(Context, _movingNodes.Select(x => x.ID).ToArray(), _movingNodesDelta));
AddBatchedUndoAction(new MoveNodesAction(Context, _movingNodes.Select(x => x.ID).ToArray(), _movingNodesDelta));
_movingNodes.Clear();
}
_movingNodesDelta = Float2.Zero;
@@ -630,12 +749,36 @@ namespace FlaxEditor.Surface
{
// Check if any control is under the mouse
_cmStartPos = location;
if (controlUnderMouse == null)
if (controlUnderMouse == null && !_isLazyConnecting)
{
showPrimaryMenu = true;
}
}
_mouseMoveAmount = 0;
if (_isLazyConnecting)
{
if (_lazyConnectStartNode != null && _lazyConnectEndNode != null && _lazyConnectStartNode != _lazyConnectEndNode)
{
// First check if there is a type matching input and output where input
OutputBox startNodeOutput = (OutputBox)_lazyConnectStartNode.GetBoxes().FirstOrDefault(b => b.IsOutput, null);
InputBox endNodeInput = null;
if (startNodeOutput != null)
endNodeInput = (InputBox)_lazyConnectEndNode.GetBoxes().FirstOrDefault(b => !b.IsOutput && b.CurrentType == startNodeOutput.CurrentType && !b.HasAnyConnection && b.IsActive && b.CanConnectWith(startNodeOutput), null);
// Perform less strict checks (less ideal conditions for connection but still good) if the first checks failed
if (endNodeInput == null)
endNodeInput = (InputBox)_lazyConnectEndNode.GetBoxes().FirstOrDefault(b => !b.IsOutput && !b.HasAnyConnection && b.CanConnectWith(startNodeOutput), null);
if (startNodeOutput != null && endNodeInput != null)
TryConnect(startNodeOutput, endNodeInput);
}
_isLazyConnecting = false;
_lazyConnectStartNode = null;
_lazyConnectEndNode = null;
}
}
if (_middleMouseDown && button == MouseButton.Middle)
{
@@ -651,7 +794,7 @@ namespace FlaxEditor.Surface
{
// Surface was not moved with MMB so try to remove connection underneath
var mousePos = _rootControl.PointFromParent(ref location);
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox))
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox, MouseOverConnectionDistance))
{
var action = new EditNodeConnections(inputBox.ParentNode.Context, inputBox.ParentNode);
inputBox.BreakConnection(outputBox);
@@ -704,13 +847,21 @@ namespace FlaxEditor.Surface
private void MoveSelectedNodes(Float2 delta)
{
// TODO: undo
List<MoveNodesAction> undoActions = new List<MoveNodesAction>();
delta /= _targetScale;
OnGetNodesToMove();
foreach (var node in _movingNodes)
{
node.Location += delta;
if (Undo != null)
undoActions.Add(new MoveNodesAction(Context, new[] { node.ID }, delta));
}
_isMovingSelection = false;
MarkAsEdited(false);
if (undoActions.Count > 0)
Undo?.AddAction(new MultiUndoAction(undoActions, "Moved "));
}
/// <inheritdoc />
@@ -837,6 +988,29 @@ namespace FlaxEditor.Surface
return false;
}
private SurfaceNode GetClosestNodeAtLocation(Float2 location)
{
SurfaceNode currentClosestNode = null;
float currentClosestDistanceSquared = float.MaxValue;
foreach (var node in Nodes)
{
if (node is SurfaceComment || node is ParticleEmitterNode)
continue;
Float2 nodeSurfaceLocation = _rootControl.PointToParent(node.Center);
float distanceSquared = Float2.DistanceSquared(location, nodeSurfaceLocation);
if (distanceSquared < currentClosestDistanceSquared)
{
currentClosestNode = node;
currentClosestDistanceSquared = distanceSquared;
}
}
return currentClosestNode;
}
private void ResetInput()
{
InputText = "";
@@ -845,7 +1019,8 @@ namespace FlaxEditor.Surface
private void CurrentInputTextChanged(string currentInputText)
{
if (string.IsNullOrEmpty(currentInputText))
// Check if focus selected nodes binding is being pressed to prevent it triggering primary menu
if (string.IsNullOrEmpty(currentInputText) || _focusSelectedNodeBinding.Process(RootWindow))
return;
if (IsPrimaryMenuOpened || !CanEdit)
{
@@ -1025,7 +1200,7 @@ namespace FlaxEditor.Surface
return new Float2(xLocation, yLocation);
}
private bool IntersectsConnection(Float2 mousePosition, out InputBox inputBox, out OutputBox outputBox)
private bool IntersectsConnection(Float2 mousePosition, out InputBox inputBox, out OutputBox outputBox, float distance)
{
for (int i = 0; i < Nodes.Count; i++)
{
@@ -1035,7 +1210,7 @@ namespace FlaxEditor.Surface
{
for (int k = 0; k < ob.Connections.Count; k++)
{
if (ob.IntersectsConnection(ob.Connections[k], ref mousePosition))
if (ob.IntersectsConnection(ob.Connections[k], ref mousePosition, distance))
{
outputBox = ob;
inputBox = ob.Connections[k] as InputBox;

View File

@@ -217,7 +217,7 @@ namespace FlaxEditor.Surface
set
{
// Clamp
value = Mathf.Clamp(value, 0.05f, 1.6f);
value = Mathf.Clamp(value, 0.05f, 1.85f);
// Check if value will change
if (Mathf.Abs(value - _targetScale) > 0.0001f)
@@ -423,8 +423,9 @@ namespace FlaxEditor.Surface
new InputActionsContainer.Binding(options => options.NodesAlignLeft, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Left); }),
new InputActionsContainer.Binding(options => options.NodesAlignCenter, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Center); }),
new InputActionsContainer.Binding(options => options.NodesAlignRight, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Right); }),
new InputActionsContainer.Binding(options => options.NodesDistributeHorizontal, () => { DistributeNodes(SelectedNodes, false); }),
new InputActionsContainer.Binding(options => options.NodesDistributeVertical, () => { DistributeNodes(SelectedNodes, true); }),
new InputActionsContainer.Binding(options => options.NodesDistributeHorizontal, () => { DistributeNodes(SelectedNodes, false); }),
new InputActionsContainer.Binding(options => options.NodesDistributeVertical, () => { DistributeNodes(SelectedNodes, true); }),
new InputActionsContainer.Binding(options => options.FocusSelectedNodes, () => { FocusSelectionOrWholeGraph(); }),
});
Context.ControlSpawned += OnSurfaceControlSpawned;
@@ -436,7 +437,10 @@ namespace FlaxEditor.Surface
DragHandlers.Add(_dragAssets = new DragAssets<DragDropEventArgs>(ValidateDragItem));
DragHandlers.Add(_dragParameters = new DragNames<DragDropEventArgs>(SurfaceParameter.DragPrefix, ValidateDragParameter));
OnEditorOptionsChanged(Editor.Instance.Options.Options);
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged;
}
private void OnScriptsReloadBegin()
@@ -446,6 +450,11 @@ namespace FlaxEditor.Surface
_cmPrimaryMenu = null;
}
private void OnEditorOptionsChanged(EditorOptions options)
{
_focusSelectedNodeBinding = options.Input.FocusSelectedNodes;
}
/// <summary>
/// Gets the display name of the connection type used in the surface.
/// </summary>
@@ -502,7 +511,7 @@ namespace FlaxEditor.Surface
{
GroupID = Custom.GroupID,
Name = "Custom",
Color = Color.Wheat
Color = Color.Wheat.RGBMultiplied(0.4f),
};
}
else
@@ -583,6 +592,11 @@ namespace FlaxEditor.Surface
/// </summary>
public virtual bool CanSetParameters => false;
/// <summary>
/// Gets a value indicating whether surface private parameters can be used, otherwise they will remain hidden.
/// </summary>
public virtual bool CanShowPrivateParameters => false;
/// <summary>
/// True of the context menu should make use of a description panel drawn at the bottom of the menu
/// </summary>
@@ -643,6 +657,37 @@ namespace FlaxEditor.Surface
ViewCenterPosition = areaRect.Center;
}
/// <summary>
/// Adjusts the view to focus on the currently selected nodes, or the entire graph if no nodes are selected.
/// </summary>
public void FocusSelectionOrWholeGraph()
{
if (SelectedNodes.Count > 0)
ShowSelection();
else
ShowWholeGraph();
}
/// <summary>
/// Shows the selected controls by changing the view scale and the position.
/// </summary>
public void ShowSelection()
{
var selection = SelectedControls;
if (selection.Count == 0)
return;
// Calculate the bounds of all selected controls
Rectangle bounds = selection[0].Bounds;
for (int i = 1; i < selection.Count; i++)
bounds = Rectangle.Union(bounds, selection[i].Bounds);
// Add margin
bounds = bounds.MakeExpanded(250.0f);
ShowArea(bounds);
}
/// <summary>
/// Shows the given surface node by changing the view scale and the position and focuses the node.
/// </summary>
@@ -1066,6 +1111,7 @@ namespace FlaxEditor.Surface
_cmPrimaryMenu?.Dispose();
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged;
base.OnDestroy();
}

View File

@@ -254,9 +254,10 @@ namespace FlaxEditor.Surface
public SurfaceParameter GetParameter(Guid id)
{
SurfaceParameter result = null;
for (int i = 0; i < Parameters.Count; i++)
var parameters = Parameters;
for (int i = 0; i < parameters.Count; i++)
{
var parameter = Parameters[i];
var parameter = parameters[i];
if (parameter.ID == id)
{
result = parameter;
@@ -274,9 +275,10 @@ namespace FlaxEditor.Surface
public SurfaceParameter GetParameter(string name)
{
SurfaceParameter result = null;
for (int i = 0; i < Parameters.Count; i++)
var parameters = Parameters;
for (int i = 0; i < parameters.Count; i++)
{
var parameter = Parameters[i];
var parameter = parameters[i];
if (parameter.Name == name)
{
result = parameter;

View File

@@ -187,6 +187,9 @@ namespace FlaxEditor.Surface
/// <inheritdoc />
public override bool CanSetParameters => true;
/// <inheritdoc />
public override bool CanShowPrivateParameters => true;
/// <inheritdoc />
public override bool UseContextMenuDescriptionPanel => true;

View File

@@ -89,7 +89,7 @@ namespace FlaxEditor.Tools.Terrain
if (!terrain.HasPatch(ref patchCoord) && _planeModel)
{
var planeSize = 100.0f;
var patchSize = terrain.ChunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
var patchSize = terrain.PatchSize;
Matrix world = Matrix.RotationX(-Mathf.PiOverTwo) *
Matrix.Scaling(patchSize / planeSize) *
Matrix.Translation(patchSize * (0.5f + patchCoord.X), 0, patchSize * (0.5f + patchCoord.Y)) *

View File

@@ -69,9 +69,9 @@ namespace FlaxEditor.Tools.Terrain.Paint
var splatmapIndex = ActiveSplatmapIndex;
var splatmapIndexOther = (splatmapIndex + 1) % 2;
var chunkSize = terrain.ChunkSize;
var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
var heightmapSize = terrain.HeightmapSize;
var heightmapLength = heightmapSize * heightmapSize;
var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
var patchSize = terrain.PatchSize;
var tempBuffer = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, splatmapIndex).ToPointer();
var tempBufferOther = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, (splatmapIndex + 1) % 2).ToPointer();
var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex;

View File

@@ -70,9 +70,9 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
// Prepare
var chunkSize = terrain.ChunkSize;
var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
var heightmapSize = terrain.HeightmapSize;
var heightmapLength = heightmapSize * heightmapSize;
var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
var patchSize = terrain.PatchSize;
var tempBuffer = (float*)gizmo.GetHeightmapTempBuffer(heightmapLength * sizeof(float)).ToPointer();
var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex;

View File

@@ -382,7 +382,8 @@ bool TerrainTools::ExportTerrain(Terrain* terrain, String outputFolder)
const Int2 heightmapSize = size * Terrain::ChunksCountEdge * terrain->GetChunkSize() + 1;
Array<float> heightmap;
heightmap.Resize(heightmapSize.X * heightmapSize.Y);
heightmap.SetAll(firstPatch->GetHeightmapData()[0]);
if (const float* heightmapData = firstPatch->GetHeightmapData())
heightmap.SetAll(heightmapData[0]);
// Fill heightmap with data from all patches
const int32 rowSize = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1;
@@ -392,8 +393,16 @@ bool TerrainTools::ExportTerrain(Terrain* terrain, String outputFolder)
const Int2 pos(patch->GetX() - start.X, patch->GetZ() - start.Y);
const float* src = patch->GetHeightmapData();
float* dst = heightmap.Get() + pos.X * (rowSize - 1) + pos.Y * heightmapSize.X * (rowSize - 1);
for (int32 row = 0; row < rowSize; row++)
Platform::MemoryCopy(dst + row * heightmapSize.X, src + row * rowSize, rowSize * sizeof(float));
if (src)
{
for (int32 row = 0; row < rowSize; row++)
Platform::MemoryCopy(dst + row * heightmapSize.X, src + row * rowSize, rowSize * sizeof(float));
}
else
{
for (int32 row = 0; row < rowSize; row++)
Platform::MemoryClear(dst + row * heightmapSize.X, rowSize * sizeof(float));
}
}
// Interpolate to 16-bit int

View File

@@ -85,8 +85,7 @@ namespace FlaxEditor.Tools.Terrain.Undo
{
_terrain = terrain.ID;
_patches = new List<PatchData>(4);
var chunkSize = terrain.ChunkSize;
var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
var heightmapSize = terrain.HeightmapSize;
_heightmapLength = heightmapSize * heightmapSize;
_heightmapDataSize = _heightmapLength * stride;

View File

@@ -41,7 +41,7 @@ namespace FlaxEditor.Actions
ActionString = name;
_pasteParent = pasteParent;
_idsMapping = new Dictionary<Guid, Guid>(objectIds.Length * 4);
_idsMapping = new Dictionary<Guid, Guid>(objectIds.Length);
for (int i = 0; i < objectIds.Length; i++)
{
_idsMapping[objectIds[i]] = Guid.NewGuid();
@@ -72,13 +72,24 @@ namespace FlaxEditor.Actions
/// <summary>
/// Links the broken parent reference (missing parent). By default links the actor to the first scene.
/// </summary>
/// <param name="actor">The actor.</param>
protected virtual void LinkBrokenParentReference(Actor actor)
/// <param name="actorNode">The actor node.</param>
protected virtual void LinkBrokenParentReference(ActorNode actorNode)
{
// Link to the first scene root
if (Level.ScenesCount == 0)
throw new Exception("Failed to paste actor with a broken reference. No loaded scenes.");
actor.SetParent(Level.GetScene(0), false);
actorNode.Actor.SetParent(Level.GetScene(0), false);
}
/// <summary>
/// Checks if actor has a broken parent reference. For example, it's linked to the parent that is indie prefab editor while it should be pasted into scene.
/// </summary>
/// <param name="actorNode">The actor node.</param>
protected virtual void CheckBrokenParentReference(ActorNode actorNode)
{
// Ensure pasted object ends up on a scene
if (actorNode.Actor.Scene == null)
LinkBrokenParentReference(actorNode);
}
/// <inheritdoc />
@@ -103,16 +114,13 @@ namespace FlaxEditor.Actions
for (int i = 0; i < actors.Length; i++)
{
var actor = actors[i];
// Check if has no parent linked (broken reference eg. old parent not existing)
if (actor.Parent == null)
{
LinkBrokenParentReference(actor);
}
var node = GetNode(actor.ID);
if (node is ActorNode actorNode)
{
// Check if has no parent linked (broken reference eg. old parent not existing)
if (actor.Parent == null)
LinkBrokenParentReference(actorNode);
nodes.Add(actorNode);
}
}
@@ -136,6 +144,12 @@ namespace FlaxEditor.Actions
nodeParents[i].Actor.SetParent(pasteParentNode.Actor, false);
}
}
else
{
// Sanity check on pasted actor to ensure they end up i na proper context (scene editor or specific prefab editor)
foreach (var node in nodeParents)
CheckBrokenParentReference(node);
}
// Store previously looked up names and the results
Dictionary<string, bool> foundNamesResults = new();

View File

@@ -166,7 +166,6 @@ namespace FlaxEditor.Viewport
#endif
// Input
internal bool _disableInputUpdate;
private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown;
private Float2 _startPos;
@@ -1229,7 +1228,7 @@ namespace FlaxEditor.Viewport
/// Orients the viewport.
/// </summary>
/// <param name="orientation">The orientation.</param>
protected void OrientViewport(Quaternion orientation)
public void OrientViewport(Quaternion orientation)
{
OrientViewport(ref orientation);
}
@@ -1238,7 +1237,7 @@ namespace FlaxEditor.Viewport
/// Orients the viewport.
/// </summary>
/// <param name="orientation">The orientation.</param>
protected virtual void OrientViewport(ref Quaternion orientation)
public virtual void OrientViewport(ref Quaternion orientation)
{
if (ViewportCamera is FPSCamera fpsCamera)
{

View File

@@ -1,18 +1,19 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using Object = FlaxEngine.Object;
using FlaxEditor.Content;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Options;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEditor.Viewport.Modes;
using FlaxEditor.Viewport.Widgets;
using FlaxEditor.Windows;
using FlaxEngine;
using FlaxEngine.Gizmo;
using FlaxEngine.GUI;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport
{
@@ -26,6 +27,7 @@ namespace FlaxEditor.Viewport
private readonly ContextMenuButton _showGridButton;
private readonly ContextMenuButton _showNavigationButton;
private readonly ContextMenuButton _toggleGameViewButton;
private readonly ContextMenuButton _showDirectionGizmoButton;
private SelectionOutline _customSelectionOutline;
/// <summary>
@@ -108,13 +110,14 @@ namespace FlaxEditor.Viewport
private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32);
private EditorSpritesRenderer _editorSpritesRenderer;
private ViewportRubberBandSelector _rubberBandSelector;
private DirectionGizmo _directionGizmo;
private bool _gameViewActive;
private ViewFlags _preGameViewFlags;
private ViewMode _preGameViewViewMode;
private bool _gameViewWasGridShown;
private bool _gameViewWasFpsCounterShown;
private bool _gameViewWasNagivationShown;
private bool _gameViewWasNavigationShown;
/// <summary>
/// Drag and drop handlers
@@ -226,6 +229,13 @@ namespace FlaxEditor.Viewport
// Add rubber band selector
_rubberBandSelector = new ViewportRubberBandSelector(this);
// Add direction gizmo
_directionGizmo = new DirectionGizmo(this)
{
AnchorPreset = AnchorPresets.TopRight,
Parent = this,
};
// Add grid
Grid = new GridGizmo(this);
Grid.EnabledChanged += gizmo => _showGridButton.Icon = gizmo.Enabled ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
@@ -244,6 +254,11 @@ namespace FlaxEditor.Viewport
_showNavigationButton = ViewWidgetShowMenu.AddButton("Navigation", inputOptions.ToggleNavMeshVisibility, () => ShowNavigation = !ShowNavigation);
_showNavigationButton.CloseMenuOnClick = false;
// Show direction gizmo widget
_showDirectionGizmoButton = ViewWidgetShowMenu.AddButton("Direction Gizmo", () => _directionGizmo.Visible = !_directionGizmo.Visible);
_showDirectionGizmoButton.AutoCheck = true;
_showDirectionGizmoButton.CloseMenuOnClick = false;
// Game View
ViewWidgetButtonMenu.AddSeparator();
_toggleGameViewButton = ViewWidgetButtonMenu.AddButton("Game View", inputOptions.ToggleGameView, ToggleGameView);
@@ -277,6 +292,18 @@ namespace FlaxEditor.Viewport
// Game View
InputActions.Add(options => options.ToggleGameView, ToggleGameView);
editor.Options.OptionsChanged += OnEditorOptionsChanged;
OnEditorOptionsChanged(editor.Options.Options);
}
private void OnEditorOptionsChanged(EditorOptions options)
{
_directionGizmo.Visible = options.Viewport.ShowDirectionGizmo;
_showDirectionGizmoButton.Checked = _directionGizmo.Visible;
_directionGizmo.Size = new Float2(DirectionGizmo.DefaultGizmoSize * options.Viewport.DirectionGizmoScale);
_directionGizmo.LocalX = -_directionGizmo.Size.X * 0.5f;
_directionGizmo.LocalY = _directionGizmo.Size.Y * 0.5f + ViewportWidgetsContainer.WidgetsHeight;
}
/// <inheritdoc />
@@ -514,14 +541,14 @@ namespace FlaxEditor.Viewport
_preGameViewViewMode = Task.ViewMode;
_gameViewWasGridShown = Grid.Enabled;
_gameViewWasFpsCounterShown = ShowFpsCounter;
_gameViewWasNagivationShown = ShowNavigation;
_gameViewWasNavigationShown = ShowNavigation;
}
// Set flags & values
Task.ViewFlags = _gameViewActive ? _preGameViewFlags : ViewFlags.DefaultGame;
Task.ViewMode = _gameViewActive ? _preGameViewViewMode : ViewMode.Default;
ShowFpsCounter = _gameViewActive ? _gameViewWasFpsCounterShown : false;
ShowNavigation = _gameViewActive ? _gameViewWasNagivationShown : false;
ShowNavigation = _gameViewActive ? _gameViewWasNavigationShown : false;
Grid.Enabled = _gameViewActive ? _gameViewWasGridShown : false;
_gameViewActive = !_gameViewActive;
@@ -647,7 +674,7 @@ namespace FlaxEditor.Viewport
}
/// <inheritdoc />
protected override void OrientViewport(ref Quaternion orientation)
public override void OrientViewport(ref Quaternion orientation)
{
if (TransformGizmo.SelectedParents.Count != 0)
FocusSelection(ref orientation);

View File

@@ -681,7 +681,7 @@ namespace FlaxEditor.Viewport
}
/// <inheritdoc />
protected override void OrientViewport(ref Quaternion orientation)
public override void OrientViewport(ref Quaternion orientation)
{
if (TransformGizmo.SelectedParents.Count != 0)
FocusSelection(ref orientation);

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