Merge branch 'FlaxEngine:master' into Nodes

This commit is contained in:
NoriteSC
2023-10-25 07:03:46 +02:00
committed by GitHub
481 changed files with 21336 additions and 3997 deletions

View File

@@ -220,8 +220,9 @@ namespace FlaxEditor.Content.GUI
// Remove references and unlink items
for (int i = 0; i < _items.Count; i++)
{
_items[i].Parent = null;
_items[i].RemoveReference(this);
var item = _items[i];
item.Parent = null;
item.RemoveReference(this);
}
_items.Clear();
@@ -263,11 +264,12 @@ namespace FlaxEditor.Content.GUI
// Add references and link items
for (int i = 0; i < items.Count; i++)
{
if (items[i].Visible)
var item = items[i];
if (item.Visible && !_items.Contains(item))
{
items[i].Parent = this;
items[i].AddReference(this);
_items.Add(items[i]);
item.Parent = this;
item.AddReference(this);
_items.Add(item);
}
}
if (selection != null)
@@ -279,6 +281,8 @@ namespace FlaxEditor.Content.GUI
// Sort items depending on sortMethod parameter
_children.Sort(((control, control1) =>
{
if (control == null || control1 == null)
return 0;
if (sortType == SortType.AlphabeticReverse)
{
if (control.CompareTo(control1) > 0)
@@ -520,8 +524,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++)
{

View File

@@ -323,8 +323,6 @@ namespace FlaxEditor.Content
/// <param name="value">The new path.</param>
internal virtual void UpdatePath(string value)
{
Assert.AreNotEqual(Path, value);
// Set path
Path = StringUtils.NormalizePath(value);
FileName = System.IO.Path.GetFileName(value);

View 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
});
}
}
}

View File

@@ -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 />

View File

@@ -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 />

View File

@@ -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 />

View File

@@ -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 />

View File

@@ -30,6 +30,12 @@ namespace FlaxEditor.Content
return item is SceneItem;
}
/// <inheritdoc />
public override bool AcceptsAsset(string typeName, string path)
{
return (typeName == Scene.AssetTypename || typeName == Scene.EditorPickerTypename) && path.EndsWith(FileExtension, StringComparison.OrdinalIgnoreCase);
}
/// <inheritdoc />
public override bool CanCreate(ContentFolder targetLocation)
{

View File

@@ -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 />

View File

@@ -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 />

View File

@@ -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 />
@@ -338,18 +406,16 @@ namespace FlaxEditor.Content.Thumbnails
for (int i = 0; i < maxChecks; i++)
{
var request = _requests[i];
try
{
if (request.IsReady)
{
return request;
}
}
catch (Exception ex)
{
Editor.LogWarning(ex);
Editor.LogWarning($"Failed to prepare thumbnail rendering for {request.Item.ShortName}.");
Editor.LogWarning(ex);
_requests.RemoveAt(i--);
}
}
@@ -368,7 +434,6 @@ namespace FlaxEditor.Content.Thumbnails
// Create atlas
if (PreviewsCache.Create(path))
{
// Error
Editor.LogError("Failed to create thumbnails atlas.");
return null;
}
@@ -377,7 +442,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;
}
@@ -449,7 +513,6 @@ namespace FlaxEditor.Content.Thumbnails
for (int i = 0; i < checks; i++)
{
var request = _requests[i];
try
{
if (request.IsReady)
@@ -463,8 +526,9 @@ namespace FlaxEditor.Content.Thumbnails
}
catch (Exception ex)
{
Editor.LogWarning(ex);
Editor.LogWarning($"Failed to prepare thumbnail rendering for {request.Item.ShortName}.");
Editor.LogWarning(ex);
_requests.RemoveAt(i--);
}
}

View File

@@ -104,4 +104,19 @@ bool LinuxPlatformTools::OnDeployBinaries(CookingData& data)
return false;
}
void LinuxPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir)
{
// Pick the first executable file
Array<String> files;
FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
for (auto& file : files)
{
if (FileSystem::GetExtension(file).IsEmpty())
{
executableFile = file;
break;
}
}
}
#endif

View File

@@ -20,6 +20,7 @@ public:
ArchitectureType GetArchitecture() const override;
bool UseSystemDotnet() const override;
bool OnDeployBinaries(CookingData& data) override;
void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override;
};
#endif

View File

@@ -17,9 +17,7 @@
#include "Editor/ProjectInfo.h"
#include "Editor/Cooker/GameCooker.h"
#include "Editor/Utilities/EditorUtilities.h"
#include "pugixml/pugixml_extra.hpp"
#include <ThirdParty/pugixml/pugixml_extra.hpp>
using namespace pugi;
IMPLEMENT_SETTINGS_GETTER(MacPlatformSettings, MacPlatform);
@@ -251,4 +249,19 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
return false;
}
void MacPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir)
{
// Pick the first executable file
Array<String> files;
FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
for (auto& file : files)
{
if (FileSystem::GetExtension(file).IsEmpty())
{
executableFile = file;
break;
}
}
}
#endif

View File

@@ -27,6 +27,7 @@ public:
bool IsNativeCodeFile(CookingData& data, const String& file) override;
void OnBuildStarted(CookingData& data) override;
bool OnPostProcess(CookingData& data) override;
void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override;
};
#endif

View File

@@ -131,7 +131,8 @@ bool DeployDataStep::Perform(CookingData& data)
if (FileSystem::DirectoryExists(dstDotnet))
{
String cachedData;
File::ReadAllText(dotnetCacheFilePath, cachedData);
if (FileSystem::FileExists(dotnetCacheFilePath))
File::ReadAllText(dotnetCacheFilePath, cachedData);
if (cachedData != dotnetCachedValue)
{
FileSystem::DeleteDirectory(dstDotnet);
@@ -360,7 +361,7 @@ bool DeployDataStep::Perform(CookingData& data)
data.AddRootEngineAsset(PRE_INTEGRATED_GF_ASSET_NAME);
data.AddRootEngineAsset(SMAA_AREA_TEX);
data.AddRootEngineAsset(SMAA_SEARCH_TEX);
if (data.Configuration != BuildConfiguration::Release)
if (!buildSettings.SkipDefaultFonts)
data.AddRootEngineAsset(TEXT("Editor/Fonts/Roboto-Regular"));
// Register custom assets (eg. plugins)

View File

@@ -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)

View 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();
}
}
}

View 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;
}
}
}

View File

@@ -1,6 +1,13 @@
using FlaxEditor.CustomEditors.Editors;
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.Actions;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using System.Collections.Generic;
namespace FlaxEditor.CustomEditors.Dedicated;
@@ -10,6 +17,10 @@ namespace FlaxEditor.CustomEditors.Dedicated;
[CustomEditor(typeof(MissingScript)), DefaultEditor]
public class MissingScriptEditor : GenericEditor
{
private DropPanel _dropPanel;
private Button _replaceScriptButton;
private CheckBox _shouldReplaceAllCheckbox;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
@@ -18,9 +29,137 @@ public class MissingScriptEditor : GenericEditor
base.Initialize(layout);
return;
}
_dropPanel = dropPanel;
_dropPanel.HeaderTextColor = Color.OrangeRed;
dropPanel.HeaderTextColor = Color.OrangeRed;
var replaceScriptPanel = new Panel
{
Parent = _dropPanel,
Height = 64,
};
_replaceScriptButton = new Button
{
Text = "Replace Script",
TooltipText = "Replaces the missing script with a given script type",
AnchorPreset = AnchorPresets.TopCenter,
Width = 240,
Height = 24,
X = -120,
Y = 0,
Parent = replaceScriptPanel,
};
_replaceScriptButton.Clicked += OnReplaceScriptButtonClicked;
var replaceAllLabel = new Label
{
Text = "Replace all matching missing scripts",
TooltipText = "Whether or not to apply this script change to all scripts missing the same type.",
AnchorPreset = AnchorPresets.BottomCenter,
Y = -34,
Parent = replaceScriptPanel,
};
replaceAllLabel.X -= FlaxEngine.GUI.Style.Current.FontSmall.MeasureText(replaceAllLabel.Text).X;
_shouldReplaceAllCheckbox = new CheckBox
{
TooltipText = replaceAllLabel.TooltipText,
AnchorPreset = AnchorPresets.BottomCenter,
Y = -34,
Parent = replaceScriptPanel,
};
float centerDifference = (_shouldReplaceAllCheckbox.Right - replaceAllLabel.Left) / 2;
replaceAllLabel.X += centerDifference;
_shouldReplaceAllCheckbox.X += centerDifference;
base.Initialize(layout);
}
private void FindActorsWithMatchingMissingScript(List<MissingScript> missingScripts)
{
foreach (Actor actor in Level.GetActors(typeof(Actor)))
{
for (int scriptIndex = 0; scriptIndex < actor.ScriptsCount; scriptIndex++)
{
Script actorScript = actor.Scripts[scriptIndex];
if (actorScript is not MissingScript missingActorScript)
continue;
MissingScript currentMissing = Values[0] as MissingScript;
if (missingActorScript.MissingTypeName != currentMissing.MissingTypeName)
continue;
missingScripts.Add(missingActorScript);
}
}
}
private void RunReplacementMultiCast(List<IUndoAction> actions)
{
if (actions.Count == 0)
{
Editor.LogWarning("Failed to replace scripts!");
return;
}
var multiAction = new MultiUndoAction(actions);
multiAction.Do();
var presenter = ParentEditor.Presenter;
if (presenter != null)
{
presenter.Undo.AddAction(multiAction);
presenter.Control.Focus();
}
}
private void ReplaceScript(ScriptType script, bool replaceAllInScene)
{
var actions = new List<IUndoAction>(4);
var missingScripts = new List<MissingScript>();
if (!replaceAllInScene)
missingScripts.Add((MissingScript)Values[0]);
else
FindActorsWithMatchingMissingScript(missingScripts);
foreach (var missingScript in missingScripts)
actions.Add(AddRemoveScript.Add(missingScript.Actor, script));
RunReplacementMultiCast(actions);
for (int actionIdx = 0; actionIdx < actions.Count; actionIdx++)
{
AddRemoveScript addRemoveScriptAction = (AddRemoveScript)actions[actionIdx];
int orderInParent = addRemoveScriptAction.GetOrderInParent();
Script newScript = missingScripts[actionIdx].Actor.Scripts[orderInParent];
missingScripts[actionIdx].ReferenceScript = newScript;
}
actions.Clear();
foreach (var missingScript in missingScripts)
actions.Add(AddRemoveScript.Remove(missingScript));
RunReplacementMultiCast(actions);
}
private void OnReplaceScriptButtonClicked()
{
var scripts = Editor.Instance.CodeEditing.Scripts.Get();
if (scripts.Count == 0)
{
// No scripts
var cm1 = new ContextMenu();
cm1.AddButton("No scripts in project");
cm1.Show(_dropPanel, _replaceScriptButton.BottomLeft);
return;
}
// Show context menu with list of scripts to add
var cm = new ItemsListContextMenu(180);
for (int i = 0; i < scripts.Count; i++)
cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i]));
cm.ItemClicked += item => ReplaceScript((ScriptType)item.Tag, _shouldReplaceAllCheckbox.Checked);
cm.SortItems();
cm.Show(_dropPanel, _replaceScriptButton.BottomLeft - new Float2((cm.Width - _replaceScriptButton.Width) / 2, 0));
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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>();

View File

@@ -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;

View File

@@ -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;
}
}
}
}
}

View File

@@ -22,7 +22,7 @@ namespace FlaxEditor.CustomEditors.Elements
/// <summary>
/// [Deprecated on 26.05.2022, expires on 26.05.2024]
/// </summary>
[System.Obsolete("Deprecated in 1.4")]
[System.Obsolete("Deprecated in 1.4, use ValueBox instead")]
public DoubleValueBox DoubleValue => ValueBox;
/// <summary>

View File

@@ -22,7 +22,7 @@ namespace FlaxEditor.CustomEditors.Elements
/// <summary>
/// [Deprecated on 26.05.2022, expires on 26.05.2024]
/// </summary>
[System.Obsolete("Deprecated in 1.4, ValueBox instead")]
[System.Obsolete("Deprecated in 1.4, use ValueBox instead")]
public FloatValueBox FloatValue => ValueBox;
/// <summary>

View File

@@ -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++)
{

View File

@@ -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;
@@ -365,7 +364,7 @@ namespace FlaxEditor
{
foreach (var preview in activePreviews)
{
if (preview == loadingPreview ||
if (preview == loadingPreview ||
(preview.Instance != null && (preview.Instance == control || preview.Instance.HasActorInHierarchy(control))))
{
// Link it to the prefab preview to see it in the editor
@@ -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,6 +931,11 @@ namespace FlaxEditor
/// The <see cref="FlaxEngine.Animation"/>.
/// </summary>
Animation = 11,
/// <summary>
/// The <see cref="FlaxEngine.BehaviorTree"/>.
/// </summary>
BehaviorTree = 12,
}
/// <summary>

View File

@@ -482,8 +482,8 @@ namespace FlaxEditor.GUI
Focus();
});
if (_selected != null)
{
var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path);
{
var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path);
popup.ScrollToAndHighlightItemByName(selectedAssetName);
}
}

View File

@@ -72,7 +72,7 @@ namespace FlaxEditor.GUI.ContextMenu
// Hide parent CM popups and set itself as child
parentContextMenu.ShowChild(ContextMenu, PointToParent(ParentContextMenu, new Float2(Width, 0)));
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{

View File

@@ -319,7 +319,9 @@ namespace FlaxEditor.GUI.Dialogs
protected override void OnShow()
{
// Auto cancel on lost focus
#if !PLATFORM_LINUX
((WindowRootControl)Root).Window.LostFocus += OnCancel;
#endif
base.OnShow();
}

View File

@@ -293,7 +293,7 @@ namespace FlaxEditor.GUI.Dialogs
if (Root != null)
{
bool shiftDown = Root.GetKey(KeyboardKeys.Shift);
Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next);
Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next);
}
return true;
}

View File

@@ -44,11 +44,11 @@ namespace FlaxEditor.GUI.Docking
var mousePos = window.MousePosition;
var previousSize = window.Size;
window.Restore();
window.Position = FlaxEngine.Input.MouseScreenPosition - mousePos * window.Size / previousSize;
window.Position = Platform.MousePosition - mousePos * window.Size / previousSize;
}
// Calculate dragging offset and move window to the destination position
var mouseScreenPosition = FlaxEngine.Input.MouseScreenPosition;
var mouseScreenPosition = Platform.MousePosition;
// If the _toMove window was not focused when initializing this window, the result vector only contains zeros
// and to prevent a failure, we need to perform an update for the drag offset at later time which will be done in the OnMouseMove event handler.
@@ -83,6 +83,7 @@ namespace FlaxEditor.GUI.Docking
// Enable hit window presentation
Proxy.Window.RenderingEnabled = true;
Proxy.Window.Show();
Proxy.Window.Focus();
}
/// <summary>
@@ -113,7 +114,7 @@ namespace FlaxEditor.GUI.Docking
var window = _toMove.Window?.Window;
if (window == null)
return;
var mouse = FlaxEngine.Input.MouseScreenPosition;
var mouse = Platform.MousePosition;
// Move base window
window.Position = mouse - _dragOffset;
@@ -193,7 +194,7 @@ namespace FlaxEditor.GUI.Docking
// Move window to the mouse position (with some offset for caption bar)
var window = (WindowRootControl)toMove.Root;
var mouse = FlaxEngine.Input.MouseScreenPosition;
var mouse = Platform.MousePosition;
window.Window.Position = mouse - new Float2(8, 8);
// Get floating panel
@@ -244,7 +245,7 @@ namespace FlaxEditor.GUI.Docking
private void UpdateRects()
{
// Cache mouse position
_mouse = FlaxEngine.Input.MouseScreenPosition;
_mouse = Platform.MousePosition;
// Check intersection with any dock panel
var uiMouse = _mouse;
@@ -270,15 +271,16 @@ namespace FlaxEditor.GUI.Docking
// Cache dock rectangles
var size = _rectDock.Size;
var offset = _rectDock.Location;
float BorderMargin = 4.0f;
float ProxyHintWindowsSize2 = Proxy.HintWindowsSize * 0.5f;
float centerX = size.X * 0.5f;
float centerY = size.Y * 0.5f;
_rUpper = new Rectangle(centerX - ProxyHintWindowsSize2, BorderMargin, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
_rBottom = new Rectangle(centerX - ProxyHintWindowsSize2, size.Y - Proxy.HintWindowsSize - BorderMargin, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
_rLeft = new Rectangle(BorderMargin, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
_rRight = new Rectangle(size.X - Proxy.HintWindowsSize - BorderMargin, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
_rCenter = new Rectangle(centerX - ProxyHintWindowsSize2, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
var borderMargin = 4.0f;
var hintWindowsSize = Proxy.HintWindowsSize * Platform.DpiScale;
var hintWindowsSize2 = hintWindowsSize * 0.5f;
var centerX = size.X * 0.5f;
var centerY = size.Y * 0.5f;
_rUpper = new Rectangle(centerX - hintWindowsSize2, borderMargin, hintWindowsSize, hintWindowsSize) + offset;
_rBottom = new Rectangle(centerX - hintWindowsSize2, size.Y - hintWindowsSize - borderMargin, hintWindowsSize, hintWindowsSize) + offset;
_rLeft = new Rectangle(borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
_rRight = new Rectangle(size.X - hintWindowsSize - borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
_rCenter = new Rectangle(centerX - hintWindowsSize2, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
// Hit test
DockState toSet = DockState.Float;
@@ -428,7 +430,6 @@ namespace FlaxEditor.GUI.Docking
{
if (Window == null)
{
// Create proxy window
var settings = CreateWindowSettings.Default;
settings.Title = "DockHint.Window";
settings.Size = initSize;
@@ -440,12 +441,10 @@ namespace FlaxEditor.GUI.Docking
settings.IsRegularWindow = false;
settings.SupportsTransparency = true;
settings.ShowInTaskbar = false;
settings.ShowAfterFirstPaint = true;
settings.ShowAfterFirstPaint = false;
settings.IsTopmost = true;
Window = Platform.CreateWindow(ref settings);
// Set opacity and background color
Window.Opacity = 0.6f;
Window.GUI.BackgroundColor = Style.Current.DragWindow;
}
@@ -465,7 +464,7 @@ namespace FlaxEditor.GUI.Docking
var settings = CreateWindowSettings.Default;
settings.Title = name;
settings.Size = new Float2(HintWindowsSize);
settings.Size = new Float2(HintWindowsSize * Platform.DpiScale);
settings.AllowInput = false;
settings.AllowMaximize = false;
settings.AllowMinimize = false;
@@ -479,7 +478,6 @@ namespace FlaxEditor.GUI.Docking
settings.ShowAfterFirstPaint = false;
win = Platform.CreateWindow(ref settings);
win.Opacity = 0.6f;
win.GUI.BackgroundColor = Style.Current.DragWindow;
}

View File

@@ -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;
}
}

View File

@@ -20,7 +20,7 @@ namespace FlaxEditor.GUI.Input
: this(false, 0, 0)
{
}
/// <summary>
/// Init search box
/// </summary>
@@ -28,7 +28,7 @@ namespace FlaxEditor.GUI.Input
: base(isMultiline, x, y, width)
{
WatermarkText = "Search...";
ClearSearchButton = new Button
{
Parent = this,

View File

@@ -182,6 +182,7 @@ namespace FlaxEditor.GUI.Input
}
SlidingEnd?.Invoke();
Defocus();
Parent?.Focus();
}
/// <inheritdoc />

View File

@@ -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;

View File

@@ -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));
}
}

View File

@@ -241,7 +241,7 @@ namespace FlaxEditor.GUI
{
DoubleClick?.Invoke();
RowDoubleClick?.Invoke(this);
return base.OnMouseDoubleClick(location, button);
}

View File

@@ -191,6 +191,8 @@ namespace FlaxEditor.GUI.Tabs
get => _autoTabsSizeAuto;
set
{
if (_autoTabsSizeAuto == value)
return;
_autoTabsSizeAuto = value;
PerformLayout();
}
@@ -204,11 +206,11 @@ namespace FlaxEditor.GUI.Tabs
get => _orientation;
set
{
if (_orientation == value)
return;
_orientation = value;
if (UseScroll)
TabsPanel.ScrollBars = _orientation == Orientation.Horizontal ? ScrollBars.Horizontal : ScrollBars.Vertical;
PerformLayout();
}
}
@@ -402,6 +404,14 @@ namespace FlaxEditor.GUI.Tabs
tabHeader.Size = tabsSize;
}
}
else if (UseScroll)
{
// If scroll bar is visible it covers part of the tab header so include this in tab size to improve usability
if (_orientation == Orientation.Horizontal && TabsPanel.HScrollBar.Visible)
tabsSize.Y += TabsPanel.HScrollBar.Height;
else if (_orientation == Orientation.Vertical && TabsPanel.VScrollBar.Visible)
tabsSize.X += TabsPanel.VScrollBar.Width;
}
// Fit the tabs panel
TabsPanel.Size = _orientation == Orientation.Horizontal

View File

@@ -776,11 +776,20 @@ namespace FlaxEditor.GUI.Tree
// Check if mouse hits arrow
if (_mouseOverArrow && HasAnyVisibleChild)
{
// Toggle open state
if (_opened)
Collapse();
if (ParentTree.Root.GetKey(KeyboardKeys.Alt))
{
if (_opened)
CollapseAll();
else
ExpandAll();
}
else
Expand();
{
if (_opened)
Collapse();
else
Expand();
}
}
// Check if mouse hits bar

View File

@@ -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.");
}
}
}

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using FlaxEngine;
namespace FlaxEditor.Gizmo
@@ -10,6 +11,11 @@ namespace FlaxEditor.Gizmo
[HideInEditor]
public interface IGizmoOwner
{
/// <summary>
/// Gets the gizmos collection.
/// </summary>
FlaxEditor.Viewport.EditorViewport Viewport { get; }
/// <summary>
/// Gets the gizmos collection.
/// </summary>
@@ -94,5 +100,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);
}
}

View File

@@ -162,10 +162,23 @@ namespace FlaxEditor.Gizmo
// Scale gizmo to fit on-screen
Vector3 position = Position;
Vector3 vLength = Owner.ViewPosition - position;
float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize;
_screenScale = (float)(vLength.Length / GizmoScaleFactor * gizmoSize);
if (Owner.Viewport.UseOrthographicProjection)
{
//[hack] this is far form ideal the View Position is in wrong location, any think using the View Position will have problem
//the camera system needs rewrite the to be a camera on springarm, similar how the ArcBallCamera is handled
//the ortho projection cannot exist with fps camera because there is no
// - focus point to calculate correct View Position with Orthographic Scale as a reference and Orthographic Scale from View Position
// with make the camera jump
// - and deaph so w and s movment in orto mode moves the cliping plane now
float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize;
_screenScale = gizmoSize * (50 * Owner.Viewport.OrthographicScale);
}
else
{
Vector3 vLength = Owner.ViewPosition - position;
float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize;
_screenScale = (float)(vLength.Length / GizmoScaleFactor * gizmoSize);
}
// Setup world
Quaternion orientation = GetSelectedObject(0).Orientation;
_gizmoWorld = new Transform(position, orientation, new Float3(_screenScale));

View File

@@ -184,6 +184,7 @@ enum class NewAssetType
ParticleEmitterFunction = 9,
AnimationGraphFunction = 10,
Animation = 11,
BehaviorTree = 12,
};
DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateAsset(NewAssetType type, MString* outputPathObj)
@@ -227,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;
}
@@ -509,7 +513,9 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_RunVisualScriptBreakpointLoopTick(floa
WindowsManager::WindowsLocker.Unlock();
}
WindowsManager::WindowsLocker.Lock();
for (auto& win : WindowsManager::Windows)
Array<Window*, InlinedAllocation<32>> windows;
windows.Add(WindowsManager::Windows);
for (Window* win : windows)
{
if (win->IsVisible())
win->OnUpdate(deltaTime);

View File

@@ -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;
@@ -660,8 +649,6 @@ namespace FlaxEditor.Modules
// Special case for folders
if (item is ContentFolder folder)
{
// TODO: maybe don't remove folders recursive but at once?
// Delete all children
if (folder.Children.Count > 0)
{
@@ -675,13 +662,15 @@ namespace FlaxEditor.Modules
// Remove directory
if (deletedByUser && Directory.Exists(path))
{
// Flush files removal before removing folder (loaded assets remove file during object destruction in Asset::OnDeleteObject)
FlaxEngine.Scripting.FlushRemovedObjects();
try
{
Directory.Delete(path, true);
}
catch (Exception ex)
{
// Error
Editor.LogWarning(ex);
Editor.LogWarning(string.Format("Cannot remove folder \'{0}\'", path));
return;
@@ -822,10 +811,9 @@ namespace FlaxEditor.Modules
{
if (node == null)
return;
// Temporary data
var folder = node.Folder;
var path = folder.Path;
var canHaveAssets = node.CanHaveAssets;
if (_isDuringFastSetup)
{
@@ -844,20 +832,38 @@ namespace FlaxEditor.Modules
var child = folder.Children[i];
if (!child.Exists)
{
// Send info
// Item doesn't exist anymore
Editor.Log(string.Format($"Content item \'{child.Path}\' has been removed"));
// Destroy it
Delete(child, false);
i--;
}
else if (canHaveAssets && child is AssetItem childAsset)
{
// Check if asset type doesn't match the item proxy (eg. item reimported as Material Instance instead of Material)
if (FlaxEngine.Content.GetAssetInfo(child.Path, out var assetInfo))
{
bool changed = assetInfo.ID != childAsset.ID;
if (!changed && assetInfo.TypeName != childAsset.TypeName)
{
// Use proxy check (eg. scene asset might accept different typename than AssetInfo reports)
var proxy = GetAssetProxy(childAsset.TypeName, child.Path);
if (proxy == null)
proxy = GetAssetProxy(assetInfo.TypeName, child.Path);
changed = !proxy.AcceptsAsset(assetInfo.TypeName, child.Path);
}
if (changed)
{
OnAssetTypeInfoChanged(childAsset, ref assetInfo);
i--;
}
}
}
}
}
// Find files
var files = Directory.GetFiles(path, "*.*", SearchOption.TopDirectoryOnly);
if (node.CanHaveAssets)
if (canHaveAssets)
{
LoadAssets(node, files);
}
@@ -1072,6 +1078,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>());
@@ -1145,17 +1152,19 @@ namespace FlaxEditor.Modules
RebuildInternal();
Editor.ContentImporting.ImportFileEnd += ContentImporting_ImportFileDone;
Editor.ContentImporting.ImportFileEnd += (obj, failed) =>
{
var path = obj.ResultUrl;
if (!failed)
FlaxEngine.Scripting.InvokeOnUpdate(() => OnImportFileDone(path));
};
_enableEvents = true;
}
private void ContentImporting_ImportFileDone(IFileEntryAction obj, bool failed)
private void OnImportFileDone(string path)
{
if (failed)
return;
// Check if already has that element
var item = Find(obj.ResultUrl);
var item = Find(path);
if (item is BinaryAssetItem binaryAssetItem)
{
// Get asset info from the registry (content layer will update cache it just after import)
@@ -1165,19 +1174,8 @@ namespace FlaxEditor.Modules
// For eg. change texture to sprite atlas on reimport
if (binaryAssetItem.TypeName != assetInfo.TypeName)
{
// Asset type has been changed!
Editor.LogWarning(string.Format("Asset \'{0}\' changed type from {1} to {2}", item.Path, binaryAssetItem.TypeName, assetInfo.TypeName));
Editor.Windows.CloseAllEditors(item);
// Remove this item from the database and some related data
var toRefresh = binaryAssetItem.ParentFolder;
binaryAssetItem.Dispose();
toRefresh.Children.Remove(binaryAssetItem);
if (!binaryAssetItem.HasDefaultThumbnail)
{
// Delete old thumbnail and remove it from the cache
Editor.Instance.Thumbnails.DeletePreview(binaryAssetItem);
}
OnAssetTypeInfoChanged(binaryAssetItem, ref assetInfo);
// Refresh the parent folder to find the new asset (it should have different type or some other format)
RefreshFolder(toRefresh, false);
@@ -1194,6 +1192,23 @@ namespace FlaxEditor.Modules
}
}
private void OnAssetTypeInfoChanged(AssetItem assetItem, ref AssetInfo assetInfo)
{
// Asset type has been changed!
Editor.LogWarning(string.Format("Asset \'{0}\' changed type from {1} to {2}", assetItem.Path, assetItem.TypeName, assetInfo.TypeName));
Editor.Windows.CloseAllEditors(assetItem);
// Remove this item from the database and some related data
assetItem.Dispose();
assetItem.ParentFolder.Children.Remove(assetItem);
// Delete old thumbnail and remove it from the cache
if (!assetItem.HasDefaultThumbnail)
{
Editor.Instance.Thumbnails.DeletePreview(assetItem);
}
}
internal void OnDirectoryEvent(MainContentTreeNode node, FileSystemEventArgs e)
{
// Ensure to be ready for external events

View File

@@ -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>
@@ -90,7 +104,7 @@ namespace FlaxEditor.Modules
hint = "Too long name.";
return false;
}
if (item.IsFolder && shortName.EndsWith("."))
{
hint = "Name cannot end with '.'";

View File

@@ -55,7 +55,7 @@ namespace FlaxEditor.Modules
public event Action ImportingQueueBegin;
/// <summary>
/// Occurs when file is being imported.
/// Occurs when file is being imported. Can be called on non-main thread.
/// </summary>
public event Action<IFileEntryAction> ImportFileBegin;
@@ -67,12 +67,12 @@ namespace FlaxEditor.Modules
public delegate void ImportFileEndDelegate(IFileEntryAction entry, bool failed);
/// <summary>
/// Occurs when file importing end.
/// Occurs when file importing end. Can be called on non-main thread.
/// </summary>
public event ImportFileEndDelegate ImportFileEnd;
/// <summary>
/// Occurs when assets importing ends.
/// Occurs when assets importing ends. Can be called on non-main thread.
/// </summary>
public event Action ImportingQueueEnd;

View File

@@ -133,7 +133,7 @@ namespace FlaxEditor.Modules
return;
var actorsList = new List<Actor>();
Utilities.Utils.GetActorsTree(actorsList, actor);
var actions = new IUndoAction[actorsList.Count];
for (int i = 0; i < actorsList.Count; i++)
actions[i] = BreakPrefabLinkAction.Linked(actorsList[i]);

View File

@@ -453,7 +453,7 @@ namespace FlaxEditor.Modules
{
Editor.Windows.SceneWin.Focus();
}
// fix scene window layout
Editor.Windows.SceneWin.PerformLayout();
Editor.Windows.SceneWin.PerformLayout();
@@ -520,7 +520,7 @@ namespace FlaxEditor.Modules
Undo.AddAction(new MultiUndoAction(pasteAction, selectAction));
OnSelectionChanged();
}
// Scroll to new selected node while pasting
Editor.Windows.SceneWin.ScrollToSelectedNode();
}
@@ -620,7 +620,7 @@ namespace FlaxEditor.Modules
Undo.AddAction(new MultiUndoAction(undoActions));
OnSelectionChanged();
}
// Scroll to new selected node while duplicating
Editor.Windows.SceneWin.ScrollToSelectedNode();
}

View File

@@ -332,7 +332,7 @@ namespace FlaxEditor.Modules
continue;
scenes.Add(s);
}
// In play-mode Editor mocks the level streaming script
if (Editor.IsPlayMode)
{

View File

@@ -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;
@@ -27,10 +29,10 @@ namespace FlaxEditor.Modules.SourceCodeEditing
private static bool CheckFunc(ScriptType scriptType)
{
if (scriptType.IsStatic ||
scriptType.IsGenericType ||
!scriptType.IsPublic ||
scriptType.HasAttribute(typeof(HideInEditorAttribute), true) ||
if (scriptType.IsStatic ||
scriptType.IsGenericType ||
!scriptType.IsPublic ||
scriptType.HasAttribute(typeof(HideInEditorAttribute), true) ||
scriptType.HasAttribute(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false))
return false;
var managedType = TypeUtils.GetType(scriptType);
@@ -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,15 +332,89 @@ 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()
{
base.OnUpdate();
// Automatic project files generation after workspace modifications
if (_autoGenerateScriptsProjectFiles && ScriptsBuilder.IsSourceWorkspaceDirty)
if (_autoGenerateScriptsProjectFiles && ScriptsBuilder.IsSourceWorkspaceDirty && !ScriptsBuilder.IsCompiling)
{
Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync();
// Try to delay generation when a lot of files are added at once
if (ScriptsBuilder.IsSourceDirtyFor(TimeSpan.FromMilliseconds(150)))
Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync();
}
}
@@ -361,6 +442,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing
Scripts.ClearTypes();
Controls.ClearTypes();
AnimGraphNodes.ClearTypes();
BehaviorTreeNodes.ClearTypes();
TypesCleared?.Invoke();
}

View File

@@ -2,7 +2,6 @@
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI;
@@ -11,7 +10,6 @@ using FlaxEditor.GUI.Dialogs;
using FlaxEditor.GUI.Input;
using FlaxEditor.Progress.Handlers;
using FlaxEditor.SceneGraph;
using FlaxEditor.SceneGraph.Actors;
using FlaxEditor.Utilities;
using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Windows;
@@ -208,6 +206,7 @@ namespace FlaxEditor.Modules
_toolStripScale.Checked = gizmoMode == TransformGizmoBase.Mode.Scale;
//
_toolStripBuildScenes.Enabled = (canEditScene && !isPlayMode) || Editor.StateMachine.BuildingScenesState.IsActive;
_toolStripBuildScenes.Visible = Editor.Options.Options.General.BuildActions?.Length != 0;
_toolStripCook.Enabled = Editor.Windows.GameCookerWin.CanBuild(Platform.PlatformType) && !GameCooker.IsRunning;
//
var play = _toolStripPlay;
@@ -299,7 +298,7 @@ namespace FlaxEditor.Modules
else
text = "Ready";
if(ProgressVisible)
if (ProgressVisible)
{
color = Style.Current.Statusbar.Loading;
}
@@ -402,7 +401,7 @@ namespace FlaxEditor.Modules
{
UpdateStatusBar();
}
else if(ProgressVisible)
else if (ProgressVisible)
{
UpdateStatusBar();
}
@@ -557,7 +556,7 @@ namespace FlaxEditor.Modules
cm.AddButton("Game Settings", () =>
{
var item = Editor.ContentDatabase.Find(GameSettings.GameSettingsAssetPath);
if(item != null)
if (item != null)
Editor.ContentEditing.Open(item);
});
@@ -653,7 +652,7 @@ namespace FlaxEditor.Modules
cm.AddButton("Information about Flax", () => new AboutDialog().Show());
}
private void OnOptionsChanged(FlaxEditor.Options.EditorOptions options)
private void OnOptionsChanged(EditorOptions options)
{
var inputOptions = options.Input;
@@ -688,6 +687,8 @@ namespace FlaxEditor.Modules
_menuToolsTakeScreenshot.ShortKeys = inputOptions.TakeScreenshot.ToString();
MainMenuShortcutKeysUpdated?.Invoke();
UpdateToolstrip();
}
private void InitToolstrip(RootControl mainWindow)
@@ -709,11 +710,11 @@ namespace FlaxEditor.Modules
_toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip($"Change Gizmo tool mode to Scale ({inputOptions.ScaleMode})");
ToolStrip.AddSeparator();
// Cook scenes
// Build scenes
_toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip($"Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options ({inputOptions.BuildScenesData})");
// Cook and run
_toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip($"Cook & Run - build game for the current platform and run it locally ({inputOptions.Play})");
_toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip($"Cook & Run - build game for the current platform and run it locally ({inputOptions.CookAndRun})");
_toolStripCook.ContextMenu = new ContextMenu();
_toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked);
_toolStripCook.ContextMenu.AddSeparator();

View File

@@ -237,7 +237,11 @@ namespace FlaxEditor.Modules
/// </summary>
public void LoadDefaultLayout()
{
LoadLayout(StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/LayoutDefault.xml"));
var path = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/LayoutDefault.xml");
if (File.Exists(path))
{
LoadLayout(path);
}
}
/// <summary>
@@ -276,9 +280,6 @@ namespace FlaxEditor.Modules
// Get metadata
int version = int.Parse(root.Attributes["Version"].Value, CultureInfo.InvariantCulture);
var virtualDesktopBounds = Platform.VirtualDesktopBounds;
var virtualDesktopSafeLeftCorner = virtualDesktopBounds.Location;
var virtualDesktopSafeRightCorner = virtualDesktopBounds.BottomRight;
switch (version)
{
@@ -288,31 +289,9 @@ namespace FlaxEditor.Modules
if (MainWindow)
{
var mainWindowNode = root["MainWindow"];
Rectangle bounds = LoadBounds(mainWindowNode["Bounds"]);
bool isMaximized = bool.Parse(mainWindowNode.GetAttribute("IsMaximized"));
// Clamp position to match current desktop dimensions (if window was on desktop that is now inactive)
if (bounds.X < virtualDesktopSafeLeftCorner.X || bounds.Y < virtualDesktopSafeLeftCorner.Y || bounds.X > virtualDesktopSafeRightCorner.X || bounds.Y > virtualDesktopSafeRightCorner.Y)
bounds.Location = virtualDesktopSafeLeftCorner;
if (isMaximized)
{
if (MainWindow.IsMaximized)
MainWindow.Restore();
MainWindow.ClientPosition = bounds.Location;
MainWindow.Maximize();
}
else
{
if (Mathf.Min(bounds.Size.X, bounds.Size.Y) >= 1)
{
MainWindow.ClientBounds = bounds;
}
else
{
MainWindow.ClientPosition = bounds.Location;
}
}
bool isMaximized = true, isMinimized = false;
Rectangle bounds = LoadBounds(mainWindowNode, ref isMaximized, ref isMinimized);
LoadWindow(MainWindow, ref bounds, isMaximized, false);
}
// Load master panel structure
@@ -332,11 +311,13 @@ namespace FlaxEditor.Modules
continue;
// Get window properties
Rectangle bounds = LoadBounds(child["Bounds"]);
bool isMaximized = false, isMinimized = false;
Rectangle bounds = LoadBounds(child, ref isMaximized, ref isMinimized);
// Create window and floating dock panel
var window = FloatWindowDockPanel.CreateFloatWindow(MainWindow.GUI, bounds.Location, bounds.Size, WindowStartPosition.Manual, string.Empty);
var panel = new FloatWindowDockPanel(masterPanel, window.GUI);
LoadWindow(panel.Window.Window, ref bounds, isMaximized, isMinimized);
// Load structure
LoadPanel(child, panel);
@@ -493,23 +474,67 @@ namespace FlaxEditor.Modules
private static void SaveBounds(XmlWriter writer, Window win)
{
var bounds = win.ClientBounds;
writer.WriteAttributeString("X", bounds.X.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("Y", bounds.Y.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("Width", bounds.Width.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("Height", bounds.Height.ToString(CultureInfo.InvariantCulture));
writer.WriteStartElement("Bounds");
{
var bounds = win.ClientBounds;
writer.WriteAttributeString("X", bounds.X.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("Y", bounds.Y.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("Width", bounds.Width.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("Height", bounds.Height.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("IsMaximized", win.IsMaximized.ToString());
writer.WriteAttributeString("IsMinimized", win.IsMinimized.ToString());
}
writer.WriteEndElement();
}
private static Rectangle LoadBounds(XmlElement node)
private static Rectangle LoadBounds(XmlElement node, ref bool isMaximized, ref bool isMinimized)
{
float x = float.Parse(node.GetAttribute("X"), CultureInfo.InvariantCulture);
float y = float.Parse(node.GetAttribute("Y"), CultureInfo.InvariantCulture);
float width = float.Parse(node.GetAttribute("Width"), CultureInfo.InvariantCulture);
float height = float.Parse(node.GetAttribute("Height"), CultureInfo.InvariantCulture);
var bounds = node["Bounds"];
var isMaximizedText = bounds.GetAttribute("IsMaximized");
if (!string.IsNullOrEmpty(isMaximizedText))
isMaximized = bool.Parse(isMaximizedText);
var isMinimizedText = bounds.GetAttribute("IsMinimized");
if (!string.IsNullOrEmpty(isMinimizedText))
isMinimized = bool.Parse(isMinimizedText);
float x = float.Parse(bounds.GetAttribute("X"), CultureInfo.InvariantCulture);
float y = float.Parse(bounds.GetAttribute("Y"), CultureInfo.InvariantCulture);
float width = float.Parse(bounds.GetAttribute("Width"), CultureInfo.InvariantCulture);
float height = float.Parse(bounds.GetAttribute("Height"), CultureInfo.InvariantCulture);
return new Rectangle(x, y, width, height);
}
private static void LoadWindow(Window win, ref Rectangle bounds, bool isMaximized, bool isMinimized)
{
var virtualDesktopBounds = Platform.VirtualDesktopBounds;
var virtualDesktopSafeLeftCorner = virtualDesktopBounds.Location;
var virtualDesktopSafeRightCorner = virtualDesktopBounds.BottomRight;
// Clamp position to match current desktop dimensions (if window was on desktop that is now inactive)
if (bounds.X < virtualDesktopSafeLeftCorner.X || bounds.Y < virtualDesktopSafeLeftCorner.Y || bounds.X > virtualDesktopSafeRightCorner.X || bounds.Y > virtualDesktopSafeRightCorner.Y)
bounds.Location = virtualDesktopSafeLeftCorner;
if (isMaximized)
{
if (win.IsMaximized)
win.Restore();
win.ClientPosition = bounds.Location;
win.Maximize();
}
else
{
if (Mathf.Min(bounds.Size.X, bounds.Size.Y) >= 1)
{
win.ClientBounds = bounds;
}
else
{
win.ClientPosition = bounds.Location;
}
if (isMinimized)
win.Minimize();
}
}
private class LayoutNameDialog : Dialog
{
private TextBox _textbox;
@@ -609,13 +634,8 @@ namespace FlaxEditor.Modules
if (MainWindow)
{
writer.WriteStartElement("MainWindow");
writer.WriteAttributeString("IsMaximized", MainWindow.IsMaximized.ToString());
writer.WriteStartElement("Bounds");
SaveBounds(writer, MainWindow);
writer.WriteEndElement();
writer.WriteEndElement();
}
// Master panel structure
@@ -628,22 +648,13 @@ namespace FlaxEditor.Modules
{
var panel = masterPanel.FloatingPanels[i];
var window = panel.Window;
if (window == null)
{
Editor.LogWarning("Floating panel has missing window");
continue;
}
writer.WriteStartElement("Float");
SavePanel(writer, panel);
writer.WriteStartElement("Bounds");
SaveBounds(writer, window.Window);
writer.WriteEndElement();
writer.WriteEndElement();
}
writer.WriteEndElement();

View File

@@ -117,7 +117,7 @@ namespace FlaxEditor.Options
/// <summary>
/// Gets or sets the sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh).
/// </summary>
[EditorDisplay("General"), EditorOrder(200), Tooltip("The sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh).")]
[EditorDisplay("General"), EditorOrder(200), ExpandGroups, Tooltip("The sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh).")]
public BuildAction[] BuildActions { get; set; } =
{
BuildAction.CSG,

View File

@@ -244,11 +244,11 @@ namespace FlaxEditor.Options
CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC),
ProgressNormal = Color.FromBgra(0xFF0ad328),
Statusbar = new Style.StatusbarStyle()
Statusbar = new Style.StatusbarStyle
{
PlayMode = Color.FromBgra(0xFF2F9135),
Failed = Color.FromBgra(0xFF9C2424),
Loading = Color.FromBgra(0xFF2D2D30)
Loading = Color.FromBgra(0xFF2D2D30),
},
// Fonts
@@ -271,7 +271,7 @@ namespace FlaxEditor.Options
Scale = Editor.Icons.Scale32,
Scalar = Editor.Icons.Scalar32,
SharedTooltip = new Tooltip()
SharedTooltip = new Tooltip(),
};
style.DragWindow = style.BackgroundSelected * 0.7f;

View File

@@ -19,25 +19,25 @@ namespace FlaxEditor.Progress.Handlers
public ImportAssetsProgress()
{
var importing = Editor.Instance.ContentImporting;
importing.ImportingQueueBegin += OnStart;
importing.ImportingQueueEnd += OnEnd;
importing.ImportingQueueBegin += () => FlaxEngine.Scripting.InvokeOnUpdate(OnStart);
importing.ImportingQueueEnd += () => FlaxEngine.Scripting.InvokeOnUpdate(OnEnd);
importing.ImportFileBegin += OnImportFileBegin;
}
private void OnImportFileBegin(IFileEntryAction importFileEntry)
{
string info;
if (importFileEntry is ImportFileEntry)
_currentInfo = string.Format("Importing \'{0}\'", System.IO.Path.GetFileName(importFileEntry.SourceUrl));
info = string.Format("Importing \'{0}\'", System.IO.Path.GetFileName(importFileEntry.SourceUrl));
else
_currentInfo = string.Format("Creating \'{0}\'", importFileEntry.SourceUrl);
UpdateProgress();
}
private void UpdateProgress()
{
var importing = Editor.Instance.ContentImporting;
var info = string.Format("{0} ({1}/{2})...", _currentInfo, importing.ImportBatchDone, importing.ImportBatchSize);
OnUpdate(importing.ImportingProgress, info);
info = string.Format("Creating \'{0}\'", importFileEntry.SourceUrl);
FlaxEngine.Scripting.InvokeOnUpdate(() =>
{
_currentInfo = info;
var importing = Editor.Instance.ContentImporting;
var text = string.Format("{0} ({1}/{2})...", _currentInfo, importing.ImportBatchDone, importing.ImportBatchSize);
OnUpdate(importing.ImportingProgress, text);
});
}
}
}

View File

@@ -16,7 +16,7 @@ namespace FlaxEditor.Progress
/// </summary>
/// <param name="handler">The calling handler.</param>
public delegate void ProgressDelegate(ProgressHandler handler);
/// <summary>
/// Progress failed handler event delegate
/// </summary>
@@ -127,7 +127,7 @@ namespace FlaxEditor.Progress
{
if (!_isActive)
throw new InvalidOperationException("Already ended.");
_isActive = false;
_progress = 0;
_infoText = string.Empty;

View File

@@ -66,7 +66,8 @@ namespace FlaxEditor.SceneGraph.GUI
_orderInParent = actor.OrderInParent;
Visible = (actor.HideFlags & HideFlags.HideInHierarchy) == 0;
var id = actor.ID;
// Pick the correct id when inside a prefab window.
var id = actor.HasPrefabLink && actor.Scene == null ? actor.PrefabObjectID : actor.ID;
if (Editor.Instance.ProjectCache.IsExpandedActor(ref id))
{
Expand(true);
@@ -171,7 +172,8 @@ namespace FlaxEditor.SceneGraph.GUI
// Restore cached state on query filter clear
if (noFilter && actor != null)
{
var id = actor.ID;
// Pick the correct id when inside a prefab window.
var id = actor.HasPrefabLink && actor.Scene.Scene == null ? actor.PrefabObjectID : actor.ID;
isExpanded = Editor.Instance.ProjectCache.IsExpandedActor(ref id);
}
@@ -301,10 +303,12 @@ namespace FlaxEditor.SceneGraph.GUI
protected override void OnExpandedChanged()
{
base.OnExpandedChanged();
var actor = Actor;
if (!IsLayoutLocked && Actor)
if (!IsLayoutLocked && actor)
{
var id = Actor.ID;
// Pick the correct id when inside a prefab window.
var id = actor.HasPrefabLink && actor.Scene == null ? actor.PrefabObjectID : actor.ID;
Editor.Instance.ProjectCache.SetExpandedActor(ref id, IsExpanded);
}
}

View File

@@ -286,81 +286,17 @@ namespace VisualStudio
return "Visual Studio open timout";
}
static ComPtr<EnvDTE::ProjectItem> FindItem(const ComPtr<EnvDTE::ProjectItems>& projectItems, BSTR filePath)
{
long count;
projectItems->get_Count(&count);
if (count == 0)
return nullptr;
for (long i = 1; i <= count; i++) // They are counting from [1..Count]
{
ComPtr<EnvDTE::ProjectItem> projectItem;
projectItems->Item(_variant_t(i), &projectItem);
if (!projectItem)
continue;
short fileCount = 0;
projectItem->get_FileCount(&fileCount);
for (short fileIndex = 1; fileIndex <= fileCount; fileIndex++)
{
_bstr_t filename;
projectItem->get_FileNames(fileIndex, filename.GetAddress());
if (filename.GetBSTR() != nullptr && AreFilePathsEqual(filePath, filename))
{
return projectItem;
}
}
ComPtr<EnvDTE::ProjectItems> childProjectItems;
projectItem->get_ProjectItems(&childProjectItems);
if (childProjectItems)
{
ComPtr<EnvDTE::ProjectItem> result = FindItem(childProjectItems, filePath);
if (result)
return result;
}
}
return nullptr;
}
static ComPtr<EnvDTE::ProjectItem> FindItem(const ComPtr<EnvDTE::_Solution>& solution, BSTR filePath)
{
static ComPtr<EnvDTE::ProjectItem> FindItem(const ComPtr<EnvDTE::_Solution>& solution, BSTR filePath)
{
HRESULT result;
ComPtr<EnvDTE::Projects> projects;
result = solution->get_Projects(&projects);
ComPtr<EnvDTE::ProjectItem> projectItem;
result = solution->FindProjectItem(filePath, &projectItem);
if (FAILED(result))
return nullptr;
long projectsCount = 0;
result = projects->get_Count(&projectsCount);
if (FAILED(result))
return nullptr;
for (long projectIndex = 1; projectIndex <= projectsCount; projectIndex++) // They are counting from [1..Count]
{
ComPtr<EnvDTE::Project> project;
result = projects->Item(_variant_t(projectIndex), &project);
if (FAILED(result) || !project)
continue;
ComPtr<EnvDTE::ProjectItems> projectItems;
result = project->get_ProjectItems(&projectItems);
if (FAILED(result) || !projectItems)
continue;
auto projectItem = FindItem(projectItems, filePath);
if (projectItem)
{
return projectItem;
}
}
return nullptr;
}
return projectItem;
}
// Opens a file on a specific line in a running Visual Studio instance.
//

View File

@@ -170,7 +170,7 @@ bool ScriptsBuilder::IsSourceWorkspaceDirty()
bool ScriptsBuilder::IsSourceDirtyFor(const TimeSpan& timeout)
{
ScopeLock scopeLock(_locker);
return _lastSourceCodeEdited > (_lastCompileAction + timeout);
return _lastSourceCodeEdited > _lastCompileAction && DateTime::Now() > _lastSourceCodeEdited + timeout;
}
bool ScriptsBuilder::IsCompiling()
@@ -266,7 +266,7 @@ bool ScriptsBuilder::RunBuildTool(const StringView& args, const StringView& work
bool ScriptsBuilder::GenerateProject(const StringView& customArgs)
{
String args(TEXT("-log -genproject "));
String args(TEXT("-log -mutex -genproject "));
args += customArgs;
_wasProjectStructureChanged = false;
return RunBuildTool(args);
@@ -669,7 +669,7 @@ void ScriptsBuilderService::Update()
}
// Check if compile code (if has been edited)
const TimeSpan timeToCallCompileIfDirty = TimeSpan::FromMilliseconds(50);
const TimeSpan timeToCallCompileIfDirty = TimeSpan::FromMilliseconds(150);
auto mainWindow = Engine::MainWindow;
if (ScriptsBuilder::IsSourceDirtyFor(timeToCallCompileIfDirty) && mainWindow && mainWindow->IsFocused())
{

View File

@@ -68,7 +68,7 @@ public:
/// </summary>
/// <param name="timeout">Time to use for checking.</param>
/// <returns>True if source code is dirty, otherwise false.</returns>
static bool IsSourceDirtyFor(const TimeSpan& timeout);
API_FUNCTION() static bool IsSourceDirtyFor(const TimeSpan& timeout);
/// <summary>
/// Returns true if scripts are being now compiled/reloaded.

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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 />

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -34,6 +34,9 @@ namespace FlaxEditor.Surface.Archetypes
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" />
public class Sample : SurfaceNode
{
private AssetSelect _assetSelect;
private Box _assetBox;
/// <inheritdoc />
public Sample(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
@@ -49,21 +52,47 @@ namespace FlaxEditor.Surface.Archetypes
}
/// <inheritdoc />
public override void OnSurfaceLoaded()
public override void OnSurfaceLoaded(SurfaceNodeActions action)
{
base.OnSurfaceLoaded();
base.OnSurfaceLoaded(action);
if (Surface != null)
{
_assetSelect = GetChild<AssetSelect>();
if (TryGetBox(8, out var box))
{
_assetBox = box;
_assetSelect.Visible = !_assetBox.HasAnyConnection;
}
UpdateTitle();
}
}
private void UpdateTitle()
{
var asset = Editor.Instance.ContentDatabase.Find((Guid)Values[0]);
Title = asset?.ShortName ?? "Animation";
if (_assetBox != null)
Title = _assetBox.HasAnyConnection || asset == null ? "Animation" : asset.ShortName;
else
Title = asset?.ShortName ?? "Animation";
var style = Style.Current;
Resize(Mathf.Max(230, style.FontLarge.MeasureText(Title).X + 30), 160);
}
/// <inheritdoc />
public override void ConnectionTick(Box box)
{
base.ConnectionTick(box);
if (_assetBox == null)
return;
if (box.ID != _assetBox.ID)
return;
_assetSelect.Visible = !box.HasAnyConnection;
UpdateTitle();
}
}
/// <summary>
@@ -162,9 +191,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();
@@ -305,7 +334,8 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(0, "Speed", true, typeof(float), 5, 1),
NodeElementArchetype.Factory.Input(1, "Loop", true, typeof(bool), 6, 2),
NodeElementArchetype.Factory.Input(2, "Start Position", true, typeof(float), 7, 3),
NodeElementArchetype.Factory.Asset(0, Surface.Constants.LayoutOffsetY * 3, 0, typeof(FlaxEngine.Animation)),
NodeElementArchetype.Factory.Input(3, "Animation Asset", true, typeof(FlaxEngine.Animation), 8),
NodeElementArchetype.Factory.Asset(0, Surface.Constants.LayoutOffsetY * 4, 0, typeof(FlaxEngine.Animation)),
}
},
new NodeArchetype

View 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),
},
};
}
}

View File

@@ -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();

View File

@@ -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

View File

@@ -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();

View File

@@ -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;
@@ -745,12 +745,12 @@ namespace FlaxEditor.Surface.Archetypes
base.OnDestroy();
}
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
return false;
}
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
return inputType.IsVoid;
}
@@ -1009,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)
@@ -1038,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
@@ -1162,7 +1162,7 @@ namespace FlaxEditor.Surface.Archetypes
base.OnDestroy();
}
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
if (nodeArch.Tag is not ScriptMemberInfo memberInfo)
return false;
@@ -1188,7 +1188,7 @@ namespace FlaxEditor.Surface.Archetypes
return false;
}
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
if (nodeArch.Tag is not ScriptMemberInfo memberInfo)
return false;
@@ -1240,9 +1240,9 @@ namespace FlaxEditor.Surface.Archetypes
}
/// <inheritdoc />
public override void OnLoaded()
public override void OnLoaded(SurfaceNodeActions action)
{
base.OnLoaded();
base.OnLoaded(action);
UpdateSignature();
}
@@ -1758,9 +1758,9 @@ namespace FlaxEditor.Surface.Archetypes
}
/// <inheritdoc />
public override void OnLoaded()
public override void OnLoaded(SurfaceNodeActions action)
{
base.OnLoaded();
base.OnLoaded(action);
LoadSignature();
@@ -1773,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;
@@ -1801,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++)
@@ -1810,7 +1810,7 @@ namespace FlaxEditor.Surface.Archetypes
node.OnFunctionDeleted(this);
}
base.OnDeleted();
base.OnDeleted(action);
}
/// <inheritdoc />
@@ -1836,12 +1836,12 @@ namespace FlaxEditor.Surface.Archetypes
base.OnDestroy();
}
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
return false;
}
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
return inputType.IsVoid;
}
@@ -1974,15 +1974,15 @@ 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)
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
if (scriptType == ScriptType.Null)
@@ -2011,7 +2011,7 @@ namespace FlaxEditor.Surface.Archetypes
return false;
}
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
if (scriptType == ScriptType.Null)
@@ -2085,15 +2085,15 @@ 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)
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
if (outputType.IsVoid)
return true;
@@ -2130,7 +2130,7 @@ namespace FlaxEditor.Surface.Archetypes
return false;
}
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
return inputType.IsVoid;
}
@@ -2303,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;
@@ -2353,7 +2353,7 @@ namespace FlaxEditor.Surface.Archetypes
base.OnDestroy();
}
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
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)
@@ -2373,7 +2373,7 @@ namespace FlaxEditor.Surface.Archetypes
return false;
}
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
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)
@@ -2398,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);
}
}
@@ -2413,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);
}
}

View File

@@ -176,7 +176,7 @@ namespace FlaxEditor.Surface.Archetypes
Size = new Float2(200, 100),
DefaultValues = new object[]
{
0.0f,
0.5f,
},
Elements = new[]
{

View File

@@ -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));

View File

@@ -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];
@@ -217,7 +217,7 @@ namespace FlaxEditor.Surface.Archetypes
{
}
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
var typeName = (string)nodeArch.DefaultValues[0];
var type = TypeUtils.GetType(typeName);
@@ -234,7 +234,7 @@ namespace FlaxEditor.Surface.Archetypes
return false;
}
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
var typeName = (string)nodeArch.DefaultValues[0];
var type = TypeUtils.GetType(typeName);
@@ -255,7 +255,7 @@ namespace FlaxEditor.Surface.Archetypes
{
}
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
var typeName = (string)nodeArch.DefaultValues[0];
var type = TypeUtils.GetType(typeName);
@@ -267,7 +267,7 @@ namespace FlaxEditor.Surface.Archetypes
return false;
}
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
var typeName = (string)nodeArch.DefaultValues[0];
var type = TypeUtils.GetType(typeName);

View File

@@ -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,

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}
@@ -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];
@@ -890,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];
@@ -941,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];
@@ -993,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];
@@ -1065,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);
@@ -1255,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 />

View 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();
}
}
}

View File

@@ -28,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.
@@ -209,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);
}
@@ -307,6 +308,8 @@ namespace FlaxEditor.Surface.ContextMenu
{
group.UnlockChildrenRecursive();
SortGroups();
if (ShowExpanded)
group.Open(false);
group.PerformLayout();
if (_searchBox.TextLength != 0)
{
@@ -353,6 +356,8 @@ namespace FlaxEditor.Surface.ContextMenu
if (_contextSensitiveSearchEnabled)
group.EvaluateVisibilityWithBox(_selectedBox);
group.SortChildren();
if (ShowExpanded)
group.Open(false);
group.Parent = _groupsPanel;
_groups.Add(group);
@@ -618,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);

View File

@@ -110,11 +110,11 @@ namespace FlaxEditor.Surface.ContextMenu
bool isCompatible = false;
if (startBox.IsOutput && _archetype.IsInputCompatible != null)
{
isCompatible |= _archetype.IsInputCompatible.Invoke(_archetype, startBox.CurrentType, _archetype.ConnectionsHints);
isCompatible |= _archetype.IsInputCompatible.Invoke(_archetype, startBox.CurrentType, _archetype.ConnectionsHints, startBox.ParentNode.Context);
}
else if (!startBox.IsOutput && _archetype.IsOutputCompatible != null)
{
isCompatible |= _archetype.IsOutputCompatible.Invoke(_archetype, startBox.CurrentType, startBox.ParentNode.Archetype.ConnectionsHints);
isCompatible |= _archetype.IsOutputCompatible.Invoke(_archetype, startBox.CurrentType, startBox.ParentNode.Archetype.ConnectionsHints, startBox.ParentNode.Context);
}
else if (_archetype.Elements != null)
{

View File

@@ -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>
@@ -494,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)
{
@@ -658,12 +625,17 @@ 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);
}
}
@@ -752,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 />

View File

@@ -1438,7 +1438,7 @@ namespace FlaxEditor.Surface.Elements
base.Draw();
// Box
DrawBox();
Surface.Style.DrawBox(this);
// Draw text
var style = Style.Current;

View File

@@ -27,12 +27,19 @@ 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);
@@ -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);
}
@@ -135,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;
@@ -152,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);
}
}
@@ -162,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 />
@@ -176,7 +184,7 @@ namespace FlaxEditor.Surface.Elements
base.Draw();
// Box
DrawBox();
Surface.Style.DrawBox(this);
// Draw text
var style = Style.Current;

View File

@@ -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 />

View File

@@ -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 />

View File

@@ -92,7 +92,7 @@ namespace FlaxEditor.Surface
/// <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);
public delegate bool IsCompatible(NodeArchetype nodeArch, ScriptType portType, ConnectionsHint hint, VisjectSurfaceContext context);
/// <summary>
/// Unique node type ID within a single group.

View File

@@ -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>

View File

@@ -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,
}
}

View File

@@ -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 />

View File

@@ -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 />

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -62,7 +62,7 @@ namespace FlaxEditor.Surface
/// <summary>
/// The node archetype.
/// </summary>
public readonly NodeArchetype Archetype;
public NodeArchetype Archetype;
/// <summary>
/// The group archetype.
@@ -165,25 +165,22 @@ 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);
}
/// <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;
@@ -220,7 +217,7 @@ namespace FlaxEditor.Surface
width = Mathf.Max(width, control.Right + 4 - Constants.NodeMarginX);
height = Mathf.Max(height, control.Bottom + 4 - Constants.NodeMarginY - Constants.NodeHeaderSize);
}
else
else if (!_headerRect.Intersects(control.Bounds))
{
width = Mathf.Max(width, control.Width + 4);
height = Mathf.Max(height, control.Height + 4);
@@ -393,6 +390,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>
@@ -882,9 +892,9 @@ namespace FlaxEditor.Surface
}
/// <inheritdoc />
public override void OnSurfaceLoaded()
public override void OnSurfaceLoaded(SurfaceNodeActions action)
{
base.OnSurfaceLoaded();
base.OnSurfaceLoaded(action);
UpdateBoxesTypes();
@@ -896,11 +906,11 @@ namespace FlaxEditor.Surface
}
/// <inheritdoc />
public override void OnDeleted()
public override void OnDeleted(SurfaceNodeActions action)
{
RemoveConnections();
base.OnDeleted();
base.OnDeleted(action);
}
/// <summary>
@@ -926,7 +936,7 @@ namespace FlaxEditor.Surface
OnValuesChanged();
Surface?.MarkAsEdited(graphEdited);
if (Surface?.Undo != null)
if (Surface != null)
Surface.AddBatchedUndoAction(new EditNodeValuesAction(this, before, graphEdited));
_isDuringValuesEditing = false;
@@ -953,7 +963,7 @@ namespace FlaxEditor.Surface
OnValuesChanged();
Surface.MarkAsEdited(graphEdited);
if (Surface?.Undo != null)
if (Surface != null)
Surface.AddBatchedUndoAction(new EditNodeValuesAction(this, before, graphEdited));
_isDuringValuesEditing = false;
@@ -1020,9 +1030,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

View File

@@ -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>

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