diff --git a/Source/Editor/Content/Create/PrefabCreateEntry.cs b/Source/Editor/Content/Create/PrefabCreateEntry.cs
new file mode 100644
index 000000000..06d0c94d4
--- /dev/null
+++ b/Source/Editor/Content/Create/PrefabCreateEntry.cs
@@ -0,0 +1,173 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+using System;
+using FlaxEditor.Scripting;
+using FlaxEngine;
+using FlaxEngine.GUI;
+using Object = FlaxEngine.Object;
+
+namespace FlaxEditor.Content.Create
+{
+ ///
+ /// Prefab asset creating handler. Allows to specify base actor to use as the root.
+ ///
+ ///
+ public class PrefabCreateEntry : CreateFileEntry
+ {
+ ///
+ /// The create options.
+ ///
+ public class Options
+ {
+ ///
+ /// The root actor.
+ ///
+ [TypeReference(typeof(FlaxEngine.Actor), nameof(IsValid))]
+ [Tooltip("The actor type of the root of the new Prefab.")]
+ public Type RootActorType = typeof(EmptyActor);
+
+ private static bool IsValid(Type type)
+ {
+ return (type.IsPublic || type.IsNestedPublic) && !type.IsAbstract && !type.IsGenericType;
+ }
+ }
+
+ private readonly Options _options = new Options();
+
+ ///
+ public override object Settings => _options;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The result file url.
+ public PrefabCreateEntry(string resultUrl)
+ : base("Settings", resultUrl)
+ {
+ }
+
+ ///
+ public override bool Create()
+ {
+ var actorType = new ScriptType(_options.RootActorType ?? typeof(EmptyActor));
+ Actor actor;
+ try
+ {
+ actor = actorType.CreateInstance() as Actor;
+ Object.Destroy(actor, 20.0f);
+ }
+ catch (Exception ex)
+ {
+ Editor.LogError("Failed to create prefab with root actor type: " + actorType.Name);
+ Editor.LogWarning(ex);
+ return true;
+ }
+
+ return PrefabManager.CreatePrefab(actor, ResultUrl, true);
+ }
+ }
+
+ ///
+ /// Widget asset creating handler. Allows to specify base UIControl to use as the root.
+ ///
+ ///
+ public class WidgetCreateEntry : CreateFileEntry
+ {
+ ///
+ /// The create options.
+ ///
+ public class Options
+ {
+ ///
+ /// Which mode is used to initialize this widget.
+ ///
+ public enum WidgetMode
+ {
+ ///
+ /// Initialize the widget with a UICanvas.
+ ///
+ Canvas,
+
+ ///
+ /// Initialize the widget with a UIControl.
+ ///
+ Control
+ }
+
+ ///
+ /// The mode used to initialize the widget.
+ ///
+ [Tooltip("Whether to initialize the widget with a canvas or a control.")]
+ public WidgetMode WidgetInitializationMode = WidgetMode.Control;
+
+ bool ShowRoot => WidgetInitializationMode == WidgetMode.Control;
+
+ ///
+ /// The root control.
+ ///
+ [TypeReference(typeof(Control), nameof(IsValid))]
+ [Tooltip("The control type of the root of the new Widget's root control."), VisibleIf(nameof(ShowRoot))]
+ public Type RootControlType = typeof(Button);
+
+ private static bool IsValid(Type type)
+ {
+ return (type.IsPublic || type.IsNestedPublic) && !type.IsAbstract && !type.IsGenericType;
+ }
+ }
+
+ private readonly Options _options = new Options();
+
+ ///
+ public override object Settings => _options;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The result file url.
+ public WidgetCreateEntry(string resultUrl)
+ : base("Settings", resultUrl)
+ {
+ }
+
+ ///
+ public override bool Create()
+ {
+ Actor actor = null;
+
+ if (_options.WidgetInitializationMode == Options.WidgetMode.Control)
+ {
+ var controlType = new ScriptType(_options.RootControlType ?? typeof(Control));
+ Control control;
+ try
+ {
+ control = controlType.CreateInstance() as Control;
+ }
+ catch (Exception ex)
+ {
+ Editor.LogError("Failed to create widget with root control type: " + controlType.Name);
+ Editor.LogWarning(ex);
+ return true;
+ }
+
+ actor = new UIControl
+ {
+ Control = control,
+ Name = controlType.Name
+ };
+ }
+ else if (_options.WidgetInitializationMode == Options.WidgetMode.Canvas)
+ {
+ actor = new UICanvas();
+ }
+
+ if (actor == null)
+ {
+ Editor.LogError("Failed to create widget. Final actor was null.");
+ return true;
+ }
+ Object.Destroy(actor, 20.0f);
+
+ return PrefabManager.CreatePrefab(actor, ResultUrl, true);
+ }
+ }
+}
diff --git a/Source/Editor/Content/Items/PrefabItem.cs b/Source/Editor/Content/Items/PrefabItem.cs
index a61e07e7c..3638f274f 100644
--- a/Source/Editor/Content/Items/PrefabItem.cs
+++ b/Source/Editor/Content/Items/PrefabItem.cs
@@ -11,6 +11,8 @@ namespace FlaxEditor.Content
///
public sealed class PrefabItem : JsonAssetItem
{
+ private string _cachedTypeDescription = null;
+
///
/// Initializes a new instance of the class.
///
@@ -42,6 +44,26 @@ namespace FlaxEditor.Content
///
public override SpriteHandle DefaultThumbnail => SpriteHandle.Invalid;
+ ///
+ public override string TypeDescription
+ {
+ get
+ {
+ if (_cachedTypeDescription == null)
+ {
+ _cachedTypeDescription = "Prefab";
+ var prefab = FlaxEngine.Content.Load(ID);
+ if (prefab)
+ {
+ Actor root = prefab.GetDefaultInstance();
+ if (root is UIControl or UICanvas)
+ _cachedTypeDescription = "Widget";
+ }
+ }
+ return _cachedTypeDescription;
+ }
+ }
+
///
public override bool IsOfType(Type type)
{
diff --git a/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs b/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs
index 8c1204128..be2530efa 100644
--- a/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs
+++ b/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs
@@ -3,6 +3,9 @@
using System;
using FlaxEditor.Content.Create;
using FlaxEditor.Content.Thumbnails;
+using FlaxEditor.GUI.ContextMenu;
+using FlaxEditor.GUI.Timeline;
+using FlaxEditor.GUI.Timeline.Tracks;
using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
@@ -48,6 +51,63 @@ namespace FlaxEditor.Content
Editor.Instance.ContentImporting.Create(new ParticleEmitterCreateEntry(outputPath));
}
+ ///
+ public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item)
+ {
+ base.OnContentWindowContextMenu(menu, item);
+
+ if (item is BinaryAssetItem binaryAssetItem)
+ {
+ var button = menu.AddButton("Create Particle System", CreateParticleSystemClicked);
+ button.Tag = binaryAssetItem;
+ }
+ }
+
+ private void CreateParticleSystemClicked(ContextMenuButton obj)
+ {
+ var binaryAssetItem = (BinaryAssetItem)obj.Tag;
+ CreateParticleSystem(binaryAssetItem);
+ }
+
+ ///
+ /// Creates the particle system from the given particle emitter.
+ ///
+ /// The particle emitter item to use as a base for the particle system.
+ public static void CreateParticleSystem(BinaryAssetItem emitterItem)
+ {
+ var particleSystemName = emitterItem.ShortName + " Particle System";
+ var particleSystemProxy = Editor.Instance.ContentDatabase.GetProxy();
+ Editor.Instance.Windows.ContentWin.NewItem(particleSystemProxy, null, item => OnParticleSystemCreated(item, emitterItem), particleSystemName);
+ }
+
+ private static void OnParticleSystemCreated(ContentItem item, BinaryAssetItem particleItem)
+ {
+ var assetItem = (AssetItem)item;
+ var particleSystem = FlaxEngine.Content.LoadAsync(assetItem.ID);
+ if (particleSystem == null || particleSystem.WaitForLoaded())
+ {
+ Editor.LogError("Failed to load created particle system.");
+ return;
+ }
+
+ ParticleEmitter emitter = FlaxEngine.Content.LoadAsync(particleItem.ID);
+ if (emitter == null || emitter.WaitForLoaded())
+ {
+ Editor.LogError("Failed to load base particle emitter.");
+ }
+
+ ParticleSystemPreview tempPreview = new ParticleSystemPreview(false);
+ ParticleSystemTimeline timeline = new ParticleSystemTimeline(tempPreview);
+ timeline.Load(particleSystem);
+
+ var track = (ParticleEmitterTrack)timeline.NewTrack(ParticleEmitterTrack.GetArchetype());
+ track.Asset = emitter;
+ track.TrackMedia.DurationFrames = timeline.DurationFrames;
+ track.Rename(particleItem.ShortName);
+ timeline.AddTrack(track);
+ timeline.Save(particleSystem);
+ }
+
///
public override void OnThumbnailDrawPrepare(ThumbnailRequest request)
{
diff --git a/Source/Editor/Content/Proxy/PrefabProxy.cs b/Source/Editor/Content/Proxy/PrefabProxy.cs
index d2971f296..27bece29c 100644
--- a/Source/Editor/Content/Proxy/PrefabProxy.cs
+++ b/Source/Editor/Content/Proxy/PrefabProxy.cs
@@ -1,14 +1,13 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
-using System.IO;
+using FlaxEditor.Content.Create;
using FlaxEditor.Content.Thumbnails;
using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
-using Object = FlaxEngine.Object;
namespace FlaxEditor.Content
{
@@ -90,14 +89,8 @@ namespace FlaxEditor.Content
var transform = Transform.Identity;
if (!(arg is Actor actor))
{
- // Create default prefab root object
- actor = new EmptyActor
- {
- Name = "Root"
- };
-
- // Cleanup it after usage
- Object.Destroy(actor, 20.0f);
+ Editor.Instance.ContentImporting.Create(new PrefabCreateEntry(outputPath));
+ return;
}
else if (actor.HasScene)
{
@@ -251,18 +244,8 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- // Create prefab with UI Control
- var actor = new UIControl
- {
- Name = Path.GetFileNameWithoutExtension(outputPath),
- StaticFlags = StaticFlags.None,
- };
- actor.Control = new Button
- {
- Text = "Button",
- };
- PrefabManager.CreatePrefab(actor, outputPath, false);
- Object.Destroy(actor, 20.0f);
+ Editor.Instance.ContentImporting.Create(new WidgetCreateEntry(outputPath));
+ return;
}
}
}
diff --git a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs
index e05ca80d9..551dd1beb 100644
--- a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs
+++ b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs
@@ -2,6 +2,7 @@
using System;
using FlaxEditor.Content.Thumbnails;
+using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
@@ -39,6 +40,59 @@ namespace FlaxEditor.Content
///
public override Type AssetType => typeof(SkinnedModel);
+ ///
+ public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item)
+ {
+ base.OnContentWindowContextMenu(menu, item);
+
+ if (item is BinaryAssetItem binaryAssetItem)
+ {
+ var button = menu.AddButton("Create Animation Graph", CreateAnimationGraphClicked);
+ button.Tag = binaryAssetItem;
+ }
+ }
+
+ private void CreateAnimationGraphClicked(ContextMenuButton obj)
+ {
+ var binaryAssetItem = (BinaryAssetItem)obj.Tag;
+ CreateAnimationGraph(binaryAssetItem);
+ }
+
+ ///
+ /// Creates the animation graph from the given particle emitter.
+ ///
+ /// The skinned model item to use as the base model for the animation graph.
+ public static void CreateAnimationGraph(BinaryAssetItem skinnedModelItem)
+ {
+ var animationGraphName = skinnedModelItem.ShortName + " Graph";
+ var animationGraphProxy = Editor.Instance.ContentDatabase.GetProxy();
+ Editor.Instance.Windows.ContentWin.NewItem(animationGraphProxy, null, item => OnAnimationGraphCreated(item, skinnedModelItem), animationGraphName);
+ }
+
+ private static void OnAnimationGraphCreated(ContentItem item, BinaryAssetItem skinnedModelItem)
+ {
+ var skinnedModel = FlaxEngine.Content.Load(skinnedModelItem.ID);
+ if (skinnedModel == null)
+ {
+ Editor.LogError("Failed to load base skinned model.");
+ return;
+ }
+
+ // Hack the animation graph window to modify the base model of the animation graph.
+ // TODO: implement it without window logic (load AnimGraphSurface and set AnimationGraphWindow.BaseModelId to model)
+ AnimationGraphWindow win = new AnimationGraphWindow(Editor.Instance, item as AssetItem);
+ win.Show();
+
+ // Ensure the window knows the asset is loaded so we can save it later.
+ win.Asset.WaitForLoaded();
+ win.Update(0); // Call Update() to refresh the loaded flag.
+
+ win.SetBaseModel(skinnedModel);
+ win.Surface.MarkAsEdited();
+ win.Save();
+ win.Close();
+ }
+
///
public override void OnThumbnailDrawPrepare(ThumbnailRequest request)
{
diff --git a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs
index 9d65e5f7b..e75becf72 100644
--- a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs
+++ b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs
@@ -295,6 +295,15 @@ namespace FlaxEditor.Windows.Assets
base.SetParameter(index, value);
}
+ ///
+ /// Sets the base model of the animation graph this window is editing.
+ ///
+ /// The new base model.
+ public void SetBaseModel(SkinnedModel baseModel)
+ {
+ _properties.BaseModel = baseModel;
+ }
+
///
protected override void UnlinkItem()
{