Merge branch 'master' into visject_grid_snap
This commit is contained in:
@@ -2,35 +2,35 @@
|
||||
using System.Collections.Generic;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace %namespace%
|
||||
namespace %namespace%;
|
||||
|
||||
/// <summary>
|
||||
/// %class% Script.
|
||||
/// </summary>
|
||||
public class %class% : Script
|
||||
{
|
||||
/// <summary>
|
||||
/// %class% Script.
|
||||
/// </summary>
|
||||
public class %class% : Script
|
||||
/// <inheritdoc/>
|
||||
public override void OnStart()
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void OnStart()
|
||||
{
|
||||
// Here you can add code that needs to be called when script is created, just before the first game update
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnEnable()
|
||||
{
|
||||
// Here you can add code that needs to be called when script is enabled (eg. register for events)
|
||||
}
|
||||
// Here you can add code that needs to be called when script is created, just before the first game update
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnEnable()
|
||||
{
|
||||
// Here you can add code that needs to be called when script is enabled (eg. register for events)
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnDisable()
|
||||
{
|
||||
// Here you can add code that needs to be called when script is disabled (eg. unregister from events)
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override void OnDisable()
|
||||
{
|
||||
// Here you can add code that needs to be called when script is disabled (eg. unregister from events)
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnUpdate()
|
||||
{
|
||||
// Here you can add code that needs to be called every frame
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override void OnUpdate()
|
||||
{
|
||||
// Here you can add code that needs to be called every frame
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"Name": "Flax",
|
||||
"Version": {
|
||||
"Major": 1,
|
||||
"Minor": 6,
|
||||
"Build": 6345
|
||||
"Minor": 7,
|
||||
"Build": 6401
|
||||
},
|
||||
"Company": "Flax",
|
||||
"Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.",
|
||||
|
||||
@@ -256,6 +256,8 @@
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=comperand/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=coord/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=cubemap/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Deformer/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=deformers/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=defragmentation/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Delaunay/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Defocus/@EntryIndexedValue">True</s:Boolean>
|
||||
@@ -321,6 +323,7 @@
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=raycast/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=raycasting/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=raycasts/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=reachability/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=readback/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Reimports/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=reimported/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace FlaxEditor.Content.GUI
|
||||
_validDragOver = true;
|
||||
result = DragDropEffect.Copy;
|
||||
}
|
||||
else if (_dragActors.HasValidDrag)
|
||||
else if (_dragActors != null && _dragActors.HasValidDrag)
|
||||
{
|
||||
_validDragOver = true;
|
||||
result = DragDropEffect.Move;
|
||||
@@ -94,7 +94,7 @@ namespace FlaxEditor.Content.GUI
|
||||
result = DragDropEffect.Copy;
|
||||
}
|
||||
// Check if drop actor(s)
|
||||
else if (_dragActors.HasValidDrag)
|
||||
else if (_dragActors != null && _dragActors.HasValidDrag)
|
||||
{
|
||||
// Import actors
|
||||
var currentFolder = Editor.Instance.Windows.ContentWin.CurrentViewFolder;
|
||||
|
||||
@@ -520,8 +520,8 @@ namespace FlaxEditor.Content.GUI
|
||||
{
|
||||
int min = _selection.Min(x => x.IndexInParent);
|
||||
int max = _selection.Max(x => x.IndexInParent);
|
||||
min = Mathf.Min(min, item.IndexInParent);
|
||||
max = Mathf.Max(max, item.IndexInParent);
|
||||
min = Mathf.Max(Mathf.Min(min, item.IndexInParent), 0);
|
||||
max = Mathf.Min(Mathf.Max(max, item.IndexInParent), _children.Count - 1);
|
||||
var selection = new List<ContentItem>(_selection);
|
||||
for (int i = min; i <= max; i++)
|
||||
{
|
||||
|
||||
@@ -1,144 +1,52 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.CustomEditors.Editors;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.Interop;
|
||||
using FlaxEngine.Tools;
|
||||
|
||||
namespace FlaxEngine.Tools
|
||||
{
|
||||
partial class AudioTool
|
||||
{
|
||||
partial struct Options
|
||||
{
|
||||
private bool ShowBtiDepth => Format != AudioFormat.Vorbis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="FlaxEngine.Tools.AudioTool.Options"/>.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(FlaxEngine.Tools.AudioTool.Options)), DefaultEditor]
|
||||
public class AudioToolOptionsEditor : GenericEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override List<ItemInfo> GetItemsForType(ScriptType type)
|
||||
{
|
||||
// Show both fields and properties
|
||||
return GetItemsForType(type, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace FlaxEditor.Content.Import
|
||||
{
|
||||
/// <summary>
|
||||
/// Proxy object to present audio import settings in <see cref="ImportFilesDialog"/>.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public class AudioImportSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom set of bit depth audio import sizes.
|
||||
/// The settings data.
|
||||
/// </summary>
|
||||
public enum CustomBitDepth
|
||||
{
|
||||
/// <summary>
|
||||
/// The 8.
|
||||
/// </summary>
|
||||
_8 = 8,
|
||||
|
||||
/// <summary>
|
||||
/// The 16.
|
||||
/// </summary>
|
||||
_16 = 16,
|
||||
|
||||
/// <summary>
|
||||
/// The 24.
|
||||
/// </summary>
|
||||
_24 = 24,
|
||||
|
||||
/// <summary>
|
||||
/// The 32.
|
||||
/// </summary>
|
||||
_32 = 32,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the bit depth to enum.
|
||||
/// </summary>
|
||||
/// <param name="f">The bit depth.</param>
|
||||
/// <returns>The converted enum.</returns>
|
||||
public static CustomBitDepth ConvertBitDepth(int f)
|
||||
{
|
||||
FieldInfo[] fields = typeof(CustomBitDepth).GetFields();
|
||||
for (int i = 0; i < fields.Length; i++)
|
||||
{
|
||||
var field = fields[i];
|
||||
if (field.Name.Equals("value__"))
|
||||
continue;
|
||||
|
||||
if (f == (int)field.GetRawConstantValue())
|
||||
return (CustomBitDepth)f;
|
||||
}
|
||||
|
||||
return CustomBitDepth._16;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The audio data format to import the audio clip as.
|
||||
/// </summary>
|
||||
[EditorOrder(10), DefaultValue(AudioFormat.Vorbis), Tooltip("The audio data format to import the audio clip as.")]
|
||||
public AudioFormat Format { get; set; } = AudioFormat.Vorbis;
|
||||
|
||||
/// <summary>
|
||||
/// The audio data compression quality. Used only if target format is using compression. Value 0 means the smallest size, value 1 means the best quality.
|
||||
/// </summary>
|
||||
[EditorOrder(15), DefaultValue(0.4f), Limit(0, 1, 0.01f), Tooltip("The audio data compression quality. Used only if target format is using compression. Value 0 means the smallest size, value 1 means the best quality.")]
|
||||
public float CompressionQuality { get; set; } = 0.4f;
|
||||
|
||||
/// <summary>
|
||||
/// Disables dynamic audio streaming. The whole clip will be loaded into the memory. Useful for small clips (eg. gunfire sounds).
|
||||
/// </summary>
|
||||
[EditorOrder(20), DefaultValue(false), Tooltip("Disables dynamic audio streaming. The whole clip will be loaded into the memory. Useful for small clips (eg. gunfire sounds).")]
|
||||
public bool DisableStreaming { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Checks should the clip be played as spatial (3D) audio or as normal audio. 3D audio is stored in Mono format.
|
||||
/// </summary>
|
||||
[EditorOrder(30), DefaultValue(false), EditorDisplay(null, "Is 3D"), Tooltip("Checks should the clip be played as spatial (3D) audio or as normal audio. 3D audio is stored in Mono format.")]
|
||||
public bool Is3D { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The size of a single sample in bits. The clip will be converted to this bit depth on import.
|
||||
/// </summary>
|
||||
[EditorOrder(40), DefaultValue(CustomBitDepth._16), Tooltip("The size of a single sample in bits. The clip will be converted to this bit depth on import.")]
|
||||
public CustomBitDepth BitDepth { get; set; } = CustomBitDepth._16;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct InternalOptions
|
||||
{
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public AudioFormat Format;
|
||||
public byte DisableStreaming;
|
||||
public byte Is3D;
|
||||
public int BitDepth;
|
||||
public float Quality;
|
||||
}
|
||||
|
||||
internal void ToInternal(out InternalOptions options)
|
||||
{
|
||||
options = new InternalOptions
|
||||
{
|
||||
Format = Format,
|
||||
DisableStreaming = (byte)(DisableStreaming ? 1 : 0),
|
||||
Is3D = (byte)(Is3D ? 1 : 0),
|
||||
Quality = CompressionQuality,
|
||||
BitDepth = (int)BitDepth,
|
||||
};
|
||||
}
|
||||
|
||||
internal void FromInternal(ref InternalOptions options)
|
||||
{
|
||||
Format = options.Format;
|
||||
DisableStreaming = options.DisableStreaming != 0;
|
||||
Is3D = options.Is3D != 0;
|
||||
CompressionQuality = options.Quality;
|
||||
BitDepth = ConvertBitDepth(options.BitDepth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries the restore the asset import options from the target resource file.
|
||||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <param name="assetPath">The asset path.</param>
|
||||
/// <returns>True settings has been restored, otherwise false.</returns>
|
||||
public static bool TryRestore(ref AudioImportSettings options, string assetPath)
|
||||
{
|
||||
if (AudioImportEntry.Internal_GetAudioImportOptions(assetPath, out var internalOptions))
|
||||
{
|
||||
// Restore settings
|
||||
options.FromInternal(ref internalOptions);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
[EditorDisplay(null, EditorDisplayAttribute.InlineStyle)]
|
||||
public AudioTool.Options Settings = AudioTool.Options.Default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -147,7 +55,7 @@ namespace FlaxEditor.Content.Import
|
||||
/// <seealso cref="AssetImportEntry" />
|
||||
public partial class AudioImportEntry : AssetImportEntry
|
||||
{
|
||||
private AudioImportSettings _settings = new AudioImportSettings();
|
||||
private AudioImportSettings _settings = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AudioImportEntry"/> class.
|
||||
@@ -157,7 +65,7 @@ namespace FlaxEditor.Content.Import
|
||||
: base(ref request)
|
||||
{
|
||||
// Try to restore target asset Audio import options (useful for fast reimport)
|
||||
AudioImportSettings.TryRestore(ref _settings, ResultUrl);
|
||||
Editor.TryRestoreImportOptions(ref _settings.Settings, ResultUrl);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -166,27 +74,23 @@ namespace FlaxEditor.Content.Import
|
||||
/// <inheritdoc />
|
||||
public override bool TryOverrideSettings(object settings)
|
||||
{
|
||||
if (settings is AudioImportSettings o)
|
||||
if (settings is AudioImportSettings s)
|
||||
{
|
||||
_settings = o;
|
||||
_settings.Settings = s.Settings;
|
||||
return true;
|
||||
}
|
||||
if (settings is AudioTool.Options o)
|
||||
{
|
||||
_settings.Settings = o;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Import()
|
||||
{
|
||||
return Editor.Import(SourceUrl, ResultUrl, _settings);
|
||||
return Editor.Import(SourceUrl, ResultUrl, _settings.Settings);
|
||||
}
|
||||
|
||||
#region Internal Calls
|
||||
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "AudioImportEntryInternal_GetAudioImportOptions", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
|
||||
[return: MarshalAs(UnmanagedType.U1)]
|
||||
internal static partial bool Internal_GetAudioImportOptions(string path, out AudioImportSettings.InternalOptions result);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,7 +486,7 @@ namespace FlaxEditor.Content
|
||||
else
|
||||
Render2D.FillRectangle(rectangle, Color.Black);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Draws the item thumbnail.
|
||||
/// </summary>
|
||||
@@ -684,7 +684,7 @@ namespace FlaxEditor.Content
|
||||
var thumbnailSize = size.X;
|
||||
thumbnailRect = new Rectangle(0, 0, thumbnailSize, thumbnailSize);
|
||||
nameAlignment = TextAlignment.Center;
|
||||
|
||||
|
||||
if (this is ContentFolder)
|
||||
{
|
||||
// Small shadow
|
||||
@@ -692,7 +692,7 @@ namespace FlaxEditor.Content
|
||||
var color = Color.Black.AlphaMultiplied(0.2f);
|
||||
Render2D.FillRectangle(shadowRect, color);
|
||||
Render2D.FillRectangle(clientRect, style.Background.RGBMultiplied(1.25f));
|
||||
|
||||
|
||||
if (isSelected)
|
||||
Render2D.FillRectangle(clientRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground);
|
||||
else if (IsMouseOver)
|
||||
@@ -706,14 +706,14 @@ namespace FlaxEditor.Content
|
||||
var shadowRect = new Rectangle(2, 2, clientRect.Width + 1, clientRect.Height + 1);
|
||||
var color = Color.Black.AlphaMultiplied(0.2f);
|
||||
Render2D.FillRectangle(shadowRect, color);
|
||||
|
||||
|
||||
Render2D.FillRectangle(clientRect, style.Background.RGBMultiplied(1.25f));
|
||||
Render2D.FillRectangle(TextRectangle, style.LightBackground);
|
||||
|
||||
|
||||
var accentHeight = 2 * view.ViewScale;
|
||||
var barRect = new Rectangle(0, thumbnailRect.Height - accentHeight, clientRect.Width, accentHeight);
|
||||
Render2D.FillRectangle(barRect, Color.DimGray);
|
||||
|
||||
|
||||
DrawThumbnail(ref thumbnailRect, false);
|
||||
if (isSelected)
|
||||
{
|
||||
@@ -733,18 +733,18 @@ namespace FlaxEditor.Content
|
||||
var thumbnailSize = size.Y - 2 * DefaultMarginSize;
|
||||
thumbnailRect = new Rectangle(DefaultMarginSize, DefaultMarginSize, thumbnailSize, thumbnailSize);
|
||||
nameAlignment = TextAlignment.Near;
|
||||
|
||||
|
||||
if (isSelected)
|
||||
Render2D.FillRectangle(clientRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground);
|
||||
else if (IsMouseOver)
|
||||
Render2D.FillRectangle(clientRect, style.BackgroundHighlighted);
|
||||
|
||||
|
||||
DrawThumbnail(ref thumbnailRect);
|
||||
break;
|
||||
}
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
|
||||
// Draw short name
|
||||
Render2D.PushClip(ref textRect);
|
||||
Render2D.DrawText(style.FontMedium, ShowFileExtension || view.ShowFileExtensions ? FileName : ShortName, textRect, style.Foreground, nameAlignment, TextAlignment.Center, TextWrapping.WrapWords, 1f, 0.95f);
|
||||
|
||||
66
Source/Editor/Content/Proxy/BehaviorTreeProxy.cs
Normal file
66
Source/Editor/Content/Proxy/BehaviorTreeProxy.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using FlaxEditor.Content.Thumbnails;
|
||||
using FlaxEditor.Windows;
|
||||
using FlaxEditor.Windows.Assets;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Content
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="BehaviorTree"/> asset proxy object.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Content.BinaryAssetProxy" />
|
||||
[ContentContextMenu("New/AI/Behavior Tree")]
|
||||
public class BehaviorTreeProxy : BinaryAssetProxy
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Behavior Tree";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanReimport(ContentItem item)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override EditorWindow Open(Editor editor, ContentItem item)
|
||||
{
|
||||
return new BehaviorTreeWindow(editor, item as BinaryAssetItem);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Color AccentColor => Color.FromRGB(0x3256A8);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type AssetType => typeof(BehaviorTree);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanCreate(ContentFolder targetLocation)
|
||||
{
|
||||
return targetLocation.CanHaveAssets;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Create(string outputPath, object arg)
|
||||
{
|
||||
if (Editor.CreateAsset(Editor.NewAssetType.BehaviorTree, outputPath))
|
||||
throw new Exception("Failed to create new asset.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnThumbnailDrawBegin(ThumbnailRequest request, ContainerControl guiRoot, GPUContext context)
|
||||
{
|
||||
guiRoot.AddChild(new Label
|
||||
{
|
||||
Text = Path.GetFileNameWithoutExtension(request.Asset.Path),
|
||||
Offsets = Margin.Zero,
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Wrapping = TextWrapping.WrapWords
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,12 +54,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override bool CanDrawThumbnail(ThumbnailRequest request)
|
||||
{
|
||||
if (!_preview.HasLoadedAssets)
|
||||
return false;
|
||||
|
||||
// Check if all mip maps are streamed
|
||||
var asset = (CubeTexture)request.Asset;
|
||||
return asset.ResidentMipLevels >= Mathf.Max(1, (int)(asset.MipLevels * ThumbnailsModule.MinimumRequiredResourcesQuality));
|
||||
return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((CubeTexture)request.Asset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override bool CanDrawThumbnail(ThumbnailRequest request)
|
||||
{
|
||||
return _preview.HasLoadedAssets;
|
||||
return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((MaterialInstance)request.Asset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override bool CanDrawThumbnail(ThumbnailRequest request)
|
||||
{
|
||||
return _preview.HasLoadedAssets;
|
||||
return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Material)request.Asset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -82,12 +82,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override bool CanDrawThumbnail(ThumbnailRequest request)
|
||||
{
|
||||
if (!_preview.HasLoadedAssets)
|
||||
return false;
|
||||
|
||||
// Check if asset is streamed enough
|
||||
var asset = (Model)request.Asset;
|
||||
return asset.LoadedLODs >= Mathf.Max(1, (int)(asset.LODs.Length * ThumbnailsModule.MinimumRequiredResourcesQuality));
|
||||
return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Model)request.Asset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -54,15 +54,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override bool CanDrawThumbnail(ThumbnailRequest request)
|
||||
{
|
||||
if (!_preview.HasLoadedAssets)
|
||||
return false;
|
||||
|
||||
// Check if asset is streamed enough
|
||||
var asset = (SkinnedModel)request.Asset;
|
||||
var lods = asset.LODs.Length;
|
||||
if (asset.IsLoaded && lods == 0)
|
||||
return true; // Skeleton-only model
|
||||
return asset.LoadedLODs >= Mathf.Max(1, (int)(lods * ThumbnailsModule.MinimumRequiredResourcesQuality));
|
||||
return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((SkinnedModel)request.Asset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -57,11 +57,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override bool CanDrawThumbnail(ThumbnailRequest request)
|
||||
{
|
||||
// Check if asset is streamed enough
|
||||
var asset = (Texture)request.Asset;
|
||||
var mipLevels = asset.MipLevels;
|
||||
var minMipLevels = Mathf.Min(mipLevels, 7);
|
||||
return asset.ResidentMipLevels >= Mathf.Max(minMipLevels, (int)(mipLevels * ThumbnailsModule.MinimumRequiredResourcesQuality));
|
||||
return ThumbnailsModule.HasMinimumQuality((Texture)request.Asset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -125,6 +125,74 @@ namespace FlaxEditor.Content.Thumbnails
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool HasMinimumQuality(TextureBase asset)
|
||||
{
|
||||
var mipLevels = asset.MipLevels;
|
||||
var minMipLevels = Mathf.Min(mipLevels, 7);
|
||||
return asset.IsLoaded && asset.ResidentMipLevels >= Mathf.Max(minMipLevels, (int)(mipLevels * MinimumRequiredResourcesQuality));
|
||||
}
|
||||
|
||||
internal static bool HasMinimumQuality(Model asset)
|
||||
{
|
||||
if (!asset.IsLoaded)
|
||||
return false;
|
||||
var lods = asset.LODs.Length;
|
||||
var slots = asset.MaterialSlots;
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
if (slot.Material && !HasMinimumQuality(slot.Material))
|
||||
return false;
|
||||
}
|
||||
return asset.LoadedLODs >= Mathf.Max(1, (int)(lods * MinimumRequiredResourcesQuality));
|
||||
}
|
||||
|
||||
internal static bool HasMinimumQuality(SkinnedModel asset)
|
||||
{
|
||||
var lods = asset.LODs.Length;
|
||||
if (asset.IsLoaded && lods == 0)
|
||||
return true; // Skeleton-only model
|
||||
var slots = asset.MaterialSlots;
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
if (slot.Material && !HasMinimumQuality(slot.Material))
|
||||
return false;
|
||||
}
|
||||
return asset.LoadedLODs >= Mathf.Max(1, (int)(lods * MinimumRequiredResourcesQuality));
|
||||
}
|
||||
|
||||
internal static bool HasMinimumQuality(MaterialBase asset)
|
||||
{
|
||||
if (asset is MaterialInstance asInstance)
|
||||
return HasMinimumQuality(asInstance);
|
||||
return HasMinimumQualityInternal(asset);
|
||||
}
|
||||
|
||||
internal static bool HasMinimumQuality(Material asset)
|
||||
{
|
||||
return HasMinimumQualityInternal(asset);
|
||||
}
|
||||
|
||||
internal static bool HasMinimumQuality(MaterialInstance asset)
|
||||
{
|
||||
if (!HasMinimumQualityInternal(asset))
|
||||
return false;
|
||||
var baseMaterial = asset.BaseMaterial;
|
||||
return baseMaterial == null || HasMinimumQualityInternal(baseMaterial);
|
||||
}
|
||||
|
||||
private static bool HasMinimumQualityInternal(MaterialBase asset)
|
||||
{
|
||||
if (!asset.IsLoaded)
|
||||
return false;
|
||||
var parameters = asset.Parameters;
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
if (parameter.Value is TextureBase asTexture && !HasMinimumQuality(asTexture))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#region IContentItemOwner
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -368,7 +436,6 @@ namespace FlaxEditor.Content.Thumbnails
|
||||
// Create atlas
|
||||
if (PreviewsCache.Create(path))
|
||||
{
|
||||
// Error
|
||||
Editor.LogError("Failed to create thumbnails atlas.");
|
||||
return null;
|
||||
}
|
||||
@@ -377,7 +444,6 @@ namespace FlaxEditor.Content.Thumbnails
|
||||
var atlas = FlaxEngine.Content.LoadAsync<PreviewsCache>(path);
|
||||
if (atlas == null)
|
||||
{
|
||||
// Error
|
||||
Editor.LogError("Failed to load thumbnails atlas.");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include "Editor/ProjectInfo.h"
|
||||
#include "Editor/Cooker/GameCooker.h"
|
||||
#include "Editor/Utilities/EditorUtilities.h"
|
||||
#include <ThirdParty/pugixml/pugixml.hpp>
|
||||
#include <ThirdParty/pugixml/pugixml_extra.hpp>
|
||||
using namespace pugi;
|
||||
|
||||
IMPLEMENT_SETTINGS_GETTER(MacPlatformSettings, MacPlatform);
|
||||
@@ -170,17 +170,17 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
|
||||
const String plistPath = data.DataOutputPath / TEXT("Info.plist");
|
||||
{
|
||||
xml_document doc;
|
||||
xml_node plist = doc.child_or_append(PUGIXML_TEXT("plist"));
|
||||
xml_node_extra plist = xml_node_extra(doc).child_or_append(PUGIXML_TEXT("plist"));
|
||||
plist.append_attribute(PUGIXML_TEXT("version")).set_value(PUGIXML_TEXT("1.0"));
|
||||
xml_node dict = plist.child_or_append(PUGIXML_TEXT("dict"));
|
||||
xml_node_extra dict = plist.child_or_append(PUGIXML_TEXT("dict"));
|
||||
|
||||
#define ADD_ENTRY(key, value) \
|
||||
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT(key)); \
|
||||
dict.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT(value))
|
||||
dict.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT(key)); \
|
||||
dict.append_child_with_value(PUGIXML_TEXT("string"), PUGIXML_TEXT(value))
|
||||
#define ADD_ENTRY_STR(key, value) \
|
||||
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT(key)); \
|
||||
dict.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT(key)); \
|
||||
{ std::u16string valueStr(value.GetText()); \
|
||||
dict.append_child(PUGIXML_TEXT("string")).set_child_value(pugi::string_t(valueStr.begin(), valueStr.end()).c_str()); }
|
||||
dict.append_child_with_value(PUGIXML_TEXT("string"), pugi::string_t(valueStr.begin(), valueStr.end()).c_str()); }
|
||||
|
||||
ADD_ENTRY("CFBundleDevelopmentRegion", "English");
|
||||
ADD_ENTRY("CFBundlePackageType", "APPL");
|
||||
@@ -194,22 +194,22 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
|
||||
ADD_ENTRY_STR("CFBundleVersion", projectVersion);
|
||||
ADD_ENTRY_STR("NSHumanReadableCopyright", gameSettings->CopyrightNotice);
|
||||
|
||||
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("CFBundleSupportedPlatforms"));
|
||||
xml_node CFBundleSupportedPlatforms = dict.append_child(PUGIXML_TEXT("array"));
|
||||
CFBundleSupportedPlatforms.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT("MacOSX"));
|
||||
dict.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT("CFBundleSupportedPlatforms"));
|
||||
xml_node_extra CFBundleSupportedPlatforms = dict.append_child(PUGIXML_TEXT("array"));
|
||||
CFBundleSupportedPlatforms.append_child_with_value(PUGIXML_TEXT("string"), PUGIXML_TEXT("MacOSX"));
|
||||
|
||||
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("LSMinimumSystemVersionByArchitecture"));
|
||||
xml_node LSMinimumSystemVersionByArchitecture = dict.append_child(PUGIXML_TEXT("dict"));
|
||||
dict.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT("LSMinimumSystemVersionByArchitecture"));
|
||||
xml_node_extra LSMinimumSystemVersionByArchitecture = dict.append_child(PUGIXML_TEXT("dict"));
|
||||
switch (_arch)
|
||||
{
|
||||
case ArchitectureType::x64:
|
||||
LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("x86_64"));
|
||||
LSMinimumSystemVersionByArchitecture.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT("x86_64"));
|
||||
break;
|
||||
case ArchitectureType::ARM64:
|
||||
LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("arm64"));
|
||||
LSMinimumSystemVersionByArchitecture.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT("arm64"));
|
||||
break;
|
||||
}
|
||||
LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT("10.15"));
|
||||
LSMinimumSystemVersionByArchitecture.append_child_with_value(PUGIXML_TEXT("string"), PUGIXML_TEXT("10.15"));
|
||||
|
||||
#undef ADD_ENTRY
|
||||
#undef ADD_ENTRY_STR
|
||||
|
||||
@@ -37,6 +37,23 @@ namespace FlaxEditor.CustomEditors
|
||||
UseDefault = 1 << 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The interface for Editor context that owns the presenter. Can be <see cref="FlaxEditor.Windows.PropertiesWindow"/> or <see cref="FlaxEditor.Windows.Assets.PrefabWindow"/> or other window/panel - custom editor scan use it for more specific features.
|
||||
/// </summary>
|
||||
public interface IPresenterOwner
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the viewport linked with properties presenter (optional, null if unused).
|
||||
/// </summary>
|
||||
public Viewport.EditorViewport PresenterViewport { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Selects the scene objects.
|
||||
/// </summary>
|
||||
/// <param name="nodes">The nodes to select</param>
|
||||
public void Select(List<SceneGraph.SceneGraphNode> nodes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main class for Custom Editors used to present selected objects properties and allow to modify them.
|
||||
/// </summary>
|
||||
@@ -68,8 +85,15 @@ namespace FlaxEditor.CustomEditors
|
||||
/// <inheritdoc />
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
// Update editors
|
||||
_presenter.Update();
|
||||
try
|
||||
{
|
||||
// Update editors
|
||||
_presenter.Update();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
FlaxEditor.Editor.LogWarning(ex);
|
||||
}
|
||||
|
||||
base.Update(deltaTime);
|
||||
}
|
||||
@@ -254,7 +278,7 @@ namespace FlaxEditor.CustomEditors
|
||||
/// <summary>
|
||||
/// The Editor context that owns this presenter. Can be <see cref="FlaxEditor.Windows.PropertiesWindow"/> or <see cref="FlaxEditor.Windows.Assets.PrefabWindow"/> or other window/panel - custom editor scan use it for more specific features.
|
||||
/// </summary>
|
||||
public object Owner;
|
||||
public IPresenterOwner Owner;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text to show when no object is selected.
|
||||
@@ -270,7 +294,24 @@ namespace FlaxEditor.CustomEditors
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value indicating whether properties are read-only.
|
||||
/// </summary>
|
||||
public bool ReadOnly
|
||||
{
|
||||
get => _readOnly;
|
||||
set
|
||||
{
|
||||
if (_readOnly != value)
|
||||
{
|
||||
_readOnly = value;
|
||||
UpdateReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _buildOnUpdate;
|
||||
private bool _readOnly;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomEditorPresenter"/> class.
|
||||
@@ -278,7 +319,7 @@ namespace FlaxEditor.CustomEditors
|
||||
/// <param name="undo">The undo. It's optional.</param>
|
||||
/// <param name="noSelectionText">The custom text to display when no object is selected. Default is No selection.</param>
|
||||
/// <param name="owner">The owner of the presenter.</param>
|
||||
public CustomEditorPresenter(Undo undo, string noSelectionText = null, object owner = null)
|
||||
public CustomEditorPresenter(Undo undo, string noSelectionText = null, IPresenterOwner owner = null)
|
||||
{
|
||||
Undo = undo;
|
||||
Owner = owner;
|
||||
@@ -364,6 +405,8 @@ namespace FlaxEditor.CustomEditors
|
||||
// Restore scroll value
|
||||
if (parentScrollV > -1)
|
||||
panel.VScrollBar.Value = parentScrollV;
|
||||
if (_readOnly)
|
||||
UpdateReadOnly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -374,6 +417,16 @@ namespace FlaxEditor.CustomEditors
|
||||
_buildOnUpdate = true;
|
||||
}
|
||||
|
||||
private void UpdateReadOnly()
|
||||
{
|
||||
// Only scrollbars are enabled
|
||||
foreach (var child in Panel.Children)
|
||||
{
|
||||
if (!(child is ScrollBar))
|
||||
child.Enabled = !_readOnly;
|
||||
}
|
||||
}
|
||||
|
||||
private void ExpandGroups(LayoutElementsContainer c, bool open)
|
||||
{
|
||||
if (c is Elements.GroupElement group)
|
||||
|
||||
97
Source/Editor/CustomEditors/Dedicated/ClothEditor.cs
Normal file
97
Source/Editor/CustomEditors/Dedicated/ClothEditor.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Json;
|
||||
using FlaxEngine.Tools;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="Cloth"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ActorEditor" />
|
||||
[CustomEditor(typeof(Cloth)), DefaultEditor]
|
||||
class ClothEditor : ActorEditor
|
||||
{
|
||||
private ClothPaintingGizmoMode _gizmoMode;
|
||||
private Viewport.Modes.EditorGizmoMode _prevMode;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
if (Values.Count != 1)
|
||||
return;
|
||||
|
||||
// Add gizmo painting mode to the viewport
|
||||
var owner = Presenter.Owner;
|
||||
if (owner == null)
|
||||
return;
|
||||
var gizmoOwner = owner as IGizmoOwner ?? owner.PresenterViewport as IGizmoOwner;
|
||||
if (gizmoOwner == null)
|
||||
return;
|
||||
var gizmos = gizmoOwner.Gizmos;
|
||||
_gizmoMode = new ClothPaintingGizmoMode();
|
||||
|
||||
var projectCache = Editor.Instance.ProjectCache;
|
||||
if (projectCache.TryGetCustomData("ClothGizmoPaintValue", out var cachedPaintValue))
|
||||
_gizmoMode.PaintValue = JsonSerializer.Deserialize<float>(cachedPaintValue);
|
||||
if (projectCache.TryGetCustomData("ClothGizmoContinuousPaint", out var cachedContinuousPaint))
|
||||
_gizmoMode.ContinuousPaint = JsonSerializer.Deserialize<bool>(cachedContinuousPaint);
|
||||
if (projectCache.TryGetCustomData("ClothGizmoBrushFalloff", out var cachedBrushFalloff))
|
||||
_gizmoMode.BrushFalloff = JsonSerializer.Deserialize<float>(cachedBrushFalloff);
|
||||
if (projectCache.TryGetCustomData("ClothGizmoBrushSize", out var cachedBrushSize))
|
||||
_gizmoMode.BrushSize = JsonSerializer.Deserialize<float>(cachedBrushSize);
|
||||
if (projectCache.TryGetCustomData("ClothGizmoBrushStrength", out var cachedBrushStrength))
|
||||
_gizmoMode.BrushStrength = JsonSerializer.Deserialize<float>(cachedBrushStrength);
|
||||
|
||||
gizmos.AddMode(_gizmoMode);
|
||||
_prevMode = gizmos.ActiveMode;
|
||||
gizmos.ActiveMode = _gizmoMode;
|
||||
_gizmoMode.Gizmo.SetPaintCloth((Cloth)Values[0]);
|
||||
|
||||
// Insert gizmo mode options to properties editing
|
||||
var paintGroup = layout.Group("Cloth Painting");
|
||||
var paintValue = new ReadOnlyValueContainer(new ScriptType(typeof(ClothPaintingGizmoMode)), _gizmoMode);
|
||||
paintGroup.Object(paintValue);
|
||||
{
|
||||
var grid = paintGroup.CustomContainer<UniformGridPanel>();
|
||||
var gridControl = grid.CustomControl;
|
||||
gridControl.ClipChildren = false;
|
||||
gridControl.Height = Button.DefaultHeight;
|
||||
gridControl.SlotsHorizontally = 2;
|
||||
gridControl.SlotsVertically = 1;
|
||||
grid.Button("Fill", "Fills the cloth particles with given paint value.").Button.Clicked += _gizmoMode.Gizmo.Fill;
|
||||
grid.Button("Reset", "Clears the cloth particles paint.").Button.Clicked += _gizmoMode.Gizmo.Reset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Deinitialize()
|
||||
{
|
||||
// Cleanup gizmos
|
||||
if (_gizmoMode != null)
|
||||
{
|
||||
var gizmos = _gizmoMode.Owner.Gizmos;
|
||||
if (gizmos.ActiveMode == _gizmoMode)
|
||||
gizmos.ActiveMode = _prevMode;
|
||||
gizmos.RemoveMode(_gizmoMode);
|
||||
var projectCache = Editor.Instance.ProjectCache;
|
||||
projectCache.SetCustomData("ClothGizmoPaintValue", JsonSerializer.Serialize(_gizmoMode.PaintValue, typeof(float)));
|
||||
projectCache.SetCustomData("ClothGizmoContinuousPaint", JsonSerializer.Serialize(_gizmoMode.ContinuousPaint, typeof(bool)));
|
||||
projectCache.SetCustomData("ClothGizmoBrushFalloff", JsonSerializer.Serialize(_gizmoMode.BrushFalloff, typeof(float)));
|
||||
projectCache.SetCustomData("ClothGizmoBrushSize", JsonSerializer.Serialize(_gizmoMode.BrushSize, typeof(float)));
|
||||
projectCache.SetCustomData("ClothGizmoBrushStrength", JsonSerializer.Serialize(_gizmoMode.BrushStrength, typeof(float)));
|
||||
_gizmoMode.Dispose();
|
||||
_gizmoMode = null;
|
||||
}
|
||||
_prevMode = null;
|
||||
|
||||
base.Deinitialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
336
Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs
Normal file
336
Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs
Normal file
@@ -0,0 +1,336 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Editors;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="ModelInstanceActor.MeshReference"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ActorEditor" />
|
||||
[CustomEditor(typeof(ModelInstanceActor.MeshReference)), DefaultEditor]
|
||||
public class MeshReferenceEditor : CustomEditor
|
||||
{
|
||||
private class MeshRefPickerControl : Control
|
||||
{
|
||||
private ModelInstanceActor.MeshReference _value = new ModelInstanceActor.MeshReference { LODIndex = -1, MeshIndex = -1 };
|
||||
private string _valueName;
|
||||
private Float2 _mousePos;
|
||||
|
||||
public string[][] MeshNames;
|
||||
public event Action ValueChanged;
|
||||
|
||||
public ModelInstanceActor.MeshReference Value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
if (_value.LODIndex == value.LODIndex && _value.MeshIndex == value.MeshIndex)
|
||||
return;
|
||||
_value = value;
|
||||
if (value.LODIndex == -1 || value.MeshIndex == -1)
|
||||
_valueName = null;
|
||||
else if (MeshNames.Length == 1)
|
||||
_valueName = MeshNames[value.LODIndex][value.MeshIndex];
|
||||
else
|
||||
_valueName = $"LOD{value.LODIndex} - {MeshNames[value.LODIndex][value.MeshIndex]}";
|
||||
ValueChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public MeshRefPickerControl()
|
||||
: base(0, 0, 50, 16)
|
||||
{
|
||||
}
|
||||
|
||||
private void ShowDropDownMenu()
|
||||
{
|
||||
// Show context menu with tree structure of LODs and meshes
|
||||
Focus();
|
||||
var cm = new ItemsListContextMenu(200);
|
||||
var meshNames = MeshNames;
|
||||
var actor = _value.Actor;
|
||||
for (int lodIndex = 0; lodIndex < meshNames.Length; lodIndex++)
|
||||
{
|
||||
var item = new ItemsListContextMenu.Item
|
||||
{
|
||||
Name = "LOD" + lodIndex,
|
||||
Tag = new ModelInstanceActor.MeshReference { Actor = actor, LODIndex = lodIndex, MeshIndex = 0 },
|
||||
TintColor = new Color(0.8f, 0.8f, 1.0f, 0.8f),
|
||||
};
|
||||
cm.AddItem(item);
|
||||
|
||||
for (int meshIndex = 0; meshIndex < meshNames[lodIndex].Length; meshIndex++)
|
||||
{
|
||||
item = new ItemsListContextMenu.Item
|
||||
{
|
||||
Name = " " + meshNames[lodIndex][meshIndex],
|
||||
Tag = new ModelInstanceActor.MeshReference { Actor = actor, LODIndex = lodIndex, MeshIndex = meshIndex },
|
||||
};
|
||||
if (_value.LODIndex == lodIndex && _value.MeshIndex == meshIndex)
|
||||
item.BackgroundColor = FlaxEngine.GUI.Style.Current.BackgroundSelected;
|
||||
cm.AddItem(item);
|
||||
}
|
||||
}
|
||||
cm.ItemClicked += item => Value = (ModelInstanceActor.MeshReference)item.Tag;
|
||||
cm.Show(Parent, BottomLeft);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
// Cache data
|
||||
var style = FlaxEngine.GUI.Style.Current;
|
||||
bool isSelected = _valueName != null;
|
||||
bool isEnabled = EnabledInHierarchy;
|
||||
var frameRect = new Rectangle(0, 0, Width, 16);
|
||||
if (isSelected)
|
||||
frameRect.Width -= 16;
|
||||
frameRect.Width -= 16;
|
||||
var nameRect = new Rectangle(2, 1, frameRect.Width - 4, 14);
|
||||
var button1Rect = new Rectangle(nameRect.Right + 2, 1, 14, 14);
|
||||
var button2Rect = new Rectangle(button1Rect.Right + 2, 1, 14, 14);
|
||||
|
||||
// Draw frame
|
||||
Render2D.DrawRectangle(frameRect, isEnabled && (IsMouseOver || IsNavFocused) ? style.BorderHighlighted : style.BorderNormal);
|
||||
|
||||
// Check if has item selected
|
||||
if (isSelected)
|
||||
{
|
||||
// Draw name
|
||||
Render2D.PushClip(nameRect);
|
||||
Render2D.DrawText(style.FontMedium, _valueName, nameRect, isEnabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center);
|
||||
Render2D.PopClip();
|
||||
|
||||
// Draw deselect button
|
||||
Render2D.DrawSprite(style.Cross, button1Rect, isEnabled && button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Draw info
|
||||
Render2D.DrawText(style.FontMedium, "-", nameRect, isEnabled ? Color.OrangeRed : Color.DarkOrange, TextAlignment.Near, TextAlignment.Center);
|
||||
}
|
||||
|
||||
// Draw picker button
|
||||
var pickerRect = isSelected ? button2Rect : button1Rect;
|
||||
Render2D.DrawSprite(style.ArrowDown, pickerRect, isEnabled && pickerRect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseEnter(Float2 location)
|
||||
{
|
||||
_mousePos = location;
|
||||
|
||||
base.OnMouseEnter(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
_mousePos = Float2.Minimum;
|
||||
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
_mousePos = location;
|
||||
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
// Cache data
|
||||
bool isSelected = _valueName != null;
|
||||
var frameRect = new Rectangle(0, 0, Width, 16);
|
||||
if (isSelected)
|
||||
frameRect.Width -= 16;
|
||||
frameRect.Width -= 16;
|
||||
var nameRect = new Rectangle(2, 1, frameRect.Width - 4, 14);
|
||||
var button1Rect = new Rectangle(nameRect.Right + 2, 1, 14, 14);
|
||||
var button2Rect = new Rectangle(button1Rect.Right + 2, 1, 14, 14);
|
||||
|
||||
// Deselect
|
||||
if (isSelected && button1Rect.Contains(ref location))
|
||||
Value = new ModelInstanceActor.MeshReference { Actor = null, LODIndex = -1, MeshIndex = -1 };
|
||||
|
||||
// Picker dropdown menu
|
||||
if ((isSelected ? button2Rect : button1Rect).Contains(ref location))
|
||||
ShowDropDownMenu();
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
||||
{
|
||||
Focus();
|
||||
|
||||
// Open model editor window
|
||||
if (_value.Actor is StaticModel staticModel)
|
||||
Editor.Instance.ContentEditing.Open(staticModel.Model);
|
||||
else if (_value.Actor is AnimatedModel animatedModel)
|
||||
Editor.Instance.ContentEditing.Open(animatedModel.SkinnedModel);
|
||||
|
||||
return base.OnMouseDoubleClick(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSubmit()
|
||||
{
|
||||
base.OnSubmit();
|
||||
|
||||
ShowDropDownMenu();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
MeshNames = null;
|
||||
_valueName = null;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
private ModelInstanceActor _actor;
|
||||
private CustomElement<FlaxObjectRefPickerControl> _actorPicker;
|
||||
private CustomElement<MeshRefPickerControl> _meshPicker;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
// Get the context actor to pick the mesh from it
|
||||
if (GetActor(out var actor))
|
||||
{
|
||||
// TODO: support editing multiple values
|
||||
layout.Label("Different values");
|
||||
return;
|
||||
}
|
||||
_actor = actor;
|
||||
|
||||
var showActorPicker = actor == null || ParentEditor.Values.All(x => x is not Cloth);
|
||||
if (showActorPicker)
|
||||
{
|
||||
// Actor reference picker
|
||||
_actorPicker = layout.Custom<FlaxObjectRefPickerControl>();
|
||||
_actorPicker.CustomControl.Type = new ScriptType(typeof(ModelInstanceActor));
|
||||
_actorPicker.CustomControl.ValueChanged += () => SetValue(new ModelInstanceActor.MeshReference { Actor = (ModelInstanceActor)_actorPicker.CustomControl.Value });
|
||||
}
|
||||
|
||||
if (actor != null)
|
||||
{
|
||||
// Get mesh names hierarchy
|
||||
string[][] meshNames;
|
||||
if (actor is StaticModel staticModel)
|
||||
{
|
||||
var model = staticModel.Model;
|
||||
if (model == null || model.WaitForLoaded())
|
||||
return;
|
||||
var materials = model.MaterialSlots;
|
||||
var lods = model.LODs;
|
||||
meshNames = new string[lods.Length][];
|
||||
for (int lodIndex = 0; lodIndex < lods.Length; lodIndex++)
|
||||
{
|
||||
var lodMeshes = lods[lodIndex].Meshes;
|
||||
meshNames[lodIndex] = new string[lodMeshes.Length];
|
||||
for (int meshIndex = 0; meshIndex < lodMeshes.Length; meshIndex++)
|
||||
{
|
||||
var mesh = lodMeshes[meshIndex];
|
||||
var materialName = materials[mesh.MaterialSlotIndex].Name;
|
||||
if (string.IsNullOrEmpty(materialName) && materials[mesh.MaterialSlotIndex].Material)
|
||||
materialName = Path.GetFileNameWithoutExtension(materials[mesh.MaterialSlotIndex].Material.Path);
|
||||
if (string.IsNullOrEmpty(materialName))
|
||||
meshNames[lodIndex][meshIndex] = $"Mesh {meshIndex}";
|
||||
else
|
||||
meshNames[lodIndex][meshIndex] = $"Mesh {meshIndex} ({materialName})";
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (actor is AnimatedModel animatedModel)
|
||||
{
|
||||
var skinnedModel = animatedModel.SkinnedModel;
|
||||
if (skinnedModel == null || skinnedModel.WaitForLoaded())
|
||||
return;
|
||||
var materials = skinnedModel.MaterialSlots;
|
||||
var lods = skinnedModel.LODs;
|
||||
meshNames = new string[lods.Length][];
|
||||
for (int lodIndex = 0; lodIndex < lods.Length; lodIndex++)
|
||||
{
|
||||
var lodMeshes = lods[lodIndex].Meshes;
|
||||
meshNames[lodIndex] = new string[lodMeshes.Length];
|
||||
for (int meshIndex = 0; meshIndex < lodMeshes.Length; meshIndex++)
|
||||
{
|
||||
var mesh = lodMeshes[meshIndex];
|
||||
var materialName = materials[mesh.MaterialSlotIndex].Name;
|
||||
if (string.IsNullOrEmpty(materialName) && materials[mesh.MaterialSlotIndex].Material)
|
||||
materialName = Path.GetFileNameWithoutExtension(materials[mesh.MaterialSlotIndex].Material.Path);
|
||||
if (string.IsNullOrEmpty(materialName))
|
||||
meshNames[lodIndex][meshIndex] = $"Mesh {meshIndex}";
|
||||
else
|
||||
meshNames[lodIndex][meshIndex] = $"Mesh {meshIndex} ({materialName})";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
return; // Not supported model type
|
||||
|
||||
// Mesh reference picker
|
||||
_meshPicker = layout.Custom<MeshRefPickerControl>();
|
||||
_meshPicker.CustomControl.MeshNames = meshNames;
|
||||
_meshPicker.CustomControl.Value = (ModelInstanceActor.MeshReference)Values[0];
|
||||
_meshPicker.CustomControl.ValueChanged += () => SetValue(_meshPicker.CustomControl.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (_actorPicker != null)
|
||||
{
|
||||
GetActor(out var actor);
|
||||
_actorPicker.CustomControl.Value = actor;
|
||||
if (actor != _actor)
|
||||
{
|
||||
RebuildLayout();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_meshPicker != null)
|
||||
{
|
||||
_meshPicker.CustomControl.Value = (ModelInstanceActor.MeshReference)Values[0];
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetActor(out ModelInstanceActor actor)
|
||||
{
|
||||
actor = null;
|
||||
foreach (ModelInstanceActor.MeshReference value in Values)
|
||||
{
|
||||
if (actor == null)
|
||||
actor = value.Actor;
|
||||
else if (actor != value.Actor)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public class MissingScriptEditor : GenericEditor
|
||||
}
|
||||
|
||||
dropPanel.HeaderTextColor = Color.OrangeRed;
|
||||
|
||||
|
||||
base.Initialize(layout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
grid.Button("Remove bone").Button.ButtonClicked += OnRemoveBone;
|
||||
}
|
||||
|
||||
if (Presenter.Owner is Windows.PropertiesWindow || Presenter.Owner is Windows.Assets.PrefabWindow)
|
||||
if (Presenter.Owner != null)
|
||||
{
|
||||
// Selection
|
||||
var grid = editorGroup.CustomContainer<UniformGridPanel>();
|
||||
@@ -309,10 +309,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
if (node != null)
|
||||
selection.Add(node);
|
||||
}
|
||||
if (Presenter.Owner is Windows.PropertiesWindow propertiesWindow)
|
||||
propertiesWindow.Editor.SceneEditing.Select(selection);
|
||||
else if (Presenter.Owner is Windows.Assets.PrefabWindow prefabWindow)
|
||||
prefabWindow.Select(selection);
|
||||
Presenter.Owner.Select(selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,27 +258,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
/// Small image control added per script group that allows to drag and drop a reference to it. Also used to reorder the scripts.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.Image" />
|
||||
internal class ScriptDragIcon : Image
|
||||
internal class DragImage : Image
|
||||
{
|
||||
private ScriptsEditor _editor;
|
||||
private bool _isMouseDown;
|
||||
private Float2 _mouseDownPos;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target script.
|
||||
/// Action called when drag event should start.
|
||||
/// </summary>
|
||||
public Script Script => (Script)Tag;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ScriptDragIcon"/> class.
|
||||
/// </summary>
|
||||
/// <param name="editor">The script editor.</param>
|
||||
/// <param name="script">The target script.</param>
|
||||
public ScriptDragIcon(ScriptsEditor editor, Script script)
|
||||
{
|
||||
Tag = script;
|
||||
_editor = editor;
|
||||
}
|
||||
public Action<DragImage> Drag;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseEnter(Float2 location)
|
||||
@@ -291,11 +279,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
// Check if start drag drop
|
||||
if (_isMouseDown)
|
||||
{
|
||||
DoDrag();
|
||||
_isMouseDown = false;
|
||||
Drag(this);
|
||||
}
|
||||
|
||||
base.OnMouseLeave();
|
||||
@@ -304,11 +291,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
// Check if start drag drop
|
||||
if (_isMouseDown && Float2.Distance(location, _mouseDownPos) > 10.0f)
|
||||
{
|
||||
DoDrag();
|
||||
_isMouseDown = false;
|
||||
Drag(this);
|
||||
}
|
||||
|
||||
base.OnMouseMove(location);
|
||||
@@ -319,8 +305,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
// Clear flag
|
||||
_isMouseDown = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
@@ -331,21 +317,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
// Set flag
|
||||
_isMouseDown = true;
|
||||
_mouseDownPos = location;
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
private void DoDrag()
|
||||
{
|
||||
var script = Script;
|
||||
_editor.OnScriptDragChange(true, script);
|
||||
DoDragDrop(DragScripts.GetDragData(script));
|
||||
_editor.OnScriptDragChange(false, script);
|
||||
}
|
||||
}
|
||||
|
||||
internal class ScriptArrangeBar : Control
|
||||
@@ -643,7 +621,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
_scriptToggles[i] = scriptToggle;
|
||||
|
||||
// Add drag button to the group
|
||||
var scriptDrag = new ScriptDragIcon(this, script)
|
||||
var scriptDrag = new DragImage
|
||||
{
|
||||
TooltipText = "Script reference",
|
||||
AutoFocus = true,
|
||||
@@ -654,6 +632,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
Margin = new Margin(1),
|
||||
Brush = new SpriteBrush(Editor.Instance.Icons.DragBar12),
|
||||
Tag = script,
|
||||
Drag = img =>
|
||||
{
|
||||
var s = (Script)img.Tag;
|
||||
OnScriptDragChange(true, s);
|
||||
img.DoDragDrop(DragScripts.GetDragData(s));
|
||||
OnScriptDragChange(false, s);
|
||||
}
|
||||
};
|
||||
|
||||
// Add settings button to the group
|
||||
|
||||
@@ -348,7 +348,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
|
||||
if (!CanEditTangent())
|
||||
return;
|
||||
|
||||
|
||||
var index = _lastPointSelected.Index;
|
||||
var currentTangentInPosition = _selectedSpline.GetSplineLocalTangent(index, true).Translation;
|
||||
var currentTangentOutPosition = _selectedSpline.GetSplineLocalTangent(index, false).Translation;
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
LinkValues = Editor.Instance.Windows.PropertiesWin.ScaleLinked;
|
||||
|
||||
// Add button with the link icon
|
||||
|
||||
|
||||
_linkButton = new Button
|
||||
{
|
||||
BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Link32),
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Utilities;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="BehaviorKnowledgeSelector{T}"/> and <see cref="BehaviorKnowledgeSelectorAny"/>.
|
||||
/// </summary>
|
||||
public sealed class BehaviorKnowledgeSelectorEditor : CustomEditor
|
||||
{
|
||||
private ClickableLabel _label;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_label = layout.ClickableLabel(Path).CustomControl;
|
||||
_label.RightClick += ShowPicker;
|
||||
var button = new Button
|
||||
{
|
||||
Size = new Float2(16.0f),
|
||||
Text = "...",
|
||||
TooltipText = "Edit...",
|
||||
Parent = _label,
|
||||
};
|
||||
button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
|
||||
button.Clicked += ShowPicker;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
// Update label
|
||||
_label.Text = _label.TooltipText = Path;
|
||||
}
|
||||
|
||||
private string Path
|
||||
{
|
||||
get
|
||||
{
|
||||
var v = Values[0];
|
||||
if (v is BehaviorKnowledgeSelectorAny any)
|
||||
return any.Path;
|
||||
if (v is string str)
|
||||
return str;
|
||||
var pathField = v.GetType().GetField("Path");
|
||||
return pathField.GetValue(v) as string;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.Equals(Path, value, StringComparison.Ordinal))
|
||||
return;
|
||||
var v = Values[0];
|
||||
if (v is BehaviorKnowledgeSelectorAny)
|
||||
v = new BehaviorKnowledgeSelectorAny(value);
|
||||
else if (v is string)
|
||||
v = value;
|
||||
else
|
||||
{
|
||||
var pathField = v.GetType().GetField("Path");
|
||||
pathField.SetValue(v, value);
|
||||
}
|
||||
SetValue(v);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowPicker()
|
||||
{
|
||||
// Get Behavior Knowledge to select from
|
||||
var behaviorTreeWindow = Presenter.Owner as Windows.Assets.BehaviorTreeWindow;
|
||||
var rootNode = behaviorTreeWindow?.RootNode;
|
||||
if (rootNode == null)
|
||||
return;
|
||||
var typed = ScriptType.Null;
|
||||
var valueType = Values[0].GetType();
|
||||
if (valueType.Name == "BehaviorKnowledgeSelector`1")
|
||||
{
|
||||
// Get typed selector type to show only assignable items
|
||||
typed = new ScriptType(valueType.GenericTypeArguments[0]);
|
||||
}
|
||||
|
||||
// Get customization options
|
||||
var attributes = Values.GetAttributes();
|
||||
var attribute = (BehaviorKnowledgeSelectorAttribute)attributes?.FirstOrDefault(x => x is BehaviorKnowledgeSelectorAttribute);
|
||||
bool isGoalSelector = false;
|
||||
if (attribute != null)
|
||||
{
|
||||
isGoalSelector = attribute.IsGoalSelector;
|
||||
}
|
||||
|
||||
// Create menu with tree-like structure and search box
|
||||
var menu = Utilities.Utils.CreateSearchPopup(out var searchBox, out var tree, 0, true);
|
||||
var selected = Path;
|
||||
|
||||
// Empty
|
||||
var noneNode = new TreeNode
|
||||
{
|
||||
Text = "<none>",
|
||||
TooltipText = "Deselect value",
|
||||
Parent = tree,
|
||||
};
|
||||
if (string.IsNullOrEmpty(selected))
|
||||
tree.Select(noneNode);
|
||||
|
||||
if (!isGoalSelector)
|
||||
{
|
||||
// Blackboard
|
||||
SetupPickerTypeItems(tree, typed, selected, "Blackboard", "Blackboard/", rootNode.BlackboardType);
|
||||
}
|
||||
|
||||
// Goals
|
||||
var goalTypes = rootNode.GoalTypes;
|
||||
if (goalTypes?.Length != 0)
|
||||
{
|
||||
var goalsNode = new TreeNode
|
||||
{
|
||||
Text = "Goal",
|
||||
TooltipText = "List of goal types defined in Blackboard Tree",
|
||||
Parent = tree,
|
||||
};
|
||||
foreach (var goalTypeName in goalTypes)
|
||||
{
|
||||
var goalType = TypeUtils.GetType(goalTypeName);
|
||||
if (goalType == null)
|
||||
continue;
|
||||
var goalTypeNode = SetupPickerTypeItems(tree, typed, selected, goalType.Name, "Goal/" + goalTypeName + "/", goalTypeName, !isGoalSelector);
|
||||
goalTypeNode.Parent = goalsNode;
|
||||
}
|
||||
goalsNode.ExpandAll(true);
|
||||
}
|
||||
|
||||
tree.SelectedChanged += delegate(List<TreeNode> before, List<TreeNode> after)
|
||||
{
|
||||
if (after.Count == 1)
|
||||
{
|
||||
menu.Hide();
|
||||
Path = after[0].Tag as string;
|
||||
}
|
||||
};
|
||||
menu.Show(_label, new Float2(0, _label.Height));
|
||||
}
|
||||
|
||||
private TreeNode SetupPickerTypeItems(Tree tree, ScriptType typed, string selected, string text, string typePath, string typeName, bool addItems = true)
|
||||
{
|
||||
var type = TypeUtils.GetType(typeName);
|
||||
if (type == null)
|
||||
return null;
|
||||
var typeNode = new TreeNode
|
||||
{
|
||||
Text = text,
|
||||
TooltipText = type.TypeName,
|
||||
Tag = typePath, // Ability to select whole item type data (eg. whole blackboard value)
|
||||
Parent = tree,
|
||||
};
|
||||
if (typed && !typed.IsAssignableFrom(type))
|
||||
typeNode.Tag = null;
|
||||
if (string.Equals(selected, (string)typeNode.Tag, StringComparison.Ordinal))
|
||||
tree.Select(typeNode);
|
||||
if (addItems)
|
||||
{
|
||||
var items = GenericEditor.GetItemsForType(type, type.IsClass, true);
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (typed && !typed.IsAssignableFrom(item.Info.ValueType))
|
||||
continue;
|
||||
var itemPath = typePath + item.Info.Name;
|
||||
var node = new TreeNode
|
||||
{
|
||||
Text = item.DisplayName,
|
||||
TooltipText = item.TooltipText,
|
||||
Tag = itemPath,
|
||||
Parent = typeNode,
|
||||
};
|
||||
if (string.Equals(selected, itemPath, StringComparison.Ordinal))
|
||||
tree.Select(node);
|
||||
// TODO: add support for nested items (eg. field from blackboard structure field)
|
||||
}
|
||||
typeNode.Expand(true);
|
||||
}
|
||||
return typeNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// Describes object property/field information for custom editors pipeline.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.IComparable" />
|
||||
protected class ItemInfo : IComparable
|
||||
public class ItemInfo : IComparable
|
||||
{
|
||||
private Options.GeneralOptions.MembersOrder _membersOrder;
|
||||
|
||||
@@ -248,7 +248,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// <param name="useProperties">True if use type properties.</param>
|
||||
/// <param name="useFields">True if use type fields.</param>
|
||||
/// <returns>The items.</returns>
|
||||
protected List<ItemInfo> GetItemsForType(ScriptType type, bool useProperties, bool useFields)
|
||||
public static List<ItemInfo> GetItemsForType(ScriptType type, bool useProperties, bool useFields)
|
||||
{
|
||||
var items = new List<ItemInfo>();
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
}
|
||||
|
||||
// Value
|
||||
var values = new CustomValueContainer(type, (instance, index) => instance, (instance, index, value) => { });
|
||||
var values = new CustomValueContainer(type, (instance, index) => instance);
|
||||
values.AddRange(Values);
|
||||
var editor = CustomEditorsUtil.CreateEditor(type);
|
||||
var style = editor.Style;
|
||||
@@ -160,7 +160,6 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
var option = _options[comboBox.SelectedIndex];
|
||||
if (option.Type != null)
|
||||
value = option.Creator(option.Type);
|
||||
|
||||
}
|
||||
SetValue(value);
|
||||
RebuildLayoutOnRefresh();
|
||||
|
||||
@@ -634,7 +634,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText);
|
||||
if (textSize.Y > button.Width)
|
||||
button.Width = textSize.Y + 2;
|
||||
|
||||
|
||||
button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
|
||||
button.Clicked += ShowPicker;
|
||||
}
|
||||
|
||||
@@ -464,6 +464,11 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// </summary>
|
||||
public class TypeNameEditor : TypeEditorBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Prevents spamming log if Value contains missing type to skip research in subsequential Refresh ticks.
|
||||
/// </summary>
|
||||
private string _lastTypeNameError;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
@@ -484,8 +489,19 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (!HasDifferentValues && Values[0] is string asTypename)
|
||||
_element.CustomControl.Value = TypeUtils.GetType(asTypename);
|
||||
if (!HasDifferentValues && Values[0] is string asTypename &&
|
||||
!string.Equals(asTypename, _lastTypeNameError, StringComparison.Ordinal))
|
||||
{
|
||||
try
|
||||
{
|
||||
_element.CustomControl.Value = TypeUtils.GetType(asTypename);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_element.CustomControl.Value == null && asTypename.Length != 0)
|
||||
_lastTypeNameError = asTypename;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ namespace FlaxEditor.CustomEditors.GUI
|
||||
{
|
||||
// Clear flag
|
||||
_mouseOverSplitter = false;
|
||||
|
||||
|
||||
if (_cursorChanged)
|
||||
{
|
||||
Cursor = CursorType.Default;
|
||||
|
||||
@@ -38,15 +38,12 @@ namespace FlaxEditor.CustomEditors
|
||||
/// </summary>
|
||||
/// <param name="valueType">Type of the value.</param>
|
||||
/// <param name="getter">The value getter.</param>
|
||||
/// <param name="setter">The value setter.</param>
|
||||
/// <param name="setter">The value setter (can be null if value is read-only).</param>
|
||||
/// <param name="attributes">The custom type attributes used to override the value editor logic or appearance (eg. instance of <see cref="LimitAttribute"/>).</param>
|
||||
public CustomValueContainer(ScriptType valueType, GetDelegate getter, SetDelegate setter, object[] attributes = null)
|
||||
public CustomValueContainer(ScriptType valueType, GetDelegate getter, SetDelegate setter = null, object[] attributes = null)
|
||||
: base(ScriptMemberInfo.Null, valueType)
|
||||
{
|
||||
if (getter == null || setter == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
_getter = getter;
|
||||
_getter = getter ?? throw new ArgumentNullException();
|
||||
_setter = setter;
|
||||
_attributes = attributes;
|
||||
}
|
||||
@@ -57,9 +54,9 @@ namespace FlaxEditor.CustomEditors
|
||||
/// <param name="valueType">Type of the value.</param>
|
||||
/// <param name="initialValue">The initial value.</param>
|
||||
/// <param name="getter">The value getter.</param>
|
||||
/// <param name="setter">The value setter.</param>
|
||||
/// <param name="setter">The value setter (can be null if value is read-only).</param>
|
||||
/// <param name="attributes">The custom type attributes used to override the value editor logic or appearance (eg. instance of <see cref="LimitAttribute"/>).</param>
|
||||
public CustomValueContainer(ScriptType valueType, object initialValue, GetDelegate getter, SetDelegate setter, object[] attributes = null)
|
||||
public CustomValueContainer(ScriptType valueType, object initialValue, GetDelegate getter, SetDelegate setter = null, object[] attributes = null)
|
||||
: this(valueType, getter, setter, attributes)
|
||||
{
|
||||
Add(initialValue);
|
||||
@@ -89,6 +86,8 @@ namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
if (instanceValues == null || instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
if (_setter == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
@@ -105,6 +104,8 @@ namespace FlaxEditor.CustomEditors
|
||||
throw new ArgumentException();
|
||||
if (values == null || values.Count != Count)
|
||||
throw new ArgumentException();
|
||||
if (_setter == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
@@ -120,6 +121,8 @@ namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
if (instanceValues == null || instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
if (_setter == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
|
||||
@@ -8,7 +8,6 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.Marshalling;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.Content.Import;
|
||||
using FlaxEditor.Content.Settings;
|
||||
using FlaxEditor.Content.Thumbnails;
|
||||
using FlaxEditor.Modules;
|
||||
@@ -154,12 +153,12 @@ namespace FlaxEditor
|
||||
public ContentFindingModule ContentFinding;
|
||||
|
||||
/// <summary>
|
||||
/// The scripts editing
|
||||
/// The scripts editing.
|
||||
/// </summary>
|
||||
public CodeEditingModule CodeEditing;
|
||||
|
||||
/// <summary>
|
||||
/// The scripts documentation
|
||||
/// The scripts documentation.
|
||||
/// </summary>
|
||||
public CodeDocsModule CodeDocs;
|
||||
|
||||
@@ -179,7 +178,7 @@ namespace FlaxEditor
|
||||
public ProjectCacheModule ProjectCache;
|
||||
|
||||
/// <summary>
|
||||
/// The undo/redo
|
||||
/// The undo/redo.
|
||||
/// </summary>
|
||||
public EditorUndo Undo;
|
||||
|
||||
@@ -726,8 +725,8 @@ namespace FlaxEditor
|
||||
|
||||
// Cleanup
|
||||
Undo.Dispose();
|
||||
Surface.VisualScriptSurface.NodesCache.Clear();
|
||||
Surface.AnimGraphSurface.NodesCache.Clear();
|
||||
foreach (var cache in Surface.VisjectSurface.NodesCache.Caches.ToArray())
|
||||
cache.Clear();
|
||||
Instance = null;
|
||||
|
||||
// Invoke new instance if need to open a project
|
||||
@@ -797,7 +796,6 @@ namespace FlaxEditor
|
||||
{
|
||||
if (projectFilePath == null || !File.Exists(projectFilePath))
|
||||
{
|
||||
// Error
|
||||
MessageBox.Show("Missing project");
|
||||
return;
|
||||
}
|
||||
@@ -933,21 +931,11 @@ namespace FlaxEditor
|
||||
/// The <see cref="FlaxEngine.Animation"/>.
|
||||
/// </summary>
|
||||
Animation = 11,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imports the audio asset file to the target location.
|
||||
/// </summary>
|
||||
/// <param name="inputPath">The source file path.</param>
|
||||
/// <param name="outputPath">The result asset file path.</param>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns>True if importing failed, otherwise false.</returns>
|
||||
public static bool Import(string inputPath, string outputPath, AudioImportSettings settings)
|
||||
{
|
||||
if (settings == null)
|
||||
throw new ArgumentNullException();
|
||||
settings.ToInternal(out var internalOptions);
|
||||
return Internal_ImportAudio(inputPath, outputPath, ref internalOptions);
|
||||
/// <summary>
|
||||
/// The <see cref="FlaxEngine.BehaviorTree"/>.
|
||||
/// </summary>
|
||||
BehaviorTree = 12,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1667,10 +1655,6 @@ namespace FlaxEditor
|
||||
[return: MarshalAs(UnmanagedType.U1)]
|
||||
internal static partial bool Internal_CloneAssetFile(string dstPath, string srcPath, ref Guid dstId);
|
||||
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_ImportAudio", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
|
||||
[return: MarshalAs(UnmanagedType.U1)]
|
||||
internal static partial bool Internal_ImportAudio(string inputPath, string outputPath, ref AudioImportSettings.InternalOptions options);
|
||||
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetAudioClipMetadata", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
|
||||
internal static partial void Internal_GetAudioClipMetadata(IntPtr obj, out int originalSize, out int importedSize);
|
||||
|
||||
|
||||
@@ -154,6 +154,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
}
|
||||
|
||||
// Unlock and perform controls update
|
||||
Location = Float2.Zero;
|
||||
UnlockChildrenRecursive();
|
||||
PerformLayout();
|
||||
|
||||
@@ -162,7 +163,6 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
var dpiSize = Size * dpiScale;
|
||||
var locationWS = parent.PointToWindow(location);
|
||||
var locationSS = parentWin.PointToScreen(locationWS);
|
||||
Location = Float2.Zero;
|
||||
var monitorBounds = Platform.GetMonitorBounds(locationSS);
|
||||
var rightBottomLocationSS = locationSS + dpiSize;
|
||||
bool isUp = false, isLeft = false;
|
||||
|
||||
@@ -58,7 +58,6 @@ namespace FlaxEditor.GUI.Drag
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
return new DragDataText(DragPrefix + item.ID.ToString("N"));
|
||||
}
|
||||
|
||||
@@ -71,11 +70,9 @@ namespace FlaxEditor.GUI.Drag
|
||||
{
|
||||
if (items == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
string text = DragPrefix;
|
||||
foreach (var item in items)
|
||||
text += item.ID.ToString("N") + '\n';
|
||||
|
||||
return new DragDataText(text);
|
||||
}
|
||||
|
||||
@@ -83,9 +80,7 @@ namespace FlaxEditor.GUI.Drag
|
||||
/// Tries to parse the drag data.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>
|
||||
/// Gathered objects or empty IEnumerable if cannot get any valid.
|
||||
/// </returns>
|
||||
/// <returns>Gathered objects or empty IEnumerable if cannot get any valid.</returns>
|
||||
public override IEnumerable<Script> FromDragData(DragData data)
|
||||
{
|
||||
if (data is DragDataText dataText)
|
||||
@@ -97,12 +92,9 @@ namespace FlaxEditor.GUI.Drag
|
||||
var results = new List<Script>(ids.Length);
|
||||
for (int i = 0; i < ids.Length; i++)
|
||||
{
|
||||
// Find element
|
||||
if (Guid.TryParse(ids[i], out Guid id))
|
||||
{
|
||||
var obj = FlaxEngine.Object.Find<Script>(ref id);
|
||||
|
||||
// Check it
|
||||
if (obj != null)
|
||||
results.Add(obj);
|
||||
}
|
||||
@@ -111,11 +103,11 @@ namespace FlaxEditor.GUI.Drag
|
||||
return results.ToArray();
|
||||
}
|
||||
}
|
||||
return new Script[0];
|
||||
return Utils.GetEmptyArray<Script>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse the drag data to validate if it has valid scripts darg.
|
||||
/// Tries to parse the drag data to validate if it has valid scripts drag.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>True if drag data has valid scripts, otherwise false.</returns>
|
||||
@@ -138,7 +130,6 @@ namespace FlaxEditor.GUI.Drag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Utilities;
|
||||
|
||||
@@ -101,8 +101,11 @@ namespace FlaxEditor.GUI
|
||||
if (_isValid(type))
|
||||
{
|
||||
var attributes = type.GetAttributes(true);
|
||||
if (attributes.FirstOrDefault(x => x is HideInEditorAttribute) == null)
|
||||
if (attributes.FirstOrDefault(x => x is HideInEditorAttribute || x is System.Runtime.CompilerServices.CompilerGeneratedAttribute) == null)
|
||||
{
|
||||
var mType = type.Type;
|
||||
if (mType != null && mType.IsValueType && mType.ReflectedType != null && string.Equals(mType.ReflectedType.Name, "<PrivateImplementationDetails>", StringComparison.Ordinal))
|
||||
continue;
|
||||
AddItem(new TypeItemView(type, attributes));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
fov = cam.FieldOfView;
|
||||
customAspectRatio = cam.CustomAspectRatio;
|
||||
view.RenderLayersMask = cam.RenderLayersMask;
|
||||
view.Flags = cam.RenderFlags;
|
||||
view.Mode = cam.RenderMode;
|
||||
}
|
||||
|
||||
// Try to evaluate camera properties based on the animated tracks
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Viewport.Modes;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Gizmo
|
||||
@@ -13,7 +14,10 @@ namespace FlaxEditor.Gizmo
|
||||
[HideInEditor]
|
||||
public class GizmosCollection : List<GizmoBase>
|
||||
{
|
||||
private IGizmoOwner _owner;
|
||||
private GizmoBase _active;
|
||||
private EditorGizmoMode _activeMode;
|
||||
private readonly List<EditorGizmoMode> _modes = new List<EditorGizmoMode>();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when active gizmo tool gets changed.
|
||||
@@ -31,7 +35,7 @@ namespace FlaxEditor.Gizmo
|
||||
if (_active == value)
|
||||
return;
|
||||
if (value != null && !Contains(value))
|
||||
throw new InvalidOperationException("Invalid Gizmo.");
|
||||
throw new ArgumentException("Not added.");
|
||||
|
||||
_active?.OnDeactivated();
|
||||
_active = value;
|
||||
@@ -40,6 +44,46 @@ namespace FlaxEditor.Gizmo
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the active gizmo mode.
|
||||
/// </summary>
|
||||
public EditorGizmoMode ActiveMode
|
||||
{
|
||||
get => _activeMode;
|
||||
set
|
||||
{
|
||||
if (_activeMode == value)
|
||||
return;
|
||||
if (value != null)
|
||||
{
|
||||
if (!_modes.Contains(value))
|
||||
throw new ArgumentException("Not added.");
|
||||
if (value.Owner != _owner)
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
_activeMode?.OnDeactivated();
|
||||
Active = null;
|
||||
_activeMode = value;
|
||||
_activeMode?.OnActivated();
|
||||
ActiveModeChanged?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when active mode gets changed.
|
||||
/// </summary>
|
||||
public event Action<EditorGizmoMode> ActiveModeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Init.
|
||||
/// </summary>
|
||||
/// <param name="owner">The gizmos owner interface.</param>
|
||||
public GizmosCollection(IGizmoOwner owner)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified item.
|
||||
/// </summary>
|
||||
@@ -57,7 +101,65 @@ namespace FlaxEditor.Gizmo
|
||||
public new void Clear()
|
||||
{
|
||||
Active = null;
|
||||
ActiveMode = null;
|
||||
foreach (var mode in _modes)
|
||||
mode.Dispose();
|
||||
_modes.Clear();
|
||||
|
||||
base.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the mode to the viewport.
|
||||
/// </summary>
|
||||
/// <param name="mode">The mode.</param>
|
||||
public void AddMode(EditorGizmoMode mode)
|
||||
{
|
||||
if (mode == null)
|
||||
throw new ArgumentNullException(nameof(mode));
|
||||
if (_modes.Contains(mode))
|
||||
throw new ArgumentException("Already added.");
|
||||
if (mode.Owner != null)
|
||||
throw new ArgumentException("Already added to other viewport.");
|
||||
|
||||
_modes.Add(mode);
|
||||
mode.Init(_owner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the mode from the viewport.
|
||||
/// </summary>
|
||||
/// <param name="mode">The mode.</param>
|
||||
public void RemoveMode(EditorGizmoMode mode)
|
||||
{
|
||||
if (mode == null)
|
||||
throw new ArgumentNullException(nameof(mode));
|
||||
if (!_modes.Contains(mode))
|
||||
throw new ArgumentException("Not added.");
|
||||
if (mode.Owner != _owner)
|
||||
throw new ArgumentException("Not added to this viewport.");
|
||||
|
||||
if (_activeMode == mode)
|
||||
ActiveMode = null;
|
||||
_modes.Remove(mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the active mode.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The mode type.</typeparam>
|
||||
/// <returns>The activated mode.</returns>
|
||||
public T SetActiveMode<T>() where T : EditorGizmoMode
|
||||
{
|
||||
for (int i = 0; i < _modes.Count; i++)
|
||||
{
|
||||
if (_modes[i] is T mode)
|
||||
{
|
||||
ActiveMode = mode;
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
throw new ArgumentException("Not added mode to activate.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Gizmo
|
||||
@@ -94,5 +95,11 @@ namespace FlaxEditor.Gizmo
|
||||
/// Gets the root tree node for the scene graph.
|
||||
/// </summary>
|
||||
SceneGraph.RootNode SceneGraphRoot { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Selects the scene objects.
|
||||
/// </summary>
|
||||
/// <param name="nodes">The nodes to select</param>
|
||||
void Select(List<SceneGraph.SceneGraphNode> nodes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,43 +50,6 @@
|
||||
|
||||
Guid ManagedEditor::ObjectID(0x91970b4e, 0x99634f61, 0x84723632, 0x54c776af);
|
||||
|
||||
// Disable warning C4800: 'const byte': forcing value to bool 'true' or 'false' (performance warning)
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning( push )
|
||||
#pragma warning( disable : 4800)
|
||||
#endif
|
||||
|
||||
struct InternalAudioOptions
|
||||
{
|
||||
AudioFormat Format;
|
||||
byte DisableStreaming;
|
||||
byte Is3D;
|
||||
int32 BitDepth;
|
||||
float Quality;
|
||||
|
||||
static void Convert(InternalAudioOptions* from, ImportAudio::Options* to)
|
||||
{
|
||||
to->Format = from->Format;
|
||||
to->DisableStreaming = from->DisableStreaming;
|
||||
to->Is3D = from->Is3D;
|
||||
to->BitDepth = from->BitDepth;
|
||||
to->Quality = from->Quality;
|
||||
}
|
||||
|
||||
static void Convert(ImportAudio::Options* from, InternalAudioOptions* to)
|
||||
{
|
||||
to->Format = from->Format;
|
||||
to->DisableStreaming = from->DisableStreaming;
|
||||
to->Is3D = from->Is3D;
|
||||
to->BitDepth = from->BitDepth;
|
||||
to->Quality = from->Quality;
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning( pop )
|
||||
#endif
|
||||
|
||||
// Pack log messages into a single scratch buffer to reduce dynamic memory allocations
|
||||
CriticalSection CachedLogDataLocker;
|
||||
Array<byte> CachedLogData;
|
||||
@@ -221,6 +184,7 @@ enum class NewAssetType
|
||||
ParticleEmitterFunction = 9,
|
||||
AnimationGraphFunction = 10,
|
||||
Animation = 11,
|
||||
BehaviorTree = 12,
|
||||
};
|
||||
|
||||
DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateAsset(NewAssetType type, MString* outputPathObj)
|
||||
@@ -264,6 +228,9 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateAsset(NewAssetType type, MString
|
||||
case NewAssetType::Animation:
|
||||
tag = AssetsImportingManager::CreateAnimationTag;
|
||||
break;
|
||||
case NewAssetType::BehaviorTree:
|
||||
tag = AssetsImportingManager::CreateBehaviorTreeTag;
|
||||
break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
@@ -295,16 +262,6 @@ DEFINE_INTERNAL_CALL(MString*) EditorInternal_CanImport(MString* extensionObj)
|
||||
return importer ? MUtils::ToString(importer->ResultExtension) : nullptr;
|
||||
}
|
||||
|
||||
DEFINE_INTERNAL_CALL(bool) EditorInternal_ImportAudio(MString* inputPathObj, MString* outputPathObj, InternalAudioOptions* optionsObj)
|
||||
{
|
||||
ImportAudio::Options options;
|
||||
InternalAudioOptions::Convert(optionsObj, &options);
|
||||
String inputPath, outputPath;
|
||||
MUtils::ToString(inputPathObj, inputPath);
|
||||
MUtils::ToString(outputPathObj, outputPath);
|
||||
return ManagedEditor::Import(inputPath, outputPath, &options);
|
||||
}
|
||||
|
||||
DEFINE_INTERNAL_CALL(void) EditorInternal_GetAudioClipMetadata(AudioClip* clip, int32* originalSize, int32* importedSize)
|
||||
{
|
||||
INTERNAL_CALL_CHECK(clip);
|
||||
@@ -765,24 +722,6 @@ DEFINE_INTERNAL_CALL(MTypeObject*) CustomEditorsUtilInternal_GetCustomEditor(MTy
|
||||
return CustomEditorsUtil::GetCustomEditor(targetType);
|
||||
}
|
||||
|
||||
DEFINE_INTERNAL_CALL(bool) AudioImportEntryInternal_GetAudioImportOptions(MString* pathObj, InternalAudioOptions* result)
|
||||
{
|
||||
String path;
|
||||
MUtils::ToString(pathObj, path);
|
||||
FileSystem::NormalizePath(path);
|
||||
|
||||
ImportAudio::Options options;
|
||||
if (ImportAudio::TryGetImportOptions(path, options))
|
||||
{
|
||||
// Convert into managed storage
|
||||
InternalAudioOptions::Convert(&options, result);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_INTERNAL_CALL(MArray*) LayersAndTagsSettingsInternal_GetCurrentLayers(int* layersCount)
|
||||
{
|
||||
*layersCount = Math::Max(1, Level::GetNonEmptyLayerNamesCount());
|
||||
@@ -830,3 +769,15 @@ bool ManagedEditor::TryRestoreImportOptions(ModelTool::Options& options, String
|
||||
FileSystem::NormalizePath(assetPath);
|
||||
return ImportModelFile::TryGetImportOptions(assetPath, options);
|
||||
}
|
||||
|
||||
bool ManagedEditor::Import(const String& inputPath, const String& outputPath, const AudioTool::Options& options)
|
||||
{
|
||||
return Import(inputPath, outputPath, (void*)&options);
|
||||
}
|
||||
|
||||
bool ManagedEditor::TryRestoreImportOptions(AudioTool::Options& options, String assetPath)
|
||||
{
|
||||
// Get options from model
|
||||
FileSystem::NormalizePath(assetPath);
|
||||
return ImportAudio::TryGetImportOptions(assetPath, options);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "Engine/ShadowsOfMordor/Types.h"
|
||||
#include "Engine/Tools/TextureTool/TextureTool.h"
|
||||
#include "Engine/Tools/ModelTool/ModelTool.h"
|
||||
#include "Engine/Tools/AudioTool/AudioTool.h"
|
||||
|
||||
namespace CSG
|
||||
{
|
||||
@@ -190,6 +191,25 @@ public:
|
||||
API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) ModelTool::Options& options, String assetPath);
|
||||
#endif
|
||||
|
||||
#if COMPILE_WITH_AUDIO_TOOL
|
||||
/// <summary>
|
||||
/// Imports the audio asset file to the target location.
|
||||
/// </summary>
|
||||
/// <param name="inputPath">The source file path.</param>
|
||||
/// <param name="outputPath">The result asset file path.</param>
|
||||
/// <param name="options">The import settings.</param>
|
||||
/// <returns>True if importing failed, otherwise false.</returns>
|
||||
API_FUNCTION() static bool Import(const String& inputPath, const String& outputPath, const AudioTool::Options& options);
|
||||
|
||||
/// <summary>
|
||||
/// Tries the restore the asset import options from the target resource file.
|
||||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <param name="assetPath">The asset path.</param>
|
||||
/// <returns>True settings has been restored, otherwise false.</returns>
|
||||
API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) AudioTool::Options& options, String assetPath);
|
||||
#endif
|
||||
|
||||
private:
|
||||
void OnEditorAssemblyLoaded(MAssembly* assembly);
|
||||
|
||||
|
||||
@@ -373,7 +373,6 @@ namespace FlaxEditor.Modules
|
||||
// Note: we use content backend because file may be in use or sth, it's safe
|
||||
if (FlaxEngine.Content.RenameAsset(oldPath, newPath))
|
||||
{
|
||||
// Error
|
||||
Editor.LogError(string.Format("Cannot rename asset \'{0}\' to \'{1}\'", oldPath, newPath));
|
||||
return true;
|
||||
}
|
||||
@@ -387,7 +386,6 @@ namespace FlaxEditor.Modules
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Error
|
||||
Editor.LogWarning(ex);
|
||||
Editor.LogError(string.Format("Cannot rename asset \'{0}\' to \'{1}\'", oldPath, newPath));
|
||||
return true;
|
||||
@@ -418,7 +416,6 @@ namespace FlaxEditor.Modules
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Error
|
||||
Editor.LogWarning(ex);
|
||||
Editor.LogError(string.Format("Cannot move folder \'{0}\' to \'{1}\'", oldPath, newPath));
|
||||
return;
|
||||
@@ -479,7 +476,6 @@ namespace FlaxEditor.Modules
|
||||
|
||||
if (item.IsFolder && Directory.Exists(newPath))
|
||||
{
|
||||
// Error
|
||||
MessageBox.Show("Cannot move folder. Target location already exists.");
|
||||
return;
|
||||
}
|
||||
@@ -489,7 +485,6 @@ namespace FlaxEditor.Modules
|
||||
var newParent = Find(newDirPath) as ContentFolder;
|
||||
if (newParent == null)
|
||||
{
|
||||
// Error
|
||||
MessageBox.Show("Cannot move item. Missing target location.");
|
||||
return;
|
||||
}
|
||||
@@ -511,7 +506,6 @@ namespace FlaxEditor.Modules
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Error
|
||||
Editor.LogWarning(ex);
|
||||
Editor.LogError(string.Format("Cannot move folder \'{0}\' to \'{1}\'", oldPath, newPath));
|
||||
return;
|
||||
@@ -531,7 +525,6 @@ namespace FlaxEditor.Modules
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Error
|
||||
Editor.LogWarning(ex);
|
||||
Editor.LogWarning(string.Format("Cannot remove folder \'{0}\'", oldPath));
|
||||
return;
|
||||
@@ -566,7 +559,6 @@ namespace FlaxEditor.Modules
|
||||
{
|
||||
if (item == null || !item.Exists)
|
||||
{
|
||||
// Error
|
||||
MessageBox.Show("Cannot move item. It's missing.");
|
||||
return;
|
||||
}
|
||||
@@ -590,7 +582,6 @@ namespace FlaxEditor.Modules
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Error
|
||||
Editor.LogWarning(ex);
|
||||
Editor.LogError(string.Format("Cannot copy folder \'{0}\' to \'{1}\'", sourcePath, targetPath));
|
||||
return;
|
||||
@@ -615,7 +606,6 @@ namespace FlaxEditor.Modules
|
||||
// Note: we use content backend because file may be in use or sth, it's safe
|
||||
if (Editor.ContentEditing.CloneAssetFile(sourcePath, targetPath, Guid.NewGuid()))
|
||||
{
|
||||
// Error
|
||||
Editor.LogError(string.Format("Cannot copy asset \'{0}\' to \'{1}\'", sourcePath, targetPath));
|
||||
return;
|
||||
}
|
||||
@@ -629,7 +619,6 @@ namespace FlaxEditor.Modules
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Error
|
||||
Editor.LogWarning(ex);
|
||||
Editor.LogError(string.Format("Cannot copy asset \'{0}\' to \'{1}\'", sourcePath, targetPath));
|
||||
return;
|
||||
@@ -681,7 +670,6 @@ namespace FlaxEditor.Modules
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Error
|
||||
Editor.LogWarning(ex);
|
||||
Editor.LogWarning(string.Format("Cannot remove folder \'{0}\'", path));
|
||||
return;
|
||||
@@ -1072,6 +1060,7 @@ namespace FlaxEditor.Modules
|
||||
Proxy.Add(new SkeletonMaskProxy());
|
||||
Proxy.Add(new GameplayGlobalsProxy());
|
||||
Proxy.Add(new VisualScriptProxy());
|
||||
Proxy.Add(new BehaviorTreeProxy());
|
||||
Proxy.Add(new LocalizedStringTableProxy());
|
||||
Proxy.Add(new FileProxy());
|
||||
Proxy.Add(new SpawnableJsonAssetProxy<PhysicalMaterial>());
|
||||
|
||||
@@ -19,6 +19,20 @@ namespace FlaxEditor.Modules
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the specified asset in dedicated editor window.
|
||||
/// </summary>
|
||||
/// <param name="asset">The asset.</param>
|
||||
/// <param name="disableAutoShow">True if disable automatic window showing. Used during workspace layout loading to deserialize it faster.</param>
|
||||
/// <returns>Opened window or null if cannot open item.</returns>
|
||||
public EditorWindow Open(Asset asset, bool disableAutoShow = false)
|
||||
{
|
||||
if (asset == null)
|
||||
throw new ArgumentNullException();
|
||||
var item = Editor.ContentDatabase.FindAsset(asset.ID);
|
||||
return item != null ? Open(item) : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the specified item in dedicated editor window.
|
||||
/// </summary>
|
||||
|
||||
@@ -37,11 +37,6 @@ namespace FlaxEditor.Modules
|
||||
/// </summary>
|
||||
public event Action<Prefab, Actor> PrefabApplied;
|
||||
|
||||
/// <summary>
|
||||
/// Locally cached actor for prefab creation.
|
||||
/// </summary>
|
||||
private Actor _prefabCreationActor;
|
||||
|
||||
internal PrefabsModule(Editor editor)
|
||||
: base(editor)
|
||||
{
|
||||
@@ -65,13 +60,14 @@ namespace FlaxEditor.Modules
|
||||
/// To create prefab manually (from code) use <see cref="PrefabManager.CreatePrefab"/> method.
|
||||
/// </remarks>
|
||||
/// <param name="selection">The scene selection to use.</param>
|
||||
public void CreatePrefab(List<SceneGraphNode> selection)
|
||||
/// <param name="prefabWindow">The prefab window that creates it.</param>
|
||||
public void CreatePrefab(List<SceneGraphNode> selection, Windows.Assets.PrefabWindow prefabWindow = null)
|
||||
{
|
||||
if (selection == null)
|
||||
selection = Editor.SceneEditing.Selection;
|
||||
if (selection.Count == 1 && selection[0] is ActorNode actorNode && actorNode.CanCreatePrefab)
|
||||
{
|
||||
CreatePrefab(actorNode.Actor);
|
||||
CreatePrefab(actorNode.Actor, true, prefabWindow);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +88,8 @@ namespace FlaxEditor.Modules
|
||||
/// </summary>
|
||||
/// <param name="actor">The root prefab actor.</param>
|
||||
/// <param name="rename">Allow renaming or not</param>
|
||||
public void CreatePrefab(Actor actor, bool rename)
|
||||
/// <param name="prefabWindow">The prefab window that creates it.</param>
|
||||
public void CreatePrefab(Actor actor, bool rename, Windows.Assets.PrefabWindow prefabWindow = null)
|
||||
{
|
||||
// Skip in invalid states
|
||||
if (!Editor.StateMachine.CurrentState.CanEditContent)
|
||||
@@ -105,42 +102,47 @@ namespace FlaxEditor.Modules
|
||||
PrefabCreating?.Invoke(actor);
|
||||
|
||||
var proxy = Editor.ContentDatabase.GetProxy<Prefab>();
|
||||
_prefabCreationActor = actor;
|
||||
Editor.Windows.ContentWin.NewItem(proxy, actor, OnPrefabCreated, actor.Name, rename);
|
||||
Editor.Windows.ContentWin.NewItem(proxy, actor, contentItem => OnPrefabCreated(contentItem, actor, prefabWindow), actor.Name, rename);
|
||||
}
|
||||
|
||||
private void OnPrefabCreated(ContentItem contentItem)
|
||||
private void OnPrefabCreated(ContentItem contentItem, Actor actor, Windows.Assets.PrefabWindow prefabWindow)
|
||||
{
|
||||
if (contentItem is PrefabItem prefabItem)
|
||||
{
|
||||
PrefabCreated?.Invoke(prefabItem);
|
||||
}
|
||||
|
||||
// Skip in invalid states
|
||||
if (!Editor.StateMachine.CurrentState.CanEditScene)
|
||||
return;
|
||||
Undo undo = null;
|
||||
if (prefabWindow != null)
|
||||
{
|
||||
prefabWindow.MarkAsEdited();
|
||||
undo = prefabWindow.Undo;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Skip in invalid states
|
||||
if (!Editor.StateMachine.CurrentState.CanEditScene)
|
||||
return;
|
||||
undo = Editor.Undo;
|
||||
}
|
||||
|
||||
// Record undo for prefab creating (backend links the target instance with the prefab)
|
||||
if (Editor.Undo.Enabled)
|
||||
if (undo.Enabled)
|
||||
{
|
||||
if (!_prefabCreationActor)
|
||||
if (!actor)
|
||||
return;
|
||||
|
||||
var actorsList = new List<Actor>();
|
||||
Utilities.Utils.GetActorsTree(actorsList, _prefabCreationActor);
|
||||
Utilities.Utils.GetActorsTree(actorsList, actor);
|
||||
|
||||
var actions = new IUndoAction[actorsList.Count];
|
||||
for (int i = 0; i < actorsList.Count; i++)
|
||||
{
|
||||
var action = BreakPrefabLinkAction.Linked(actorsList[i]);
|
||||
actions[i] = action;
|
||||
}
|
||||
Undo.AddAction(new MultiUndoAction(actions));
|
||||
|
||||
_prefabCreationActor = null;
|
||||
actions[i] = BreakPrefabLinkAction.Linked(actorsList[i]);
|
||||
undo.AddAction(new MultiUndoAction(actions));
|
||||
}
|
||||
|
||||
Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout();
|
||||
Editor.Windows.PropertiesWin.Presenter.BuildLayout();
|
||||
if (prefabWindow != null)
|
||||
prefabWindow.Presenter.BuildLayout();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using FlaxEditor.Options;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
@@ -178,6 +180,11 @@ namespace FlaxEditor.Modules.SourceCodeEditing
|
||||
/// </summary>
|
||||
public readonly CachedCustomAnimGraphNodesCollection AnimGraphNodes = new CachedCustomAnimGraphNodesCollection(32, new ScriptType(typeof(AnimationGraph.CustomNodeArchetypeFactoryAttribute)), IsTypeValidScriptingType, HasAssemblyValidScriptingTypes);
|
||||
|
||||
/// <summary>
|
||||
/// The Behavior Tree custom nodes collection.
|
||||
/// </summary>
|
||||
public readonly CachedTypesCollection BehaviorTreeNodes = new CachedTypesCollection(64, new ScriptType(typeof(BehaviorTreeNode)), IsTypeValidScriptingType, HasAssemblyValidScriptingTypes);
|
||||
|
||||
internal CodeEditingModule(Editor editor)
|
||||
: base(editor)
|
||||
{
|
||||
@@ -325,6 +332,78 @@ namespace FlaxEditor.Modules.SourceCodeEditing
|
||||
Editor.Instance.CodeEditing.SelectedEditor = editor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts creating a new module
|
||||
/// </summary>
|
||||
internal void CreateModule(string path, string moduleName, bool editorModule, bool cpp)
|
||||
{
|
||||
if (string.IsNullOrEmpty(moduleName) || string.IsNullOrEmpty(path))
|
||||
{
|
||||
Editor.LogWarning("Failed to create module due to no name");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create folder
|
||||
var moduleFolderPath = Path.Combine(path, moduleName);
|
||||
Directory.CreateDirectory(moduleFolderPath);
|
||||
|
||||
// Create module
|
||||
var moduleText = "using Flax.Build;\n" +
|
||||
"using Flax.Build.NativeCpp;\n" +
|
||||
$"\npublic class {moduleName} : Game{(editorModule ? "Editor" : "")}Module\n" +
|
||||
"{\n " +
|
||||
"/// <inheritdoc />\n" +
|
||||
" public override void Init()\n" +
|
||||
" {\n" +
|
||||
" base.Init();\n" +
|
||||
"\n" +
|
||||
" // C#-only scripting if false\n" +
|
||||
$" BuildNativeCode = {(cpp ? "true" : "false")};\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
" /// <inheritdoc />\n" +
|
||||
" public override void Setup(BuildOptions options)\n" +
|
||||
" {" +
|
||||
"\n" +
|
||||
" base.Setup(options);\n" +
|
||||
"\n" +
|
||||
" options.ScriptingAPI.IgnoreMissingDocumentationWarnings = true;\n" +
|
||||
"\n" +
|
||||
" // Here you can modify the build options for your game module\n" +
|
||||
" // To reference another module use: options.PublicDependencies.Add(\"Audio\");\n" +
|
||||
" // To add C++ define use: options.PublicDefinitions.Add(\"COMPILE_WITH_FLAX\");\n" +
|
||||
" // To learn more see scripting documentation.\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
moduleText = Encoding.UTF8.GetString(Encoding.Default.GetBytes(moduleText));
|
||||
var modulePath = Path.Combine(moduleFolderPath, $"{moduleName}.Build.cs");
|
||||
File.WriteAllText(modulePath, moduleText);
|
||||
Editor.Log($"Module created at {modulePath}");
|
||||
|
||||
// Get editor target and target files and add module
|
||||
var files = Directory.GetFiles(path);
|
||||
var targetModuleText = $"Modules.Add(\"{moduleName}\");\n ";
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (!file.Contains(".Build.cs", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
var targetText = File.ReadAllText(file);
|
||||
|
||||
// Skip game project if it is suppose to be an editor module
|
||||
if (editorModule && targetText.Contains("GameProjectTarget", StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
// TODO: Handle edge case when there are no modules in a target
|
||||
var index = targetText.IndexOf("Modules.Add");
|
||||
if (index != -1)
|
||||
{
|
||||
var newText = targetText.Insert(index, targetModuleText);
|
||||
File.WriteAllText(file, newText);
|
||||
Editor.Log($"Module added to Target: {file}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnUpdate()
|
||||
{
|
||||
@@ -361,6 +440,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing
|
||||
Scripts.ClearTypes();
|
||||
Controls.ClearTypes();
|
||||
AnimGraphNodes.ClearTypes();
|
||||
BehaviorTreeNodes.ClearTypes();
|
||||
TypesCleared?.Invoke();
|
||||
}
|
||||
|
||||
|
||||
@@ -553,6 +553,13 @@ namespace FlaxEditor.Modules
|
||||
cm.AddSeparator();
|
||||
_menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes);
|
||||
_menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search);
|
||||
cm.AddSeparator();
|
||||
cm.AddButton("Game Settings", () =>
|
||||
{
|
||||
var item = Editor.ContentDatabase.Find(GameSettings.GameSettingsAssetPath);
|
||||
if(item != null)
|
||||
Editor.ContentEditing.Open(item);
|
||||
});
|
||||
|
||||
// Scene
|
||||
MenuScene = MainMenu.AddButton("Scene");
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace FlaxEditor.Options
|
||||
[DefaultValue(60.0f), Limit(0, 666)]
|
||||
[EditorDisplay("General", "Editor FPS"), EditorOrder(110), Tooltip("Limit for the editor draw/update frames per second rate (FPS). Use higher values if you need more responsive interface or lower values to use less device power. Value 0 disables any limits.")]
|
||||
public float EditorFPS { get; set; } = 60.0f;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets The FPS of the editor when the editor window is not focused. Usually set to lower then the editor FPS.
|
||||
/// </summary>
|
||||
@@ -203,7 +203,7 @@ namespace FlaxEditor.Options
|
||||
[DefaultValue(5), Limit(1)]
|
||||
[EditorDisplay("Auto Save", "Auto Save Frequency"), EditorOrder(801), Tooltip("The interval between auto saves (in minutes)")]
|
||||
public int AutoSaveFrequency { get; set; } = 5;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the time before the auto save that the popup shows (in seconds).
|
||||
/// </summary>
|
||||
|
||||
@@ -166,7 +166,19 @@ namespace FlaxEditor.Options
|
||||
/// Gets or sets the output log text font.
|
||||
/// </summary>
|
||||
[EditorDisplay("Output Log", "Text Font"), EditorOrder(320), Tooltip("The output log text font.")]
|
||||
public FontReference OutputLogTextFont { get; set; } = new FontReference(FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont), 10);
|
||||
public FontReference OutputLogTextFont
|
||||
{
|
||||
get => _outputLogFont;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
_outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont), 10);
|
||||
else if (!value.Font)
|
||||
_outputLogFont.Font = FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont);
|
||||
else
|
||||
_outputLogFont = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the output log text color.
|
||||
@@ -225,29 +237,82 @@ namespace FlaxEditor.Options
|
||||
public int NumberOfGameClientsToLaunch = 1;
|
||||
|
||||
private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.PrimaryFont);
|
||||
private FontReference _titleFont = new FontReference(DefaultFont, 18);
|
||||
private FontReference _largeFont = new FontReference(DefaultFont, 14);
|
||||
private FontReference _mediumFont = new FontReference(DefaultFont, 9);
|
||||
private FontReference _smallFont = new FontReference(DefaultFont, 9);
|
||||
private FontReference _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont), 10);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the title font for editor UI.
|
||||
/// </summary>
|
||||
[EditorDisplay("Fonts"), EditorOrder(600), Tooltip("The title font for editor UI.")]
|
||||
public FontReference TitleFont { get; set; } = new FontReference(DefaultFont, 18);
|
||||
public FontReference TitleFont
|
||||
{
|
||||
get => _titleFont;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
_titleFont = new FontReference(DefaultFont, 18);
|
||||
else if (!value.Font)
|
||||
_titleFont.Font = DefaultFont;
|
||||
else
|
||||
_titleFont = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the large font for editor UI.
|
||||
/// </summary>
|
||||
[EditorDisplay("Fonts"), EditorOrder(610), Tooltip("The large font for editor UI.")]
|
||||
public FontReference LargeFont { get; set; } = new FontReference(DefaultFont, 14);
|
||||
public FontReference LargeFont
|
||||
{
|
||||
get => _largeFont;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
_largeFont = new FontReference(DefaultFont, 14);
|
||||
else if (!value.Font)
|
||||
_largeFont.Font = DefaultFont;
|
||||
else
|
||||
_largeFont = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the medium font for editor UI.
|
||||
/// </summary>
|
||||
[EditorDisplay("Fonts"), EditorOrder(620), Tooltip("The medium font for editor UI.")]
|
||||
public FontReference MediumFont { get; set; } = new FontReference(DefaultFont, 9);
|
||||
public FontReference MediumFont
|
||||
{
|
||||
get => _mediumFont;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
_mediumFont = new FontReference(DefaultFont, 9);
|
||||
else if (!value.Font)
|
||||
_mediumFont.Font = DefaultFont;
|
||||
else
|
||||
_mediumFont = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the small font for editor UI.
|
||||
/// </summary>
|
||||
[EditorDisplay("Fonts"), EditorOrder(630), Tooltip("The small font for editor UI.")]
|
||||
public FontReference SmallFont { get; set; } = new FontReference(DefaultFont, 9);
|
||||
public FontReference SmallFont
|
||||
{
|
||||
get => _smallFont;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
_smallFont = new FontReference(DefaultFont, 9);
|
||||
else if (!value.Font)
|
||||
_smallFont.Font = DefaultFont;
|
||||
else
|
||||
_smallFont = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Windows;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.SceneGraph.Actors
|
||||
@@ -22,9 +23,9 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnContextMenu(ContextMenu contextMenu)
|
||||
public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
|
||||
{
|
||||
base.OnContextMenu(contextMenu);
|
||||
base.OnContextMenu(contextMenu, window);
|
||||
|
||||
var actor = (AnimatedModel)Actor;
|
||||
if (actor && actor.SkinnedModel)
|
||||
|
||||
@@ -6,6 +6,8 @@ using Real = System.Double;
|
||||
using Real = System.Single;
|
||||
#endif
|
||||
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Windows;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.SceneGraph.Actors
|
||||
@@ -23,6 +25,25 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
|
||||
{
|
||||
base.OnContextMenu(contextMenu, window);
|
||||
if (window is not SceneTreeWindow win)
|
||||
return;
|
||||
var button = new ContextMenuButton(contextMenu, "Move Camera to View");
|
||||
button.Parent = contextMenu.ItemsContainer;
|
||||
contextMenu.ItemsContainer.Children.Remove(button);
|
||||
contextMenu.ItemsContainer.Children.Insert(4, button);
|
||||
button.Clicked += () =>
|
||||
{
|
||||
var c = Actor as Camera;
|
||||
var viewport = Editor.Instance.Windows.EditWin.Viewport;
|
||||
c.Position = viewport.ViewPosition;
|
||||
c.Orientation = viewport.ViewOrientation;
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool RayCastSelf(ref RayCastData ray, out Real distance, out Vector3 normal)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System.IO;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.SceneGraph.GUI;
|
||||
using FlaxEditor.Windows;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.SceneGraph.Actors
|
||||
@@ -65,7 +66,7 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
public override SceneNode ParentScene => this;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnContextMenu(ContextMenu contextMenu)
|
||||
public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
|
||||
{
|
||||
contextMenu.AddSeparator();
|
||||
var path = Scene.Path;
|
||||
@@ -80,7 +81,7 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
if (Level.ScenesCount > 1)
|
||||
contextMenu.AddButton("Unload all but this scene", OnUnloadAllButSelectedScene).LinkTooltip("Unloads all of the active scenes except for the selected scene.").Enabled = Editor.Instance.StateMachine.CurrentState.CanChangeScene;
|
||||
|
||||
base.OnContextMenu(contextMenu);
|
||||
base.OnContextMenu(contextMenu, window);
|
||||
}
|
||||
|
||||
private void OnSelect()
|
||||
|
||||
@@ -9,6 +9,7 @@ using Real = System.Single;
|
||||
using System;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Modules;
|
||||
using FlaxEditor.Windows;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.Json;
|
||||
using Object = FlaxEngine.Object;
|
||||
@@ -203,9 +204,9 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnContextMenu(ContextMenu contextMenu)
|
||||
public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
|
||||
{
|
||||
ParentNode.OnContextMenu(contextMenu);
|
||||
ParentNode.OnContextMenu(contextMenu, window);
|
||||
}
|
||||
|
||||
public static SceneGraphNode Create(StateData state)
|
||||
@@ -272,9 +273,9 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
DebugDraw.DrawSphere(new BoundingSphere(pos, tangentSize), Color.YellowGreen, 0, false);
|
||||
}
|
||||
|
||||
public override void OnContextMenu(ContextMenu contextMenu)
|
||||
public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
|
||||
{
|
||||
ParentNode.OnContextMenu(contextMenu);
|
||||
ParentNode.OnContextMenu(contextMenu, window);
|
||||
}
|
||||
|
||||
public override void OnDispose()
|
||||
@@ -354,9 +355,9 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnContextMenu(ContextMenu contextMenu)
|
||||
public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
|
||||
{
|
||||
base.OnContextMenu(contextMenu);
|
||||
base.OnContextMenu(contextMenu, window);
|
||||
|
||||
contextMenu.AddButton("Add spline model", OnAddSplineModel);
|
||||
contextMenu.AddButton("Add spline collider", OnAddSplineCollider);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Windows;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.SceneGraph.Actors
|
||||
@@ -21,9 +22,9 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnContextMenu(ContextMenu contextMenu)
|
||||
public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
|
||||
{
|
||||
base.OnContextMenu(contextMenu);
|
||||
base.OnContextMenu(contextMenu, window);
|
||||
|
||||
contextMenu.AddButton("Add collider", OnAddMeshCollider).Enabled = ((StaticModel)Actor).Model != null;
|
||||
}
|
||||
|
||||
@@ -599,7 +599,7 @@ namespace FlaxEditor.SceneGraph.GUI
|
||||
// Drag scripts
|
||||
else if (_dragScripts != null && _dragScripts.HasValidDrag)
|
||||
{
|
||||
foreach(var script in _dragScripts.Objects)
|
||||
foreach (var script in _dragScripts.Objects)
|
||||
{
|
||||
var customAction = script.HasPrefabLink ? new ReparentAction(script) : null;
|
||||
using (new UndoBlock(ActorNode.Root.Undo, script, "Change script parent", customAction))
|
||||
@@ -616,7 +616,7 @@ namespace FlaxEditor.SceneGraph.GUI
|
||||
var spawnParent = myActor;
|
||||
if (DragOverMode == DragItemPositioning.Above || DragOverMode == DragItemPositioning.Below)
|
||||
spawnParent = newParent;
|
||||
|
||||
|
||||
for (int i = 0; i < _dragAssets.Objects.Count; i++)
|
||||
{
|
||||
var item = _dragAssets.Objects[i];
|
||||
@@ -720,7 +720,7 @@ namespace FlaxEditor.SceneGraph.GUI
|
||||
for (var i = 0; i < tree.Selection.Count; i++)
|
||||
{
|
||||
var e = tree.Selection[i];
|
||||
|
||||
|
||||
// Skip if parent is already selected to keep correct parenting
|
||||
if (tree.Selection.Contains(e.Parent))
|
||||
continue;
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Modules;
|
||||
using FlaxEditor.SceneGraph.Actors;
|
||||
using FlaxEditor.Windows;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.SceneGraph
|
||||
@@ -339,7 +340,7 @@ namespace FlaxEditor.SceneGraph
|
||||
/// <summary>
|
||||
/// Called when scene tree window wants to show the context menu. Allows to add custom options.
|
||||
/// </summary>
|
||||
public virtual void OnContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu contextMenu)
|
||||
public virtual void OnContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu contextMenu, EditorWindow window)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -1393,5 +1393,32 @@ namespace FlaxEditor.Scripting
|
||||
return _custom.GetMembers(name, MemberTypes.Method, bindingAttr).FirstOrDefault();
|
||||
return ScriptMemberInfo.Null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Basic check to see if a type could be casted to another type
|
||||
/// </summary>
|
||||
/// <param name="from">Source type</param>
|
||||
/// <param name="to">Target type</param>
|
||||
/// <returns>True if the type can be casted</returns>
|
||||
public static bool CanCast(ScriptType from, ScriptType to)
|
||||
{
|
||||
if (from == to)
|
||||
return true;
|
||||
if (from == Null || to == Null)
|
||||
return false;
|
||||
return (from.Type != typeof(void) && from.Type != typeof(FlaxEngine.Object)) &&
|
||||
(to.Type != typeof(void) && to.Type != typeof(FlaxEngine.Object)) &&
|
||||
from.IsAssignableFrom(to);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Basic check to see if this type could be casted to another type
|
||||
/// </summary>
|
||||
/// <param name="to">Target type</param>
|
||||
/// <returns>True if the type can be casted</returns>
|
||||
public bool CanCastTo(ScriptType to)
|
||||
{
|
||||
return CanCast(this, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,7 +385,12 @@ namespace FlaxEngine.Utilities
|
||||
return type.IsValueType && !type.IsEnum && !type.IsPrimitive;
|
||||
}
|
||||
|
||||
internal static bool IsDelegate(Type type)
|
||||
/// <summary>
|
||||
/// Checks if the input type represents a delegate.
|
||||
/// </summary>
|
||||
/// <param name="type">The input type of the object to check.</param>
|
||||
/// <returns>Returns true if the input type represents a delegate.</returns>
|
||||
public static bool IsDelegate(this Type type)
|
||||
{
|
||||
return typeof(MulticastDelegate).IsAssignableFrom(type.BaseType);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface.ContextMenu;
|
||||
@@ -89,188 +87,7 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
};
|
||||
|
||||
internal static class NodesCache
|
||||
{
|
||||
private static readonly object _locker = new object();
|
||||
private static int _version;
|
||||
private static Task _task;
|
||||
private static VisjectCM _taskContextMenu;
|
||||
private static Dictionary<KeyValuePair<string, ushort>, GroupArchetype> _cache;
|
||||
|
||||
public static void Wait()
|
||||
{
|
||||
_task?.Wait();
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
Wait();
|
||||
|
||||
if (_cache != null && _cache.Count != 0)
|
||||
{
|
||||
OnCodeEditingTypesCleared();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Get(VisjectCM contextMenu)
|
||||
{
|
||||
Wait();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
if (_cache == null)
|
||||
_cache = new Dictionary<KeyValuePair<string, ushort>, GroupArchetype>();
|
||||
contextMenu.LockChildrenRecursive();
|
||||
|
||||
// Check if has cached groups
|
||||
if (_cache.Count != 0)
|
||||
{
|
||||
// Check if context menu doesn't have the recent cached groups
|
||||
if (!contextMenu.Groups.Any(g => g.Archetypes[0].Tag is int asInt && asInt == _version))
|
||||
{
|
||||
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
|
||||
foreach (var g in groups)
|
||||
contextMenu.RemoveGroup(g);
|
||||
foreach (var g in _cache.Values)
|
||||
contextMenu.AddGroup(g);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove any old groups from context menu
|
||||
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
|
||||
foreach (var g in groups)
|
||||
contextMenu.RemoveGroup(g);
|
||||
|
||||
// Register for scripting types reload
|
||||
Editor.Instance.CodeEditing.TypesCleared += OnCodeEditingTypesCleared;
|
||||
|
||||
// Run caching on an async
|
||||
_task = Task.Run(OnActiveContextMenuShowAsync);
|
||||
_taskContextMenu = contextMenu;
|
||||
}
|
||||
|
||||
contextMenu.UnlockChildrenRecursive();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnActiveContextMenuShowAsync()
|
||||
{
|
||||
Profiler.BeginEvent("Setup Anim Graph Context Menu (async)");
|
||||
|
||||
foreach (var scriptType in Editor.Instance.CodeEditing.All.Get())
|
||||
{
|
||||
if (!SurfaceUtils.IsValidVisualScriptType(scriptType))
|
||||
continue;
|
||||
|
||||
// Skip Newtonsoft.Json stuff
|
||||
var scriptTypeTypeName = scriptType.TypeName;
|
||||
if (scriptTypeTypeName.StartsWith("Newtonsoft.Json."))
|
||||
continue;
|
||||
var scriptTypeName = scriptType.Name;
|
||||
|
||||
// Enum
|
||||
if (scriptType.IsEnum)
|
||||
{
|
||||
// Create node archetype
|
||||
var node = (NodeArchetype)Archetypes.Constants.Nodes[10].Clone();
|
||||
node.DefaultValues[0] = Activator.CreateInstance(scriptType.Type);
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = scriptTypeName;
|
||||
node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 2);
|
||||
if (!_cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(243, 156, 18),
|
||||
Tag = _version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
_cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Add node to the group
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Structure
|
||||
if (scriptType.IsValueType)
|
||||
{
|
||||
if (scriptType.IsVoid)
|
||||
continue;
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 4);
|
||||
if (!_cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(155, 89, 182),
|
||||
Tag = _version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
_cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
var tooltip = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
|
||||
// Create Pack node archetype
|
||||
var node = (NodeArchetype)Archetypes.Packing.Nodes[6].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Pack " + scriptTypeName;
|
||||
node.Description = tooltip;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
|
||||
// Create Unpack node archetype
|
||||
node = (NodeArchetype)Archetypes.Packing.Nodes[13].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Unpack " + scriptTypeName;
|
||||
node.Description = tooltip;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Add group to context menu (on a main thread)
|
||||
FlaxEngine.Scripting.InvokeOnUpdate(() =>
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
_taskContextMenu.AddGroups(_cache.Values);
|
||||
_taskContextMenu = null;
|
||||
}
|
||||
});
|
||||
|
||||
Profiler.EndEvent();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
_task = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnCodeEditingTypesCleared()
|
||||
{
|
||||
Wait();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
_cache.Clear();
|
||||
_version++;
|
||||
}
|
||||
|
||||
Editor.Instance.CodeEditing.TypesCleared -= OnCodeEditingTypesCleared;
|
||||
}
|
||||
}
|
||||
private static NodesCache _nodesCache = new NodesCache(IterateNodesCache);
|
||||
|
||||
/// <summary>
|
||||
/// The state machine editing context menu.
|
||||
@@ -345,7 +162,7 @@ namespace FlaxEditor.Surface
|
||||
_cmStateMachineMenu = new VisjectCM(new VisjectCM.InitInfo
|
||||
{
|
||||
Groups = StateMachineGroupArchetypes,
|
||||
CanSpawnNode = arch => true,
|
||||
CanSpawnNode = (_, _) => true,
|
||||
});
|
||||
_cmStateMachineMenu.ShowExpanded = true;
|
||||
}
|
||||
@@ -378,9 +195,7 @@ namespace FlaxEditor.Surface
|
||||
// Check if show additional nodes in the current surface context
|
||||
if (activeCM != _cmStateMachineMenu)
|
||||
{
|
||||
Profiler.BeginEvent("Setup Anim Graph Context Menu");
|
||||
NodesCache.Get(activeCM);
|
||||
Profiler.EndEvent();
|
||||
_nodesCache.Get(activeCM);
|
||||
|
||||
base.OnShowPrimaryMenu(activeCM, location, startBox);
|
||||
|
||||
@@ -394,7 +209,86 @@ namespace FlaxEditor.Surface
|
||||
|
||||
private void OnActiveContextMenuVisibleChanged(Control activeCM)
|
||||
{
|
||||
NodesCache.Wait();
|
||||
_nodesCache.Wait();
|
||||
}
|
||||
|
||||
private static void IterateNodesCache(ScriptType scriptType, Dictionary<KeyValuePair<string, ushort>, GroupArchetype> cache, int version)
|
||||
{
|
||||
// Skip Newtonsoft.Json stuff
|
||||
var scriptTypeTypeName = scriptType.TypeName;
|
||||
if (scriptTypeTypeName.StartsWith("Newtonsoft.Json."))
|
||||
return;
|
||||
var scriptTypeName = scriptType.Name;
|
||||
|
||||
// Enum
|
||||
if (scriptType.IsEnum)
|
||||
{
|
||||
// Create node archetype
|
||||
var node = (NodeArchetype)Archetypes.Constants.Nodes[10].Clone();
|
||||
node.DefaultValues[0] = Activator.CreateInstance(scriptType.Type);
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = scriptTypeName;
|
||||
node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 2);
|
||||
if (!cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(243, 156, 18),
|
||||
Tag = version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Add node to the group
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
return;
|
||||
}
|
||||
|
||||
// Structure
|
||||
if (scriptType.IsValueType)
|
||||
{
|
||||
if (scriptType.IsVoid)
|
||||
return;
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 4);
|
||||
if (!cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(155, 89, 182),
|
||||
Tag = version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
var tooltip = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
|
||||
// Create Pack node archetype
|
||||
var node = (NodeArchetype)Archetypes.Packing.Nodes[6].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Pack " + scriptTypeName;
|
||||
node.Description = tooltip;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
|
||||
// Create Unpack node archetype
|
||||
node = (NodeArchetype)Archetypes.Packing.Nodes[13].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Unpack " + scriptTypeName;
|
||||
node.Description = tooltip;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -406,9 +300,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUseNodeType(NodeArchetype nodeArchetype)
|
||||
public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
return (nodeArchetype.Flags & NodeFlags.AnimGraph) != 0 && base.CanUseNodeType(nodeArchetype);
|
||||
return (nodeArchetype.Flags & NodeFlags.AnimGraph) != 0 && base.CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -488,7 +382,7 @@ namespace FlaxEditor.Surface
|
||||
_cmStateMachineTransitionMenu = null;
|
||||
}
|
||||
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
|
||||
NodesCache.Wait();
|
||||
_nodesCache.Wait();
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUseNodeType(NodeArchetype nodeArchetype)
|
||||
public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
if (nodeArchetype.Title == "Function Input")
|
||||
return true;
|
||||
@@ -44,7 +44,7 @@ namespace FlaxEditor.Surface
|
||||
if (Context == RootContext && nodeArchetype.Title == "Function Output")
|
||||
return true;
|
||||
|
||||
return base.CanUseNodeType(nodeArchetype);
|
||||
return base.CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -542,9 +542,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
@@ -174,17 +174,17 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
// Ensure to have unique name
|
||||
var title = StateMachineTitle;
|
||||
@@ -484,7 +484,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
var startPos = PointToParent(ref center);
|
||||
targetState.GetConnectionEndPoint(ref startPos, out var endPos);
|
||||
var color = style.Foreground;
|
||||
StateMachineState.DrawConnection(Surface, ref startPos, ref endPos, ref color);
|
||||
StateMachineState.DrawConnection(ref startPos, ref endPos, ref color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,7 +514,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <inheritdoc />
|
||||
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
{
|
||||
StateMachineState.DrawConnection(Surface, ref startPos, ref endPos, ref color);
|
||||
StateMachineState.DrawConnection(ref startPos, ref endPos, ref color);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -680,11 +680,10 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <summary>
|
||||
/// Draws the connection between two state machine nodes.
|
||||
/// </summary>
|
||||
/// <param name="surface">The surface.</param>
|
||||
/// <param name="startPos">The start position.</param>
|
||||
/// <param name="endPos">The end position.</param>
|
||||
/// <param name="color">The line color.</param>
|
||||
public static void DrawConnection(VisjectSurface surface, ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
public static void DrawConnection(ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
{
|
||||
var sub = endPos - startPos;
|
||||
var length = sub.Length;
|
||||
@@ -695,11 +694,14 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
float rotation = Float2.Dot(dir, Float2.UnitY);
|
||||
if (endPos.X < startPos.X)
|
||||
rotation = 2 - rotation;
|
||||
// TODO: make it look better (fix the math)
|
||||
var arrowTransform = Matrix3x3.Translation2D(new Float2(-16.0f, -8.0f)) * Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) * Matrix3x3.Translation2D(endPos);
|
||||
var sprite = Editor.Instance.Icons.VisjectArrowClosed32;
|
||||
var arrowTransform =
|
||||
Matrix3x3.Translation2D(-6.5f, -8) *
|
||||
Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) *
|
||||
Matrix3x3.Translation2D(endPos - dir * 8);
|
||||
|
||||
Render2D.PushTransform(ref arrowTransform);
|
||||
Render2D.DrawSprite(Editor.Instance.Icons.VisjectArrowClosed32, arrowRect, color);
|
||||
Render2D.DrawSprite(sprite, arrowRect, color);
|
||||
Render2D.PopTransform();
|
||||
|
||||
endPos -= dir * 4.0f;
|
||||
@@ -742,9 +744,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
LoadTransitions();
|
||||
|
||||
@@ -1293,7 +1295,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
isMouseOver = Float2.DistanceSquared(ref mousePosition, ref point) < 25.0f;
|
||||
}
|
||||
var color = isMouseOver ? Color.Wheat : t.LineColor;
|
||||
DrawConnection(Surface, ref t.StartPos, ref t.EndPos, ref color);
|
||||
DrawConnection(ref t.StartPos, ref t.EndPos, ref color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1322,7 +1324,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <inheritdoc />
|
||||
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
{
|
||||
DrawConnection(Surface, ref startPos, ref endPos, ref color);
|
||||
DrawConnection(ref startPos, ref endPos, ref color);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1433,9 +1435,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
public override int TransitionsDataIndex => 2;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
// Ensure to have unique name
|
||||
var title = StateTitle;
|
||||
@@ -1453,9 +1455,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
@@ -49,9 +49,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
if (Surface != null)
|
||||
UpdateTitle();
|
||||
@@ -162,9 +162,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
// Peek deserialized boxes
|
||||
_blendPoses.Clear();
|
||||
|
||||
887
Source/Editor/Surface/Archetypes/BehaviorTree.cs
Normal file
887
Source/Editor/Surface/Archetypes/BehaviorTree.cs
Normal file
@@ -0,0 +1,887 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.CustomEditors.Dedicated;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Utilities;
|
||||
|
||||
namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains archetypes for nodes from the Behavior Tree group.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public static class BehaviorTree
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for Behavior Tree nodes wrapped inside <see cref="SurfaceNode" />.
|
||||
/// </summary>
|
||||
internal class NodeBase : SurfaceNode
|
||||
{
|
||||
protected const float ConnectionAreaMargin = 12.0f;
|
||||
protected const float ConnectionAreaHeight = 12.0f;
|
||||
protected const float DecoratorsMarginX = 5.0f;
|
||||
protected const float DecoratorsMarginY = 2.0f;
|
||||
|
||||
protected bool _debugRelevant;
|
||||
protected string _debugInfo;
|
||||
protected Float2 _debugInfoSize;
|
||||
protected ScriptType _type;
|
||||
internal bool _isValueEditing;
|
||||
|
||||
public BehaviorTreeNode Instance;
|
||||
|
||||
protected NodeBase(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
}
|
||||
|
||||
public static string GetTitle(ScriptType scriptType)
|
||||
{
|
||||
var title = scriptType.Name;
|
||||
if (title.StartsWith("BehaviorTree"))
|
||||
title = title.Substring(12);
|
||||
if (title.EndsWith("Node"))
|
||||
title = title.Substring(0, title.Length - 4);
|
||||
if (title.EndsWith("Decorator"))
|
||||
title = title.Substring(0, title.Length - 9);
|
||||
title = Utilities.Utils.GetPropertyNameUI(title);
|
||||
return title;
|
||||
}
|
||||
|
||||
public virtual void UpdateDebug(Behavior behavior)
|
||||
{
|
||||
BehaviorTreeNode instance = null;
|
||||
if (behavior)
|
||||
{
|
||||
// Try to use instance from the currently debugged behavior
|
||||
// TODO: support nodes from nested trees
|
||||
instance = behavior.Tree.GetNodeInstance(ID);
|
||||
}
|
||||
var size = _debugInfoSize;
|
||||
UpdateDebugInfo(instance, behavior);
|
||||
if (size != _debugInfoSize)
|
||||
ResizeAuto();
|
||||
}
|
||||
|
||||
protected virtual void UpdateTitle()
|
||||
{
|
||||
string title = null;
|
||||
if (Instance != null)
|
||||
{
|
||||
title = Instance.Name;
|
||||
if (string.IsNullOrEmpty(title))
|
||||
title = GetTitle(_type);
|
||||
}
|
||||
else
|
||||
{
|
||||
var typeName = (string)Values[0];
|
||||
title = "Missing Type " + typeName;
|
||||
}
|
||||
Title = title;
|
||||
}
|
||||
|
||||
protected virtual void UpdateDebugInfo(BehaviorTreeNode instance = null, Behavior behavior = null)
|
||||
{
|
||||
_debugRelevant = false;
|
||||
_debugInfo = null;
|
||||
_debugInfoSize = Float2.Zero;
|
||||
if (!instance)
|
||||
instance = Instance;
|
||||
if (instance)
|
||||
{
|
||||
// Get debug description for the node based on the current settings
|
||||
_debugRelevant = Behavior.GetNodeDebugRelevancy(instance, behavior);
|
||||
_debugInfo = Behavior.GetNodeDebugInfo(instance, behavior);
|
||||
if (!string.IsNullOrEmpty(_debugInfo))
|
||||
_debugInfoSize = Style.Current.FontSmall.MeasureText(_debugInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded(action);
|
||||
|
||||
// Setup node type and data
|
||||
var typeName = (string)Values[0];
|
||||
_type = TypeUtils.GetType(typeName);
|
||||
if (_type != null)
|
||||
{
|
||||
TooltipText = Editor.Instance.CodeDocs.GetTooltip(_type);
|
||||
try
|
||||
{
|
||||
// Load node instance from data
|
||||
Instance = (BehaviorTreeNode)_type.CreateInstance();
|
||||
var instanceData = (byte[])Values[1];
|
||||
FlaxEngine.Json.JsonSerializer.LoadFromBytes(Instance, instanceData, Globals.EngineBuildNumber);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Editor.LogError("Failed to load Behavior Tree node of type " + typeName);
|
||||
Editor.LogWarning(ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Instance = null;
|
||||
}
|
||||
|
||||
UpdateDebugInfo();
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
public override void OnValuesChanged()
|
||||
{
|
||||
base.OnValuesChanged();
|
||||
|
||||
// Skip updating instance when it's being edited by user via UI
|
||||
if (!_isValueEditing)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
// Reload node instance from data
|
||||
var instanceData = (byte[])Values[1];
|
||||
if (instanceData == null || instanceData.Length == 0)
|
||||
{
|
||||
// Recreate instance data to default state if previous state was empty
|
||||
var defaultInstance = (BehaviorTreeNode)_type.CreateInstance(); // TODO: use default instance from native ScriptingType
|
||||
instanceData = FlaxEngine.Json.JsonSerializer.SaveToBytes(defaultInstance);
|
||||
}
|
||||
FlaxEngine.Json.JsonSerializer.LoadFromBytes(Instance, instanceData, Globals.EngineBuildNumber);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Editor.LogError("Failed to load Behavior Tree node of type " + _type);
|
||||
Editor.LogWarning(ex);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateDebugInfo();
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned(action);
|
||||
|
||||
ResizeAuto();
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
// Debug Info
|
||||
if (!string.IsNullOrEmpty(_debugInfo))
|
||||
{
|
||||
var style = Style.Current;
|
||||
Render2D.DrawText(style.FontSmall, _debugInfo, new Rectangle(4, _headerRect.Bottom + 4, _debugInfoSize), style.Foreground);
|
||||
}
|
||||
|
||||
// Debug relevancy outline
|
||||
if (_debugRelevant)
|
||||
{
|
||||
var colorTop = Color.LightYellow;
|
||||
var colorBottom = Color.Yellow;
|
||||
var backgroundRect = new Rectangle(Float2.One, Size - new Float2(2.0f));
|
||||
Render2D.DrawRectangle(backgroundRect, colorTop, colorTop, colorBottom, colorBottom);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (IsDisposing)
|
||||
return;
|
||||
_debugInfo = null;
|
||||
_type = ScriptType.Null;
|
||||
FlaxEngine.Object.Destroy(ref Instance);
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Customized <see cref="SurfaceNode" /> for the Behavior Tree node.
|
||||
/// </summary>
|
||||
internal class Node : NodeBase
|
||||
{
|
||||
private InputBox _input;
|
||||
private OutputBox _output;
|
||||
internal List<Decorator> _decorators;
|
||||
|
||||
internal static SurfaceNode Create(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
{
|
||||
return new Node(id, context, nodeArch, groupArch);
|
||||
}
|
||||
|
||||
internal Node(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
}
|
||||
|
||||
public unsafe List<uint> DecoratorIds
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = new List<uint>();
|
||||
var ids = Values.Length >= 3 ? Values[2] as byte[] : null;
|
||||
if (ids != null)
|
||||
{
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
int count = ids.Length / sizeof(uint);
|
||||
for (int i = 0; i < count; i++)
|
||||
result.Add(ptr[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
set => SetDecoratorIds(value, true);
|
||||
}
|
||||
|
||||
public unsafe List<Decorator> Decorators
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_decorators == null)
|
||||
{
|
||||
_decorators = new List<Decorator>();
|
||||
var ids = Values.Length >= 3 ? Values[2] as byte[] : null;
|
||||
if (ids != null)
|
||||
{
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
int count = ids.Length / sizeof(uint);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var decorator = Surface.FindNode(ptr[i]) as Decorator;
|
||||
if (decorator != null)
|
||||
_decorators.Add(decorator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return _decorators;
|
||||
}
|
||||
set
|
||||
{
|
||||
_decorators = null;
|
||||
var ids = new byte[sizeof(uint) * value.Count];
|
||||
if (value != null)
|
||||
{
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
for (var i = 0; i < value.Count; i++)
|
||||
ptr[i] = value[i].ID;
|
||||
}
|
||||
}
|
||||
SetValue(2, ids);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void SetDecoratorIds(List<uint> value, bool withUndo)
|
||||
{
|
||||
var ids = new byte[sizeof(uint) * value.Count];
|
||||
if (value != null)
|
||||
{
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
for (var i = 0; i < value.Count; i++)
|
||||
ptr[i] = value[i];
|
||||
}
|
||||
}
|
||||
if (withUndo)
|
||||
SetValue(2, ids);
|
||||
else
|
||||
{
|
||||
Values[2] = ids;
|
||||
OnValuesChanged();
|
||||
Surface?.MarkAsEdited();
|
||||
}
|
||||
}
|
||||
|
||||
public override unsafe SurfaceNode[] SealedNodes
|
||||
{
|
||||
get
|
||||
{
|
||||
// Return decorator nodes attached to this node to be moved/copied/pasted as a one
|
||||
SurfaceNode[] result = null;
|
||||
var ids = Values.Length >= 3 ? Values[2] as byte[] : null;
|
||||
if (ids != null)
|
||||
{
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
int count = ids.Length / sizeof(uint);
|
||||
result = new SurfaceNode[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var decorator = Surface.FindNode(ptr[i]) as Decorator;
|
||||
if (decorator != null)
|
||||
result[i] = decorator;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnShowSecondaryContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu menu, Float2 location)
|
||||
{
|
||||
base.OnShowSecondaryContextMenu(menu, location);
|
||||
|
||||
if (!Surface.CanEdit)
|
||||
return;
|
||||
|
||||
menu.AddSeparator();
|
||||
|
||||
var nodeTypes = Editor.Instance.CodeEditing.BehaviorTreeNodes.Get();
|
||||
|
||||
if (_input.Enabled) // Root node cannot have decorators
|
||||
{
|
||||
var decorators = menu.AddChildMenu("Add Decorator");
|
||||
var decoratorType = new ScriptType(typeof(BehaviorTreeDecorator));
|
||||
foreach (var nodeType in nodeTypes)
|
||||
{
|
||||
if (nodeType != decoratorType && decoratorType.IsAssignableFrom(nodeType))
|
||||
{
|
||||
var button = decorators.ContextMenu.AddButton(GetTitle(nodeType));
|
||||
button.Tag = nodeType;
|
||||
button.TooltipText = Editor.Instance.CodeDocs.GetTooltip(nodeType);
|
||||
button.ButtonClicked += OnAddDecoratorButtonClicked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddDecoratorButtonClicked(ContextMenuButton button)
|
||||
{
|
||||
var nodeType = (ScriptType)button.Tag;
|
||||
|
||||
// Spawn decorator
|
||||
var decorator = Context.SpawnNode(19, 3, Location, new object[]
|
||||
{
|
||||
nodeType.TypeName,
|
||||
Utils.GetEmptyArray<byte>(),
|
||||
});
|
||||
|
||||
// Add decorator to the node
|
||||
var decorators = Decorators;
|
||||
decorators.Add((Decorator)decorator);
|
||||
Decorators = decorators;
|
||||
}
|
||||
|
||||
public override void OnValuesChanged()
|
||||
{
|
||||
// Reject cached value
|
||||
_decorators = null;
|
||||
|
||||
base.OnValuesChanged();
|
||||
|
||||
ResizeAuto();
|
||||
}
|
||||
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded(action);
|
||||
|
||||
// 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);
|
||||
|
||||
// Setup node type and data
|
||||
var flagsRoot = NodeFlags.NoRemove | NodeFlags.NoCloseButton | NodeFlags.NoSpawnViaPaste;
|
||||
var flags = Archetype.Flags & ~flagsRoot;
|
||||
if (_type != null)
|
||||
{
|
||||
bool isRoot = _type.Type == typeof(BehaviorTreeRootNode);
|
||||
_input.Enabled = _input.Visible = !isRoot;
|
||||
_output.Enabled = _output.Visible = new ScriptType(typeof(BehaviorTreeCompoundNode)).IsAssignableFrom(_type);
|
||||
if (isRoot)
|
||||
flags |= flagsRoot;
|
||||
}
|
||||
if (Archetype.Flags != flags)
|
||||
{
|
||||
// Apply custom flags
|
||||
Archetype = (NodeArchetype)Archetype.Clone();
|
||||
Archetype.Flags = flags;
|
||||
}
|
||||
|
||||
ResizeAuto();
|
||||
}
|
||||
|
||||
public override unsafe void OnPasted(Dictionary<uint, uint> idsMapping)
|
||||
{
|
||||
base.OnPasted(idsMapping);
|
||||
|
||||
// Update decorators
|
||||
var ids = Values.Length >= 3 ? Values[2] as byte[] : null;
|
||||
if (ids != null)
|
||||
{
|
||||
_decorators = null;
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
int count = ids.Length / sizeof(uint);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (idsMapping.TryGetValue(ptr[i], out var id))
|
||||
{
|
||||
// Fix previous parent node to re-apply layout (in case it was forced by spawned decorator)
|
||||
var decorator = Surface.FindNode(ptr[i]) as Decorator;
|
||||
var decoratorNode = decorator?.Node;
|
||||
if (decoratorNode != null)
|
||||
decoratorNode.ResizeAuto();
|
||||
|
||||
// Update mapping to the new node
|
||||
ptr[i] = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
Values[2] = ids;
|
||||
ResizeAuto();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
ResizeAuto();
|
||||
Surface.NodeDeleted += OnNodeDeleted;
|
||||
}
|
||||
|
||||
private void OnNodeDeleted(SurfaceNode node)
|
||||
{
|
||||
if (node is Decorator decorator && Decorators.Contains(decorator))
|
||||
{
|
||||
// Decorator was spawned (eg. via undo)
|
||||
_decorators = null;
|
||||
ResizeAuto();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ResizeAuto()
|
||||
{
|
||||
if (Surface == null)
|
||||
return;
|
||||
var width = 0.0f;
|
||||
var height = 0.0f;
|
||||
var titleLabelFont = Style.Current.FontLarge;
|
||||
width = Mathf.Max(width, 100.0f);
|
||||
width = Mathf.Max(width, titleLabelFont.MeasureText(Title).X + 30);
|
||||
if (_debugInfoSize.X > 0)
|
||||
{
|
||||
width = Mathf.Max(width, _debugInfoSize.X + 8.0f);
|
||||
height += _debugInfoSize.Y + 8.0f;
|
||||
}
|
||||
if (_input != null && _input.Visible)
|
||||
height += ConnectionAreaHeight;
|
||||
if (_output != null && _output.Visible)
|
||||
height += ConnectionAreaHeight;
|
||||
var decorators = Decorators;
|
||||
foreach (var decorator in decorators)
|
||||
{
|
||||
decorator.ResizeAuto();
|
||||
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);
|
||||
UpdateRectangles();
|
||||
}
|
||||
|
||||
protected override void UpdateRectangles()
|
||||
{
|
||||
Rectangle bounds = Bounds;
|
||||
if (_input != null && _input.Visible)
|
||||
{
|
||||
_input.Bounds = new Rectangle(ConnectionAreaMargin, 0, Width - ConnectionAreaMargin * 2, ConnectionAreaHeight);
|
||||
bounds.Location.Y += _input.Height;
|
||||
}
|
||||
var decorators = Decorators;
|
||||
var indexInParent = IndexInParent;
|
||||
foreach (var decorator in decorators)
|
||||
{
|
||||
decorator.Bounds = new Rectangle(bounds.Location.X + DecoratorsMarginX, bounds.Location.Y, bounds.Width - 2 * DecoratorsMarginX, decorator.Height);
|
||||
bounds.Location.Y += decorator.Height + DecoratorsMarginY;
|
||||
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 closeButtonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
|
||||
const float closeButtonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
|
||||
_headerRect = new Rectangle(0, bounds.Y - Y, bounds.Width, headerSize);
|
||||
_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;
|
||||
_output.Bounds = new Rectangle(ConnectionAreaMargin, bounds.Height - ConnectionAreaHeight, bounds.Width - ConnectionAreaMargin * 2, ConnectionAreaHeight);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnLocationChanged()
|
||||
{
|
||||
base.OnLocationChanged();
|
||||
|
||||
// Sync attached elements placement
|
||||
UpdateRectangles();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Customized <see cref="SurfaceNode" /> for the Behavior Tree decorator.
|
||||
/// </summary>
|
||||
internal class Decorator : NodeBase
|
||||
{
|
||||
private sealed class DragDecorator : DragHelper<uint, DragEventArgs>
|
||||
{
|
||||
public const string DragPrefix = "DECORATOR!?";
|
||||
|
||||
public DragDecorator(Func<uint, bool> validateFunction)
|
||||
: base(validateFunction)
|
||||
{
|
||||
}
|
||||
|
||||
public override DragData ToDragData(uint item) => new DragDataText(DragPrefix + item);
|
||||
|
||||
public override DragData ToDragData(IEnumerable<uint> items)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override IEnumerable<uint> FromDragData(DragData data)
|
||||
{
|
||||
if (data is DragDataText dataText)
|
||||
{
|
||||
if (dataText.Text.StartsWith(DragPrefix))
|
||||
{
|
||||
var id = dataText.Text.Remove(0, DragPrefix.Length).Split('\n');
|
||||
return new[] { uint.Parse(id[0]) };
|
||||
}
|
||||
}
|
||||
return Utils.GetEmptyArray<uint>();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ReorderDecoratorAction : IUndoAction
|
||||
{
|
||||
public VisjectSurface Surface;
|
||||
public uint DecoratorId, PrevNodeId, NewNodeId;
|
||||
public int PrevIndex, NewIndex;
|
||||
|
||||
public string ActionString => "Reorder decorator";
|
||||
|
||||
private void Do(uint nodeId, int index)
|
||||
{
|
||||
var decorator = Surface.FindNode(DecoratorId) as Decorator;
|
||||
if (decorator == null)
|
||||
throw new Exception("Missing decorator");
|
||||
var node = decorator.Node;
|
||||
var decorators = node.DecoratorIds;
|
||||
decorators.Remove(DecoratorId);
|
||||
if (node.ID != nodeId)
|
||||
{
|
||||
node.SetDecoratorIds(decorators, false);
|
||||
node = Surface.FindNode(nodeId) as Node;
|
||||
decorators = node.DecoratorIds;
|
||||
}
|
||||
if (index < 0 || index >= decorators.Count)
|
||||
decorators.Add(DecoratorId);
|
||||
else
|
||||
decorators.Insert(index, DecoratorId);
|
||||
node.SetDecoratorIds(decorators, false);
|
||||
}
|
||||
|
||||
public void Do()
|
||||
{
|
||||
Do(NewNodeId, NewIndex);
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
Do(PrevNodeId, PrevIndex);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Surface = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static SurfaceNode Create(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
{
|
||||
return new Decorator(id, context, nodeArch, groupArch);
|
||||
}
|
||||
|
||||
private DragImage _dragIcon;
|
||||
private DragDecorator _dragDecorator;
|
||||
private float _dragLocation = -1;
|
||||
|
||||
internal Decorator(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
_dragDecorator = new DragDecorator(ValidateDrag);
|
||||
}
|
||||
|
||||
public Node Node
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var node in Surface.Nodes)
|
||||
{
|
||||
if (node is Node n && n.DecoratorIds.Contains(ID))
|
||||
return n;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Color FooterColor => Color.Transparent;
|
||||
|
||||
protected override Float2 CalculateNodeSize(float width, float height)
|
||||
{
|
||||
if (_debugInfoSize.X > 0)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
protected override void UpdateRectangles()
|
||||
{
|
||||
base.UpdateRectangles();
|
||||
|
||||
_footerRect = Rectangle.Empty;
|
||||
if (_dragIcon != null)
|
||||
_dragIcon.Bounds = new Rectangle(_closeButtonRect.X - _closeButtonRect.Width, _closeButtonRect.Y, _closeButtonRect.Size);
|
||||
}
|
||||
|
||||
protected override void UpdateTitle()
|
||||
{
|
||||
// Update parent node on title change
|
||||
var title = Title;
|
||||
base.UpdateTitle();
|
||||
if (title != Title)
|
||||
Node?.ResizeAuto();
|
||||
}
|
||||
|
||||
protected override void UpdateDebugInfo(BehaviorTreeNode instance, Behavior behavior)
|
||||
{
|
||||
// Update parent node on debug text change
|
||||
var debugInfoSize = _debugInfoSize;
|
||||
base.UpdateDebugInfo(instance, behavior);
|
||||
if (debugInfoSize != _debugInfoSize)
|
||||
Node?.ResizeAuto();
|
||||
}
|
||||
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
// Add drag button to reorder decorator
|
||||
_dragIcon = new DragImage
|
||||
{
|
||||
AnchorPreset = AnchorPresets.TopRight,
|
||||
Color = Style.Current.ForegroundGrey,
|
||||
Parent = this,
|
||||
Margin = new Margin(1),
|
||||
Visible = Surface.CanEdit,
|
||||
Brush = new SpriteBrush(Editor.Instance.Icons.DragBar12),
|
||||
Tag = this,
|
||||
Drag = img => { img.DoDragDrop(_dragDecorator.ToDragData(ID)); }
|
||||
};
|
||||
|
||||
base.OnLoaded(action);
|
||||
}
|
||||
|
||||
private bool ValidateDrag(uint id)
|
||||
{
|
||||
return Surface.FindNode(id) != null;
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
var style = Style.Current;
|
||||
|
||||
// Outline
|
||||
if (!_isSelected)
|
||||
{
|
||||
var rect = new Rectangle(Float2.Zero, Size);
|
||||
Render2D.DrawRectangle(rect, style.BorderHighlighted);
|
||||
}
|
||||
|
||||
// Drag hint
|
||||
if (IsDragOver && _dragDecorator.HasValidDrag)
|
||||
{
|
||||
var rect = new Rectangle(0, _dragLocation < Height * 0.5f ? 0 : Height - 6, Width, 6);
|
||||
Render2D.FillRectangle(rect, style.BackgroundSelected);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
if (action == SurfaceNodeActions.Undo)
|
||||
{
|
||||
// Update parent node layout when restoring decorator from undo
|
||||
var node = Node;
|
||||
if (node != null)
|
||||
{
|
||||
node._decorators = null;
|
||||
node.ResizeAuto();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSurfaceCanEditChanged(bool canEdit)
|
||||
{
|
||||
base.OnSurfaceCanEditChanged(canEdit);
|
||||
|
||||
if (_dragIcon != null)
|
||||
_dragIcon.Visible = canEdit;
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragEnter(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
if (_dragDecorator.OnDragEnter(data))
|
||||
{
|
||||
_dragLocation = location.Y;
|
||||
result = DragDropEffect.Move;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragMove(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
if (_dragDecorator.HasValidDrag)
|
||||
{
|
||||
_dragLocation = location.Y;
|
||||
result = DragDropEffect.Move;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void OnDragLeave()
|
||||
{
|
||||
_dragLocation = -1;
|
||||
_dragDecorator.OnDragLeave();
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragDrop(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
if (_dragDecorator.HasValidDrag)
|
||||
{
|
||||
// Reorder or reparent decorator
|
||||
var decorator = (Decorator)Surface.FindNode(_dragDecorator.Objects[0]);
|
||||
var prevNode = decorator.Node;
|
||||
var prevIndex = prevNode.Decorators.IndexOf(decorator);
|
||||
var newNode = Node;
|
||||
var newIndex = newNode.Decorators.IndexOf(this);
|
||||
if (_dragLocation >= Height * 0.5f)
|
||||
newIndex++;
|
||||
if (prevIndex != newIndex || prevNode != newNode)
|
||||
{
|
||||
var action = new ReorderDecoratorAction
|
||||
{
|
||||
Surface = Surface,
|
||||
DecoratorId = decorator.ID,
|
||||
PrevNodeId = prevNode.ID,
|
||||
PrevIndex = prevIndex,
|
||||
NewNodeId = newNode.ID,
|
||||
NewIndex = newIndex,
|
||||
};
|
||||
action.Do();
|
||||
Surface.Undo?.AddAction(action);
|
||||
}
|
||||
_dragLocation = -1;
|
||||
_dragDecorator.OnDragDrop();
|
||||
result = DragDropEffect.Move;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The nodes for that group.
|
||||
/// </summary>
|
||||
public static NodeArchetype[] Nodes =
|
||||
{
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 1, // Task Node
|
||||
Create = Node.Create,
|
||||
Flags = NodeFlags.BehaviorTreeGraph | NodeFlags.NoSpawnViaGUI,
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
string.Empty, // Type Name
|
||||
Utils.GetEmptyArray<byte>(), // Instance Data
|
||||
null, // List of Decorator Nodes IDs
|
||||
},
|
||||
Size = new Float2(100, 0),
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Input(0, string.Empty, true, ScriptType.Void, 0),
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, ScriptType.Void, 1),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 2, // Root Node
|
||||
Create = Node.Create,
|
||||
Flags = NodeFlags.BehaviorTreeGraph | NodeFlags.NoSpawnViaGUI,
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
typeof(BehaviorTreeRootNode).FullName, // Root node
|
||||
Utils.GetEmptyArray<byte>(), // Instance Data
|
||||
},
|
||||
Size = new Float2(100, 0),
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Input(0, string.Empty, true, ScriptType.Void, 0),
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, ScriptType.Void, 1),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 3, // Decorator Node
|
||||
Create = Decorator.Create,
|
||||
Flags = NodeFlags.BehaviorTreeGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoMove,
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
string.Empty, // Type Name
|
||||
Utils.GetEmptyArray<byte>(), // Instance Data
|
||||
},
|
||||
Size = new Float2(100, 0),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -50,9 +50,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
// Restore saved input boxes layout
|
||||
if (Values[0] is byte[] data)
|
||||
@@ -62,9 +62,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateBoxes();
|
||||
GetBox(0).CurrentTypeChanged += box => UpdateBoxes();
|
||||
|
||||
@@ -43,9 +43,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
box.CurrentType = new ScriptType(Values[0].GetType());
|
||||
}
|
||||
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
var box = (OutputBox)GetBox(0);
|
||||
if (Values[0] == null)
|
||||
@@ -100,9 +100,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
base.OnValuesChanged();
|
||||
}
|
||||
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
_output = (OutputBox)Elements[0];
|
||||
_typePicker = new TypePickerControl
|
||||
@@ -238,9 +238,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
base.OnValuesChanged();
|
||||
}
|
||||
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
_output = (OutputBox)Elements[0];
|
||||
_keyTypePicker = new TypePickerControl
|
||||
|
||||
@@ -25,9 +25,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
// Restore saved output boxes layout
|
||||
var count = (int)Values[0];
|
||||
@@ -35,9 +35,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
AddBox(true, i + 1, i, string.Empty, new ScriptType(typeof(void)), true);
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
_removeButton = new Button(0, 0, 20, 20)
|
||||
{
|
||||
@@ -107,9 +107,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
// Restore saved output boxes layout
|
||||
if (Values[0] is byte[] data)
|
||||
@@ -119,9 +119,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateBoxes();
|
||||
GetBox(1).CurrentTypeChanged += box => UpdateBoxes();
|
||||
|
||||
@@ -54,9 +54,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
protected abstract Asset LoadSignature(Guid id, out string[] types, out string[] names);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
FlaxEngine.Content.AssetReloading += OnAssetReloading;
|
||||
FlaxEngine.Content.AssetDisposing += OnContentAssetDisposing;
|
||||
@@ -275,17 +275,17 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
_nameField.Text = SignatureName;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
// Ensure to have unique name
|
||||
var name = SignatureName;
|
||||
@@ -397,9 +397,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
_outputBox = GetBox(0);
|
||||
_outputBox.CurrentType = SignatureType;
|
||||
@@ -466,9 +466,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
_inputBox = GetBox(0);
|
||||
_inputBox.CurrentType = SignatureType;
|
||||
@@ -634,18 +634,18 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
protected override Color FooterColor => new Color(200, 11, 112);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
Title = SurfaceUtils.GetMethodDisplayName((string)Values[0]);
|
||||
UpdateSignature();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
// Update the boxes connection types to match the signature
|
||||
// Do it after surface load so connections can receive update on type changes of the method parameter
|
||||
@@ -663,9 +663,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
var method = GetMethod();
|
||||
_parameters = null;
|
||||
@@ -744,6 +744,16 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
return inputType.IsVoid;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class InvokeMethodNode : SurfaceNode
|
||||
@@ -999,9 +1009,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
var method = GetMethod(out _, out _, out var parameters);
|
||||
if (method && parameters != null)
|
||||
@@ -1028,18 +1038,18 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
Title = SurfaceUtils.GetMethodDisplayName((string)Values[1]);
|
||||
UpdateSignature();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
// Update the boxes connection types to match the signature
|
||||
// Do it after surface load so connections can receive update on type changes of the method parameter
|
||||
@@ -1151,6 +1161,54 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
if (nodeArch.Tag is not ScriptMemberInfo memberInfo)
|
||||
return false;
|
||||
|
||||
if (!memberInfo.IsStatic)
|
||||
{
|
||||
if (VisjectSurface.FullCastCheck(memberInfo.DeclaringType, outputType, hint))
|
||||
return true;
|
||||
}
|
||||
|
||||
var parameters = memberInfo.GetParameters();
|
||||
bool isPure = (parameters.Length == 0 && !memberInfo.ValueType.IsVoid);
|
||||
if (outputType.IsVoid)
|
||||
return !isPure;
|
||||
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
if (param.IsOut)
|
||||
continue;
|
||||
if (VisjectSurface.FullCastCheck(param.Type, outputType, hint))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
if (nodeArch.Tag is not ScriptMemberInfo memberInfo)
|
||||
return false;
|
||||
if (VisjectSurface.FullCastCheck(memberInfo.ValueType, inputType, hint))
|
||||
return true;
|
||||
|
||||
var parameters = memberInfo.GetParameters();
|
||||
bool isPure = (parameters.Length == 0 && !memberInfo.ValueType.IsVoid);
|
||||
if (inputType.IsVoid)
|
||||
return !isPure;
|
||||
|
||||
foreach (var param in memberInfo.GetParameters())
|
||||
{
|
||||
if (!param.IsOut)
|
||||
continue;
|
||||
if (VisjectSurface.FullCastCheck(param.Type, inputType, hint))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ReturnNode : SurfaceNode
|
||||
@@ -1182,9 +1240,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
UpdateSignature();
|
||||
}
|
||||
@@ -1700,9 +1758,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
LoadSignature();
|
||||
|
||||
@@ -1715,9 +1773,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
// Setup initial signature
|
||||
var defaultSignature = _signature.Node == null;
|
||||
@@ -1743,7 +1801,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDeleted()
|
||||
public override void OnDeleted(SurfaceNodeActions action)
|
||||
{
|
||||
// Send event
|
||||
for (int i = 0; i < Surface.Nodes.Count; i++)
|
||||
@@ -1752,7 +1810,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
node.OnFunctionDeleted(this);
|
||||
}
|
||||
|
||||
base.OnDeleted();
|
||||
base.OnDeleted(action);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1777,6 +1835,16 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
return inputType.IsVoid;
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class FieldNodeBase : SurfaceNode
|
||||
@@ -1906,13 +1974,71 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
Title = "Get " + SurfaceUtils.GetMethodDisplayName((string)Values[1]);
|
||||
UpdateSignature();
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
|
||||
if (scriptType == ScriptType.Null)
|
||||
return false;
|
||||
|
||||
var members = scriptType.GetMembers((string)nodeArch.DefaultValues[1], MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
|
||||
foreach (var member in members)
|
||||
{
|
||||
if (!SurfaceUtils.IsValidVisualScriptField(member))
|
||||
continue;
|
||||
|
||||
if (member)
|
||||
{
|
||||
if (!member.IsStatic && VisjectSurface.FullCastCheck(scriptType, outputType, hint))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var isStatic = (bool)nodeArch.DefaultValues[3];
|
||||
if (!isStatic && VisjectSurface.FullCastCheck(scriptType, outputType, hint))
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
|
||||
if (scriptType == ScriptType.Null)
|
||||
return false;
|
||||
|
||||
var members = scriptType.GetMembers((string)nodeArch.DefaultValues[1], MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
|
||||
foreach (var member in members)
|
||||
{
|
||||
if (!SurfaceUtils.IsValidVisualScriptField(member))
|
||||
continue;
|
||||
|
||||
if (member)
|
||||
{
|
||||
if (VisjectSurface.FullCastCheck(member.ValueType, inputType, hint))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var typeName = (string)nodeArch.DefaultValues[2];
|
||||
if (VisjectSurface.FullCastCheck(TypeUtils.GetType(typeName), inputType, hint))
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SetFieldNode : FieldNodeBase
|
||||
@@ -1959,13 +2085,55 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
Title = "Set " + SurfaceUtils.GetMethodDisplayName((string)Values[1]);
|
||||
UpdateSignature();
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
if (outputType.IsVoid)
|
||||
return true;
|
||||
|
||||
var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
|
||||
if (scriptType == ScriptType.Null)
|
||||
return false;
|
||||
|
||||
var members = scriptType.GetMembers((string)nodeArch.DefaultValues[1], MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
|
||||
foreach (var member in members)
|
||||
{
|
||||
if (!SurfaceUtils.IsValidVisualScriptField(member))
|
||||
continue;
|
||||
|
||||
if (member)
|
||||
{
|
||||
if (VisjectSurface.FullCastCheck(member.ValueType, outputType, hint))
|
||||
return true;
|
||||
if (!member.IsStatic && VisjectSurface.FullCastCheck(scriptType, outputType, hint))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var typeName = (string)nodeArch.DefaultValues[2];
|
||||
if (VisjectSurface.FullCastCheck(TypeUtils.GetType(typeName), outputType, hint))
|
||||
return true;
|
||||
var isStatic = (bool)nodeArch.DefaultValues[3];
|
||||
if (!isStatic && VisjectSurface.FullCastCheck(scriptType, outputType, hint))
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
return inputType.IsVoid;
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class EventBaseNode : SurfaceNode, IFunctionsDependantNode
|
||||
@@ -2135,9 +2303,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
// Find reflection information about event
|
||||
_signature = null;
|
||||
@@ -2184,6 +2352,43 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
// Event based nodes always have a pulse input, so it's always compatible with void
|
||||
if (outputType.IsVoid)
|
||||
return true;
|
||||
|
||||
var eventName = (string)nodeArch.DefaultValues[1];
|
||||
var eventType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
|
||||
var member = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
|
||||
if (member && SurfaceUtils.IsValidVisualScriptEvent(member))
|
||||
{
|
||||
if (!member.IsStatic)
|
||||
{
|
||||
if (VisjectSurface.FullCastCheck(eventType, outputType, hint))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
// Event based nodes always have a pulse output, so it's always compatible with void
|
||||
if (inputType.IsVoid)
|
||||
return true;
|
||||
|
||||
var eventName = (string)nodeArch.DefaultValues[1];
|
||||
var eventType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
|
||||
var member = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
|
||||
if (member && SurfaceUtils.IsValidVisualScriptEvent(member))
|
||||
{
|
||||
if (VisjectSurface.FullCastCheck(member.ValueType, inputType, hint))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class BindEventNode : EventBaseNode
|
||||
@@ -2193,11 +2398,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
Title = "Bind " + (string)Values[1];
|
||||
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2208,11 +2413,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
Title = "Unbind " + (string)Values[1];
|
||||
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2265,6 +2470,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Title = string.Empty,
|
||||
Description = "Overrides the base class method with custom implementation",
|
||||
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoSpawnViaPaste,
|
||||
IsInputCompatible = MethodOverrideNode.IsInputCompatible,
|
||||
IsOutputCompatible = MethodOverrideNode.IsOutputCompatible,
|
||||
Size = new Float2(240, 60),
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
@@ -2277,6 +2484,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
TypeID = 4,
|
||||
Create = (id, context, arch, groupArch) => new InvokeMethodNode(id, context, arch, groupArch),
|
||||
IsInputCompatible = InvokeMethodNode.IsInputCompatible,
|
||||
IsOutputCompatible = InvokeMethodNode.IsOutputCompatible,
|
||||
Title = string.Empty,
|
||||
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
|
||||
Size = new Float2(240, 60),
|
||||
@@ -2317,6 +2526,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
TypeID = 6,
|
||||
Create = (id, context, arch, groupArch) => new VisualScriptFunctionNode(id, context, arch, groupArch),
|
||||
IsInputCompatible = VisualScriptFunctionNode.IsInputCompatible,
|
||||
IsOutputCompatible = VisualScriptFunctionNode.IsOutputCompatible,
|
||||
Title = "New Function",
|
||||
Description = "Adds a new function to the script",
|
||||
Flags = NodeFlags.VisualScriptGraph,
|
||||
@@ -2330,6 +2541,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
TypeID = 7,
|
||||
Create = (id, context, arch, groupArch) => new GetFieldNode(id, context, arch, groupArch),
|
||||
IsInputCompatible = GetFieldNode.IsInputCompatible,
|
||||
IsOutputCompatible = GetFieldNode.IsOutputCompatible,
|
||||
Title = string.Empty,
|
||||
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
|
||||
Size = new Float2(240, 60),
|
||||
@@ -2345,6 +2558,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
TypeID = 8,
|
||||
Create = (id, context, arch, groupArch) => new SetFieldNode(id, context, arch, groupArch),
|
||||
IsInputCompatible = SetFieldNode.IsInputCompatible,
|
||||
IsOutputCompatible = SetFieldNode.IsOutputCompatible,
|
||||
Title = string.Empty,
|
||||
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
|
||||
Size = new Float2(240, 60),
|
||||
@@ -2361,6 +2576,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
TypeID = 9,
|
||||
Create = (id, context, arch, groupArch) => new BindEventNode(id, context, arch, groupArch),
|
||||
IsInputCompatible = EventBaseNode.IsInputCompatible,
|
||||
IsOutputCompatible = EventBaseNode.IsOutputCompatible,
|
||||
Title = string.Empty,
|
||||
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
|
||||
Size = new Float2(260, 60),
|
||||
@@ -2383,6 +2600,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
TypeID = 10,
|
||||
Create = (id, context, arch, groupArch) => new UnbindEventNode(id, context, arch, groupArch),
|
||||
IsInputCompatible = EventBaseNode.IsInputCompatible,
|
||||
IsOutputCompatible = EventBaseNode.IsOutputCompatible,
|
||||
Title = string.Empty,
|
||||
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
|
||||
Size = new Float2(260, 60),
|
||||
|
||||
@@ -197,9 +197,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
// Fix emissive box (it's a strange error)
|
||||
GetBox(3).CurrentType = new ScriptType(typeof(Float3));
|
||||
|
||||
@@ -30,9 +30,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
_in0 = (InputBox)GetBox(0);
|
||||
_in1 = (InputBox)GetBox(1);
|
||||
@@ -111,9 +111,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
// Update title and the tooltip
|
||||
var typeName = (string)Values[0];
|
||||
@@ -216,6 +216,35 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
: base(id, context, nodeArch, groupArch, false)
|
||||
{
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
var typeName = (string)nodeArch.DefaultValues[0];
|
||||
var type = TypeUtils.GetType(typeName);
|
||||
if (type)
|
||||
{
|
||||
var fields = type.GetMembers(BindingFlags.Public | BindingFlags.Instance).Where(x => x.IsField).ToArray();
|
||||
var fieldsLength = fields.Length;
|
||||
for (var i = 0; i < fieldsLength; i++)
|
||||
{
|
||||
if (VisjectSurface.FullCastCheck(fields[i].ValueType, outputType, hint))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
var typeName = (string)nodeArch.DefaultValues[0];
|
||||
var type = TypeUtils.GetType(typeName);
|
||||
if (type)
|
||||
{
|
||||
if (VisjectSurface.FullCastCheck(type, inputType, hint))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class UnpackStructureNode : StructureNode
|
||||
@@ -225,6 +254,35 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
: base(id, context, nodeArch, groupArch, true)
|
||||
{
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
var typeName = (string)nodeArch.DefaultValues[0];
|
||||
var type = TypeUtils.GetType(typeName);
|
||||
if (type)
|
||||
{
|
||||
if (VisjectSurface.FullCastCheck(type, outputType, hint))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
var typeName = (string)nodeArch.DefaultValues[0];
|
||||
var type = TypeUtils.GetType(typeName);
|
||||
if (type)
|
||||
{
|
||||
var fields = type.GetMembers(BindingFlags.Public | BindingFlags.Instance).Where(x => x.IsField).ToArray();
|
||||
var fieldsLength = fields.Length;
|
||||
for (var i = 0; i < fieldsLength; i++)
|
||||
{
|
||||
if (VisjectSurface.FullCastCheck(fields[i].ValueType, inputType, hint))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -351,6 +409,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TypeID = 26,
|
||||
Title = "Pack Structure",
|
||||
Create = (id, context, arch, groupArch) => new PackStructureNode(id, context, arch, groupArch),
|
||||
IsInputCompatible = PackStructureNode.IsInputCompatible,
|
||||
IsOutputCompatible = PackStructureNode.IsOutputCompatible,
|
||||
Description = "Makes the structure data to from the components.",
|
||||
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI,
|
||||
Size = new Float2(180, 20),
|
||||
@@ -461,6 +521,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TypeID = 36,
|
||||
Title = "Unpack Structure",
|
||||
Create = (id, context, arch, groupArch) => new UnpackStructureNode(id, context, arch, groupArch),
|
||||
IsInputCompatible = UnpackStructureNode.IsInputCompatible,
|
||||
IsOutputCompatible = UnpackStructureNode.IsOutputCompatible,
|
||||
Description = "Breaks the structure data to allow extracting components from it.",
|
||||
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI,
|
||||
Size = new Float2(180, 20),
|
||||
|
||||
@@ -426,9 +426,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
if (Surface != null)
|
||||
{
|
||||
@@ -438,9 +438,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateTitle();
|
||||
}
|
||||
@@ -490,6 +490,56 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
_combobox.Width = Width - 30;
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
if (inputType == ScriptType.Object)
|
||||
return true;
|
||||
|
||||
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
|
||||
ScriptType type = parameter?.Type ?? ScriptType.Null;
|
||||
|
||||
if (parameter == null || type == ScriptType.Null || parameter.Type.Type == null)
|
||||
return false;
|
||||
if (DefaultPrototypes == null || !DefaultPrototypes.TryGetValue(parameter.Type.Type, out var elements))
|
||||
{
|
||||
return VisjectSurface.FullCastCheck(inputType, type, hint);
|
||||
}
|
||||
if (elements == null)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < elements.Length; i++)
|
||||
{
|
||||
if(elements[i].Type != NodeElementType.Output)
|
||||
continue;
|
||||
|
||||
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, inputType, hint))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
|
||||
ScriptType type = parameter?.Type ?? ScriptType.Null;
|
||||
|
||||
if (parameter == null || type == ScriptType.Null)
|
||||
return false;
|
||||
if (parameter.Type.Type == null || DefaultPrototypes == null || !DefaultPrototypes.TryGetValue(parameter.Type.Type, out var elements))
|
||||
return false;
|
||||
if (elements == null)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < elements.Length; i++)
|
||||
{
|
||||
if(elements[i].Type != NodeElementType.Input)
|
||||
continue;
|
||||
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, outputType, hint))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -675,6 +725,53 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool UseNormalMaps => false;
|
||||
|
||||
internal new static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
if (inputType == ScriptType.Object)
|
||||
return true;
|
||||
|
||||
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
|
||||
ScriptType type = parameter?.Type ?? ScriptType.Null;
|
||||
|
||||
if (parameter == null || type == ScriptType.Null)
|
||||
return false;
|
||||
if (parameter.Type.Type == null || DefaultPrototypesParticleEmitter == null || !DefaultPrototypesParticleEmitter.TryGetValue(parameter.Type.Type, out var elements))
|
||||
return false;
|
||||
if (elements == null)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < elements.Length; i++)
|
||||
{
|
||||
if(elements[i].Type != NodeElementType.Output)
|
||||
continue;
|
||||
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, inputType, hint))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal new static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
|
||||
ScriptType type = parameter?.Type ?? ScriptType.Null;
|
||||
|
||||
if (parameter == null || type == ScriptType.Null)
|
||||
return false;
|
||||
if (parameter.Type.Type == null || DefaultPrototypesParticleEmitter == null || !DefaultPrototypesParticleEmitter.TryGetValue(parameter.Type.Type, out var elements))
|
||||
return false;
|
||||
if (elements == null)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < elements.Length; i++)
|
||||
{
|
||||
if(elements[i].Type != NodeElementType.Input)
|
||||
continue;
|
||||
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, outputType, hint))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -692,6 +789,22 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool UseNormalMaps => false;
|
||||
|
||||
internal new static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
if (inputType == ScriptType.Object)
|
||||
return true;
|
||||
|
||||
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
|
||||
ScriptType type = parameter?.Type ?? ScriptType.Null;
|
||||
|
||||
return VisjectSurface.FullCastCheck(inputType, type, hint);
|
||||
}
|
||||
|
||||
internal new static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -832,9 +945,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
if (Surface != null)
|
||||
UpdateCombo();
|
||||
@@ -842,9 +955,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
if (Surface != null)
|
||||
{
|
||||
@@ -874,6 +987,22 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
_combobox.Width = Width - 50;
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
return inputType == ScriptType.Void;
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
if (outputType == ScriptType.Void)
|
||||
return true;
|
||||
|
||||
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
|
||||
ScriptType type = parameter?.Type ?? ScriptType.Null;
|
||||
|
||||
return VisjectSurface.FullCastCheck(outputType, type, hint);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -885,6 +1014,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
TypeID = 1,
|
||||
Create = (id, context, arch, groupArch) => new SurfaceNodeParamsGet(id, context, arch, groupArch),
|
||||
IsInputCompatible = SurfaceNodeParamsGet.IsInputCompatible,
|
||||
IsOutputCompatible = SurfaceNodeParamsGet.IsOutputCompatible,
|
||||
Title = "Get Parameter",
|
||||
Description = "Parameter value getter",
|
||||
Flags = NodeFlags.MaterialGraph | NodeFlags.AnimGraph,
|
||||
@@ -902,6 +1033,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
TypeID = 2,
|
||||
Create = (id, context, arch, groupArch) => new SurfaceNodeParamsGetParticleEmitter(id, context, arch, groupArch),
|
||||
IsInputCompatible = SurfaceNodeParamsGetParticleEmitter.IsInputCompatible,
|
||||
IsOutputCompatible = SurfaceNodeParamsGetParticleEmitter.IsOutputCompatible,
|
||||
Title = "Get Parameter",
|
||||
Description = "Parameter value getter",
|
||||
Flags = NodeFlags.ParticleEmitterGraph,
|
||||
@@ -919,6 +1052,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
TypeID = 3,
|
||||
Create = (id, context, arch, groupArch) => new SurfaceNodeParamsGetVisualScript(id, context, arch, groupArch),
|
||||
IsInputCompatible = SurfaceNodeParamsGetVisualScript.IsInputCompatible,
|
||||
IsOutputCompatible = SurfaceNodeParamsGetVisualScript.IsOutputCompatible,
|
||||
Title = "Get Parameter",
|
||||
Description = "Parameter value getter",
|
||||
Flags = NodeFlags.VisualScriptGraph,
|
||||
@@ -936,6 +1071,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
TypeID = 4,
|
||||
Create = (id, context, arch, groupArch) => new SurfaceNodeParamsSet(id, context, arch, groupArch),
|
||||
IsInputCompatible = SurfaceNodeParamsSet.IsInputCompatible,
|
||||
IsOutputCompatible = SurfaceNodeParamsSet.IsOutputCompatible,
|
||||
Title = "Set Parameter",
|
||||
Description = "Parameter value setter",
|
||||
Flags = NodeFlags.VisualScriptGraph,
|
||||
|
||||
@@ -268,20 +268,20 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
_enabled.Checked = ModuleEnabled;
|
||||
_enabled.StateChanged += OnEnabledStateChanged;
|
||||
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
ParticleSurface?.ArrangeModulesNodes();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
ParticleSurface.ArrangeModulesNodes();
|
||||
}
|
||||
@@ -295,11 +295,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDeleted()
|
||||
public override void OnDeleted(SurfaceNodeActions action)
|
||||
{
|
||||
ParticleSurface.ArrangeModulesNodes();
|
||||
|
||||
base.OnDeleted();
|
||||
base.OnDeleted(action);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -324,9 +324,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateOutputBoxType();
|
||||
}
|
||||
@@ -381,9 +381,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateInputBox();
|
||||
}
|
||||
@@ -416,9 +416,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateTextBox();
|
||||
}
|
||||
|
||||
@@ -214,9 +214,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
if (Surface == null)
|
||||
return;
|
||||
@@ -265,9 +265,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateOutputBoxType();
|
||||
}
|
||||
|
||||
@@ -38,9 +38,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
@@ -193,9 +193,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
var upperLeft = GetBox(0).BottomLeft;
|
||||
var upperRight = GetBox(1).BottomRight;
|
||||
@@ -477,9 +477,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
var upperLeft = GetBox(0).BottomLeft;
|
||||
var upperRight = GetBox(1).BottomRight;
|
||||
@@ -657,9 +657,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
UpdateCombo();
|
||||
}
|
||||
@@ -682,9 +682,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
var type = ScriptType.Null;
|
||||
if (Context.Surface is VisualScriptSurface visjectSurface)
|
||||
@@ -710,9 +710,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
UpdateOutputBox();
|
||||
}
|
||||
@@ -763,9 +763,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
UpdateOutputBox();
|
||||
}
|
||||
@@ -787,7 +787,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
}
|
||||
|
||||
private class AsNode : SurfaceNode
|
||||
internal class AsNode : SurfaceNode
|
||||
{
|
||||
private TypePickerControl _picker;
|
||||
|
||||
@@ -822,9 +822,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
if (Surface != null)
|
||||
_picker.ValueTypeName = (string)Values[0];
|
||||
@@ -838,6 +838,15 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
box.CurrentType = type ? type : ScriptType.FlaxObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the type of the picker and the type of the output box
|
||||
/// </summary>
|
||||
/// <param name="type">Target Type</param>
|
||||
public void SetPickerValue(ScriptType type)
|
||||
{
|
||||
_picker.Value = type;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
@@ -881,9 +890,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
if (Surface != null)
|
||||
_picker.ValueTypeName = (string)Values[0];
|
||||
@@ -932,9 +941,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
if (Surface != null)
|
||||
_picker.ValueTypeName = (string)Values[0];
|
||||
@@ -984,9 +993,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
if (Surface != null)
|
||||
_picker.ValueTypeName = (string)Values[0];
|
||||
@@ -999,6 +1008,15 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
GetBox(4).CurrentType = type ? type : _picker.Type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the type of the picker and the type of the output box
|
||||
/// </summary>
|
||||
/// <param name="type">Target Type</param>
|
||||
public void SetPickerValue(ScriptType type)
|
||||
{
|
||||
_picker.Value = type;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
@@ -1047,9 +1065,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
_input = (InputBox)GetBox(0);
|
||||
_output = (OutputBox)GetBox(1);
|
||||
@@ -1237,7 +1255,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <inheritdoc />
|
||||
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
{
|
||||
OutputBox.DrawConnection(ref startPos, ref endPos, ref color, 2);
|
||||
OutputBox.DrawConnection(Surface.Style, ref startPos, ref endPos, ref color, 2);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
199
Source/Editor/Surface/BehaviorTreeSurface.cs
Normal file
199
Source/Editor/Surface/BehaviorTreeSurface.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface.ContextMenu;
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Surface
|
||||
{
|
||||
/// <summary>
|
||||
/// The Visject Surface implementation for the Behavior Tree graphs.
|
||||
/// </summary>
|
||||
/// <seealso cref="VisjectSurface" />
|
||||
[HideInEditor]
|
||||
public class BehaviorTreeSurface : VisjectSurface
|
||||
{
|
||||
private static NodesCache _nodesCache = new NodesCache(IterateNodesCache);
|
||||
|
||||
/// <inheritdoc />
|
||||
public BehaviorTreeSurface(IVisjectSurfaceOwner owner, Action onSave, FlaxEditor.Undo undo)
|
||||
: base(owner, onSave, undo, CreateStyle())
|
||||
{
|
||||
}
|
||||
|
||||
private static SurfaceStyle CreateStyle()
|
||||
{
|
||||
var editor = Editor.Instance;
|
||||
var style = SurfaceStyle.CreateStyleHandler(editor);
|
||||
style.DrawBox = DrawBox;
|
||||
style.DrawConnection = DrawConnection;
|
||||
return style;
|
||||
}
|
||||
|
||||
private static void DrawBox(Box box)
|
||||
{
|
||||
var rect = new Rectangle(Float2.Zero, box.Size);
|
||||
const float minBoxSize = 5.0f;
|
||||
if (rect.Size.LengthSquared < minBoxSize * minBoxSize)
|
||||
return;
|
||||
|
||||
var style = FlaxEngine.GUI.Style.Current;
|
||||
var color = style.LightBackground;
|
||||
if (box.IsMouseOver)
|
||||
color *= 1.2f;
|
||||
Render2D.FillRectangle(rect, color);
|
||||
}
|
||||
|
||||
private static void DrawConnection(Float2 start, Float2 end, Color color, float thickness)
|
||||
{
|
||||
Archetypes.Animation.StateMachineStateBase.DrawConnection(ref start, ref end, ref color);
|
||||
}
|
||||
|
||||
private void OnActiveContextMenuVisibleChanged(Control activeCM)
|
||||
{
|
||||
_nodesCache.Wait();
|
||||
}
|
||||
|
||||
private static void IterateNodesCache(ScriptType scriptType, Dictionary<KeyValuePair<string, ushort>, GroupArchetype> cache, int version)
|
||||
{
|
||||
// Filter by BT node types only
|
||||
if (!new ScriptType(typeof(BehaviorTreeNode)).IsAssignableFrom(scriptType))
|
||||
return;
|
||||
|
||||
// Skip in-built types
|
||||
if (scriptType == typeof(BehaviorTreeNode) ||
|
||||
scriptType == typeof(BehaviorTreeCompoundNode) ||
|
||||
scriptType == typeof(BehaviorTreeRootNode))
|
||||
return;
|
||||
|
||||
// Nodes-only
|
||||
if (new ScriptType(typeof(BehaviorTreeDecorator)).IsAssignableFrom(scriptType))
|
||||
return;
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>("Behavior Tree", 19);
|
||||
if (!cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(70, 220, 181),
|
||||
Tag = version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Create node archetype
|
||||
var node = (NodeArchetype)Archetypes.BehaviorTree.Nodes[0].Clone();
|
||||
node.DefaultValues[0] = scriptType.TypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = Archetypes.BehaviorTree.Node.GetTitle(scriptType);
|
||||
node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox)
|
||||
{
|
||||
activeCM.ShowExpanded = true;
|
||||
_nodesCache.Get(activeCM);
|
||||
|
||||
base.OnShowPrimaryMenu(activeCM, location, startBox);
|
||||
|
||||
activeCM.VisibleChanged += OnActiveContextMenuVisibleChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetTypeName(ScriptType type)
|
||||
{
|
||||
if (type == ScriptType.Void)
|
||||
return string.Empty; // Remove `Void` tooltip from connection areas
|
||||
return base.GetTypeName(type);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Load()
|
||||
{
|
||||
if (base.Load())
|
||||
return true;
|
||||
|
||||
// Ensure to have Root node created (UI blocks spawning of it)
|
||||
if (RootContext.FindNode(19, 2) == null)
|
||||
{
|
||||
RootContext.SpawnNode(19, 2, Float2.Zero);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
// Comments
|
||||
if (groupArchetype.GroupID == 7 && nodeArchetype.TypeID == 11)
|
||||
return true;
|
||||
|
||||
// Single root node
|
||||
if (groupArchetype.GroupID == 19 && nodeArchetype.TypeID == 2 && RootContext.FindNode(19, 2) != null)
|
||||
return false;
|
||||
|
||||
// Behavior Tree nodes only
|
||||
return (nodeArchetype.Flags & NodeFlags.BehaviorTreeGraph) != 0 &&
|
||||
groupArchetype.GroupID == 19 &&
|
||||
base.CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool ValidateDragItem(AssetItem assetItem)
|
||||
{
|
||||
if (assetItem.IsOfType<BehaviorTree>())
|
||||
return true;
|
||||
return base.ValidateDragItem(assetItem);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void HandleDragDropAssets(List<AssetItem> objects, DragDropEventArgs args)
|
||||
{
|
||||
for (int i = 0; i < objects.Count; i++)
|
||||
{
|
||||
var assetItem = objects[i];
|
||||
SurfaceNode node = null;
|
||||
|
||||
if (assetItem.IsOfType<BehaviorTree>())
|
||||
{
|
||||
var instance = new BehaviorTreeSubTreeNode();
|
||||
instance.Name = Utilities.Utils.GetPropertyNameUI(assetItem.ShortName);
|
||||
instance.Tree = (BehaviorTree)assetItem.LoadAsync();
|
||||
node = Context.SpawnNode(19, 1, args.SurfaceLocation, new object[]
|
||||
{
|
||||
typeof(BehaviorTreeSubTreeNode).FullName,
|
||||
FlaxEngine.Json.JsonSerializer.SaveToBytes(instance),
|
||||
null,
|
||||
});
|
||||
FlaxEngine.Object.Destroy(instance);
|
||||
}
|
||||
|
||||
if (node != null)
|
||||
args.SurfaceLocation.X += node.Width + 10;
|
||||
}
|
||||
base.HandleDragDropAssets(objects, args);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (IsDisposing)
|
||||
return;
|
||||
_nodesCache.Wait();
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Utilities;
|
||||
@@ -29,9 +28,10 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
/// <summary>
|
||||
/// Visject Surface node archetype spawn ability checking delegate.
|
||||
/// </summary>
|
||||
/// <param name="groupArch">The nodes group archetype to check.</param>
|
||||
/// <param name="arch">The node archetype to check.</param>
|
||||
/// <returns>True if can use this node to spawn it on a surface, otherwise false..</returns>
|
||||
public delegate bool NodeSpawnCheckDelegate(NodeArchetype arch);
|
||||
public delegate bool NodeSpawnCheckDelegate(GroupArchetype groupArch, NodeArchetype arch);
|
||||
|
||||
/// <summary>
|
||||
/// Visject Surface parameters getter delegate.
|
||||
@@ -40,6 +40,8 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
public delegate List<SurfaceParameter> ParameterGetterDelegate();
|
||||
|
||||
private readonly List<VisjectCMGroup> _groups = new List<VisjectCMGroup>(16);
|
||||
private CheckBox _contextSensitiveToggle;
|
||||
private bool _contextSensitiveSearchEnabled = true;
|
||||
private readonly TextBox _searchBox;
|
||||
private bool _waitingForInput;
|
||||
private VisjectCMGroup _surfaceParametersGroup;
|
||||
@@ -127,7 +129,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
_parameterSetNodeArchetype = info.ParameterSetNodeArchetype ?? Archetypes.Parameters.Nodes[3];
|
||||
|
||||
// Context menu dimensions
|
||||
Size = new Float2(320, 248);
|
||||
Size = new Float2(300, 400);
|
||||
|
||||
var headerPanel = new Panel(ScrollBars.None)
|
||||
{
|
||||
@@ -139,17 +141,41 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
};
|
||||
|
||||
// Title bar
|
||||
var titleFontReference = new FontReference(Style.Current.FontLarge.Asset, 10);
|
||||
var titleLabel = new Label
|
||||
{
|
||||
Width = Width - 8,
|
||||
Width = Width * 0.5f - 8f,
|
||||
Height = 20,
|
||||
X = 4,
|
||||
Parent = headerPanel,
|
||||
Text = "Select Node",
|
||||
HorizontalAlignment = TextAlignment.Center,
|
||||
Font = new FontReference(Style.Current.FontLarge.Asset, 10),
|
||||
HorizontalAlignment = TextAlignment.Near,
|
||||
Font = titleFontReference,
|
||||
};
|
||||
|
||||
// Context sensitive toggle
|
||||
var contextSensitiveLabel = new Label
|
||||
{
|
||||
Width = Width * 0.5f - 28,
|
||||
Height = 20,
|
||||
X = Width * 0.5f,
|
||||
Parent = headerPanel,
|
||||
Text = "Context Sensitive",
|
||||
TooltipText = "Should the nodes be filtered to only show those that can be connected in the current context?",
|
||||
HorizontalAlignment = TextAlignment.Far,
|
||||
Font = titleFontReference,
|
||||
};
|
||||
|
||||
_contextSensitiveToggle = new CheckBox
|
||||
{
|
||||
Width = 20,
|
||||
Height = 20,
|
||||
X = Width - 24,
|
||||
Parent = headerPanel,
|
||||
Checked = _contextSensitiveSearchEnabled,
|
||||
};
|
||||
_contextSensitiveToggle.StateChanged += OnContextSensitiveToggleStateChanged;
|
||||
|
||||
// Search box
|
||||
_searchBox = new SearchBox(false, 2, 22)
|
||||
{
|
||||
@@ -184,7 +210,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
nodes.Clear();
|
||||
foreach (var nodeArchetype in groupArchetype.Archetypes)
|
||||
{
|
||||
if ((nodeArchetype.Flags & NodeFlags.NoSpawnViaGUI) == 0 && info.CanSpawnNode(nodeArchetype))
|
||||
if ((nodeArchetype.Flags & NodeFlags.NoSpawnViaGUI) == 0 && info.CanSpawnNode(groupArchetype, nodeArchetype))
|
||||
{
|
||||
nodes.Add(nodeArchetype);
|
||||
}
|
||||
@@ -282,12 +308,18 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
{
|
||||
group.UnlockChildrenRecursive();
|
||||
SortGroups();
|
||||
if (ShowExpanded)
|
||||
group.Open(false);
|
||||
group.PerformLayout();
|
||||
if (_searchBox.TextLength != 0)
|
||||
{
|
||||
OnSearchFilterChanged();
|
||||
}
|
||||
}
|
||||
else if (_contextSensitiveSearchEnabled)
|
||||
{
|
||||
group.EvaluateVisibilityWithBox(_selectedBox);
|
||||
}
|
||||
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
@@ -321,7 +353,11 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
Parent = group
|
||||
};
|
||||
}
|
||||
if (_contextSensitiveSearchEnabled)
|
||||
group.EvaluateVisibilityWithBox(_selectedBox);
|
||||
group.SortChildren();
|
||||
if (ShowExpanded)
|
||||
group.Open(false);
|
||||
group.Parent = _groupsPanel;
|
||||
_groups.Add(group);
|
||||
|
||||
@@ -418,8 +454,26 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
return;
|
||||
|
||||
Profiler.BeginEvent("VisjectCM.OnSearchFilterChanged");
|
||||
|
||||
if (string.IsNullOrEmpty(_searchBox.Text))
|
||||
UpdateFilters();
|
||||
_searchBox.Focus();
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
|
||||
private void OnContextSensitiveToggleStateChanged(CheckBox checkBox)
|
||||
{
|
||||
// Skip events during setup or init stuff
|
||||
if (IsLayoutLocked)
|
||||
return;
|
||||
|
||||
Profiler.BeginEvent("VisjectCM.OnContextSensitiveToggleStateChanged");
|
||||
_contextSensitiveSearchEnabled = checkBox.Checked;
|
||||
UpdateFilters();
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
|
||||
private void UpdateFilters()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBox == null)
|
||||
{
|
||||
ResetView();
|
||||
Profiler.EndEvent();
|
||||
@@ -430,7 +484,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
LockChildrenRecursive();
|
||||
for (int i = 0; i < _groups.Count; i++)
|
||||
{
|
||||
_groups[i].UpdateFilter(_searchBox.Text);
|
||||
_groups[i].UpdateFilter(_searchBox.Text, _contextSensitiveSearchEnabled ? _selectedBox : null);
|
||||
_groups[i].UpdateItemSort(_selectedBox);
|
||||
}
|
||||
SortGroups();
|
||||
@@ -443,9 +497,6 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
PerformLayout();
|
||||
if (SelectedItem != null)
|
||||
_panel1.ScrollViewTo(SelectedItem);
|
||||
_searchBox.Focus();
|
||||
Profiler.EndEvent();
|
||||
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
|
||||
@@ -503,7 +554,11 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
_searchBox.Clear();
|
||||
SelectedItem = null;
|
||||
for (int i = 0; i < _groups.Count; i++)
|
||||
{
|
||||
_groups[i].ResetView();
|
||||
if (_contextSensitiveSearchEnabled)
|
||||
_groups[i].EvaluateVisibilityWithBox(_selectedBox);
|
||||
}
|
||||
UnlockChildrenRecursive();
|
||||
|
||||
SortGroups();
|
||||
@@ -568,7 +623,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
Archetypes = archetypes
|
||||
};
|
||||
|
||||
var group = CreateGroup(groupArchetype);
|
||||
var group = CreateGroup(groupArchetype, false);
|
||||
group.ArrowImageOpened = new SpriteBrush(Style.Current.ArrowDown);
|
||||
group.ArrowImageClosed = new SpriteBrush(Style.Current.ArrowRight);
|
||||
group.Close(false);
|
||||
@@ -671,9 +726,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
SelectedItem = previousSelectedItem;
|
||||
|
||||
// Scroll into view (without smoothing)
|
||||
_panel1.VScrollBar.SmoothingScale = 0;
|
||||
_panel1.ScrollViewTo(SelectedItem);
|
||||
_panel1.VScrollBar.SmoothingScale = 1;
|
||||
_panel1.ScrollViewTo(SelectedItem, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -759,5 +812,12 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
{
|
||||
return GetPreviousSiblings(item).OfType<T>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
_contextSensitiveToggle.StateChanged -= OnContextSensitiveToggleStateChanged;
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
{
|
||||
if (_children[i] is VisjectCMItem item)
|
||||
{
|
||||
item.UpdateFilter(null);
|
||||
item.UpdateFilter(null, null);
|
||||
item.UpdateScore(null);
|
||||
}
|
||||
}
|
||||
@@ -84,23 +84,42 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
/// Updates the filter.
|
||||
/// </summary>
|
||||
/// <param name="filterText">The filter text.</param>
|
||||
public void UpdateFilter(string filterText)
|
||||
/// <param name="selectedBox">The optionally selected box to show hints for it.</param>
|
||||
public void UpdateFilter(string filterText, Box selectedBox)
|
||||
{
|
||||
Profiler.BeginEvent("VisjectCMGroup.UpdateFilter");
|
||||
|
||||
// Update items
|
||||
bool isAnyVisible = false;
|
||||
bool groupHeaderMatches = QueryFilterHelper.Match(filterText, HeaderText);
|
||||
for (int i = 0; i < _children.Count; i++)
|
||||
{
|
||||
if (_children[i] is VisjectCMItem item)
|
||||
{
|
||||
item.UpdateFilter(filterText);
|
||||
item.UpdateFilter(filterText, selectedBox, groupHeaderMatches);
|
||||
isAnyVisible |= item.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
// Update header title
|
||||
if (QueryFilterHelper.Match(filterText, HeaderText))
|
||||
// Update itself
|
||||
if (isAnyVisible)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(filterText))
|
||||
Open(false);
|
||||
Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hide group if none of the items matched the filter
|
||||
Visible = false;
|
||||
}
|
||||
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
|
||||
internal void EvaluateVisibilityWithBox(Box selectedBox)
|
||||
{
|
||||
if (selectedBox == null)
|
||||
{
|
||||
for (int i = 0; i < _children.Count; i++)
|
||||
{
|
||||
@@ -109,14 +128,25 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
item.Visible = true;
|
||||
}
|
||||
}
|
||||
isAnyVisible = true;
|
||||
Visible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Profiler.BeginEvent("VisjectCMGroup.EvaluateVisibilityWithBox");
|
||||
|
||||
bool isAnyVisible = false;
|
||||
for (int i = 0; i < _children.Count; i++)
|
||||
{
|
||||
if (_children[i] is VisjectCMItem item)
|
||||
{
|
||||
item.Visible = item.CanConnectTo(selectedBox);
|
||||
isAnyVisible |= item.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
// Update itself
|
||||
if (isAnyVisible)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(filterText))
|
||||
Open(false);
|
||||
Visible = true;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine;
|
||||
@@ -56,7 +57,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
/// <param name="groupArchetype">The group archetype.</param>
|
||||
/// <param name="archetype">The archetype.</param>
|
||||
public VisjectCMItem(VisjectCMGroup group, GroupArchetype groupArchetype, NodeArchetype archetype)
|
||||
: base(0, 0, 120, 12)
|
||||
: base(0, 0, 120, 14)
|
||||
{
|
||||
Group = group;
|
||||
_groupArchetype = groupArchetype;
|
||||
@@ -77,7 +78,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
if (!Visible)
|
||||
return;
|
||||
|
||||
if (selectedBox != null && CanConnectTo(selectedBox, NodeArchetype))
|
||||
if (selectedBox != null && CanConnectTo(selectedBox))
|
||||
SortScore += 1;
|
||||
if (Data != null)
|
||||
SortScore += 1;
|
||||
@@ -92,35 +93,93 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
textRect = new Rectangle(22, 0, Width - 24, Height);
|
||||
}
|
||||
|
||||
private bool CanConnectTo(Box startBox, NodeArchetype nodeArchetype)
|
||||
/// <summary>
|
||||
/// Checks if this context menu item can be connected to a given box, before a node is actually spawned.
|
||||
/// </summary>
|
||||
/// <param name="startBox">The connected box</param>
|
||||
/// <returns>True if the connected box is compatible with this item</returns>
|
||||
public bool CanConnectTo(Box startBox)
|
||||
{
|
||||
// Is compatible if box is null for reset reasons
|
||||
if (startBox == null)
|
||||
return false;
|
||||
if (!startBox.IsOutput)
|
||||
return false; // For now, I'm only handing the output box case
|
||||
return true;
|
||||
|
||||
if (nodeArchetype.Elements != null)
|
||||
if (_archetype == null)
|
||||
return false;
|
||||
|
||||
bool isCompatible = false;
|
||||
if (startBox.IsOutput && _archetype.IsInputCompatible != null)
|
||||
{
|
||||
for (int i = 0; i < nodeArchetype.Elements.Length; i++)
|
||||
{
|
||||
if (nodeArchetype.Elements[i].Type == NodeElementType.Input &&
|
||||
startBox.CanUseType(nodeArchetype.Elements[i].ConnectionsType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
isCompatible |= _archetype.IsInputCompatible.Invoke(_archetype, startBox.CurrentType, _archetype.ConnectionsHints, startBox.ParentNode.Context);
|
||||
}
|
||||
return false;
|
||||
else if (!startBox.IsOutput && _archetype.IsOutputCompatible != null)
|
||||
{
|
||||
isCompatible |= _archetype.IsOutputCompatible.Invoke(_archetype, startBox.CurrentType, startBox.ParentNode.Archetype.ConnectionsHints, startBox.ParentNode.Context);
|
||||
}
|
||||
else if (_archetype.Elements != null)
|
||||
{
|
||||
// Check compatibility based on the defined elements in the archetype. This handles all the default groups and items
|
||||
isCompatible = CheckElementsCompatibility(startBox);
|
||||
}
|
||||
|
||||
return isCompatible;
|
||||
}
|
||||
|
||||
private bool CheckElementsCompatibility(Box startBox)
|
||||
{
|
||||
bool isCompatible = false;
|
||||
foreach (NodeElementArchetype element in _archetype.Elements)
|
||||
{
|
||||
// Ignore all elements that aren't inputs or outputs (e.g. input fields)
|
||||
if (element.Type != NodeElementType.Output && element.Type != NodeElementType.Input)
|
||||
continue;
|
||||
|
||||
// Ignore elements with the same direction as the box
|
||||
if ((startBox.IsOutput && element.Type == NodeElementType.Output) || (!startBox.IsOutput && element.Type == NodeElementType.Input))
|
||||
continue;
|
||||
|
||||
ScriptType fromType;
|
||||
ScriptType toType;
|
||||
ConnectionsHint hint;
|
||||
if (startBox.IsOutput)
|
||||
{
|
||||
fromType = element.ConnectionsType;
|
||||
toType = startBox.CurrentType;
|
||||
hint = _archetype.ConnectionsHints;
|
||||
}
|
||||
else
|
||||
{
|
||||
fromType = startBox.CurrentType;
|
||||
toType = element.ConnectionsType;
|
||||
hint = startBox.ParentNode.Archetype.ConnectionsHints;
|
||||
}
|
||||
|
||||
isCompatible |= VisjectSurface.FullCastCheck(fromType, toType, hint);
|
||||
}
|
||||
|
||||
return isCompatible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the filter.
|
||||
/// </summary>
|
||||
/// <param name="filterText">The filter text.</param>
|
||||
public void UpdateFilter(string filterText)
|
||||
/// <param name="selectedBox">The optionally selected box to show hints for it.</param>
|
||||
/// <param name="groupHeaderMatches">True if item's group header got a filter match and item should stay visible.</param>
|
||||
public void UpdateFilter(string filterText, Box selectedBox, bool groupHeaderMatches = false)
|
||||
{
|
||||
if (selectedBox != null)
|
||||
{
|
||||
Visible = CanConnectTo(selectedBox);
|
||||
if (!Visible)
|
||||
{
|
||||
_highlights?.Clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_isStartsWithMatch = _isFullMatch = false;
|
||||
if (filterText == null)
|
||||
if (string.IsNullOrEmpty(filterText))
|
||||
{
|
||||
// Clear filter
|
||||
_highlights?.Clear();
|
||||
@@ -184,7 +243,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
|
||||
Data = data;
|
||||
}
|
||||
else
|
||||
else if (!groupHeaderMatches)
|
||||
{
|
||||
// Hide
|
||||
_highlights?.Clear();
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace FlaxEditor.Surface.Elements
|
||||
_currentType = value;
|
||||
|
||||
// Check if will need to update box connections due to type change
|
||||
if ((Surface == null || Surface._isUpdatingBoxTypes == 0) && HasAnyConnection && !CanCast(prev, _currentType))
|
||||
if ((Surface == null || Surface._isUpdatingBoxTypes == 0) && HasAnyConnection && !prev.CanCastTo(_currentType))
|
||||
{
|
||||
// Remove all invalid connections and update those which still can be valid
|
||||
var connections = Connections.ToArray();
|
||||
@@ -133,6 +133,11 @@ namespace FlaxEditor.Surface.Elements
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cached color for <see cref="CurrentType"/>.
|
||||
/// </summary>
|
||||
public Color CurrentTypeColor => _currentTypeColor;
|
||||
|
||||
/// <summary>
|
||||
/// The collection of the attributes used by the box. Assigned externally. Can be used to control the default value editing for the <see cref="InputBox"/> or to provide more metadata for the surface UI.
|
||||
/// </summary>
|
||||
@@ -231,58 +236,8 @@ namespace FlaxEditor.Surface.Elements
|
||||
}
|
||||
|
||||
// Check using connection hints
|
||||
var connectionsHints = ParentNode.Archetype.ConnectionsHints;
|
||||
if (Archetype.ConnectionsType == ScriptType.Null && connectionsHints != ConnectionsHint.None)
|
||||
{
|
||||
if ((connectionsHints & ConnectionsHint.Anything) == ConnectionsHint.Anything)
|
||||
return true;
|
||||
if ((connectionsHints & ConnectionsHint.Value) == ConnectionsHint.Value && type.Type != typeof(void))
|
||||
return true;
|
||||
if ((connectionsHints & ConnectionsHint.Enum) == ConnectionsHint.Enum && type.IsEnum)
|
||||
return true;
|
||||
if ((connectionsHints & ConnectionsHint.Array) == ConnectionsHint.Array && type.IsArray)
|
||||
return true;
|
||||
if ((connectionsHints & ConnectionsHint.Dictionary) == ConnectionsHint.Dictionary && type.IsDictionary)
|
||||
return true;
|
||||
if ((connectionsHints & ConnectionsHint.Vector) == ConnectionsHint.Vector)
|
||||
{
|
||||
var t = type.Type;
|
||||
if (t == typeof(Vector2) ||
|
||||
t == typeof(Vector3) ||
|
||||
t == typeof(Vector4) ||
|
||||
t == typeof(Float2) ||
|
||||
t == typeof(Float3) ||
|
||||
t == typeof(Float4) ||
|
||||
t == typeof(Double2) ||
|
||||
t == typeof(Double3) ||
|
||||
t == typeof(Double4) ||
|
||||
t == typeof(Int2) ||
|
||||
t == typeof(Int3) ||
|
||||
t == typeof(Int4) ||
|
||||
t == typeof(Color))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ((connectionsHints & ConnectionsHint.Scalar) == ConnectionsHint.Scalar)
|
||||
{
|
||||
var t = type.Type;
|
||||
if (t == typeof(bool) ||
|
||||
t == typeof(char) ||
|
||||
t == typeof(byte) ||
|
||||
t == typeof(short) ||
|
||||
t == typeof(ushort) ||
|
||||
t == typeof(int) ||
|
||||
t == typeof(uint) ||
|
||||
t == typeof(long) ||
|
||||
t == typeof(ulong) ||
|
||||
t == typeof(float) ||
|
||||
t == typeof(double))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (VisjectSurface.IsTypeCompatible(Archetype.ConnectionsType, type, ParentNode.Archetype.ConnectionsHints))
|
||||
return true;
|
||||
|
||||
// Check independent and if there is box with bigger potential because it may block current one from changing type
|
||||
var parentArch = ParentNode.Archetype;
|
||||
@@ -296,7 +251,7 @@ namespace FlaxEditor.Surface.Elements
|
||||
var b = ParentNode.GetBox(boxes[i]);
|
||||
|
||||
// Check if its the same and tested type matches the default value type
|
||||
if (b == this && CanCast(parentArch.DefaultType, type))
|
||||
if (b == this && parentArch.DefaultType.CanCastTo(type))
|
||||
{
|
||||
// Can
|
||||
return true;
|
||||
@@ -544,44 +499,6 @@ namespace FlaxEditor.Surface.Elements
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the box GUI using <see cref="Render2D"/>.
|
||||
/// </summary>
|
||||
protected void DrawBox()
|
||||
{
|
||||
var rect = new Rectangle(Float2.Zero, Size);
|
||||
|
||||
// Size culling
|
||||
const float minBoxSize = 5.0f;
|
||||
if (rect.Size.LengthSquared < minBoxSize * minBoxSize)
|
||||
return;
|
||||
|
||||
// Debugging boxes size
|
||||
//Render2D.DrawRectangle(rect, Color.Orange); return;
|
||||
|
||||
// Draw icon
|
||||
bool hasConnections = HasAnyConnection;
|
||||
float alpha = Enabled ? 1.0f : 0.6f;
|
||||
Color color = _currentTypeColor * alpha;
|
||||
var style = Surface.Style;
|
||||
SpriteHandle icon;
|
||||
if (_currentType.Type == typeof(void))
|
||||
icon = hasConnections ? style.Icons.ArrowClose : style.Icons.ArrowOpen;
|
||||
else
|
||||
icon = hasConnections ? style.Icons.BoxClose : style.Icons.BoxOpen;
|
||||
color *= ConnectionsHighlightIntensity + 1;
|
||||
Render2D.DrawSprite(icon, rect, color);
|
||||
|
||||
// Draw selection hint
|
||||
if (_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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseEnter(Float2 location)
|
||||
{
|
||||
@@ -708,27 +625,21 @@ namespace FlaxEditor.Surface.Elements
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connections origin offset.
|
||||
/// </summary>
|
||||
public Float2 ConnectionOffset;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Float2 ConnectionOrigin
|
||||
{
|
||||
get
|
||||
{
|
||||
var center = Center;
|
||||
var center = Center + ConnectionOffset;
|
||||
return Parent.PointToParent(ref center);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CanCast(ScriptType oB, ScriptType iB)
|
||||
{
|
||||
if (oB == iB)
|
||||
return true;
|
||||
if (oB == ScriptType.Null || iB == ScriptType.Null)
|
||||
return false;
|
||||
return (oB.Type != typeof(void) && oB.Type != typeof(FlaxEngine.Object)) &&
|
||||
(iB.Type != typeof(void) && iB.Type != typeof(FlaxEngine.Object)) &&
|
||||
oB.IsAssignableFrom(iB);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool AreConnected(IConnectionInstigator other)
|
||||
{
|
||||
@@ -787,7 +698,7 @@ namespace FlaxEditor.Surface.Elements
|
||||
{
|
||||
if (!iB.CanUseType(oB.CurrentType))
|
||||
{
|
||||
if (!CanCast(oB.CurrentType, iB.CurrentType))
|
||||
if (!oB.CurrentType.CanCastTo(iB.CurrentType))
|
||||
{
|
||||
// Cannot
|
||||
return false;
|
||||
@@ -798,7 +709,7 @@ namespace FlaxEditor.Surface.Elements
|
||||
{
|
||||
if (!oB.CanUseType(iB.CurrentType))
|
||||
{
|
||||
if (!CanCast(oB.CurrentType, iB.CurrentType))
|
||||
if (!oB.CurrentType.CanCastTo(iB.CurrentType))
|
||||
{
|
||||
// Cannot
|
||||
return false;
|
||||
@@ -813,7 +724,7 @@ namespace FlaxEditor.Surface.Elements
|
||||
/// <inheritdoc />
|
||||
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
{
|
||||
OutputBox.DrawConnection(ref startPos, ref endPos, ref color, 2);
|
||||
OutputBox.DrawConnection(Surface.Style, ref startPos, ref endPos, ref color, 2);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -871,7 +782,7 @@ namespace FlaxEditor.Surface.Elements
|
||||
bool useCaster = false;
|
||||
if (!iB.CanUseType(oB.CurrentType))
|
||||
{
|
||||
if (CanCast(oB.CurrentType, iB.CurrentType))
|
||||
if (oB.CurrentType.CanCastTo(iB.CurrentType))
|
||||
useCaster = true;
|
||||
else
|
||||
return;
|
||||
@@ -881,8 +792,62 @@ namespace FlaxEditor.Surface.Elements
|
||||
if (useCaster)
|
||||
{
|
||||
// Connect via Caster
|
||||
//AddCaster(oB, iB);
|
||||
throw new NotImplementedException("AddCaster(..) function");
|
||||
const float casterXOffset = 250;
|
||||
if (Surface.Undo != null && Surface.Undo.Enabled)
|
||||
{
|
||||
bool undoEnabled = Surface.Undo.Enabled;
|
||||
Surface.Undo.Enabled = false;
|
||||
SurfaceNode node = Surface.Context.SpawnNode(7, 22, Float2.Zero); // 22 AsNode, 25 CastNode
|
||||
Surface.Undo.Enabled = undoEnabled;
|
||||
if (node is not Archetypes.Tools.AsNode castNode)
|
||||
throw new Exception("Node is not a casting node!");
|
||||
|
||||
// Set the type of the casting node
|
||||
undoEnabled = castNode.Surface.Undo.Enabled;
|
||||
castNode.Surface.Undo.Enabled = false;
|
||||
castNode.SetPickerValue(iB.CurrentType);
|
||||
castNode.Surface.Undo.Enabled = undoEnabled;
|
||||
if (node.GetBox(0) is not OutputBox castOutputBox || node.GetBox(1) is not InputBox castInputBox)
|
||||
throw new NullReferenceException("Casting failed. Cast node is invalid!");
|
||||
|
||||
// We set the position of the cast node here to set it relative to the target nodes input box
|
||||
undoEnabled = castNode.Surface.Undo.Enabled;
|
||||
castNode.Surface.Undo.Enabled = false;
|
||||
var wantedOffset = iB.ParentNode.Location - new Float2(casterXOffset, -(iB.LocalY - castOutputBox.LocalY));
|
||||
castNode.Location = Surface.Root.PointFromParent(ref wantedOffset);
|
||||
castNode.Surface.Undo.Enabled = undoEnabled;
|
||||
|
||||
var spawnNodeAction = new AddRemoveNodeAction(castNode, true);
|
||||
|
||||
var connectToCastNodeAction = new ConnectBoxesAction(castInputBox, oB, true);
|
||||
castInputBox.CreateConnection(oB);
|
||||
connectToCastNodeAction.End();
|
||||
|
||||
var connectCastToTargetNodeAction = new ConnectBoxesAction(iB, castOutputBox, true);
|
||||
iB.CreateConnection(castOutputBox);
|
||||
connectCastToTargetNodeAction.End();
|
||||
|
||||
Surface.AddBatchedUndoAction(new MultiUndoAction(spawnNodeAction, connectToCastNodeAction, connectCastToTargetNodeAction));
|
||||
}
|
||||
else
|
||||
{
|
||||
SurfaceNode node = Surface.Context.SpawnNode(7, 22, Float2.Zero); // 22 AsNode, 25 CastNode
|
||||
if (node is not Archetypes.Tools.AsNode castNode)
|
||||
throw new Exception("Node is not a casting node!");
|
||||
|
||||
// Set the type of the casting node
|
||||
castNode.SetPickerValue(iB.CurrentType);
|
||||
if (node.GetBox(0) is not OutputBox castOutputBox || node.GetBox(1) is not InputBox castInputBox)
|
||||
throw new NullReferenceException("Casting failed. Cast node is invalid!");
|
||||
|
||||
// We set the position of the cast node here to set it relative to the target nodes input box
|
||||
var wantedOffset = iB.ParentNode.Location - new Float2(casterXOffset, -(iB.LocalY - castOutputBox.LocalY));
|
||||
castNode.Location = Surface.Root.PointFromParent(ref wantedOffset);
|
||||
|
||||
castInputBox.CreateConnection(oB);
|
||||
iB.CreateConnection(castOutputBox);
|
||||
}
|
||||
Surface.MarkAsEdited();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1438,7 +1438,7 @@ namespace FlaxEditor.Surface.Elements
|
||||
base.Draw();
|
||||
|
||||
// Box
|
||||
DrawBox();
|
||||
Surface.Style.DrawBox(this);
|
||||
|
||||
// Draw text
|
||||
var style = Style.Current;
|
||||
|
||||
@@ -27,15 +27,22 @@ namespace FlaxEditor.Surface.Elements
|
||||
/// <summary>
|
||||
/// Draws the connection between two boxes.
|
||||
/// </summary>
|
||||
/// <param name="style">The Visject surface style.</param>
|
||||
/// <param name="start">The start location.</param>
|
||||
/// <param name="end">The end location.</param>
|
||||
/// <param name="color">The connection color.</param>
|
||||
/// <param name="thickness">The connection thickness.</param>
|
||||
public static void DrawConnection(ref Float2 start, ref Float2 end, ref Color color, float thickness = 1)
|
||||
public static void DrawConnection(SurfaceStyle style, ref Float2 start, ref Float2 end, ref Color color, float thickness = 1)
|
||||
{
|
||||
if (style.DrawConnection != null)
|
||||
{
|
||||
style.DrawConnection(start, end, color, thickness);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate control points
|
||||
CalculateBezierControlPoints(start, end, out var control1, out var control2);
|
||||
|
||||
|
||||
// Draw line
|
||||
Render2D.DrawBezier(start, control1, control2, end, color, thickness);
|
||||
|
||||
@@ -54,16 +61,16 @@ namespace FlaxEditor.Surface.Elements
|
||||
const float maxControlLength = 150f;
|
||||
var dst = (end - start).Length;
|
||||
var yDst = Mathf.Abs(start.Y - end.Y);
|
||||
|
||||
|
||||
// Calculate control points
|
||||
var minControlDst = dst * 0.5f;
|
||||
var maxControlDst = Mathf.Max(Mathf.Min(maxControlLength, dst), minControlLength);
|
||||
var controlDst = Mathf.Lerp(minControlDst, maxControlDst, Mathf.Clamp(yDst / minControlLength, 0f, 1f));
|
||||
|
||||
|
||||
control1 = new Float2(start.X + controlDst, start.Y);
|
||||
control2 = new Float2(end.X - controlDst, end.Y);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a point intersects a connection
|
||||
/// </summary>
|
||||
@@ -71,8 +78,8 @@ namespace FlaxEditor.Surface.Elements
|
||||
/// <param name="mousePosition">The mouse position</param>
|
||||
public bool IntersectsConnection(Box targetBox, ref Float2 mousePosition)
|
||||
{
|
||||
var startPos = Parent.PointToParent(Center);
|
||||
var endPos = targetBox.Parent.PointToParent(targetBox.Center);
|
||||
var startPos = ConnectionOrigin;
|
||||
var endPos = targetBox.ConnectionOrigin;
|
||||
return IntersectsConnection(ref startPos, ref endPos, ref mousePosition, MouseOverConnectionDistance);
|
||||
}
|
||||
|
||||
@@ -86,11 +93,13 @@ namespace FlaxEditor.Surface.Elements
|
||||
public static bool IntersectsConnection(ref Float2 start, ref Float2 end, ref Float2 point, float distance)
|
||||
{
|
||||
// Pretty much a point in rectangle check
|
||||
if ((point.X - start.X) * (end.X - point.X) < 0) return false;
|
||||
if ((point.X - start.X) * (end.X - point.X) < 0)
|
||||
return false;
|
||||
|
||||
float offset = Mathf.Sign(end.Y - start.Y) * distance;
|
||||
if ((point.Y - (start.Y - offset)) * ((end.Y + offset) - point.Y) < 0) return false;
|
||||
|
||||
if ((point.Y - (start.Y - offset)) * ((end.Y + offset) - point.Y) < 0)
|
||||
return false;
|
||||
|
||||
float squaredDistance = distance;
|
||||
CalculateBezierControlPoints(start, end, out var control1, out var control2);
|
||||
|
||||
@@ -133,14 +142,15 @@ namespace FlaxEditor.Surface.Elements
|
||||
/// </summary>
|
||||
public void DrawConnections(ref Float2 mousePosition)
|
||||
{
|
||||
float mouseOverDistance = MouseOverConnectionDistance;
|
||||
// Draw all the connections
|
||||
var startPos = Parent.PointToParent(Center);
|
||||
var style = Surface.Style;
|
||||
var mouseOverDistance = MouseOverConnectionDistance;
|
||||
var startPos = ConnectionOrigin;
|
||||
var startHighlight = ConnectionsHighlightIntensity;
|
||||
for (int i = 0; i < Connections.Count; i++)
|
||||
{
|
||||
Box targetBox = Connections[i];
|
||||
var endPos = targetBox.Parent.PointToParent(targetBox.Center);
|
||||
var endPos = targetBox.ConnectionOrigin;
|
||||
var highlight = 1 + Mathf.Max(startHighlight, targetBox.ConnectionsHighlightIntensity);
|
||||
var color = _currentTypeColor * highlight;
|
||||
|
||||
@@ -150,7 +160,7 @@ namespace FlaxEditor.Surface.Elements
|
||||
highlight += 0.5f;
|
||||
}
|
||||
|
||||
DrawConnection(ref startPos, ref endPos, ref color, highlight);
|
||||
DrawConnection(style, ref startPos, ref endPos, ref color, highlight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,9 +170,9 @@ namespace FlaxEditor.Surface.Elements
|
||||
public void DrawSelectedConnection(Box targetBox)
|
||||
{
|
||||
// Draw all the connections
|
||||
var startPos = Parent.PointToParent(Center);
|
||||
var endPos = targetBox.Parent.PointToParent(targetBox.Center);
|
||||
DrawConnection(ref startPos, ref endPos, ref _currentTypeColor, 2.5f);
|
||||
var startPos = ConnectionOrigin;
|
||||
var endPos = targetBox.ConnectionOrigin;
|
||||
DrawConnection(Surface.Style, ref startPos, ref endPos, ref _currentTypeColor, 2.5f);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -174,7 +184,7 @@ namespace FlaxEditor.Surface.Elements
|
||||
base.Draw();
|
||||
|
||||
// Box
|
||||
DrawBox();
|
||||
Surface.Style.DrawBox(this);
|
||||
|
||||
// Draw text
|
||||
var style = Style.Current;
|
||||
|
||||
@@ -36,13 +36,13 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUseNodeType(NodeArchetype nodeArchetype)
|
||||
public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
if (nodeArchetype.Title == "Function Input" ||
|
||||
nodeArchetype.Title == "Function Output")
|
||||
return true;
|
||||
|
||||
return base.CanUseNodeType(nodeArchetype);
|
||||
return base.CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -33,9 +33,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUseNodeType(NodeArchetype nodeArchetype)
|
||||
public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
return (nodeArchetype.Flags & NodeFlags.MaterialGraph) != 0 && base.CanUseNodeType(nodeArchetype);
|
||||
return (nodeArchetype.Flags & NodeFlags.MaterialGraph) != 0 && base.CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -89,6 +89,11 @@ namespace FlaxEditor.Surface
|
||||
/// <returns>The created node object.</returns>
|
||||
public delegate SurfaceNode CreateCustomNodeFunc(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given type is compatible with the given node archetype. Used for custom nodes
|
||||
/// </summary>
|
||||
public delegate bool IsCompatible(NodeArchetype nodeArch, ScriptType portType, ConnectionsHint hint, VisjectSurfaceContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Unique node type ID within a single group.
|
||||
/// </summary>
|
||||
@@ -100,6 +105,16 @@ namespace FlaxEditor.Surface
|
||||
public CreateCustomNodeFunc Create;
|
||||
|
||||
private Float2 _size;
|
||||
/// <summary>
|
||||
/// Function for asynchronously loaded nodes to check if input ports are compatible, for filtering.
|
||||
/// </summary>
|
||||
public IsCompatible IsInputCompatible;
|
||||
|
||||
/// <summary>
|
||||
/// Function for asynchronously loaded nodes to check if output ports are compatible, for filtering.
|
||||
/// </summary>
|
||||
public IsCompatible IsOutputCompatible;
|
||||
|
||||
/// <summary>
|
||||
/// Default initial size of the node.
|
||||
/// </summary>
|
||||
@@ -195,6 +210,8 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
TypeID = TypeID,
|
||||
Create = Create,
|
||||
IsInputCompatible = IsInputCompatible,
|
||||
IsOutputCompatible = IsOutputCompatible,
|
||||
Size = Size,
|
||||
Flags = Flags,
|
||||
Title = Title,
|
||||
|
||||
@@ -175,6 +175,13 @@ namespace FlaxEditor.Surface
|
||||
Color = new Color(110, 180, 81),
|
||||
Archetypes = Archetypes.Collections.Nodes
|
||||
},
|
||||
new GroupArchetype
|
||||
{
|
||||
GroupID = 19,
|
||||
Name = "Behavior Tree",
|
||||
Color = new Color(70, 220, 181),
|
||||
Archetypes = Archetypes.BehaviorTree.Nodes
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -68,9 +68,14 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
NoSpawnViaPaste = 512,
|
||||
|
||||
/// <summary>
|
||||
/// Node can be used in the Behavior Tree graphs.
|
||||
/// </summary>
|
||||
BehaviorTreeGraph = 1024,
|
||||
|
||||
/// <summary>
|
||||
/// Node can be used in the all visual graphs.
|
||||
/// </summary>
|
||||
AllGraphs = MaterialGraph | ParticleEmitterGraph | AnimGraph | VisualScriptGraph,
|
||||
AllGraphs = MaterialGraph | ParticleEmitterGraph | AnimGraph | VisualScriptGraph | BehaviorTreeGraph,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,13 +35,13 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUseNodeType(NodeArchetype nodeArchetype)
|
||||
public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
if (nodeArchetype.Title == "Function Input" ||
|
||||
nodeArchetype.Title == "Function Output")
|
||||
return true;
|
||||
|
||||
return base.CanUseNodeType(nodeArchetype);
|
||||
return base.CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -87,9 +87,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUseNodeType(NodeArchetype nodeArchetype)
|
||||
public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
return (nodeArchetype.Flags & NodeFlags.ParticleEmitterGraph) != 0 && base.CanUseNodeType(nodeArchetype);
|
||||
return (nodeArchetype.Flags & NodeFlags.ParticleEmitterGraph) != 0 && base.CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -59,9 +59,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
// Read node data
|
||||
Title = TitleValue;
|
||||
@@ -70,9 +70,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
// Randomize color
|
||||
Color = ColorValue = Color.FromHSV(new Random().NextFloat(0, 360), 0.7f, 0.25f, 0.8f);
|
||||
|
||||
@@ -122,14 +122,14 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Called when control gets loaded and added to surface.
|
||||
/// </summary>
|
||||
public virtual void OnLoaded()
|
||||
public virtual void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when surface gets loaded and nodes boxes are connected.
|
||||
/// </summary>
|
||||
public virtual void OnSurfaceLoaded()
|
||||
public virtual void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
UpdateRectangles();
|
||||
}
|
||||
@@ -137,14 +137,14 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Called after adding the control to the surface after user spawn (eg. add comment, add new node, etc.).
|
||||
/// </summary>
|
||||
public virtual void OnSpawned()
|
||||
public virtual void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on removing the control from the surface after user delete/cut operation (eg. delete comment, cut node, etc.).
|
||||
/// </summary>
|
||||
public virtual void OnDeleted()
|
||||
public virtual void OnDeleted(SurfaceNodeActions action)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// The node archetype.
|
||||
/// </summary>
|
||||
public readonly NodeArchetype Archetype;
|
||||
public NodeArchetype Archetype;
|
||||
|
||||
/// <summary>
|
||||
/// The group archetype.
|
||||
@@ -165,19 +165,16 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
if (Surface == null)
|
||||
return;
|
||||
Size = CalculateNodeSize(width, height);
|
||||
|
||||
// Update boxes on width change
|
||||
//if (!Mathf.NearEqual(prevSize.X, Size.X))
|
||||
for (int i = 0; i < Elements.Count; i++)
|
||||
{
|
||||
for (int i = 0; i < Elements.Count; i++)
|
||||
if (Elements[i] is OutputBox box)
|
||||
{
|
||||
if (Elements[i] is OutputBox box)
|
||||
{
|
||||
box.Location = box.Archetype.Position + new Float2(width, 0);
|
||||
}
|
||||
box.Location = box.Archetype.Position + new Float2(width, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Size = CalculateNodeSize(width, height);
|
||||
}
|
||||
|
||||
private Float2 GetBoxControlWidthHeight(Control control, Font boxLabelFont)
|
||||
@@ -216,7 +213,7 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Automatically resizes the node to match the title size and all the elements for best fit of the node dimensions.
|
||||
/// </summary>
|
||||
public void ResizeAuto()
|
||||
public virtual void ResizeAuto()
|
||||
{
|
||||
if (Surface == null)
|
||||
return;
|
||||
@@ -251,8 +248,16 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
else
|
||||
{
|
||||
width = Mathf.Max(width, boxSize.X);
|
||||
height = Mathf.Max(height, boxSize.Y);
|
||||
if (control.AnchorPreset == AnchorPresets.TopLeft)
|
||||
{
|
||||
width = Mathf.Max(width, boxSize.X);
|
||||
height = Mathf.Max(height, boxSize.Y);
|
||||
}
|
||||
else if (!_headerRect.Intersects(control.Bounds))
|
||||
{
|
||||
width = Mathf.Max(width, boxSize.X);
|
||||
height = Mathf.Max(height, boxSize.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,6 +430,19 @@ namespace FlaxEditor.Surface
|
||||
UpdateBoxesTypes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Array of nodes that are sealed to this node - sealed nodes are duplicated/copied/pasted/removed in a batch. Null if unused.
|
||||
/// </summary>
|
||||
public virtual SurfaceNode[] SealedNodes => null;
|
||||
|
||||
/// <summary>
|
||||
/// Called after adding the control to the surface after paste.
|
||||
/// </summary>
|
||||
/// <param name="idsMapping">The nodes IDs mapping (original node ID to pasted node ID). Can be sued to update internal node's data after paste operation from the original data.</param>
|
||||
public virtual void OnPasted(System.Collections.Generic.Dictionary<uint, uint> idsMapping)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this node uses dependent boxes.
|
||||
/// </summary>
|
||||
@@ -914,9 +932,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateBoxesTypes();
|
||||
|
||||
@@ -928,11 +946,11 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDeleted()
|
||||
public override void OnDeleted(SurfaceNodeActions action)
|
||||
{
|
||||
RemoveConnections();
|
||||
|
||||
base.OnDeleted();
|
||||
base.OnDeleted(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -958,7 +976,7 @@ namespace FlaxEditor.Surface
|
||||
OnValuesChanged();
|
||||
Surface?.MarkAsEdited(graphEdited);
|
||||
|
||||
if (Surface?.Undo != null)
|
||||
if (Surface != null)
|
||||
Surface.AddBatchedUndoAction(new EditNodeValuesAction(this, before, graphEdited));
|
||||
|
||||
_isDuringValuesEditing = false;
|
||||
@@ -985,7 +1003,7 @@ namespace FlaxEditor.Surface
|
||||
OnValuesChanged();
|
||||
Surface.MarkAsEdited(graphEdited);
|
||||
|
||||
if (Surface?.Undo != null)
|
||||
if (Surface != null)
|
||||
Surface.AddBatchedUndoAction(new EditNodeValuesAction(this, before, graphEdited));
|
||||
|
||||
_isDuringValuesEditing = false;
|
||||
@@ -1052,9 +1070,9 @@ namespace FlaxEditor.Surface
|
||||
Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center);
|
||||
|
||||
// Close button
|
||||
if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0)
|
||||
if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0 && Surface.CanEdit)
|
||||
{
|
||||
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
|
||||
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
|
||||
}
|
||||
|
||||
// Footer
|
||||
|
||||
@@ -140,6 +140,16 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
public Texture Background;
|
||||
|
||||
/// <summary>
|
||||
/// Boxes drawing callback.
|
||||
/// </summary>
|
||||
public Action<Elements.Box> DrawBox = DefaultDrawBox;
|
||||
|
||||
/// <summary>
|
||||
/// Custom box connection drawing callback (null by default).
|
||||
/// </summary>
|
||||
public Action<Float2, Float2, Color, float> DrawConnection = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color for the connection.
|
||||
/// </summary>
|
||||
@@ -204,6 +214,41 @@ namespace FlaxEditor.Surface
|
||||
color = Colors.Default;
|
||||
}
|
||||
|
||||
private static void DefaultDrawBox(Elements.Box box)
|
||||
{
|
||||
var rect = new Rectangle(Float2.Zero, box.Size);
|
||||
|
||||
// Size culling
|
||||
const float minBoxSize = 5.0f;
|
||||
if (rect.Size.LengthSquared < minBoxSize * minBoxSize)
|
||||
return;
|
||||
|
||||
// Debugging boxes size
|
||||
//Render2D.DrawRectangle(rect, Color.Orange); return;
|
||||
|
||||
// Draw icon
|
||||
bool hasConnections = box.HasAnyConnection;
|
||||
float alpha = box.Enabled ? 1.0f : 0.6f;
|
||||
Color color = box.CurrentTypeColor * alpha;
|
||||
var style = box.Surface.Style;
|
||||
SpriteHandle icon;
|
||||
if (box.CurrentType.Type == typeof(void))
|
||||
icon = hasConnections ? style.Icons.ArrowClose : style.Icons.ArrowOpen;
|
||||
else
|
||||
icon = hasConnections ? style.Icons.BoxClose : style.Icons.BoxOpen;
|
||||
color *= box.ConnectionsHighlightIntensity + 1;
|
||||
Render2D.DrawSprite(icon, rect, color);
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Function used to create style for the given surface type. Can be overriden to provide some customization via user plugin.
|
||||
/// </summary>
|
||||
|
||||
@@ -409,8 +409,8 @@ namespace FlaxEditor.Surface
|
||||
|
||||
internal static bool IsValidVisualScriptType(ScriptType scriptType)
|
||||
{
|
||||
if (!scriptType.IsPublic ||
|
||||
scriptType.HasAttribute(typeof(HideInEditorAttribute), true) ||
|
||||
if (!scriptType.IsPublic ||
|
||||
scriptType.HasAttribute(typeof(HideInEditorAttribute), true) ||
|
||||
scriptType.HasAttribute(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false))
|
||||
return false;
|
||||
if (scriptType.IsGenericType)
|
||||
|
||||
@@ -67,8 +67,8 @@ namespace FlaxEditor.Surface.Undo
|
||||
else if (_nodeValues != null && _nodeValues.Length != 0)
|
||||
throw new InvalidOperationException("Invalid node values.");
|
||||
node.Location = _nodeLocation;
|
||||
context.OnControlLoaded(node);
|
||||
node.OnSurfaceLoaded();
|
||||
context.OnControlLoaded(node, SurfaceNodeActions.Undo);
|
||||
node.OnSurfaceLoaded(SurfaceNodeActions.Undo);
|
||||
|
||||
context.MarkAsModified();
|
||||
}
|
||||
@@ -89,7 +89,7 @@ namespace FlaxEditor.Surface.Undo
|
||||
|
||||
// Remove node
|
||||
context.Nodes.Remove(node);
|
||||
context.OnControlDeleted(node);
|
||||
context.OnControlDeleted(node, SurfaceNodeActions.Undo);
|
||||
|
||||
context.MarkAsModified();
|
||||
}
|
||||
|
||||
@@ -136,6 +136,88 @@ namespace FlaxEditor.Surface
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a type is compatible with another type and can be casted by using a connection hint
|
||||
/// </summary>
|
||||
/// <param name="from">Source type</param>
|
||||
/// <param name="to">Type to check compatibility with</param>
|
||||
/// <param name="hint">Hint to check if casting is possible</param>
|
||||
/// <returns>True if the source type is compatible with the target type</returns>
|
||||
public static bool IsTypeCompatible(ScriptType from, ScriptType to, ConnectionsHint hint)
|
||||
{
|
||||
if (from == ScriptType.Null && hint != ConnectionsHint.None)
|
||||
{
|
||||
if ((hint & ConnectionsHint.Anything) == ConnectionsHint.Anything)
|
||||
return true;
|
||||
if ((hint & ConnectionsHint.Value) == ConnectionsHint.Value && to.Type != typeof(void))
|
||||
return true;
|
||||
if ((hint & ConnectionsHint.Enum) == ConnectionsHint.Enum && to.IsEnum)
|
||||
return true;
|
||||
if ((hint & ConnectionsHint.Array) == ConnectionsHint.Array && to.IsArray)
|
||||
return true;
|
||||
if ((hint & ConnectionsHint.Dictionary) == ConnectionsHint.Dictionary && to.IsDictionary)
|
||||
return true;
|
||||
if ((hint & ConnectionsHint.Vector) == ConnectionsHint.Vector)
|
||||
{
|
||||
var t = to.Type;
|
||||
if (t == typeof(Vector2) ||
|
||||
t == typeof(Vector3) ||
|
||||
t == typeof(Vector4) ||
|
||||
t == typeof(Float2) ||
|
||||
t == typeof(Float3) ||
|
||||
t == typeof(Float4) ||
|
||||
t == typeof(Double2) ||
|
||||
t == typeof(Double3) ||
|
||||
t == typeof(Double4) ||
|
||||
t == typeof(Int2) ||
|
||||
t == typeof(Int3) ||
|
||||
t == typeof(Int4) ||
|
||||
t == typeof(Color))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ((hint & ConnectionsHint.Scalar) == ConnectionsHint.Scalar)
|
||||
{
|
||||
var t = to.Type;
|
||||
if (t == typeof(bool) ||
|
||||
t == typeof(char) ||
|
||||
t == typeof(byte) ||
|
||||
t == typeof(short) ||
|
||||
t == typeof(ushort) ||
|
||||
t == typeof(int) ||
|
||||
t == typeof(uint) ||
|
||||
t == typeof(long) ||
|
||||
t == typeof(ulong) ||
|
||||
t == typeof(float) ||
|
||||
t == typeof(double))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a type is compatible with another type and can be casted by using a connection hint
|
||||
/// </summary>
|
||||
/// <param name="from">Source type</param>
|
||||
/// <param name="to">Target type</param>
|
||||
/// <param name="hint">Connection hint</param>
|
||||
/// <returns>True if any method of casting or compatibility check succeeds</returns>
|
||||
public static bool FullCastCheck(ScriptType from, ScriptType to, ConnectionsHint hint)
|
||||
{
|
||||
// Yes, from and to are switched on purpose
|
||||
if (CanUseDirectCastStatic(to, from, false))
|
||||
return true;
|
||||
if (IsTypeCompatible(from, to, hint))
|
||||
return true;
|
||||
// Same here
|
||||
return to.CanCastTo(from);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if can use direct conversion from one type to another.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
//#define DEBUG_SEARCH_TIME
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface.ContextMenu;
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEditor.Surface.Undo;
|
||||
@@ -13,6 +17,157 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
public partial class VisjectSurface
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility for easy nodes archetypes generation for Visject Surface based on scripting types.
|
||||
/// </summary>
|
||||
internal class NodesCache
|
||||
{
|
||||
public delegate void IterateType(ScriptType scriptType, Dictionary<KeyValuePair<string, ushort>, GroupArchetype> cache, int version);
|
||||
|
||||
internal static readonly List<NodesCache> Caches = new List<NodesCache>(8);
|
||||
|
||||
private readonly object _locker = new object();
|
||||
private readonly IterateType _iterator;
|
||||
private int _version;
|
||||
private Task _task;
|
||||
private VisjectCM _taskContextMenu;
|
||||
private Dictionary<KeyValuePair<string, ushort>, GroupArchetype> _cache;
|
||||
|
||||
public NodesCache(IterateType iterator)
|
||||
{
|
||||
_iterator = iterator;
|
||||
}
|
||||
|
||||
public void Wait()
|
||||
{
|
||||
if (_task != null)
|
||||
{
|
||||
Profiler.BeginEvent("Setup Context Menu");
|
||||
_task.Wait();
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Wait();
|
||||
|
||||
if (_cache != null && _cache.Count != 0)
|
||||
{
|
||||
OnCodeEditingTypesCleared();
|
||||
}
|
||||
lock (_locker)
|
||||
{
|
||||
Caches.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void Get(VisjectCM contextMenu)
|
||||
{
|
||||
Profiler.BeginEvent("Setup Context Menu");
|
||||
|
||||
Wait();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
if (_cache == null)
|
||||
{
|
||||
if (!Caches.Contains(this))
|
||||
Caches.Add(this);
|
||||
_cache = new Dictionary<KeyValuePair<string, ushort>, GroupArchetype>();
|
||||
}
|
||||
contextMenu.LockChildrenRecursive();
|
||||
|
||||
// Check if has cached groups
|
||||
if (_cache.Count != 0)
|
||||
{
|
||||
// Check if context menu doesn't have the recent cached groups
|
||||
if (!contextMenu.Groups.Any(g => g.Archetypes[0].Tag is int asInt && asInt == _version))
|
||||
{
|
||||
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
|
||||
foreach (var g in groups)
|
||||
contextMenu.RemoveGroup(g);
|
||||
foreach (var g in _cache.Values)
|
||||
contextMenu.AddGroup(g);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove any old groups from context menu
|
||||
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
|
||||
foreach (var g in groups)
|
||||
contextMenu.RemoveGroup(g);
|
||||
|
||||
// Register for scripting types reload
|
||||
Editor.Instance.CodeEditing.TypesCleared += OnCodeEditingTypesCleared;
|
||||
|
||||
// Run caching on an async
|
||||
_task = Task.Run(OnActiveContextMenuShowAsync);
|
||||
_taskContextMenu = contextMenu;
|
||||
}
|
||||
|
||||
contextMenu.UnlockChildrenRecursive();
|
||||
}
|
||||
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
|
||||
private void OnActiveContextMenuShowAsync()
|
||||
{
|
||||
Profiler.BeginEvent("Setup Context Menu (async)");
|
||||
#if DEBUG_SEARCH_TIME
|
||||
var searchStartTime = DateTime.Now;
|
||||
#endif
|
||||
|
||||
foreach (var scriptType in Editor.Instance.CodeEditing.All.Get())
|
||||
{
|
||||
if (!SurfaceUtils.IsValidVisualScriptType(scriptType))
|
||||
continue;
|
||||
|
||||
_iterator(scriptType, _cache, _version);
|
||||
}
|
||||
|
||||
// Add group to context menu (on a main thread)
|
||||
FlaxEngine.Scripting.InvokeOnUpdate(() =>
|
||||
{
|
||||
#if DEBUG_SEARCH_TIME
|
||||
var addStartTime = DateTime.Now;
|
||||
#endif
|
||||
lock (_locker)
|
||||
{
|
||||
_taskContextMenu.AddGroups(_cache.Values);
|
||||
_taskContextMenu = null;
|
||||
}
|
||||
#if DEBUG_SEARCH_TIME
|
||||
Editor.LogError($"Added items to VisjectCM in: {(DateTime.Now - addStartTime).TotalMilliseconds} ms");
|
||||
#endif
|
||||
});
|
||||
|
||||
#if DEBUG_SEARCH_TIME
|
||||
Editor.LogError($"Collected items in: {(DateTime.Now - searchStartTime).TotalMilliseconds} ms");
|
||||
#endif
|
||||
Profiler.EndEvent();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
_task = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCodeEditingTypesCleared()
|
||||
{
|
||||
Wait();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
_cache.Clear();
|
||||
_version++;
|
||||
}
|
||||
|
||||
Editor.Instance.CodeEditing.TypesCleared -= OnCodeEditingTypesCleared;
|
||||
}
|
||||
}
|
||||
|
||||
private ContextMenuButton _cmCopyButton;
|
||||
private ContextMenuButton _cmDuplicateButton;
|
||||
private ContextMenuButton _cmFormatNodesConnectionButton;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEditor.Surface.Undo;
|
||||
using FlaxEngine;
|
||||
@@ -66,6 +65,25 @@ namespace FlaxEditor.Surface
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect sealed nodes to be copied as well
|
||||
foreach (var control in selection.ToArray())
|
||||
{
|
||||
if (control is SurfaceNode node)
|
||||
{
|
||||
var sealedNodes = node.SealedNodes;
|
||||
if (sealedNodes != null)
|
||||
{
|
||||
foreach (var sealedNode in sealedNodes)
|
||||
{
|
||||
if (sealedNode != null && !selection.Contains(sealedNode))
|
||||
{
|
||||
selection.Add(sealedNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dataModel = new DataModel();
|
||||
var dataModelNodes = new List<DataModelNode>(selection.Count);
|
||||
var dataModelComments = new List<DataModelComment>();
|
||||
@@ -254,7 +272,7 @@ namespace FlaxEditor.Surface
|
||||
throw new InvalidOperationException("Unknown node type.");
|
||||
|
||||
// Validate given node type
|
||||
if (!CanUseNodeType(nodeArchetype))
|
||||
if (!CanUseNodeType(groupArchetype, nodeArchetype))
|
||||
continue;
|
||||
|
||||
// Create
|
||||
@@ -355,7 +373,7 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
}
|
||||
|
||||
Context.OnControlLoaded(node);
|
||||
Context.OnControlLoaded(node, SurfaceNodeActions.Paste);
|
||||
}
|
||||
|
||||
// Setup connections
|
||||
@@ -395,11 +413,15 @@ namespace FlaxEditor.Surface
|
||||
// Post load
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
node.Value.OnSurfaceLoaded();
|
||||
node.Value.OnSurfaceLoaded(SurfaceNodeActions.Paste);
|
||||
}
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
node.Value.OnSpawned();
|
||||
node.Value.OnSpawned(SurfaceNodeActions.Paste);
|
||||
}
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
node.Value.OnPasted(idsMapping);
|
||||
}
|
||||
|
||||
// Add undo action
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Surface
|
||||
@@ -135,8 +136,25 @@ namespace FlaxEditor.Surface
|
||||
endPos = _lastInstigatorUnderMouse.ConnectionOrigin;
|
||||
}
|
||||
|
||||
Float2 actualStartPos = startPos;
|
||||
Float2 actualEndPos = endPos;
|
||||
|
||||
if (_connectionInstigator is Archetypes.Tools.RerouteNode)
|
||||
{
|
||||
if (endPos.X < startPos.X && _lastInstigatorUnderMouse is null or Box { IsOutput: true})
|
||||
{
|
||||
actualStartPos = endPos;
|
||||
actualEndPos = startPos;
|
||||
}
|
||||
}
|
||||
else if (_connectionInstigator is Box { IsOutput: false })
|
||||
{
|
||||
actualStartPos = endPos;
|
||||
actualEndPos = startPos;
|
||||
}
|
||||
|
||||
// Draw connection
|
||||
_connectionInstigator.DrawConnectingLine(ref startPos, ref endPos, ref lineColor);
|
||||
_connectionInstigator.DrawConnectingLine(ref actualStartPos, ref actualEndPos, ref lineColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -176,7 +194,7 @@ namespace FlaxEditor.Surface
|
||||
var bezierStartPoint = new Float2(upperRight.X + offsetX * 0.75f, (upperRight.Y + bottomRight.Y) * 0.5f);
|
||||
var bezierEndPoint = inputBracket.Box.ParentNode.PointToParent(_rootControl.Parent, inputBracket.Box.Center);
|
||||
|
||||
Elements.OutputBox.DrawConnection(ref bezierStartPoint, ref bezierEndPoint, ref fadedColor);
|
||||
Elements.OutputBox.DrawConnection(Style, ref bezierStartPoint, ref bezierEndPoint, ref fadedColor);
|
||||
|
||||
// Debug Area
|
||||
//Rectangle drawRect = Rectangle.FromPoints(upperLeft, bottomRight);
|
||||
|
||||
@@ -117,15 +117,23 @@ namespace FlaxEditor.Surface
|
||||
var p1 = _rootControl.PointFromParent(ref _leftMouseDownPos);
|
||||
var p2 = _rootControl.PointFromParent(ref _mousePos);
|
||||
var selectionRect = Rectangle.FromPoints(p1, p2);
|
||||
var selectionChanged = false;
|
||||
|
||||
// Find controls to select
|
||||
for (int i = 0; i < _rootControl.Children.Count; i++)
|
||||
{
|
||||
if (_rootControl.Children[i] is SurfaceControl control)
|
||||
{
|
||||
control.IsSelected = control.IsSelectionIntersecting(ref selectionRect);
|
||||
var select = control.IsSelectionIntersecting(ref selectionRect);
|
||||
if (select != control.IsSelected)
|
||||
{
|
||||
control.IsSelected = select;
|
||||
selectionChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (selectionChanged)
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void OnSurfaceControlSpawned(SurfaceControl control)
|
||||
@@ -299,8 +307,11 @@ namespace FlaxEditor.Surface
|
||||
|
||||
_leftMouseDownPos = location;
|
||||
_movingNodesDelta += delta; // TODO: Figure out how to handle undo for differing values of _gridRoundingDelta between selected nodes. For now it will be a small error in undo.
|
||||
Cursor = CursorType.SizeAll;
|
||||
MarkAsEdited(false);
|
||||
if (_movingNodes.Count > 0)
|
||||
{
|
||||
Cursor = CursorType.SizeAll;
|
||||
MarkAsEdited(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Handled
|
||||
@@ -390,12 +401,12 @@ namespace FlaxEditor.Surface
|
||||
if (!handled)
|
||||
CustomMouseDoubleClick?.Invoke(ref location, button, ref handled);
|
||||
|
||||
if (!handled && CanEdit)
|
||||
// Insert reroute node
|
||||
if (!handled && CanEdit && CanUseNodeType(7, 29))
|
||||
{
|
||||
var mousePos = _rootControl.PointFromParent(ref _mousePos);
|
||||
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox) && GetControlUnderMouse() == null)
|
||||
{
|
||||
// Insert reroute node
|
||||
if (Undo != null)
|
||||
{
|
||||
bool undoEnabled = Undo.Enabled;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.Options;
|
||||
@@ -132,7 +133,7 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Occurs when selection gets changed.
|
||||
/// </summary>
|
||||
protected event Action SelectionChanged;
|
||||
public event Action SelectionChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The surface owner.
|
||||
@@ -272,6 +273,9 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Gets the list of the selected nodes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Don't call it too often. It does memory allocation and iterates over the surface controls to find selected nodes in the graph.
|
||||
/// </remarks>
|
||||
public List<SurfaceNode> SelectedNodes
|
||||
{
|
||||
get
|
||||
@@ -289,6 +293,9 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Gets the list of the selected controls (comments and nodes).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Don't call it too often. It does memory allocation and iterates over the surface controls to find selected nodes in the graph.
|
||||
/// </remarks>
|
||||
public List<SurfaceControl> SelectedControls
|
||||
{
|
||||
get
|
||||
@@ -540,9 +547,31 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Determines whether the specified node archetype can be used in the surface.
|
||||
/// </summary>
|
||||
/// <param name="groupID">The nodes group archetype identifier.</param>
|
||||
/// <param name="typeID">The node archetype identifier.</param>
|
||||
/// <returns>True if can use this node archetype, otherwise false.</returns>
|
||||
public bool CanUseNodeType(ushort groupID, ushort typeID)
|
||||
{
|
||||
var result = false;
|
||||
var nodeArchetypes = NodeArchetypes ?? NodeFactory.DefaultGroups;
|
||||
if (NodeFactory.GetArchetype(nodeArchetypes, groupID, typeID, out var groupArchetype, out var nodeArchetype))
|
||||
{
|
||||
var flags = nodeArchetype.Flags;
|
||||
nodeArchetype.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
nodeArchetype.Flags &= ~NodeFlags.NoSpawnViaPaste;
|
||||
result = CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
nodeArchetype.Flags = flags;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified node archetype can be used in the surface.
|
||||
/// </summary>
|
||||
/// <param name="groupArchetype">The nodes group archetype.</param>
|
||||
/// <param name="nodeArchetype">The node archetype.</param>
|
||||
/// <returns>True if can use this node archetype, otherwise false.</returns>
|
||||
public virtual bool CanUseNodeType(NodeArchetype nodeArchetype)
|
||||
public virtual bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
return (nodeArchetype.Flags & NodeFlags.NoSpawnViaPaste) == 0;
|
||||
}
|
||||
@@ -592,12 +621,17 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
public void SelectAll()
|
||||
{
|
||||
bool selectionChanged = false;
|
||||
for (int i = 0; i < _rootControl.Children.Count; i++)
|
||||
{
|
||||
if (_rootControl.Children[i] is SurfaceControl control)
|
||||
if (_rootControl.Children[i] is SurfaceControl control && !control.IsSelected)
|
||||
{
|
||||
control.IsSelected = true;
|
||||
selectionChanged = true;
|
||||
}
|
||||
}
|
||||
SelectionChanged?.Invoke();
|
||||
if (selectionChanged)
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -605,12 +639,17 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
public void ClearSelection()
|
||||
{
|
||||
bool selectionChanged = false;
|
||||
for (int i = 0; i < _rootControl.Children.Count; i++)
|
||||
{
|
||||
if (_rootControl.Children[i] is SurfaceControl control)
|
||||
if (_rootControl.Children[i] is SurfaceControl control && control.IsSelected)
|
||||
{
|
||||
control.IsSelected = false;
|
||||
selectionChanged = true;
|
||||
}
|
||||
}
|
||||
SelectionChanged?.Invoke();
|
||||
if (selectionChanged)
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -619,6 +658,8 @@ namespace FlaxEditor.Surface
|
||||
/// <param name="control">The control.</param>
|
||||
public void AddToSelection(SurfaceControl control)
|
||||
{
|
||||
if (control.IsSelected)
|
||||
return;
|
||||
control.IsSelected = true;
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
@@ -629,9 +670,22 @@ namespace FlaxEditor.Surface
|
||||
/// <param name="control">The control.</param>
|
||||
public void Select(SurfaceControl control)
|
||||
{
|
||||
ClearSelection();
|
||||
control.IsSelected = true;
|
||||
SelectionChanged?.Invoke();
|
||||
bool selectionChanged = false;
|
||||
for (int i = 0; i < _rootControl.Children.Count; i++)
|
||||
{
|
||||
if (_rootControl.Children[i] is SurfaceControl c && c != control && c.IsSelected)
|
||||
{
|
||||
c.IsSelected = false;
|
||||
selectionChanged = true;
|
||||
}
|
||||
}
|
||||
if (!control.IsSelected)
|
||||
{
|
||||
control.IsSelected = true;
|
||||
selectionChanged = true;
|
||||
}
|
||||
if (selectionChanged)
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -640,11 +694,13 @@ namespace FlaxEditor.Surface
|
||||
/// <param name="controls">The controls.</param>
|
||||
public void Select(IEnumerable<SurfaceControl> controls)
|
||||
{
|
||||
var newSelection = controls.ToList();
|
||||
var prevSelection = SelectedControls;
|
||||
if (Utils.ArraysEqual(newSelection, prevSelection))
|
||||
return;
|
||||
ClearSelection();
|
||||
foreach (var control in controls)
|
||||
{
|
||||
foreach (var control in newSelection)
|
||||
control.IsSelected = true;
|
||||
}
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
@@ -654,6 +710,8 @@ namespace FlaxEditor.Surface
|
||||
/// <param name="control">The control.</param>
|
||||
public void Deselect(SurfaceControl control)
|
||||
{
|
||||
if (!control.IsSelected)
|
||||
return;
|
||||
control.IsSelected = false;
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
@@ -692,11 +750,18 @@ namespace FlaxEditor.Surface
|
||||
/// <param name="withUndo">True if use undo/redo action for node removing.</param>
|
||||
public void Delete(IEnumerable<SurfaceControl> controls, bool withUndo = true)
|
||||
{
|
||||
if (!CanEdit || controls == null || !controls.Any())
|
||||
return;
|
||||
|
||||
var selectionChanged = false;
|
||||
List<SurfaceNode> nodes = null;
|
||||
foreach (var control in controls)
|
||||
{
|
||||
selectionChanged |= control.IsSelected;
|
||||
if (control.IsSelected)
|
||||
{
|
||||
selectionChanged = true;
|
||||
control.IsSelected = false;
|
||||
}
|
||||
|
||||
if (control is SurfaceNode node)
|
||||
{
|
||||
@@ -704,19 +769,34 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
if (nodes == null)
|
||||
nodes = new List<SurfaceNode>();
|
||||
nodes.Add(node);
|
||||
var sealedNodes = node.SealedNodes;
|
||||
if (sealedNodes != null)
|
||||
{
|
||||
foreach (var sealedNode in sealedNodes)
|
||||
{
|
||||
if (sealedNode != null)
|
||||
{
|
||||
if (sealedNode.IsSelected)
|
||||
{
|
||||
selectionChanged = true;
|
||||
sealedNode.IsSelected = false;
|
||||
}
|
||||
if (!nodes.Contains(sealedNode))
|
||||
nodes.Add(sealedNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!nodes.Contains(node))
|
||||
nodes.Add(node);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Context.OnControlDeleted(control);
|
||||
Context.OnControlDeleted(control, SurfaceNodeActions.User);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectionChanged)
|
||||
{
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
if (nodes != null)
|
||||
{
|
||||
@@ -727,7 +807,7 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
node.RemoveConnections();
|
||||
Nodes.Remove(node);
|
||||
Context.OnControlDeleted(node);
|
||||
Context.OnControlDeleted(node, SurfaceNodeActions.User);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -767,54 +847,7 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
if (!CanEdit)
|
||||
return;
|
||||
|
||||
var node = control as SurfaceNode;
|
||||
if (node == null)
|
||||
{
|
||||
Context.OnControlDeleted(control);
|
||||
MarkAsEdited();
|
||||
return;
|
||||
}
|
||||
|
||||
if ((node.Archetype.Flags & NodeFlags.NoRemove) != 0)
|
||||
return;
|
||||
|
||||
if (control.IsSelected)
|
||||
{
|
||||
control.IsSelected = false;
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
if (Undo == null || !withUndo)
|
||||
{
|
||||
// Remove node
|
||||
node.RemoveConnections();
|
||||
Nodes.Remove(node);
|
||||
Context.OnControlDeleted(node);
|
||||
}
|
||||
else
|
||||
{
|
||||
var actions = new List<IUndoAction>();
|
||||
|
||||
// Break connections for node
|
||||
{
|
||||
var action = new EditNodeConnections(Context, node);
|
||||
node.RemoveConnections();
|
||||
action.End();
|
||||
actions.Add(action);
|
||||
}
|
||||
|
||||
// Remove node
|
||||
{
|
||||
var action = new AddRemoveNodeAction(node, false);
|
||||
action.Do();
|
||||
actions.Add(action);
|
||||
}
|
||||
|
||||
Undo.AddAction(new MultiUndoAction(actions, "Remove node"));
|
||||
}
|
||||
|
||||
MarkAsEdited();
|
||||
Delete(new[] { control }, withUndo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -824,72 +857,7 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
if (!CanEdit)
|
||||
return;
|
||||
bool edited = false;
|
||||
|
||||
List<SurfaceNode> nodes = null;
|
||||
for (int i = 0; i < _rootControl.Children.Count; i++)
|
||||
{
|
||||
if (_rootControl.Children[i] is SurfaceNode node)
|
||||
{
|
||||
if (node.IsSelected && (node.Archetype.Flags & NodeFlags.NoRemove) == 0)
|
||||
{
|
||||
if (nodes == null)
|
||||
nodes = new List<SurfaceNode>();
|
||||
nodes.Add(node);
|
||||
edited = true;
|
||||
}
|
||||
}
|
||||
else if (_rootControl.Children[i] is SurfaceControl control && control.IsSelected)
|
||||
{
|
||||
i--;
|
||||
Context.OnControlDeleted(control);
|
||||
edited = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (nodes != null)
|
||||
{
|
||||
if (Undo == null)
|
||||
{
|
||||
// Remove all nodes
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
node.RemoveConnections();
|
||||
Nodes.Remove(node);
|
||||
Context.OnControlDeleted(node);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var actions = new List<IUndoAction>();
|
||||
|
||||
// Break connections for all nodes
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var action = new EditNodeConnections(Context, node);
|
||||
node.RemoveConnections();
|
||||
action.End();
|
||||
actions.Add(action);
|
||||
}
|
||||
|
||||
// Remove all nodes
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var action = new AddRemoveNodeAction(node, false);
|
||||
action.Do();
|
||||
actions.Add(action);
|
||||
}
|
||||
|
||||
Undo.AddAction(new MultiUndoAction(actions, nodes.Count == 1 ? "Remove node" : "Remove nodes"));
|
||||
}
|
||||
}
|
||||
|
||||
if (edited)
|
||||
{
|
||||
MarkAsEdited();
|
||||
}
|
||||
|
||||
SelectionChanged?.Invoke();
|
||||
Delete(SelectedControls, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user