diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
index 8d3e3a8ec..902d8c399 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
@@ -246,24 +246,51 @@ namespace FlaxEditor.Windows.Assets
// Spawning actors options
contextMenu.AddSeparator();
- var spawnMenu = contextMenu.AddChildMenu("New");
- var newActorCm = spawnMenu.ContextMenu;
- for (int i = 0; i < SceneTreeWindow.SpawnActorsGroups.Length; i++)
+
+ // go through each actor and add it to the context menu if it has the ActorContextMenu attribute
+ foreach (var actorType in Editor.CodeEditing.Actors.Get())
{
- var group = SceneTreeWindow.SpawnActorsGroups[i];
-
- if (group.Types.Length == 1)
+ if (actorType.IsAbstract || !actorType.HasAttribute(typeof(ActorContextMenuAttribute), true))
+ continue;
+
+ ActorContextMenuAttribute attribute = null;
+ foreach (var actorAttribute in actorType.GetAttributes(true))
{
- var type = group.Types[0].Value;
- newActorCm.AddButton(group.Types[0].Key, () => Spawn(type));
- }
- else
- {
- var groupCm = newActorCm.AddChildMenu(group.Name).ContextMenu;
- for (int j = 0; j < group.Types.Length; j++)
+ if (actorAttribute is ActorContextMenuAttribute actorContextMenuAttribute)
{
- var type = group.Types[j].Value;
- groupCm.AddButton(group.Types[j].Key, () => Spawn(type));
+ attribute = actorContextMenuAttribute;
+ }
+ }
+ var splitPath = attribute?.Path.Split('/');
+ ContextMenuChildMenu childCM = null;
+ bool mainCM = true;
+ for (int i = 0; i < splitPath?.Length; i++)
+ {
+ if (i == splitPath.Length - 1)
+ {
+ if (mainCM)
+ {
+ contextMenu.AddButton(splitPath[i].Trim(), () => Spawn(actorType.Type));
+ mainCM = false;
+ }
+ else
+ {
+ childCM?.ContextMenu.AddButton(splitPath[i].Trim(), () => Spawn(actorType.Type));
+ childCM.ContextMenu.AutoSort = true;
+ }
+ }
+ else
+ {
+ if (mainCM)
+ {
+ childCM = contextMenu.GetOrAddChildMenu(splitPath[i].Trim());
+ mainCM = false;
+ }
+ else
+ {
+ childCM = childCM?.ContextMenu.GetOrAddChildMenu(splitPath[i].Trim());
+ }
+ childCM.ContextMenu.AutoSort = true;
}
}
}
diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
index bef3f1b3c..ce3b42cf7 100644
--- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
@@ -60,23 +60,56 @@ namespace FlaxEditor.Windows
if (isSingleActorSelected)
{
var convertMenu = contextMenu.AddChildMenu("Convert");
- var convertActorCm = convertMenu.ContextMenu;
- for (int i = 0; i < SpawnActorsGroups.Length; i++)
+ convertMenu.ContextMenu.AutoSort = true;
+ foreach (var actorType in Editor.CodeEditing.Actors.Get())
{
- var group = SpawnActorsGroups[i];
-
- if (group.Types.Length == 1)
+ if (actorType.IsAbstract || !actorType.HasAttribute(typeof(ActorContextMenuAttribute), true))
+ continue;
+
+ ActorContextMenuAttribute attribute = null;
+ foreach (var actorAttribute in actorType.GetAttributes(true))
{
- var type = group.Types[0].Value;
- convertActorCm.AddButton(group.Types[0].Key, () => Editor.SceneEditing.Convert(type));
- }
- else
- {
- var groupCm = convertActorCm.AddChildMenu(group.Name).ContextMenu;
- for (int j = 0; j < group.Types.Length; j++)
+ if (actorAttribute is ActorContextMenuAttribute actorContextMenuAttribute)
{
- var type = group.Types[j].Value;
- groupCm.AddButton(group.Types[j].Key, () => Editor.SceneEditing.Convert(type));
+ attribute = actorContextMenuAttribute;
+ }
+ }
+ var splitPath = attribute?.Path.Split('/');
+ ContextMenuChildMenu childCM = convertMenu;
+ bool mainCM = true;
+ for (int i = 0; i < splitPath?.Length; i++)
+ {
+ if (i == splitPath.Length - 1)
+ {
+ if (mainCM)
+ {
+ convertMenu.ContextMenu.AddButton(splitPath[i].Trim(), () => Editor.SceneEditing.Convert(actorType.Type));
+ mainCM = false;
+ }
+ else
+ {
+ childCM?.ContextMenu.AddButton(splitPath[i].Trim(), () => Editor.SceneEditing.Convert(actorType.Type));
+ childCM.ContextMenu.AutoSort = true;
+ }
+ }
+ else
+ {
+ // remove new path for converting menu
+ if (splitPath[i] == "New")
+ {
+ continue;
+ }
+
+ if (mainCM)
+ {
+ childCM = convertMenu.ContextMenu.GetOrAddChildMenu(splitPath[i].Trim());
+ mainCM = false;
+ }
+ else
+ {
+ childCM = childCM?.ContextMenu.GetOrAddChildMenu(splitPath[i].Trim());
+ }
+ childCM.ContextMenu.AutoSort = true;
}
}
}
@@ -113,25 +146,51 @@ namespace FlaxEditor.Windows
// Spawning actors options
contextMenu.AddSeparator();
-
- var spawnMenu = contextMenu.AddChildMenu("New");
- var newActorCm = spawnMenu.ContextMenu;
- for (int i = 0; i < SpawnActorsGroups.Length; i++)
+
+ // go through each actor and add it to the context menu if it has the ActorContextMenu attribute
+ foreach (var actorType in Editor.CodeEditing.Actors.Get())
{
- var group = SpawnActorsGroups[i];
-
- if (group.Types.Length == 1)
+ if (actorType.IsAbstract || !actorType.HasAttribute(typeof(ActorContextMenuAttribute), true))
+ continue;
+
+ ActorContextMenuAttribute attribute = null;
+ foreach (var actorAttribute in actorType.GetAttributes(true))
{
- var type = group.Types[0].Value;
- newActorCm.AddButton(group.Types[0].Key, () => Spawn(type));
- }
- else
- {
- var groupCm = newActorCm.AddChildMenu(group.Name).ContextMenu;
- for (int j = 0; j < group.Types.Length; j++)
+ if (actorAttribute is ActorContextMenuAttribute actorContextMenuAttribute)
{
- var type = group.Types[j].Value;
- groupCm.AddButton(group.Types[j].Key, () => Spawn(type));
+ attribute = actorContextMenuAttribute;
+ }
+ }
+ var splitPath = attribute?.Path.Split('/');
+ ContextMenuChildMenu childCM = null;
+ bool mainCM = true;
+ for (int i = 0; i < splitPath?.Length; i++)
+ {
+ if (i == splitPath.Length - 1)
+ {
+ if (mainCM)
+ {
+ contextMenu.AddButton(splitPath[i].Trim(), () => Spawn(actorType.Type));
+ mainCM = false;
+ }
+ else
+ {
+ childCM?.ContextMenu.AddButton(splitPath[i].Trim(), () => Spawn(actorType.Type));
+ childCM.ContextMenu.AutoSort = true;
+ }
+ }
+ else
+ {
+ if (mainCM)
+ {
+ childCM = contextMenu.GetOrAddChildMenu(splitPath[i].Trim());
+ mainCM = false;
+ }
+ else
+ {
+ childCM = childCM?.ContextMenu.GetOrAddChildMenu(splitPath[i].Trim());
+ }
+ childCM.ContextMenu.AutoSort = true;
}
}
}
diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs
index 0046782f5..8fd6e33ee 100644
--- a/Source/Editor/Windows/SceneTreeWindow.cs
+++ b/Source/Editor/Windows/SceneTreeWindow.cs
@@ -22,113 +22,6 @@ namespace FlaxEditor.Windows
///
public partial class SceneTreeWindow : SceneEditorWindow
{
- ///
- /// The spawnable actors group.
- ///
- public struct ActorsGroup
- {
- ///
- /// The group name.
- ///
- public string Name;
-
- ///
- /// The types to spawn (name and type).
- ///
- public KeyValuePair[] Types;
- }
-
- ///
- /// The Spawnable actors (groups with single entry are inlined without a child menu)
- ///
- public static readonly ActorsGroup[] SpawnActorsGroups =
- {
- new ActorsGroup
- {
- Types = new[] { new KeyValuePair("Actor", typeof(EmptyActor)) }
- },
- new ActorsGroup
- {
- Types = new[] { new KeyValuePair("Model", typeof(StaticModel)) }
- },
- new ActorsGroup
- {
- Types = new[] { new KeyValuePair("Camera", typeof(Camera)) }
- },
- new ActorsGroup
- {
- Name = "Lights",
- Types = new[]
- {
- new KeyValuePair("Directional Light", typeof(DirectionalLight)),
- new KeyValuePair("Point Light", typeof(PointLight)),
- new KeyValuePair("Spot Light", typeof(SpotLight)),
- new KeyValuePair("Sky Light", typeof(SkyLight)),
- }
- },
- new ActorsGroup
- {
- Name = "Visuals",
- Types = new[]
- {
- new KeyValuePair("Environment Probe", typeof(EnvironmentProbe)),
- new KeyValuePair("Sky", typeof(Sky)),
- new KeyValuePair("Skybox", typeof(Skybox)),
- new KeyValuePair("Exponential Height Fog", typeof(ExponentialHeightFog)),
- new KeyValuePair("PostFx Volume", typeof(PostFxVolume)),
- new KeyValuePair("Decal", typeof(Decal)),
- new KeyValuePair("Particle Effect", typeof(ParticleEffect)),
- }
- },
- new ActorsGroup
- {
- Name = "Physics",
- Types = new[]
- {
- new KeyValuePair("Rigid Body", typeof(RigidBody)),
- new KeyValuePair("Character Controller", typeof(CharacterController)),
- new KeyValuePair("Box Collider", typeof(BoxCollider)),
- new KeyValuePair("Sphere Collider", typeof(SphereCollider)),
- new KeyValuePair("Capsule Collider", typeof(CapsuleCollider)),
- new KeyValuePair("Mesh Collider", typeof(MeshCollider)),
- new KeyValuePair("Fixed Joint", typeof(FixedJoint)),
- new KeyValuePair("Distance Joint", typeof(DistanceJoint)),
- new KeyValuePair("Slider Joint", typeof(SliderJoint)),
- new KeyValuePair("Spherical Joint", typeof(SphericalJoint)),
- new KeyValuePair("Hinge Joint", typeof(HingeJoint)),
- new KeyValuePair("D6 Joint", typeof(D6Joint)),
- }
- },
- new ActorsGroup
- {
- Name = "Other",
- Types = new[]
- {
- new KeyValuePair("Animated Model", typeof(AnimatedModel)),
- new KeyValuePair("Bone Socket", typeof(BoneSocket)),
- new KeyValuePair("CSG Box Brush", typeof(BoxBrush)),
- new KeyValuePair("Audio Source", typeof(AudioSource)),
- new KeyValuePair("Audio Listener", typeof(AudioListener)),
- new KeyValuePair("Scene Animation", typeof(SceneAnimationPlayer)),
- new KeyValuePair("Nav Mesh Bounds Volume", typeof(NavMeshBoundsVolume)),
- new KeyValuePair("Nav Link", typeof(NavLink)),
- new KeyValuePair("Nav Modifier Volume", typeof(NavModifierVolume)),
- new KeyValuePair("Spline", typeof(Spline)),
- }
- },
- new ActorsGroup
- {
- Name = "GUI",
- Types = new[]
- {
- new KeyValuePair("UI Control", typeof(UIControl)),
- new KeyValuePair("UI Canvas", typeof(UICanvas)),
- new KeyValuePair("Text Render", typeof(TextRender)),
- new KeyValuePair("Sprite Render", typeof(SpriteRender)),
- }
- },
- };
-
private TextBox _searchBox;
private Tree _tree;
private Panel _sceneTreePanel;
diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h
index 4e0fcaa40..cdeb29fa5 100644
--- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h
+++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h
@@ -11,7 +11,7 @@
///
/// The scene animation playback actor.
///
-API_CLASS() class FLAXENGINE_API SceneAnimationPlayer : public Actor, public IPostFxSettingsProvider
+API_CLASS(Attributes = "ActorContextMenu(\"New/Other/Scene Animation\")") class FLAXENGINE_API SceneAnimationPlayer : public Actor, public IPostFxSettingsProvider
{
DECLARE_SCENE_OBJECT(SceneAnimationPlayer);
diff --git a/Source/Engine/Audio/AudioListener.h b/Source/Engine/Audio/AudioListener.h
index 4c9f608fa..589c8a676 100644
--- a/Source/Engine/Audio/AudioListener.h
+++ b/Source/Engine/Audio/AudioListener.h
@@ -7,7 +7,7 @@
///
/// Represents a listener that hears audio sources. For spatial audio the volume and pitch of played audio is determined by the distance, orientation and velocity differences between the source and the listener.
///
-API_CLASS() class FLAXENGINE_API AudioListener : public Actor
+API_CLASS(Attributes = "ActorContextMenu(\"New/Audio/Audio Listener\")") class FLAXENGINE_API AudioListener : public Actor
{
DECLARE_SCENE_OBJECT(AudioListener);
private:
diff --git a/Source/Engine/Audio/AudioSource.h b/Source/Engine/Audio/AudioSource.h
index ebc8dc957..ff181674e 100644
--- a/Source/Engine/Audio/AudioSource.h
+++ b/Source/Engine/Audio/AudioSource.h
@@ -13,7 +13,7 @@
///
/// Whether or not an audio source is spatial is controlled by the assigned AudioClip.The volume and the pitch of a spatial audio source is controlled by its position and the AudioListener's position/direction/velocity.
///
-API_CLASS() class FLAXENGINE_API AudioSource : public Actor
+API_CLASS(Attributes = "ActorContextMenu(\"New/Audio/Audio Source\")") class FLAXENGINE_API AudioSource : public Actor
{
DECLARE_SCENE_OBJECT(AudioSource);
friend class AudioStreamingHandler;
diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h
index 16f2740dd..96bcb3ad5 100644
--- a/Source/Engine/Level/Actors/AnimatedModel.h
+++ b/Source/Engine/Level/Actors/AnimatedModel.h
@@ -12,7 +12,7 @@
///
/// Performs an animation and renders a skinned model.
///
-API_CLASS() class FLAXENGINE_API AnimatedModel : public ModelInstanceActor
+API_CLASS(Attributes = "ActorContextMenu(\"New/Other/Animated Model\")") class FLAXENGINE_API AnimatedModel : public ModelInstanceActor
{
DECLARE_SCENE_OBJECT(AnimatedModel);
friend class AnimationsSystem;
diff --git a/Source/Engine/Level/Actors/BoneSocket.h b/Source/Engine/Level/Actors/BoneSocket.h
index e3ed1e5f4..88fd3fa1d 100644
--- a/Source/Engine/Level/Actors/BoneSocket.h
+++ b/Source/Engine/Level/Actors/BoneSocket.h
@@ -7,7 +7,7 @@
///
/// Actor that links to the animated model skeleton node transformation.
///
-API_CLASS() class FLAXENGINE_API BoneSocket : public Actor
+API_CLASS(Attributes = "ActorContextMenu(\"New/Other/Bone Socket\")") class FLAXENGINE_API BoneSocket : public Actor
{
DECLARE_SCENE_OBJECT(BoneSocket);
diff --git a/Source/Engine/Level/Actors/BoxBrush.h b/Source/Engine/Level/Actors/BoxBrush.h
index fc30aedc2..49128ee09 100644
--- a/Source/Engine/Level/Actors/BoxBrush.h
+++ b/Source/Engine/Level/Actors/BoxBrush.h
@@ -65,7 +65,7 @@ public:
///
/// Performs CSG box brush operation that adds or removes geometry.
///
-API_CLASS() class FLAXENGINE_API BoxBrush : public Actor, public CSG::Brush
+API_CLASS(Attributes = "ActorContextMenu(\"New/Other/Box Brush\")") class FLAXENGINE_API BoxBrush : public Actor, public CSG::Brush
{
DECLARE_SCENE_OBJECT(BoxBrush);
private:
diff --git a/Source/Engine/Level/Actors/Camera.h b/Source/Engine/Level/Actors/Camera.h
index 465130110..3b6912c43 100644
--- a/Source/Engine/Level/Actors/Camera.h
+++ b/Source/Engine/Level/Actors/Camera.h
@@ -18,7 +18,7 @@
///
/// Describes the camera projection and view. Provides information about how to render scene (viewport location and direction, etc.).
///
-API_CLASS(Sealed) class FLAXENGINE_API Camera : public Actor
+API_CLASS(Sealed, Attributes = "ActorContextMenu(\"New/Camera\")") class FLAXENGINE_API Camera : public Actor
{
DECLARE_SCENE_OBJECT(Camera);
diff --git a/Source/Engine/Level/Actors/Decal.h b/Source/Engine/Level/Actors/Decal.h
index 8cc6c2df7..b0acdd81e 100644
--- a/Source/Engine/Level/Actors/Decal.h
+++ b/Source/Engine/Level/Actors/Decal.h
@@ -11,7 +11,7 @@
///
/// Actor that draws the can be used to draw a custom decals on top of the other objects.
///
-API_CLASS() class FLAXENGINE_API Decal : public Actor
+API_CLASS(Attributes = "ActorContextMenu(\"New/Visuals/Decal\")") class FLAXENGINE_API Decal : public Actor
{
DECLARE_SCENE_OBJECT(Decal);
private:
diff --git a/Source/Engine/Level/Actors/DirectionalLight.h b/Source/Engine/Level/Actors/DirectionalLight.h
index 44a400a13..822005734 100644
--- a/Source/Engine/Level/Actors/DirectionalLight.h
+++ b/Source/Engine/Level/Actors/DirectionalLight.h
@@ -7,7 +7,7 @@
///
/// Directional light emits light from direction in space.
///
-API_CLASS() class FLAXENGINE_API DirectionalLight : public LightWithShadow
+API_CLASS(Attributes = "ActorContextMenu(\"New/Lights/Directional Light\")") class FLAXENGINE_API DirectionalLight : public LightWithShadow
{
DECLARE_SCENE_OBJECT(DirectionalLight);
private:
diff --git a/Source/Engine/Level/Actors/EmptyActor.h b/Source/Engine/Level/Actors/EmptyActor.h
index 00d1cf0a8..1e77816c4 100644
--- a/Source/Engine/Level/Actors/EmptyActor.h
+++ b/Source/Engine/Level/Actors/EmptyActor.h
@@ -7,7 +7,7 @@
///
/// The empty actor that is useful to create hierarchy and/or hold scripts. See .
///
-API_CLASS() class FLAXENGINE_API EmptyActor : public Actor
+API_CLASS(Attributes = "ActorContextMenu(\"New/Actor\")") class FLAXENGINE_API EmptyActor : public Actor
{
DECLARE_SCENE_OBJECT(EmptyActor);
public:
diff --git a/Source/Engine/Level/Actors/EnvironmentProbe.h b/Source/Engine/Level/Actors/EnvironmentProbe.h
index 9284ba328..5991bdaff 100644
--- a/Source/Engine/Level/Actors/EnvironmentProbe.h
+++ b/Source/Engine/Level/Actors/EnvironmentProbe.h
@@ -10,7 +10,7 @@
///
/// Environment Probe can capture space around the objects to provide reflections.
///
-API_CLASS() class FLAXENGINE_API EnvironmentProbe : public Actor
+API_CLASS(Attributes = "ActorContextMenu(\"New/Visuals/Environment Probe\")") class FLAXENGINE_API EnvironmentProbe : public Actor
{
DECLARE_SCENE_OBJECT(EnvironmentProbe);
public:
diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.h b/Source/Engine/Level/Actors/ExponentialHeightFog.h
index fe2bf316d..3da3bc570 100644
--- a/Source/Engine/Level/Actors/ExponentialHeightFog.h
+++ b/Source/Engine/Level/Actors/ExponentialHeightFog.h
@@ -12,7 +12,7 @@
///
/// Used to create fogging effects such as clouds but with a density that is related to the height of the fog.
///
-API_CLASS() class FLAXENGINE_API ExponentialHeightFog : public Actor, public IFogRenderer
+API_CLASS(Attributes = "ActorContextMenu(\"New/Visuals/Exponential Height Fog\")") class FLAXENGINE_API ExponentialHeightFog : public Actor, public IFogRenderer
{
DECLARE_SCENE_OBJECT(ExponentialHeightFog);
private:
diff --git a/Source/Engine/Level/Actors/PointLight.h b/Source/Engine/Level/Actors/PointLight.h
index d20fb4ea5..6304175af 100644
--- a/Source/Engine/Level/Actors/PointLight.h
+++ b/Source/Engine/Level/Actors/PointLight.h
@@ -9,7 +9,7 @@
///
/// Point light emits light from point in all directions.
///
-API_CLASS() class FLAXENGINE_API PointLight : public LightWithShadow
+API_CLASS(Attributes = "ActorContextMenu(\"New/Lights/Point Light\")") class FLAXENGINE_API PointLight : public LightWithShadow
{
DECLARE_SCENE_OBJECT(PointLight);
private:
diff --git a/Source/Engine/Level/Actors/PostFxVolume.h b/Source/Engine/Level/Actors/PostFxVolume.h
index bc269eb22..6139b5506 100644
--- a/Source/Engine/Level/Actors/PostFxVolume.h
+++ b/Source/Engine/Level/Actors/PostFxVolume.h
@@ -9,7 +9,7 @@
///
/// A special type of volume that blends custom set of post process settings into the rendering.
///
-API_CLASS() class FLAXENGINE_API PostFxVolume : public BoxVolume, public IPostFxSettingsProvider
+API_CLASS(Attributes = "ActorContextMenu(\"New/Visuals/Post Fx Volume\")") class FLAXENGINE_API PostFxVolume : public BoxVolume, public IPostFxSettingsProvider
{
DECLARE_SCENE_OBJECT(PostFxVolume);
private:
diff --git a/Source/Engine/Level/Actors/Sky.h b/Source/Engine/Level/Actors/Sky.h
index 1d8bbdbf9..69dc88756 100644
--- a/Source/Engine/Level/Actors/Sky.h
+++ b/Source/Engine/Level/Actors/Sky.h
@@ -14,7 +14,7 @@ class GPUPipelineState;
///
/// Sky actor renders atmosphere around the scene with fog and sky.
///
-API_CLASS() class FLAXENGINE_API Sky : public Actor, public IAtmosphericFogRenderer, public ISkyRenderer
+API_CLASS(Attributes = "ActorContextMenu(\"New/Visuals/Sky\")") class FLAXENGINE_API Sky : public Actor, public IAtmosphericFogRenderer, public ISkyRenderer
{
DECLARE_SCENE_OBJECT(Sky);
private:
diff --git a/Source/Engine/Level/Actors/SkyLight.h b/Source/Engine/Level/Actors/SkyLight.h
index 239cccd34..649e51d29 100644
--- a/Source/Engine/Level/Actors/SkyLight.h
+++ b/Source/Engine/Level/Actors/SkyLight.h
@@ -9,7 +9,7 @@
///
/// Sky light captures the distant parts of the scene and applies it as a light. Allows to add ambient light.
///
-API_CLASS() class FLAXENGINE_API SkyLight : public Light
+API_CLASS(Attributes = "ActorContextMenu(\"New/Lights/Sky Light\")") class FLAXENGINE_API SkyLight : public Light
{
DECLARE_SCENE_OBJECT(SkyLight);
public:
diff --git a/Source/Engine/Level/Actors/Skybox.h b/Source/Engine/Level/Actors/Skybox.h
index 12faaa3a3..116bf1ca1 100644
--- a/Source/Engine/Level/Actors/Skybox.h
+++ b/Source/Engine/Level/Actors/Skybox.h
@@ -11,7 +11,7 @@
///
/// Skybox actor renders sky using custom cube texture or material.
///
-API_CLASS() class FLAXENGINE_API Skybox : public Actor, public ISkyRenderer
+API_CLASS(Attributes = "ActorContextMenu(\"New/Visuals/Sky Box\")") class FLAXENGINE_API Skybox : public Actor, public ISkyRenderer
{
DECLARE_SCENE_OBJECT(Skybox);
private:
diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h
index 86f580075..a37d510d4 100644
--- a/Source/Engine/Level/Actors/Spline.h
+++ b/Source/Engine/Level/Actors/Spline.h
@@ -8,7 +8,7 @@
///
/// Spline shape actor that defines spatial curve with utility functions for general purpose usage.
///
-API_CLASS() class FLAXENGINE_API Spline : public Actor
+API_CLASS(Attributes = "ActorContextMenu(\"New/Other/Spline\")") class FLAXENGINE_API Spline : public Actor
{
DECLARE_SCENE_OBJECT(Spline);
typedef BezierCurveKeyframe Keyframe;
diff --git a/Source/Engine/Level/Actors/SpotLight.h b/Source/Engine/Level/Actors/SpotLight.h
index 8e2fe0e36..8e8b2717c 100644
--- a/Source/Engine/Level/Actors/SpotLight.h
+++ b/Source/Engine/Level/Actors/SpotLight.h
@@ -9,7 +9,7 @@
///
/// Spot light emits light from the point in a given direction.
///
-API_CLASS() class FLAXENGINE_API SpotLight : public LightWithShadow
+API_CLASS(Attributes = "ActorContextMenu(\"New/Lights/Spot Light\")") class FLAXENGINE_API SpotLight : public LightWithShadow
{
DECLARE_SCENE_OBJECT(SpotLight);
private:
diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h
index 7ab5263df..ced193bee 100644
--- a/Source/Engine/Level/Actors/StaticModel.h
+++ b/Source/Engine/Level/Actors/StaticModel.h
@@ -10,7 +10,7 @@
///
/// Renders model on the screen.
///
-API_CLASS() class FLAXENGINE_API StaticModel : public ModelInstanceActor
+API_CLASS(Attributes = "ActorContextMenu(\"New/Model\")") class FLAXENGINE_API StaticModel : public ModelInstanceActor
{
DECLARE_SCENE_OBJECT(StaticModel);
private:
diff --git a/Source/Engine/Navigation/NavLink.h b/Source/Engine/Navigation/NavLink.h
index 50a6cc39e..244ff2f97 100644
--- a/Source/Engine/Navigation/NavLink.h
+++ b/Source/Engine/Navigation/NavLink.h
@@ -8,7 +8,7 @@
/// The off-mesh link objects used to define a custom point-to-point edge within the navigation graph.
/// An off-mesh connection is a user defined traversable connection made up to two vertices, at least one of which resides within a navigation mesh polygon allowing movement outside the navigation mesh.
///
-API_CLASS() class FLAXENGINE_API NavLink : public Actor
+API_CLASS(Attributes = "ActorContextMenu(\"New/Other/Nav Link\")") class FLAXENGINE_API NavLink : public Actor
{
DECLARE_SCENE_OBJECT(NavLink);
public:
diff --git a/Source/Engine/Navigation/NavMeshBoundsVolume.h b/Source/Engine/Navigation/NavMeshBoundsVolume.h
index 51f2e80eb..e4218a2ca 100644
--- a/Source/Engine/Navigation/NavMeshBoundsVolume.h
+++ b/Source/Engine/Navigation/NavMeshBoundsVolume.h
@@ -8,7 +8,7 @@
///
/// A special type of volume that defines the area of the scene in which navigation meshes are generated.
///
-API_CLASS() class FLAXENGINE_API NavMeshBoundsVolume : public BoxVolume
+API_CLASS(Attributes = "ActorContextMenu(\"New/Other/Nav Mesh Bounds Volume\")") class FLAXENGINE_API NavMeshBoundsVolume : public BoxVolume
{
DECLARE_SCENE_OBJECT(NavMeshBoundsVolume);
public:
diff --git a/Source/Engine/Navigation/NavModifierVolume.h b/Source/Engine/Navigation/NavModifierVolume.h
index f06149872..8c7221037 100644
--- a/Source/Engine/Navigation/NavModifierVolume.h
+++ b/Source/Engine/Navigation/NavModifierVolume.h
@@ -8,7 +8,7 @@
///
/// A special type of volume that defines the area of the scene in which navigation is restricted (eg. higher traversal cost or dynamic obstacle block).
///
-API_CLASS() class FLAXENGINE_API NavModifierVolume : public BoxVolume
+API_CLASS(Attributes = "ActorContextMenu(\"New/Other/Nav Modifier Volume\")") class FLAXENGINE_API NavModifierVolume : public BoxVolume
{
DECLARE_SCENE_OBJECT(NavModifierVolume);
public:
diff --git a/Source/Engine/Particles/ParticleEffect.h b/Source/Engine/Particles/ParticleEffect.h
index c71775ff7..d0978b92a 100644
--- a/Source/Engine/Particles/ParticleEffect.h
+++ b/Source/Engine/Particles/ParticleEffect.h
@@ -133,7 +133,7 @@ public:
///
/// The particle system instance that plays the particles simulation in the game.
///
-API_CLASS() class FLAXENGINE_API ParticleEffect : public Actor
+API_CLASS(Attributes = "ActorContextMenu(\"New/Visuals/Particle Effects\")") class FLAXENGINE_API ParticleEffect : public Actor
{
DECLARE_SCENE_OBJECT(ParticleEffect);
public:
diff --git a/Source/Engine/Physics/Actors/RigidBody.h b/Source/Engine/Physics/Actors/RigidBody.h
index a2ed21948..dd7ab3694 100644
--- a/Source/Engine/Physics/Actors/RigidBody.h
+++ b/Source/Engine/Physics/Actors/RigidBody.h
@@ -14,7 +14,7 @@ class Collider;
/// Physics simulation driven object.
///
///
-API_CLASS() class FLAXENGINE_API RigidBody : public Actor, public IPhysicsActor
+API_CLASS(Attributes = "ActorContextMenu(\"New/Physics/Rigid Body\")") class FLAXENGINE_API RigidBody : public Actor, public IPhysicsActor
{
DECLARE_SCENE_OBJECT(RigidBody);
protected:
diff --git a/Source/Engine/Physics/Colliders/BoxCollider.h b/Source/Engine/Physics/Colliders/BoxCollider.h
index d49ae33bd..5d6f107e2 100644
--- a/Source/Engine/Physics/Colliders/BoxCollider.h
+++ b/Source/Engine/Physics/Colliders/BoxCollider.h
@@ -9,7 +9,7 @@
/// A box-shaped primitive collider.
///
///
-API_CLASS() class FLAXENGINE_API BoxCollider : public Collider
+API_CLASS(Attributes = "ActorContextMenu(\"New/Physics/Box Collider\")") class FLAXENGINE_API BoxCollider : public Collider
{
DECLARE_SCENE_OBJECT(BoxCollider);
private:
diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.h b/Source/Engine/Physics/Colliders/CapsuleCollider.h
index f62bd4154..8d8c9da78 100644
--- a/Source/Engine/Physics/Colliders/CapsuleCollider.h
+++ b/Source/Engine/Physics/Colliders/CapsuleCollider.h
@@ -12,7 +12,7 @@
/// Capsules are cylinders with a half-sphere at each end centered at the origin and extending along the X axis, and two hemispherical ends.
///
///
-API_CLASS() class FLAXENGINE_API CapsuleCollider : public Collider
+API_CLASS(Attributes = "ActorContextMenu(\"New/Physics/Capsule Collider\")") class FLAXENGINE_API CapsuleCollider : public Collider
{
DECLARE_SCENE_OBJECT(CapsuleCollider);
private:
diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h
index 8087d61e8..9169723c5 100644
--- a/Source/Engine/Physics/Colliders/CharacterController.h
+++ b/Source/Engine/Physics/Colliders/CharacterController.h
@@ -9,7 +9,7 @@
/// Physical objects that allows to easily do player movement constrained by collisions without having to deal with a rigidbody.
///
///
-API_CLASS() class FLAXENGINE_API CharacterController : public Collider, public IPhysicsActor
+API_CLASS(Attributes = "ActorContextMenu(\"New/Physics/Character Controller\")") class FLAXENGINE_API CharacterController : public Collider, public IPhysicsActor
{
DECLARE_SCENE_OBJECT(CharacterController);
public:
diff --git a/Source/Engine/Physics/Colliders/MeshCollider.h b/Source/Engine/Physics/Colliders/MeshCollider.h
index a12746623..a7ecc5bca 100644
--- a/Source/Engine/Physics/Colliders/MeshCollider.h
+++ b/Source/Engine/Physics/Colliders/MeshCollider.h
@@ -10,7 +10,7 @@
/// A collider represented by an arbitrary mesh.
///
///
-API_CLASS() class FLAXENGINE_API MeshCollider : public Collider
+API_CLASS(Attributes = "ActorContextMenu(\"New/Physics/Mesh Collider\")") class FLAXENGINE_API MeshCollider : public Collider
{
DECLARE_SCENE_OBJECT(MeshCollider);
public:
diff --git a/Source/Engine/Physics/Colliders/SphereCollider.h b/Source/Engine/Physics/Colliders/SphereCollider.h
index 1638300ff..57d4d2898 100644
--- a/Source/Engine/Physics/Colliders/SphereCollider.h
+++ b/Source/Engine/Physics/Colliders/SphereCollider.h
@@ -8,7 +8,7 @@
/// A sphere-shaped primitive collider.
///
///
-API_CLASS() class FLAXENGINE_API SphereCollider : public Collider
+API_CLASS(Attributes = "ActorContextMenu(\"New/Physics/Sphere Collider\")") class FLAXENGINE_API SphereCollider : public Collider
{
DECLARE_SCENE_OBJECT(SphereCollider);
private:
diff --git a/Source/Engine/Physics/Joints/D6Joint.h b/Source/Engine/Physics/Joints/D6Joint.h
index 66819bb0b..b53d3cd28 100644
--- a/Source/Engine/Physics/Joints/D6Joint.h
+++ b/Source/Engine/Physics/Joints/D6Joint.h
@@ -160,7 +160,7 @@ public:
/// It also allows you to constrain limits to only specific axes or completely lock specific axes.
///
///
-API_CLASS() class FLAXENGINE_API D6Joint : public Joint
+API_CLASS(Attributes = "ActorContextMenu(\"New/Physics/D6 Joint\")") class FLAXENGINE_API D6Joint : public Joint
{
DECLARE_SCENE_OBJECT(D6Joint);
private:
diff --git a/Source/Engine/Physics/Joints/DistanceJoint.h b/Source/Engine/Physics/Joints/DistanceJoint.h
index cb31b8215..d36562796 100644
--- a/Source/Engine/Physics/Joints/DistanceJoint.h
+++ b/Source/Engine/Physics/Joints/DistanceJoint.h
@@ -37,7 +37,7 @@ DECLARE_ENUM_OPERATORS(DistanceJointFlag);
/// Physics joint that maintains an upper or lower (or both) bound on the distance between two bodies.
///
///
-API_CLASS() class FLAXENGINE_API DistanceJoint : public Joint
+API_CLASS(Attributes = "ActorContextMenu(\"New/Physics/Distance Joint\")") class FLAXENGINE_API DistanceJoint : public Joint
{
DECLARE_SCENE_OBJECT(DistanceJoint);
private:
diff --git a/Source/Engine/Physics/Joints/FixedJoint.h b/Source/Engine/Physics/Joints/FixedJoint.h
index 9e6280023..ce617aa40 100644
--- a/Source/Engine/Physics/Joints/FixedJoint.h
+++ b/Source/Engine/Physics/Joints/FixedJoint.h
@@ -8,7 +8,7 @@
/// Physics joint that maintains a fixed distance and orientation between its two attached bodies.
///
///
-API_CLASS() class FLAXENGINE_API FixedJoint : public Joint
+API_CLASS(Attributes = "ActorContextMenu(\"New/Physics/Fixed Joint\")") class FLAXENGINE_API FixedJoint : public Joint
{
DECLARE_SCENE_OBJECT(FixedJoint);
public:
diff --git a/Source/Engine/Physics/Joints/HingeJoint.h b/Source/Engine/Physics/Joints/HingeJoint.h
index 14e3198e5..fc0719466 100644
--- a/Source/Engine/Physics/Joints/HingeJoint.h
+++ b/Source/Engine/Physics/Joints/HingeJoint.h
@@ -75,7 +75,7 @@ public:
/// Physics joint that removes all but a single rotation degree of freedom from its two attached bodies (for example a door hinge).
///
///
-API_CLASS() class FLAXENGINE_API HingeJoint : public Joint
+API_CLASS(Attributes = "ActorContextMenu(\"New/Physics/Hinge Joint\")") class FLAXENGINE_API HingeJoint : public Joint
{
DECLARE_SCENE_OBJECT(HingeJoint);
private:
diff --git a/Source/Engine/Physics/Joints/SliderJoint.h b/Source/Engine/Physics/Joints/SliderJoint.h
index b909ee925..bab3fcc75 100644
--- a/Source/Engine/Physics/Joints/SliderJoint.h
+++ b/Source/Engine/Physics/Joints/SliderJoint.h
@@ -27,7 +27,7 @@ DECLARE_ENUM_OPERATORS(SliderJointFlag);
/// Physics joint that removes all but a single translational degree of freedom. Bodies are allowed to move along a single axis.
///
///
-API_CLASS() class FLAXENGINE_API SliderJoint : public Joint
+API_CLASS(Attributes = "ActorContextMenu(\"New/Physics/Slider Joint\")") class FLAXENGINE_API SliderJoint : public Joint
{
DECLARE_SCENE_OBJECT(SliderJoint);
private:
diff --git a/Source/Engine/Physics/Joints/SphericalJoint.h b/Source/Engine/Physics/Joints/SphericalJoint.h
index 328575fa2..591ca1357 100644
--- a/Source/Engine/Physics/Joints/SphericalJoint.h
+++ b/Source/Engine/Physics/Joints/SphericalJoint.h
@@ -29,7 +29,7 @@ DECLARE_ENUM_OPERATORS(SphericalJointFlag);
/// rotate around the anchor points, and their rotation can be limited by an elliptical cone.
///
///
-API_CLASS() class FLAXENGINE_API SphericalJoint : public Joint
+API_CLASS(Attributes = "ActorContextMenu(\"New/Physics/Spherical Joint\")") class FLAXENGINE_API SphericalJoint : public Joint
{
DECLARE_SCENE_OBJECT(SphericalJoint);
private:
diff --git a/Source/Engine/Scripting/Attributes/Editor/ActorContextMenu.cs b/Source/Engine/Scripting/Attributes/Editor/ActorContextMenu.cs
new file mode 100644
index 000000000..aab4ed92d
--- /dev/null
+++ b/Source/Engine/Scripting/Attributes/Editor/ActorContextMenu.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace FlaxEngine
+{
+ ///
+ /// This attribute is used to show actors that can be created in the scene and prefab context menus. Separate the subcontext menus with a /.
+ ///
+ [Serializable]
+ [AttributeUsage(AttributeTargets.Class)]
+ public class ActorContextMenuAttribute : Attribute
+ {
+ ///
+ /// The path to be used in the context menu
+ ///
+ public string Path;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The path to use to create the context menu
+ public ActorContextMenuAttribute(string path)
+ {
+ Path = path;
+ }
+ }
+}
diff --git a/Source/Engine/UI/SpriteRender.h b/Source/Engine/UI/SpriteRender.h
index bb976da5e..32a2d8b64 100644
--- a/Source/Engine/UI/SpriteRender.h
+++ b/Source/Engine/UI/SpriteRender.h
@@ -10,7 +10,7 @@
///
/// Sprite rendering object.
///
-API_CLASS() class FLAXENGINE_API SpriteRender : public Actor
+API_CLASS(Attributes = "ActorContextMenu(\"New/UI/Sprite Render\")") class FLAXENGINE_API SpriteRender : public Actor
{
DECLARE_SCENE_OBJECT(SpriteRender);
private:
diff --git a/Source/Engine/UI/TextRender.h b/Source/Engine/UI/TextRender.h
index 82858b580..695df7c01 100644
--- a/Source/Engine/UI/TextRender.h
+++ b/Source/Engine/UI/TextRender.h
@@ -19,7 +19,7 @@
///
/// Text rendering object.
///
-API_CLASS() class FLAXENGINE_API TextRender : public Actor
+API_CLASS(Attributes = "ActorContextMenu(\"New/UI/Text Render\")") class FLAXENGINE_API TextRender : public Actor
{
DECLARE_SCENE_OBJECT(TextRender);
private:
diff --git a/Source/Engine/UI/UICanvas.h b/Source/Engine/UI/UICanvas.h
index 4eff6cebc..1d1aefb7d 100644
--- a/Source/Engine/UI/UICanvas.h
+++ b/Source/Engine/UI/UICanvas.h
@@ -7,7 +7,7 @@
///
/// Root of the UI structure. Renders GUI and handles input events forwarding.
///
-API_CLASS(Sealed, NoConstructor) class FLAXENGINE_API UICanvas : public Actor
+API_CLASS(Sealed, NoConstructor, Attributes = "ActorContextMenu(\"New/UI/UI Canvas\")") class FLAXENGINE_API UICanvas : public Actor
{
DECLARE_SCENE_OBJECT(UICanvas);
public:
diff --git a/Source/Engine/UI/UIControl.h b/Source/Engine/UI/UIControl.h
index 27de5d855..2de1a5455 100644
--- a/Source/Engine/UI/UIControl.h
+++ b/Source/Engine/UI/UIControl.h
@@ -8,7 +8,7 @@
///
/// Contains a single GUI control (on C# side).
///
-API_CLASS(Sealed) class FLAXENGINE_API UIControl : public Actor
+API_CLASS(Sealed, Attributes = "ActorContextMenu(\"New/UI/UI Control\")") class FLAXENGINE_API UIControl : public Actor
{
DECLARE_SCENE_OBJECT(UIControl);
private: