Merge branch 'MathLib' of https://github.com/NoriteSC/FlaxEngineFork into MathLib
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,6 +12,7 @@ Source/*.csproj
|
||||
/Package_*/
|
||||
!Source/Engine/Debug
|
||||
/Source/Platforms/Editor/Linux/Mono/etc/mono/registry
|
||||
PackageEditor_Cert.command
|
||||
PackageEditor_Cert.bat
|
||||
PackagePlatforms_Cert.bat
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -33,4 +33,3 @@ public class %class% : Script
|
||||
// Here you can add code that needs to be called every frame
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -2,8 +2,9 @@
|
||||
"Name": "Flax",
|
||||
"Version": {
|
||||
"Major": 1,
|
||||
"Minor": 6,
|
||||
"Build": 6345
|
||||
"Minor": 7,
|
||||
"Revision": 0,
|
||||
"Build": 6404
|
||||
},
|
||||
"Company": "Flax",
|
||||
"Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.",
|
||||
|
||||
@@ -256,6 +256,8 @@
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=comperand/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=coord/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=cubemap/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Deformer/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=deformers/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=defragmentation/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Delaunay/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Defocus/@EntryIndexedValue">True</s:Boolean>
|
||||
@@ -321,6 +323,7 @@
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=raycast/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=raycasting/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=raycasts/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=reachability/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=readback/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Reimports/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=reimported/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
@@ -15,7 +15,7 @@ if errorlevel 1 goto BuildToolFailed
|
||||
|
||||
:: Build bindings for all editor configurations
|
||||
echo Building C# bindings...
|
||||
Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor,FlaxGame
|
||||
Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor
|
||||
|
||||
popd
|
||||
echo Done!
|
||||
|
||||
@@ -14,4 +14,4 @@ bash ./Development/Scripts/Mac/CallBuildTool.sh --genproject "$@"
|
||||
# Build bindings for all editor configurations
|
||||
echo Building C# bindings...
|
||||
# TODO: Detect the correct architecture here
|
||||
Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor,FlaxGame
|
||||
Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor
|
||||
|
||||
@@ -14,4 +14,4 @@ bash ./Development/Scripts/Linux/CallBuildTool.sh --genproject "$@"
|
||||
# Build bindings for all editor configurations
|
||||
echo Building C# bindings...
|
||||
# TODO: Detect the correct architecture here
|
||||
Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=x64 -platform=Linux --buildTargets=FlaxEditor,FlaxGame
|
||||
Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=x64 -platform=Linux --buildTargets=FlaxEditor
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<a href="https://flaxengine.com/discord"><img src="https://discordapp.com/api/guilds/437989205315158016/widget.png"/></a>
|
||||
|
||||
Flax Engine is a high quality modern 3D game engine written in C++ and C#.
|
||||
From stunning graphics to powerful scripts - Flax can give everything for your games. Designed for fast workflow with many ready to use features waiting for you right now. To learn more see the website ([www.flaxengine.com](https://flaxengine.com)).
|
||||
From stunning graphics to powerful scripts, it's designed for fast workflow with many ready-to-use features waiting for you right now. To learn more see the website ([www.flaxengine.com](https://flaxengine.com)).
|
||||
|
||||
This repository contains full source code of the Flax Engine (excluding NDA-protected platforms support). Anyone is welcome to contribute or use the modified source in Flax-based games.
|
||||
|
||||
|
||||
292
Source/Editor/Content/AssetPickerValidator.cs
Normal file
292
Source/Editor/Content/AssetPickerValidator.cs
Normal file
@@ -0,0 +1,292 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.Utilities;
|
||||
|
||||
namespace FlaxEditor.Content;
|
||||
|
||||
/// <summary>
|
||||
/// Manages and converts the selected content item to the appropriate types. Useful for drag operations.
|
||||
/// </summary>
|
||||
public class AssetPickerValidator : IContentItemOwner
|
||||
{
|
||||
private Asset _selected;
|
||||
private ContentItem _selectedItem;
|
||||
private ScriptType _type;
|
||||
private string _fileExtension;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected item.
|
||||
/// </summary>
|
||||
public ContentItem SelectedItem
|
||||
{
|
||||
get => _selectedItem;
|
||||
set
|
||||
{
|
||||
if (_selectedItem == value)
|
||||
return;
|
||||
if (value == null)
|
||||
{
|
||||
if (_selected == null && _selectedItem is SceneItem)
|
||||
{
|
||||
// Deselect scene reference
|
||||
_selectedItem.RemoveReference(this);
|
||||
_selectedItem = null;
|
||||
_selected = null;
|
||||
OnSelectedItemChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
// Deselect
|
||||
_selectedItem?.RemoveReference(this);
|
||||
_selectedItem = null;
|
||||
_selected = null;
|
||||
OnSelectedItemChanged();
|
||||
}
|
||||
else if (value is SceneItem item)
|
||||
{
|
||||
if (_selectedItem == item)
|
||||
return;
|
||||
if (!IsValid(item))
|
||||
item = null;
|
||||
|
||||
// Change value to scene reference (cannot load asset because scene can be already loaded - duplicated ID issue)
|
||||
_selectedItem?.RemoveReference(this);
|
||||
_selectedItem = item;
|
||||
_selected = null;
|
||||
_selectedItem?.AddReference(this);
|
||||
OnSelectedItemChanged();
|
||||
}
|
||||
else if (value is AssetItem assetItem)
|
||||
{
|
||||
SelectedAsset = FlaxEngine.Content.LoadAsync(assetItem.ID);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Change value
|
||||
_selectedItem?.RemoveReference(this);
|
||||
_selectedItem = value;
|
||||
_selected = null;
|
||||
OnSelectedItemChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected asset identifier.
|
||||
/// </summary>
|
||||
public Guid SelectedID
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_selected != null)
|
||||
return _selected.ID;
|
||||
if (_selectedItem is AssetItem assetItem)
|
||||
return assetItem.ID;
|
||||
return Guid.Empty;
|
||||
}
|
||||
set => SelectedItem = Editor.Instance.ContentDatabase.FindAsset(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected content item path.
|
||||
/// </summary>
|
||||
public string SelectedPath
|
||||
{
|
||||
get
|
||||
{
|
||||
string path = _selectedItem?.Path ?? _selected?.Path;
|
||||
if (path != null)
|
||||
{
|
||||
// Convert into path relative to the project (cross-platform)
|
||||
var projectFolder = Globals.ProjectFolder;
|
||||
if (path.StartsWith(projectFolder))
|
||||
path = path.Substring(projectFolder.Length + 1);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
SelectedItem = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var path = StringUtils.IsRelative(value) ? Path.Combine(Globals.ProjectFolder, value) : value;
|
||||
SelectedItem = Editor.Instance.ContentDatabase.Find(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected asset object.
|
||||
/// </summary>
|
||||
public Asset SelectedAsset
|
||||
{
|
||||
get => _selected;
|
||||
set
|
||||
{
|
||||
// Check if value won't change
|
||||
if (value == _selected)
|
||||
return;
|
||||
|
||||
// Find item from content database and check it
|
||||
var item = value ? Editor.Instance.ContentDatabase.FindAsset(value.ID) : null;
|
||||
if (item != null && !IsValid(item))
|
||||
item = null;
|
||||
|
||||
// Change value
|
||||
_selectedItem?.RemoveReference(this);
|
||||
_selectedItem = item;
|
||||
_selected = value;
|
||||
_selectedItem?.AddReference(this);
|
||||
OnSelectedItemChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use <see cref="ScriptType.Null"/> for generic file picker.
|
||||
/// </summary>
|
||||
public ScriptType AssetType
|
||||
{
|
||||
get => _type;
|
||||
set
|
||||
{
|
||||
if (_type != value)
|
||||
{
|
||||
_type = value;
|
||||
|
||||
// Auto deselect if the current value is invalid
|
||||
if (_selectedItem != null && !IsValid(_selectedItem))
|
||||
SelectedItem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content items extensions filter. Null if unused.
|
||||
/// </summary>
|
||||
public string FileExtension
|
||||
{
|
||||
get => _fileExtension;
|
||||
set
|
||||
{
|
||||
if (_fileExtension != value)
|
||||
{
|
||||
_fileExtension = value;
|
||||
|
||||
// Auto deselect if the current value is invalid
|
||||
if (_selectedItem != null && !IsValid(_selectedItem))
|
||||
SelectedItem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when selected item gets changed.
|
||||
/// </summary>
|
||||
public event Action SelectedItemChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The custom callback for assets validation. Cane be used to implement a rule for assets to pick.
|
||||
/// </summary>
|
||||
public Func<ContentItem, bool> CheckValid;
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether item is valid.
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsValid(ContentItem item)
|
||||
{
|
||||
if (_fileExtension != null && !item.Path.EndsWith(_fileExtension))
|
||||
return false;
|
||||
if (CheckValid != null && !CheckValid(item))
|
||||
return false;
|
||||
if (_type == ScriptType.Null)
|
||||
return true;
|
||||
|
||||
if (item is AssetItem assetItem)
|
||||
{
|
||||
// Faster path for binary items (in-built)
|
||||
if (assetItem is BinaryAssetItem binaryItem)
|
||||
return _type.IsAssignableFrom(new ScriptType(binaryItem.Type));
|
||||
|
||||
// Type filter
|
||||
var type = TypeUtils.GetType(assetItem.TypeName);
|
||||
if (_type.IsAssignableFrom(type))
|
||||
return true;
|
||||
|
||||
// Json assets can contain any type of the object defined by the C# type (data oriented design)
|
||||
if (assetItem is JsonAssetItem && (_type.Type == typeof(JsonAsset) || _type.Type == typeof(Asset)))
|
||||
return true;
|
||||
|
||||
// Special case for scene asset references
|
||||
if (_type.Type == typeof(SceneReference) && assetItem is SceneItem)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AssetPickerValidator"/> class.
|
||||
/// </summary>
|
||||
public AssetPickerValidator()
|
||||
: this(new ScriptType(typeof(Asset)))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AssetPickerValidator"/> class.
|
||||
/// </summary>
|
||||
/// <param name="assetType">The assets types that this picker accepts.</param>
|
||||
public AssetPickerValidator(ScriptType assetType)
|
||||
{
|
||||
_type = assetType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when selected item gets changed.
|
||||
/// </summary>
|
||||
protected virtual void OnSelectedItemChanged()
|
||||
{
|
||||
SelectedItemChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnItemDeleted(ContentItem item)
|
||||
{
|
||||
// Deselect item
|
||||
SelectedItem = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnItemRenamed(ContentItem item)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnItemReimported(ContentItem item)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnItemDispose(ContentItem item)
|
||||
{
|
||||
// Deselect item
|
||||
SelectedItem = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call to remove reference from the selected item.
|
||||
/// </summary>
|
||||
public void OnDestroy()
|
||||
{
|
||||
_selectedItem?.RemoveReference(this);
|
||||
_selectedItem = null;
|
||||
_selected = null;
|
||||
}
|
||||
}
|
||||
@@ -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++)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
66
Source/Editor/Content/Proxy/BehaviorTreeProxy.cs
Normal file
66
Source/Editor/Content/Proxy/BehaviorTreeProxy.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using FlaxEditor.Content.Thumbnails;
|
||||
using FlaxEditor.Windows;
|
||||
using FlaxEditor.Windows.Assets;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Content
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="BehaviorTree"/> asset proxy object.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Content.BinaryAssetProxy" />
|
||||
[ContentContextMenu("New/AI/Behavior Tree")]
|
||||
public class BehaviorTreeProxy : BinaryAssetProxy
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Behavior Tree";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanReimport(ContentItem item)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override EditorWindow Open(Editor editor, ContentItem item)
|
||||
{
|
||||
return new BehaviorTreeWindow(editor, item as BinaryAssetItem);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Color AccentColor => Color.FromRGB(0x3256A8);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type AssetType => typeof(BehaviorTree);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanCreate(ContentFolder targetLocation)
|
||||
{
|
||||
return targetLocation.CanHaveAssets;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Create(string outputPath, object arg)
|
||||
{
|
||||
if (Editor.CreateAsset(Editor.NewAssetType.BehaviorTree, outputPath))
|
||||
throw new Exception("Failed to create new asset.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnThumbnailDrawBegin(ThumbnailRequest request, ContainerControl guiRoot, GPUContext context)
|
||||
{
|
||||
guiRoot.AddChild(new Label
|
||||
{
|
||||
Text = Path.GetFileNameWithoutExtension(request.Asset.Path),
|
||||
Offsets = Margin.Zero,
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Wrapping = TextWrapping.WrapWords
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,12 +54,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override bool CanDrawThumbnail(ThumbnailRequest request)
|
||||
{
|
||||
if (!_preview.HasLoadedAssets)
|
||||
return false;
|
||||
|
||||
// Check if all mip maps are streamed
|
||||
var asset = (CubeTexture)request.Asset;
|
||||
return asset.ResidentMipLevels >= Mathf.Max(1, (int)(asset.MipLevels * ThumbnailsModule.MinimumRequiredResourcesQuality));
|
||||
return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((CubeTexture)request.Asset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override bool CanDrawThumbnail(ThumbnailRequest request)
|
||||
{
|
||||
return _preview.HasLoadedAssets;
|
||||
return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((MaterialInstance)request.Asset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override bool CanDrawThumbnail(ThumbnailRequest request)
|
||||
{
|
||||
return _preview.HasLoadedAssets;
|
||||
return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Material)request.Asset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -82,12 +82,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override bool CanDrawThumbnail(ThumbnailRequest request)
|
||||
{
|
||||
if (!_preview.HasLoadedAssets)
|
||||
return false;
|
||||
|
||||
// Check if asset is streamed enough
|
||||
var asset = (Model)request.Asset;
|
||||
return asset.LoadedLODs >= Mathf.Max(1, (int)(asset.LODs.Length * ThumbnailsModule.MinimumRequiredResourcesQuality));
|
||||
return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Model)request.Asset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -54,15 +54,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override bool CanDrawThumbnail(ThumbnailRequest request)
|
||||
{
|
||||
if (!_preview.HasLoadedAssets)
|
||||
return false;
|
||||
|
||||
// Check if asset is streamed enough
|
||||
var asset = (SkinnedModel)request.Asset;
|
||||
var lods = asset.LODs.Length;
|
||||
if (asset.IsLoaded && lods == 0)
|
||||
return true; // Skeleton-only model
|
||||
return asset.LoadedLODs >= Mathf.Max(1, (int)(lods * ThumbnailsModule.MinimumRequiredResourcesQuality));
|
||||
return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((SkinnedModel)request.Asset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -57,11 +57,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override bool CanDrawThumbnail(ThumbnailRequest request)
|
||||
{
|
||||
// Check if asset is streamed enough
|
||||
var asset = (Texture)request.Asset;
|
||||
var mipLevels = asset.MipLevels;
|
||||
var minMipLevels = Mathf.Min(mipLevels, 7);
|
||||
return asset.ResidentMipLevels >= Mathf.Max(minMipLevels, (int)(mipLevels * ThumbnailsModule.MinimumRequiredResourcesQuality));
|
||||
return ThumbnailsModule.HasMinimumQuality((Texture)request.Asset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -125,6 +125,74 @@ namespace FlaxEditor.Content.Thumbnails
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool HasMinimumQuality(TextureBase asset)
|
||||
{
|
||||
var mipLevels = asset.MipLevels;
|
||||
var minMipLevels = Mathf.Min(mipLevels, 7);
|
||||
return asset.IsLoaded && asset.ResidentMipLevels >= Mathf.Max(minMipLevels, (int)(mipLevels * MinimumRequiredResourcesQuality));
|
||||
}
|
||||
|
||||
internal static bool HasMinimumQuality(Model asset)
|
||||
{
|
||||
if (!asset.IsLoaded)
|
||||
return false;
|
||||
var lods = asset.LODs.Length;
|
||||
var slots = asset.MaterialSlots;
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
if (slot.Material && !HasMinimumQuality(slot.Material))
|
||||
return false;
|
||||
}
|
||||
return asset.LoadedLODs >= Mathf.Max(1, (int)(lods * MinimumRequiredResourcesQuality));
|
||||
}
|
||||
|
||||
internal static bool HasMinimumQuality(SkinnedModel asset)
|
||||
{
|
||||
var lods = asset.LODs.Length;
|
||||
if (asset.IsLoaded && lods == 0)
|
||||
return true; // Skeleton-only model
|
||||
var slots = asset.MaterialSlots;
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
if (slot.Material && !HasMinimumQuality(slot.Material))
|
||||
return false;
|
||||
}
|
||||
return asset.LoadedLODs >= Mathf.Max(1, (int)(lods * MinimumRequiredResourcesQuality));
|
||||
}
|
||||
|
||||
internal static bool HasMinimumQuality(MaterialBase asset)
|
||||
{
|
||||
if (asset is MaterialInstance asInstance)
|
||||
return HasMinimumQuality(asInstance);
|
||||
return HasMinimumQualityInternal(asset);
|
||||
}
|
||||
|
||||
internal static bool HasMinimumQuality(Material asset)
|
||||
{
|
||||
return HasMinimumQualityInternal(asset);
|
||||
}
|
||||
|
||||
internal static bool HasMinimumQuality(MaterialInstance asset)
|
||||
{
|
||||
if (!HasMinimumQualityInternal(asset))
|
||||
return false;
|
||||
var baseMaterial = asset.BaseMaterial;
|
||||
return baseMaterial == null || HasMinimumQualityInternal(baseMaterial);
|
||||
}
|
||||
|
||||
private static bool HasMinimumQualityInternal(MaterialBase asset)
|
||||
{
|
||||
if (!asset.IsLoaded)
|
||||
return false;
|
||||
var parameters = asset.Parameters;
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
if (parameter.Value is TextureBase asTexture && !HasMinimumQuality(asTexture))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#region IContentItemOwner
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -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--);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -157,6 +157,12 @@ namespace FlaxEditor.CustomEditors
|
||||
var values = _values;
|
||||
var presenter = _presenter;
|
||||
var layout = _layout;
|
||||
if (layout.Editors.Count != 1)
|
||||
{
|
||||
// There are more editors using the same layout so rebuild parent editor to prevent removing others editors
|
||||
_parent?.RebuildLayout();
|
||||
return;
|
||||
}
|
||||
var control = layout.ContainerControl;
|
||||
var parent = _parent;
|
||||
var parentScrollV = (_presenter?.Panel.Parent as Panel)?.VScrollBar?.Value ?? -1;
|
||||
|
||||
@@ -37,6 +37,23 @@ namespace FlaxEditor.CustomEditors
|
||||
UseDefault = 1 << 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The interface for Editor context that owns the presenter. Can be <see cref="FlaxEditor.Windows.PropertiesWindow"/> or <see cref="FlaxEditor.Windows.Assets.PrefabWindow"/> or other window/panel - custom editor scan use it for more specific features.
|
||||
/// </summary>
|
||||
public interface IPresenterOwner
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the viewport linked with properties presenter (optional, null if unused).
|
||||
/// </summary>
|
||||
public Viewport.EditorViewport PresenterViewport { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Selects the scene objects.
|
||||
/// </summary>
|
||||
/// <param name="nodes">The nodes to select</param>
|
||||
public void Select(List<SceneGraph.SceneGraphNode> nodes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main class for Custom Editors used to present selected objects properties and allow to modify them.
|
||||
/// </summary>
|
||||
@@ -68,8 +85,15 @@ namespace FlaxEditor.CustomEditors
|
||||
/// <inheritdoc />
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
// Update editors
|
||||
_presenter.Update();
|
||||
try
|
||||
{
|
||||
// Update editors
|
||||
_presenter.Update();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
FlaxEditor.Editor.LogWarning(ex);
|
||||
}
|
||||
|
||||
base.Update(deltaTime);
|
||||
}
|
||||
@@ -254,7 +278,7 @@ namespace FlaxEditor.CustomEditors
|
||||
/// <summary>
|
||||
/// The Editor context that owns this presenter. Can be <see cref="FlaxEditor.Windows.PropertiesWindow"/> or <see cref="FlaxEditor.Windows.Assets.PrefabWindow"/> or other window/panel - custom editor scan use it for more specific features.
|
||||
/// </summary>
|
||||
public object Owner;
|
||||
public IPresenterOwner Owner;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text to show when no object is selected.
|
||||
@@ -270,7 +294,24 @@ namespace FlaxEditor.CustomEditors
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value indicating whether properties are read-only.
|
||||
/// </summary>
|
||||
public bool ReadOnly
|
||||
{
|
||||
get => _readOnly;
|
||||
set
|
||||
{
|
||||
if (_readOnly != value)
|
||||
{
|
||||
_readOnly = value;
|
||||
UpdateReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _buildOnUpdate;
|
||||
private bool _readOnly;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomEditorPresenter"/> class.
|
||||
@@ -278,7 +319,7 @@ namespace FlaxEditor.CustomEditors
|
||||
/// <param name="undo">The undo. It's optional.</param>
|
||||
/// <param name="noSelectionText">The custom text to display when no object is selected. Default is No selection.</param>
|
||||
/// <param name="owner">The owner of the presenter.</param>
|
||||
public CustomEditorPresenter(Undo undo, string noSelectionText = null, object owner = null)
|
||||
public CustomEditorPresenter(Undo undo, string noSelectionText = null, IPresenterOwner owner = null)
|
||||
{
|
||||
Undo = undo;
|
||||
Owner = owner;
|
||||
@@ -364,6 +405,8 @@ namespace FlaxEditor.CustomEditors
|
||||
// Restore scroll value
|
||||
if (parentScrollV > -1)
|
||||
panel.VScrollBar.Value = parentScrollV;
|
||||
if (_readOnly)
|
||||
UpdateReadOnly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -374,6 +417,16 @@ namespace FlaxEditor.CustomEditors
|
||||
_buildOnUpdate = true;
|
||||
}
|
||||
|
||||
private void UpdateReadOnly()
|
||||
{
|
||||
// Only scrollbars are enabled
|
||||
foreach (var child in Panel.Children)
|
||||
{
|
||||
if (!(child is ScrollBar))
|
||||
child.Enabled = !_readOnly;
|
||||
}
|
||||
}
|
||||
|
||||
private void ExpandGroups(LayoutElementsContainer c, bool open)
|
||||
{
|
||||
if (c is Elements.GroupElement group)
|
||||
|
||||
97
Source/Editor/CustomEditors/Dedicated/ClothEditor.cs
Normal file
97
Source/Editor/CustomEditors/Dedicated/ClothEditor.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Json;
|
||||
using FlaxEngine.Tools;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="Cloth"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ActorEditor" />
|
||||
[CustomEditor(typeof(Cloth)), DefaultEditor]
|
||||
class ClothEditor : ActorEditor
|
||||
{
|
||||
private ClothPaintingGizmoMode _gizmoMode;
|
||||
private Viewport.Modes.EditorGizmoMode _prevMode;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
if (Values.Count != 1)
|
||||
return;
|
||||
|
||||
// Add gizmo painting mode to the viewport
|
||||
var owner = Presenter.Owner;
|
||||
if (owner == null)
|
||||
return;
|
||||
var gizmoOwner = owner as IGizmoOwner ?? owner.PresenterViewport as IGizmoOwner;
|
||||
if (gizmoOwner == null)
|
||||
return;
|
||||
var gizmos = gizmoOwner.Gizmos;
|
||||
_gizmoMode = new ClothPaintingGizmoMode();
|
||||
|
||||
var projectCache = Editor.Instance.ProjectCache;
|
||||
if (projectCache.TryGetCustomData("ClothGizmoPaintValue", out var cachedPaintValue))
|
||||
_gizmoMode.PaintValue = JsonSerializer.Deserialize<float>(cachedPaintValue);
|
||||
if (projectCache.TryGetCustomData("ClothGizmoContinuousPaint", out var cachedContinuousPaint))
|
||||
_gizmoMode.ContinuousPaint = JsonSerializer.Deserialize<bool>(cachedContinuousPaint);
|
||||
if (projectCache.TryGetCustomData("ClothGizmoBrushFalloff", out var cachedBrushFalloff))
|
||||
_gizmoMode.BrushFalloff = JsonSerializer.Deserialize<float>(cachedBrushFalloff);
|
||||
if (projectCache.TryGetCustomData("ClothGizmoBrushSize", out var cachedBrushSize))
|
||||
_gizmoMode.BrushSize = JsonSerializer.Deserialize<float>(cachedBrushSize);
|
||||
if (projectCache.TryGetCustomData("ClothGizmoBrushStrength", out var cachedBrushStrength))
|
||||
_gizmoMode.BrushStrength = JsonSerializer.Deserialize<float>(cachedBrushStrength);
|
||||
|
||||
gizmos.AddMode(_gizmoMode);
|
||||
_prevMode = gizmos.ActiveMode;
|
||||
gizmos.ActiveMode = _gizmoMode;
|
||||
_gizmoMode.Gizmo.SetPaintCloth((Cloth)Values[0]);
|
||||
|
||||
// Insert gizmo mode options to properties editing
|
||||
var paintGroup = layout.Group("Cloth Painting");
|
||||
var paintValue = new ReadOnlyValueContainer(new ScriptType(typeof(ClothPaintingGizmoMode)), _gizmoMode);
|
||||
paintGroup.Object(paintValue);
|
||||
{
|
||||
var grid = paintGroup.CustomContainer<UniformGridPanel>();
|
||||
var gridControl = grid.CustomControl;
|
||||
gridControl.ClipChildren = false;
|
||||
gridControl.Height = Button.DefaultHeight;
|
||||
gridControl.SlotsHorizontally = 2;
|
||||
gridControl.SlotsVertically = 1;
|
||||
grid.Button("Fill", "Fills the cloth particles with given paint value.").Button.Clicked += _gizmoMode.Gizmo.Fill;
|
||||
grid.Button("Reset", "Clears the cloth particles paint.").Button.Clicked += _gizmoMode.Gizmo.Reset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Deinitialize()
|
||||
{
|
||||
// Cleanup gizmos
|
||||
if (_gizmoMode != null)
|
||||
{
|
||||
var gizmos = _gizmoMode.Owner.Gizmos;
|
||||
if (gizmos.ActiveMode == _gizmoMode)
|
||||
gizmos.ActiveMode = _prevMode;
|
||||
gizmos.RemoveMode(_gizmoMode);
|
||||
var projectCache = Editor.Instance.ProjectCache;
|
||||
projectCache.SetCustomData("ClothGizmoPaintValue", JsonSerializer.Serialize(_gizmoMode.PaintValue, typeof(float)));
|
||||
projectCache.SetCustomData("ClothGizmoContinuousPaint", JsonSerializer.Serialize(_gizmoMode.ContinuousPaint, typeof(bool)));
|
||||
projectCache.SetCustomData("ClothGizmoBrushFalloff", JsonSerializer.Serialize(_gizmoMode.BrushFalloff, typeof(float)));
|
||||
projectCache.SetCustomData("ClothGizmoBrushSize", JsonSerializer.Serialize(_gizmoMode.BrushSize, typeof(float)));
|
||||
projectCache.SetCustomData("ClothGizmoBrushStrength", JsonSerializer.Serialize(_gizmoMode.BrushStrength, typeof(float)));
|
||||
_gizmoMode.Dispose();
|
||||
_gizmoMode = null;
|
||||
}
|
||||
_prevMode = null;
|
||||
|
||||
base.Deinitialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
349
Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs
Normal file
349
Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs
Normal file
@@ -0,0 +1,349 @@
|
||||
// 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;
|
||||
|
||||
if (ParentEditor.Values.Any(x => x is Cloth))
|
||||
{
|
||||
// Cloth always picks the parent model mesh
|
||||
if (actor == null)
|
||||
{
|
||||
layout.Label("Cloth needs to be added as a child to model actor.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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())
|
||||
{
|
||||
layout.Label("No model.");
|
||||
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())
|
||||
{
|
||||
layout.Label("No model.");
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
grid.Button("Remove bone").Button.ButtonClicked += OnRemoveBone;
|
||||
}
|
||||
|
||||
if (Presenter.Owner is Windows.PropertiesWindow || Presenter.Owner is Windows.Assets.PrefabWindow)
|
||||
if (Presenter.Owner != null)
|
||||
{
|
||||
// Selection
|
||||
var grid = editorGroup.CustomContainer<UniformGridPanel>();
|
||||
@@ -309,10 +309,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
if (node != null)
|
||||
selection.Add(node);
|
||||
}
|
||||
if (Presenter.Owner is Windows.PropertiesWindow propertiesWindow)
|
||||
propertiesWindow.Editor.SceneEditing.Select(selection);
|
||||
else if (Presenter.Owner is Windows.Assets.PrefabWindow prefabWindow)
|
||||
prefabWindow.Select(selection);
|
||||
Presenter.Owner.Select(selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,27 +258,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
/// Small image control added per script group that allows to drag and drop a reference to it. Also used to reorder the scripts.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.Image" />
|
||||
internal class ScriptDragIcon : Image
|
||||
internal class DragImage : Image
|
||||
{
|
||||
private ScriptsEditor _editor;
|
||||
private bool _isMouseDown;
|
||||
private Float2 _mouseDownPos;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target script.
|
||||
/// Action called when drag event should start.
|
||||
/// </summary>
|
||||
public Script Script => (Script)Tag;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ScriptDragIcon"/> class.
|
||||
/// </summary>
|
||||
/// <param name="editor">The script editor.</param>
|
||||
/// <param name="script">The target script.</param>
|
||||
public ScriptDragIcon(ScriptsEditor editor, Script script)
|
||||
{
|
||||
Tag = script;
|
||||
_editor = editor;
|
||||
}
|
||||
public Action<DragImage> Drag;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseEnter(Float2 location)
|
||||
@@ -291,11 +279,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
// Check if start drag drop
|
||||
if (_isMouseDown)
|
||||
{
|
||||
DoDrag();
|
||||
_isMouseDown = false;
|
||||
Drag(this);
|
||||
}
|
||||
|
||||
base.OnMouseLeave();
|
||||
@@ -304,11 +291,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
// Check if start drag drop
|
||||
if (_isMouseDown && Float2.Distance(location, _mouseDownPos) > 10.0f)
|
||||
{
|
||||
DoDrag();
|
||||
_isMouseDown = false;
|
||||
Drag(this);
|
||||
}
|
||||
|
||||
base.OnMouseMove(location);
|
||||
@@ -319,8 +305,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
// Clear flag
|
||||
_isMouseDown = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
@@ -331,21 +317,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
// Set flag
|
||||
_isMouseDown = true;
|
||||
_mouseDownPos = location;
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
private void DoDrag()
|
||||
{
|
||||
var script = Script;
|
||||
_editor.OnScriptDragChange(true, script);
|
||||
DoDragDrop(DragScripts.GetDragData(script));
|
||||
_editor.OnScriptDragChange(false, script);
|
||||
}
|
||||
}
|
||||
|
||||
internal class ScriptArrangeBar : Control
|
||||
@@ -643,7 +621,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
_scriptToggles[i] = scriptToggle;
|
||||
|
||||
// Add drag button to the group
|
||||
var scriptDrag = new ScriptDragIcon(this, script)
|
||||
var scriptDrag = new DragImage
|
||||
{
|
||||
TooltipText = "Script reference",
|
||||
AutoFocus = true,
|
||||
@@ -654,6 +632,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
Margin = new Margin(1),
|
||||
Brush = new SpriteBrush(Editor.Instance.Icons.DragBar12),
|
||||
Tag = script,
|
||||
Drag = img =>
|
||||
{
|
||||
var s = (Script)img.Tag;
|
||||
OnScriptDragChange(true, s);
|
||||
img.DoDragDrop(DragScripts.GetDragData(s));
|
||||
OnScriptDragChange(false, s);
|
||||
}
|
||||
};
|
||||
|
||||
// Add settings button to the group
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
value = 0;
|
||||
|
||||
// If selected is single actor that has children, ask if apply layer to the sub objects as well
|
||||
if (Values.IsSingleObject && (int)Values[0] != value && ParentEditor.Values[0] is Actor actor && actor.HasChildren)
|
||||
if (Values.IsSingleObject && (int)Values[0] != value && ParentEditor.Values[0] is Actor actor && actor.HasChildren && !Editor.IsPlayMode)
|
||||
{
|
||||
var valueText = comboBox.SelectedItem;
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
// Generic file picker
|
||||
assetType = ScriptType.Null;
|
||||
Picker.FileExtension = assetReference.TypeName;
|
||||
Picker.Validator.FileExtension = assetReference.TypeName;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -85,7 +85,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
}
|
||||
}
|
||||
|
||||
Picker.AssetType = assetType;
|
||||
Picker.Validator.AssetType = assetType;
|
||||
Picker.Height = height;
|
||||
Picker.SelectedItemChanged += OnSelectedItemChanged;
|
||||
}
|
||||
@@ -95,15 +95,15 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
if (_isRefreshing)
|
||||
return;
|
||||
if (typeof(AssetItem).IsAssignableFrom(_valueType.Type))
|
||||
SetValue(Picker.SelectedItem);
|
||||
SetValue(Picker.Validator.SelectedItem);
|
||||
else if (_valueType.Type == typeof(Guid))
|
||||
SetValue(Picker.SelectedID);
|
||||
SetValue(Picker.Validator.SelectedID);
|
||||
else if (_valueType.Type == typeof(SceneReference))
|
||||
SetValue(new SceneReference(Picker.SelectedID));
|
||||
SetValue(new SceneReference(Picker.Validator.SelectedID));
|
||||
else if (_valueType.Type == typeof(string))
|
||||
SetValue(Picker.SelectedPath);
|
||||
SetValue(Picker.Validator.SelectedPath);
|
||||
else
|
||||
SetValue(Picker.SelectedAsset);
|
||||
SetValue(Picker.Validator.SelectedAsset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -115,15 +115,15 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
_isRefreshing = true;
|
||||
if (Values[0] is AssetItem assetItem)
|
||||
Picker.SelectedItem = assetItem;
|
||||
Picker.Validator.SelectedItem = assetItem;
|
||||
else if (Values[0] is Guid guid)
|
||||
Picker.SelectedID = guid;
|
||||
Picker.Validator.SelectedID = guid;
|
||||
else if (Values[0] is SceneReference sceneAsset)
|
||||
Picker.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
|
||||
Picker.Validator.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
|
||||
else if (Values[0] is string path)
|
||||
Picker.SelectedPath = path;
|
||||
Picker.Validator.SelectedPath = path;
|
||||
else
|
||||
Picker.SelectedAsset = Values[0] as Asset;
|
||||
Picker.Validator.SelectedAsset = Values[0] as Asset;
|
||||
_isRefreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,12 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEditor.CustomEditors.GUI;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
@@ -135,14 +138,43 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
spacing = collection.Spacing;
|
||||
}
|
||||
|
||||
var dragArea = layout.CustomContainer<DragAreaControl>();
|
||||
dragArea.CustomControl.Editor = this;
|
||||
dragArea.CustomControl.ElementType = ElementType;
|
||||
|
||||
// Check for the AssetReferenceAttribute. In JSON assets, it can be used to filter
|
||||
// which scripts can be dragged over and dropped on this collection editor.
|
||||
var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute);
|
||||
if (assetReference != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(assetReference.TypeName))
|
||||
{
|
||||
}
|
||||
else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.')
|
||||
{
|
||||
dragArea.CustomControl.ElementType = ScriptType.Null;
|
||||
dragArea.CustomControl.FileExtension = assetReference.TypeName;
|
||||
}
|
||||
else
|
||||
{
|
||||
var customType = TypeUtils.GetType(assetReference.TypeName);
|
||||
if (customType != ScriptType.Null)
|
||||
dragArea.CustomControl.ElementType = customType;
|
||||
else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
|
||||
Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for drag and drop filter.", assetReference.TypeName));
|
||||
else
|
||||
dragArea.CustomControl.ElementType = ScriptType.Void;
|
||||
}
|
||||
}
|
||||
|
||||
// Size
|
||||
if (_readOnly || (NotNullItems && size == 0))
|
||||
{
|
||||
layout.Label("Size", size.ToString());
|
||||
dragArea.Label("Size", size.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
_size = layout.IntegerValue("Size");
|
||||
_size = dragArea.IntegerValue("Size");
|
||||
_size.IntValue.MinValue = 0;
|
||||
_size.IntValue.MaxValue = ushort.MaxValue;
|
||||
_size.IntValue.Value = size;
|
||||
@@ -152,7 +184,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
// Elements
|
||||
if (size > 0)
|
||||
{
|
||||
var panel = layout.VerticalPanel();
|
||||
var panel = dragArea.VerticalPanel();
|
||||
panel.Panel.BackgroundColor = _background;
|
||||
var elementType = ElementType;
|
||||
|
||||
@@ -212,37 +244,33 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
// Add/Remove buttons
|
||||
if (!_readOnly)
|
||||
{
|
||||
var area = layout.Space(20);
|
||||
var addButton = new Button(area.ContainerControl.Width - (16 + 16 + 2 + 2), 2, 16, 16)
|
||||
{
|
||||
Text = "+",
|
||||
TooltipText = "Add new item",
|
||||
AnchorPreset = AnchorPresets.TopRight,
|
||||
Parent = area.ContainerControl,
|
||||
Enabled = !NotNullItems || size > 0,
|
||||
};
|
||||
addButton.Clicked += () =>
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
var panel = dragArea.HorizontalPanel();
|
||||
panel.Panel.Size = new Float2(0, 20);
|
||||
panel.Panel.Margin = new Margin(2);
|
||||
|
||||
Resize(Count + 1);
|
||||
};
|
||||
var removeButton = new Button(addButton.Right + 2, addButton.Y, 16, 16)
|
||||
{
|
||||
Text = "-",
|
||||
TooltipText = "Remove last item",
|
||||
AnchorPreset = AnchorPresets.TopRight,
|
||||
Parent = area.ContainerControl,
|
||||
Enabled = size > 0,
|
||||
};
|
||||
removeButton.Clicked += () =>
|
||||
var removeButton = panel.Button("-", "Remove last item");
|
||||
removeButton.Button.Size = new Float2(16, 16);
|
||||
removeButton.Button.Enabled = size > 0;
|
||||
removeButton.Button.AnchorPreset = AnchorPresets.TopRight;
|
||||
removeButton.Button.Clicked += () =>
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
Resize(Count - 1);
|
||||
};
|
||||
|
||||
var addButton = panel.Button("+", "Add new item");
|
||||
addButton.Button.Size = new Float2(16, 16);
|
||||
addButton.Button.Enabled = !NotNullItems || size > 0;
|
||||
addButton.Button.AnchorPreset = AnchorPresets.TopRight;
|
||||
addButton.Button.Clicked += () =>
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
Resize(Count + 1);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,5 +397,232 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
}
|
||||
return base.OnDirty(editor, value, token);
|
||||
}
|
||||
|
||||
private class DragAreaControl : VerticalPanel
|
||||
{
|
||||
private DragItems _dragItems;
|
||||
private DragActors _dragActors;
|
||||
private DragHandlers _dragHandlers;
|
||||
private AssetPickerValidator _pickerValidator;
|
||||
|
||||
public ScriptType ElementType
|
||||
{
|
||||
get => _pickerValidator?.AssetType ?? ScriptType.Null;
|
||||
set => _pickerValidator = new AssetPickerValidator(value);
|
||||
}
|
||||
|
||||
public CollectionEditor Editor { get; set; }
|
||||
|
||||
public string FileExtension
|
||||
{
|
||||
set => _pickerValidator.FileExtension = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
if (_dragHandlers is { HasValidDrag: true })
|
||||
{
|
||||
var area = new Rectangle(Float2.Zero, Size);
|
||||
Render2D.FillRectangle(area, Color.Orange * 0.5f);
|
||||
Render2D.DrawRectangle(area, Color.Black);
|
||||
}
|
||||
|
||||
base.Draw();
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
_pickerValidator.OnDestroy();
|
||||
}
|
||||
|
||||
private bool ValidateActors(ActorNode node)
|
||||
{
|
||||
return node.Actor.GetScript(ElementType.Type) || ElementType.Type.IsAssignableTo(typeof(Actor));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragEnter(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
|
||||
if (_dragHandlers == null)
|
||||
{
|
||||
_dragItems = new DragItems(_pickerValidator.IsValid);
|
||||
_dragActors = new DragActors(ValidateActors);
|
||||
_dragHandlers = new DragHandlers
|
||||
{
|
||||
_dragActors,
|
||||
_dragItems
|
||||
};
|
||||
}
|
||||
return _dragHandlers.OnDragEnter(data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragMove(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
|
||||
return _dragHandlers.Effect;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDragLeave()
|
||||
{
|
||||
_dragHandlers.OnDragLeave();
|
||||
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragDrop(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
{
|
||||
_dragHandlers.OnDragDrop(null);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (_dragHandlers.HasValidDrag)
|
||||
{
|
||||
if (_dragItems.HasValidDrag)
|
||||
{
|
||||
var list = Editor.CloneValues();
|
||||
if (list == null)
|
||||
{
|
||||
if (Editor.Values.Type.IsArray)
|
||||
{
|
||||
list = TypeUtils.CreateArrayInstance(Editor.Values.Type.GetElementType(), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
list = Editor.Values.Type.CreateInstance() as IList;
|
||||
}
|
||||
}
|
||||
if (list.IsFixedSize)
|
||||
{
|
||||
var oldSize = list.Count;
|
||||
var newSize = list.Count + _dragItems.Objects.Count;
|
||||
var type = Editor.Values.Type.GetElementType();
|
||||
var array = TypeUtils.CreateArrayInstance(type, newSize);
|
||||
list.CopyTo(array, 0);
|
||||
|
||||
for (var i = oldSize; i < newSize; i++)
|
||||
{
|
||||
var validator = new AssetPickerValidator
|
||||
{
|
||||
FileExtension = _pickerValidator.FileExtension,
|
||||
AssetType = _pickerValidator.AssetType,
|
||||
SelectedItem = _dragItems.Objects[i - oldSize],
|
||||
};
|
||||
|
||||
if (typeof(AssetItem).IsAssignableFrom(ElementType.Type))
|
||||
array.SetValue(validator.SelectedItem, i);
|
||||
else if (ElementType.Type == typeof(Guid))
|
||||
array.SetValue(validator.SelectedID, i);
|
||||
else if (ElementType.Type == typeof(SceneReference))
|
||||
array.SetValue(new SceneReference(validator.SelectedID), i);
|
||||
else if (ElementType.Type == typeof(string))
|
||||
array.SetValue(validator.SelectedPath, i);
|
||||
else
|
||||
array.SetValue(validator.SelectedAsset, i);
|
||||
|
||||
validator.OnDestroy();
|
||||
}
|
||||
Editor.SetValue(array);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in _dragItems.Objects)
|
||||
{
|
||||
var validator = new AssetPickerValidator
|
||||
{
|
||||
FileExtension = _pickerValidator.FileExtension,
|
||||
AssetType = _pickerValidator.AssetType,
|
||||
SelectedItem = item,
|
||||
};
|
||||
|
||||
if (typeof(AssetItem).IsAssignableFrom(ElementType.Type))
|
||||
list.Add(validator.SelectedItem);
|
||||
else if (ElementType.Type == typeof(Guid))
|
||||
list.Add(validator.SelectedID);
|
||||
else if (ElementType.Type == typeof(SceneReference))
|
||||
list.Add(new SceneReference(validator.SelectedID));
|
||||
else if (ElementType.Type == typeof(string))
|
||||
list.Add(validator.SelectedPath);
|
||||
else
|
||||
list.Add(validator.SelectedAsset);
|
||||
|
||||
validator.OnDestroy();
|
||||
}
|
||||
Editor.SetValue(list);
|
||||
}
|
||||
}
|
||||
else if (_dragActors.HasValidDrag)
|
||||
{
|
||||
var list = Editor.CloneValues();
|
||||
if (list == null)
|
||||
{
|
||||
if (Editor.Values.Type.IsArray)
|
||||
{
|
||||
list = TypeUtils.CreateArrayInstance(Editor.Values.Type.GetElementType(), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
list = Editor.Values.Type.CreateInstance() as IList;
|
||||
}
|
||||
}
|
||||
|
||||
if (list.IsFixedSize)
|
||||
{
|
||||
var oldSize = list.Count;
|
||||
var newSize = list.Count + _dragActors.Objects.Count;
|
||||
var type = Editor.Values.Type.GetElementType();
|
||||
var array = TypeUtils.CreateArrayInstance(type, newSize);
|
||||
list.CopyTo(array, 0);
|
||||
|
||||
for (var i = oldSize; i < newSize; i++)
|
||||
{
|
||||
var actor = _dragActors.Objects[i - oldSize].Actor;
|
||||
if (ElementType.Type.IsAssignableTo(typeof(Actor)))
|
||||
{
|
||||
array.SetValue(actor, i);
|
||||
}
|
||||
else
|
||||
{
|
||||
array.SetValue(actor.GetScript(ElementType.Type), i);
|
||||
}
|
||||
}
|
||||
Editor.SetValue(array);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var actorNode in _dragActors.Objects)
|
||||
{
|
||||
if (ElementType.Type.IsAssignableTo(typeof(Actor)))
|
||||
{
|
||||
list.Add(actorNode.Actor);
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(actorNode.Actor.GetScript(ElementType.Type));
|
||||
}
|
||||
}
|
||||
Editor.SetValue(list);
|
||||
}
|
||||
}
|
||||
|
||||
_dragHandlers.OnDragDrop(null);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -72,14 +72,14 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
return;
|
||||
_isRefreshing = true;
|
||||
var slots = _modelInstance.MaterialSlots;
|
||||
var material = _materialEditor.Picker.SelectedAsset as MaterialBase;
|
||||
var material = _materialEditor.Picker.Validator.SelectedAsset as MaterialBase;
|
||||
var defaultMaterial = GPUDevice.Instance.DefaultMaterial;
|
||||
var value = (ModelInstanceEntry)Values[0];
|
||||
var prevMaterial = value.Material;
|
||||
if (!material)
|
||||
{
|
||||
// Fallback to default material
|
||||
_materialEditor.Picker.SelectedAsset = defaultMaterial;
|
||||
_materialEditor.Picker.Validator.SelectedAsset = defaultMaterial;
|
||||
value.Material = defaultMaterial;
|
||||
}
|
||||
else if (material == slots[_entryIndex].Material)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -38,15 +38,12 @@ namespace FlaxEditor.CustomEditors
|
||||
/// </summary>
|
||||
/// <param name="valueType">Type of the value.</param>
|
||||
/// <param name="getter">The value getter.</param>
|
||||
/// <param name="setter">The value setter.</param>
|
||||
/// <param name="setter">The value setter (can be null if value is read-only).</param>
|
||||
/// <param name="attributes">The custom type attributes used to override the value editor logic or appearance (eg. instance of <see cref="LimitAttribute"/>).</param>
|
||||
public CustomValueContainer(ScriptType valueType, GetDelegate getter, SetDelegate setter, object[] attributes = null)
|
||||
public CustomValueContainer(ScriptType valueType, GetDelegate getter, SetDelegate setter = null, object[] attributes = null)
|
||||
: base(ScriptMemberInfo.Null, valueType)
|
||||
{
|
||||
if (getter == null || setter == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
_getter = getter;
|
||||
_getter = getter ?? throw new ArgumentNullException();
|
||||
_setter = setter;
|
||||
_attributes = attributes;
|
||||
}
|
||||
@@ -57,9 +54,9 @@ namespace FlaxEditor.CustomEditors
|
||||
/// <param name="valueType">Type of the value.</param>
|
||||
/// <param name="initialValue">The initial value.</param>
|
||||
/// <param name="getter">The value getter.</param>
|
||||
/// <param name="setter">The value setter.</param>
|
||||
/// <param name="setter">The value setter (can be null if value is read-only).</param>
|
||||
/// <param name="attributes">The custom type attributes used to override the value editor logic or appearance (eg. instance of <see cref="LimitAttribute"/>).</param>
|
||||
public CustomValueContainer(ScriptType valueType, object initialValue, GetDelegate getter, SetDelegate setter, object[] attributes = null)
|
||||
public CustomValueContainer(ScriptType valueType, object initialValue, GetDelegate getter, SetDelegate setter = null, object[] attributes = null)
|
||||
: this(valueType, getter, setter, attributes)
|
||||
{
|
||||
Add(initialValue);
|
||||
@@ -89,6 +86,8 @@ namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
if (instanceValues == null || instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
if (_setter == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
@@ -105,6 +104,8 @@ namespace FlaxEditor.CustomEditors
|
||||
throw new ArgumentException();
|
||||
if (values == null || values.Count != Count)
|
||||
throw new ArgumentException();
|
||||
if (_setter == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
@@ -120,6 +121,8 @@ namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
if (instanceValues == null || instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
if (_setter == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
|
||||
@@ -8,7 +8,6 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.Marshalling;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.Content.Import;
|
||||
using FlaxEditor.Content.Settings;
|
||||
using FlaxEditor.Content.Thumbnails;
|
||||
using FlaxEditor.Modules;
|
||||
@@ -154,12 +153,12 @@ namespace FlaxEditor
|
||||
public ContentFindingModule ContentFinding;
|
||||
|
||||
/// <summary>
|
||||
/// The scripts editing
|
||||
/// The scripts editing.
|
||||
/// </summary>
|
||||
public CodeEditingModule CodeEditing;
|
||||
|
||||
/// <summary>
|
||||
/// The scripts documentation
|
||||
/// The scripts documentation.
|
||||
/// </summary>
|
||||
public CodeDocsModule CodeDocs;
|
||||
|
||||
@@ -179,7 +178,7 @@ namespace FlaxEditor
|
||||
public ProjectCacheModule ProjectCache;
|
||||
|
||||
/// <summary>
|
||||
/// The undo/redo
|
||||
/// The undo/redo.
|
||||
/// </summary>
|
||||
public EditorUndo Undo;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.IO;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Utilities;
|
||||
@@ -17,189 +18,21 @@ namespace FlaxEditor.GUI
|
||||
/// <seealso cref="Control" />
|
||||
/// <seealso cref="IContentItemOwner" />
|
||||
[HideInEditor]
|
||||
public class AssetPicker : Control, IContentItemOwner
|
||||
public class AssetPicker : Control
|
||||
{
|
||||
private const float DefaultIconSize = 64;
|
||||
private const float ButtonsOffset = 2;
|
||||
private const float ButtonsSize = 12;
|
||||
|
||||
private Asset _selected;
|
||||
private ContentItem _selectedItem;
|
||||
private ScriptType _type;
|
||||
private string _fileExtension;
|
||||
|
||||
private bool _isMouseDown;
|
||||
private Float2 _mouseDownPos;
|
||||
private Float2 _mousePos;
|
||||
private DragItems _dragOverElement;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected item.
|
||||
/// The asset validator. Used to ensure only appropriate items can be picked.
|
||||
/// </summary>
|
||||
public ContentItem SelectedItem
|
||||
{
|
||||
get => _selectedItem;
|
||||
set
|
||||
{
|
||||
if (_selectedItem == value)
|
||||
return;
|
||||
if (value == null)
|
||||
{
|
||||
if (_selected == null && _selectedItem is SceneItem)
|
||||
{
|
||||
// Deselect scene reference
|
||||
_selectedItem.RemoveReference(this);
|
||||
_selectedItem = null;
|
||||
_selected = null;
|
||||
OnSelectedItemChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
// Deselect
|
||||
_selectedItem?.RemoveReference(this);
|
||||
_selectedItem = null;
|
||||
_selected = null;
|
||||
OnSelectedItemChanged();
|
||||
}
|
||||
else if (value is SceneItem item)
|
||||
{
|
||||
if (_selectedItem == item)
|
||||
return;
|
||||
if (!IsValid(item))
|
||||
item = null;
|
||||
|
||||
// Change value to scene reference (cannot load asset because scene can be already loaded - duplicated ID issue)
|
||||
_selectedItem?.RemoveReference(this);
|
||||
_selectedItem = item;
|
||||
_selected = null;
|
||||
_selectedItem?.AddReference(this);
|
||||
OnSelectedItemChanged();
|
||||
}
|
||||
else if (value is AssetItem assetItem)
|
||||
{
|
||||
SelectedAsset = FlaxEngine.Content.LoadAsync(assetItem.ID);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Change value
|
||||
_selectedItem?.RemoveReference(this);
|
||||
_selectedItem = value;
|
||||
_selected = null;
|
||||
OnSelectedItemChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected asset identifier.
|
||||
/// </summary>
|
||||
public Guid SelectedID
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_selected != null)
|
||||
return _selected.ID;
|
||||
if (_selectedItem is AssetItem assetItem)
|
||||
return assetItem.ID;
|
||||
return Guid.Empty;
|
||||
}
|
||||
set => SelectedItem = Editor.Instance.ContentDatabase.FindAsset(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected content item path.
|
||||
/// </summary>
|
||||
public string SelectedPath
|
||||
{
|
||||
get
|
||||
{
|
||||
string path = _selectedItem?.Path ?? _selected?.Path;
|
||||
if (path != null)
|
||||
{
|
||||
// Convert into path relative to the project (cross-platform)
|
||||
var projectFolder = Globals.ProjectFolder;
|
||||
if (path.StartsWith(projectFolder))
|
||||
path = path.Substring(projectFolder.Length + 1);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
SelectedItem = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var path = StringUtils.IsRelative(value) ? Path.Combine(Globals.ProjectFolder, value) : value;
|
||||
SelectedItem = Editor.Instance.ContentDatabase.Find(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected asset object.
|
||||
/// </summary>
|
||||
public Asset SelectedAsset
|
||||
{
|
||||
get => _selected;
|
||||
set
|
||||
{
|
||||
// Check if value won't change
|
||||
if (value == _selected)
|
||||
return;
|
||||
|
||||
// Find item from content database and check it
|
||||
var item = value ? Editor.Instance.ContentDatabase.FindAsset(value.ID) : null;
|
||||
if (item != null && !IsValid(item))
|
||||
item = null;
|
||||
|
||||
// Change value
|
||||
_selectedItem?.RemoveReference(this);
|
||||
_selectedItem = item;
|
||||
_selected = value;
|
||||
_selectedItem?.AddReference(this);
|
||||
OnSelectedItemChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use <see cref="ScriptType.Null"/> for generic file picker.
|
||||
/// </summary>
|
||||
public ScriptType AssetType
|
||||
{
|
||||
get => _type;
|
||||
set
|
||||
{
|
||||
if (_type != value)
|
||||
{
|
||||
_type = value;
|
||||
|
||||
// Auto deselect if the current value is invalid
|
||||
if (_selectedItem != null && !IsValid(_selectedItem))
|
||||
SelectedItem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content items extensions filter. Null if unused.
|
||||
/// </summary>
|
||||
public string FileExtension
|
||||
{
|
||||
get => _fileExtension;
|
||||
set
|
||||
{
|
||||
if (_fileExtension != value)
|
||||
{
|
||||
_fileExtension = value;
|
||||
|
||||
// Auto deselect if the current value is invalid
|
||||
if (_selectedItem != null && !IsValid(_selectedItem))
|
||||
SelectedItem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
public AssetPickerValidator Validator { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when selected item gets changed.
|
||||
@@ -216,38 +49,6 @@ namespace FlaxEditor.GUI
|
||||
/// </summary>
|
||||
public bool CanEdit = true;
|
||||
|
||||
private bool IsValid(ContentItem item)
|
||||
{
|
||||
if (_fileExtension != null && !item.Path.EndsWith(_fileExtension))
|
||||
return false;
|
||||
if (CheckValid != null && !CheckValid(item))
|
||||
return false;
|
||||
if (_type == ScriptType.Null)
|
||||
return true;
|
||||
|
||||
if (item is AssetItem assetItem)
|
||||
{
|
||||
// Faster path for binary items (in-built)
|
||||
if (assetItem is BinaryAssetItem binaryItem)
|
||||
return _type.IsAssignableFrom(new ScriptType(binaryItem.Type));
|
||||
|
||||
// Type filter
|
||||
var type = TypeUtils.GetType(assetItem.TypeName);
|
||||
if (_type.IsAssignableFrom(type))
|
||||
return true;
|
||||
|
||||
// Json assets can contain any type of the object defined by the C# type (data oriented design)
|
||||
if (assetItem is JsonAssetItem && (_type.Type == typeof(JsonAsset) || _type.Type == typeof(Asset)))
|
||||
return true;
|
||||
|
||||
// Special case for scene asset references
|
||||
if (_type.Type == typeof(SceneReference) && assetItem is SceneItem)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AssetPicker"/> class.
|
||||
/// </summary>
|
||||
@@ -264,7 +65,8 @@ namespace FlaxEditor.GUI
|
||||
public AssetPicker(ScriptType assetType, Float2 location)
|
||||
: base(location, new Float2(DefaultIconSize + ButtonsOffset + ButtonsSize, DefaultIconSize))
|
||||
{
|
||||
_type = assetType;
|
||||
Validator = new AssetPickerValidator(assetType);
|
||||
Validator.SelectedItemChanged += OnSelectedItemChanged;
|
||||
_mousePos = Float2.Minimum;
|
||||
}
|
||||
|
||||
@@ -275,10 +77,10 @@ namespace FlaxEditor.GUI
|
||||
{
|
||||
// Update tooltip
|
||||
string tooltip;
|
||||
if (_selectedItem is AssetItem assetItem)
|
||||
if (Validator.SelectedItem is AssetItem assetItem)
|
||||
tooltip = assetItem.NamePath;
|
||||
else
|
||||
tooltip = SelectedPath;
|
||||
tooltip = Validator.SelectedPath;
|
||||
TooltipText = tooltip;
|
||||
|
||||
SelectedItemChanged?.Invoke();
|
||||
@@ -289,37 +91,13 @@ namespace FlaxEditor.GUI
|
||||
// Do the drag drop operation if has selected element
|
||||
if (new Rectangle(Float2.Zero, Size).Contains(ref _mouseDownPos))
|
||||
{
|
||||
if (_selected != null)
|
||||
DoDragDrop(DragAssets.GetDragData(_selected));
|
||||
else if (_selectedItem != null)
|
||||
DoDragDrop(DragItems.GetDragData(_selectedItem));
|
||||
if (Validator.SelectedAsset != null)
|
||||
DoDragDrop(DragAssets.GetDragData(Validator.SelectedAsset));
|
||||
else if (Validator.SelectedItem != null)
|
||||
DoDragDrop(DragItems.GetDragData(Validator.SelectedItem));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnItemDeleted(ContentItem item)
|
||||
{
|
||||
// Deselect item
|
||||
SelectedItem = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnItemRenamed(ContentItem item)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnItemReimported(ContentItem item)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnItemDispose(ContentItem item)
|
||||
{
|
||||
// Deselect item
|
||||
SelectedItem = null;
|
||||
}
|
||||
|
||||
private Rectangle IconRect => new Rectangle(0, 0, Height, Height);
|
||||
|
||||
private Rectangle Button1Rect => new Rectangle(Height + ButtonsOffset, 0, ButtonsSize, ButtonsSize);
|
||||
@@ -341,10 +119,10 @@ namespace FlaxEditor.GUI
|
||||
if (CanEdit)
|
||||
Render2D.DrawSprite(style.ArrowDown, button1Rect, button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
|
||||
|
||||
if (_selectedItem != null)
|
||||
if (Validator.SelectedItem != null)
|
||||
{
|
||||
// Draw item preview
|
||||
_selectedItem.DrawThumbnail(ref iconRect);
|
||||
Validator.SelectedItem.DrawThumbnail(ref iconRect);
|
||||
|
||||
// Draw buttons
|
||||
if (CanEdit)
|
||||
@@ -363,7 +141,7 @@ namespace FlaxEditor.GUI
|
||||
{
|
||||
Render2D.DrawText(
|
||||
style.FontSmall,
|
||||
_selectedItem.ShortName,
|
||||
Validator.SelectedItem.ShortName,
|
||||
new Rectangle(button1Rect.Right + 2, 0, sizeForTextLeft, ButtonsSize),
|
||||
style.Foreground,
|
||||
TextAlignment.Near,
|
||||
@@ -371,7 +149,7 @@ namespace FlaxEditor.GUI
|
||||
}
|
||||
}
|
||||
// Check if has no item but has an asset (eg. virtual asset)
|
||||
else if (_selected)
|
||||
else if (Validator.SelectedAsset)
|
||||
{
|
||||
// Draw remove button
|
||||
Render2D.DrawSprite(style.Cross, button3Rect, button3Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
|
||||
@@ -380,8 +158,8 @@ namespace FlaxEditor.GUI
|
||||
float sizeForTextLeft = Width - button1Rect.Right;
|
||||
if (sizeForTextLeft > 30)
|
||||
{
|
||||
var name = _selected.GetType().Name;
|
||||
if (_selected.IsVirtual)
|
||||
var name = Validator.SelectedAsset.GetType().Name;
|
||||
if (Validator.SelectedAsset.IsVirtual)
|
||||
name += " (virtual)";
|
||||
Render2D.DrawText(
|
||||
style.FontSmall,
|
||||
@@ -407,9 +185,7 @@ namespace FlaxEditor.GUI
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
_selectedItem?.RemoveReference(this);
|
||||
_selectedItem = null;
|
||||
_selected = null;
|
||||
Validator.OnDestroy();
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
@@ -463,57 +239,57 @@ namespace FlaxEditor.GUI
|
||||
// Buttons logic
|
||||
if (!CanEdit)
|
||||
{
|
||||
if (Button1Rect.Contains(location) && _selectedItem != null)
|
||||
if (Button1Rect.Contains(location) && Validator.SelectedItem != null)
|
||||
{
|
||||
// Select asset
|
||||
Editor.Instance.Windows.ContentWin.Select(_selectedItem);
|
||||
Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
|
||||
}
|
||||
}
|
||||
else if (Button1Rect.Contains(location))
|
||||
{
|
||||
Focus();
|
||||
if (_type != ScriptType.Null)
|
||||
if (Validator.AssetType != ScriptType.Null)
|
||||
{
|
||||
// Show asset picker popup
|
||||
var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
|
||||
var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item =>
|
||||
{
|
||||
SelectedItem = item;
|
||||
Validator.SelectedItem = item;
|
||||
RootWindow.Focus();
|
||||
Focus();
|
||||
});
|
||||
if (_selected != null)
|
||||
{
|
||||
var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path);
|
||||
if (Validator.SelectedAsset != null)
|
||||
{
|
||||
var selectedAssetName = Path.GetFileNameWithoutExtension(Validator.SelectedAsset.Path);
|
||||
popup.ScrollToAndHighlightItemByName(selectedAssetName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Show content item picker popup
|
||||
var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
|
||||
var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item =>
|
||||
{
|
||||
SelectedItem = item;
|
||||
Validator.SelectedItem = item;
|
||||
RootWindow.Focus();
|
||||
Focus();
|
||||
});
|
||||
if (_selectedItem != null)
|
||||
if (Validator.SelectedItem != null)
|
||||
{
|
||||
popup.ScrollToAndHighlightItemByName(_selectedItem.ShortName);
|
||||
popup.ScrollToAndHighlightItemByName(Validator.SelectedItem.ShortName);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_selected != null || _selectedItem != null)
|
||||
else if (Validator.SelectedAsset != null || Validator.SelectedItem != null)
|
||||
{
|
||||
if (Button2Rect.Contains(location) && _selectedItem != null)
|
||||
if (Button2Rect.Contains(location) && Validator.SelectedItem != null)
|
||||
{
|
||||
// Select asset
|
||||
Editor.Instance.Windows.ContentWin.Select(_selectedItem);
|
||||
Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
|
||||
}
|
||||
else if (Button3Rect.Contains(location))
|
||||
{
|
||||
// Deselect asset
|
||||
Focus();
|
||||
SelectedItem = null;
|
||||
Validator.SelectedItem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -540,10 +316,10 @@ namespace FlaxEditor.GUI
|
||||
{
|
||||
Focus();
|
||||
|
||||
if (_selectedItem != null && IconRect.Contains(location))
|
||||
if (Validator.SelectedItem != null && IconRect.Contains(location))
|
||||
{
|
||||
// Open it
|
||||
Editor.Instance.ContentEditing.Open(_selectedItem);
|
||||
Editor.Instance.ContentEditing.Open(Validator.SelectedItem);
|
||||
}
|
||||
|
||||
// Handled
|
||||
@@ -557,7 +333,7 @@ namespace FlaxEditor.GUI
|
||||
|
||||
// Check if drop asset
|
||||
if (_dragOverElement == null)
|
||||
_dragOverElement = new DragItems(IsValid);
|
||||
_dragOverElement = new DragItems(Validator.IsValid);
|
||||
if (CanEdit && _dragOverElement.OnDragEnter(data))
|
||||
{
|
||||
}
|
||||
@@ -590,7 +366,7 @@ namespace FlaxEditor.GUI
|
||||
if (CanEdit && _dragOverElement.HasValidDrag)
|
||||
{
|
||||
// Select element
|
||||
SelectedItem = _dragOverElement.Objects[0];
|
||||
Validator.SelectedItem = _dragOverElement.Objects[0];
|
||||
}
|
||||
|
||||
// Clear cache
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -39,6 +39,11 @@ namespace FlaxEditor.GUI.Dialogs
|
||||
/// </summary>
|
||||
public DialogResult Result => _result;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the size of the dialog.
|
||||
/// </summary>
|
||||
public Float2 DialogSize => _dialogSize;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Dialog"/> class.
|
||||
/// </summary>
|
||||
@@ -293,7 +298,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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -465,36 +465,47 @@ namespace FlaxEditor.GUI.Docking
|
||||
{
|
||||
if (Parent.Parent is SplitPanel splitter)
|
||||
{
|
||||
// Check if has any child panels
|
||||
var childPanel = new List<DockPanel>(_childPanels);
|
||||
for (int i = 0; i < childPanel.Count; i++)
|
||||
// Check if there is another nested dock panel inside this dock panel and extract it here
|
||||
var childPanels = _childPanels.ToArray();
|
||||
if (childPanels.Length != 0)
|
||||
{
|
||||
// Undock all tabs
|
||||
var panel = childPanel[i];
|
||||
int count = panel.TabsCount;
|
||||
while (count-- > 0)
|
||||
// Move tabs from child panels into this one
|
||||
DockWindow selectedTab = null;
|
||||
foreach (var childPanel in childPanels)
|
||||
{
|
||||
panel.GetTab(0).Close();
|
||||
var childPanelTabs = childPanel.Tabs.ToArray();
|
||||
for (var i = 0; i < childPanelTabs.Length; i++)
|
||||
{
|
||||
var childPanelTab = childPanelTabs[i];
|
||||
if (selectedTab == null && childPanelTab.IsSelected)
|
||||
selectedTab = childPanelTab;
|
||||
childPanel.UndockWindow(childPanelTab);
|
||||
AddTab(childPanelTab, false);
|
||||
}
|
||||
}
|
||||
if (selectedTab != null)
|
||||
SelectTab(selectedTab);
|
||||
}
|
||||
|
||||
// Unlink splitter
|
||||
var splitterParent = splitter.Parent;
|
||||
Assert.IsNotNull(splitterParent);
|
||||
splitter.Parent = null;
|
||||
|
||||
// Move controls from second split panel to the split panel parent
|
||||
var scrPanel = Parent == splitter.Panel2 ? splitter.Panel1 : splitter.Panel2;
|
||||
var srcPanelChildrenCount = scrPanel.ChildrenCount;
|
||||
for (int i = srcPanelChildrenCount - 1; i >= 0 && scrPanel.ChildrenCount > 0; i--)
|
||||
else
|
||||
{
|
||||
scrPanel.GetChild(i).Parent = splitterParent;
|
||||
}
|
||||
Assert.IsTrue(scrPanel.ChildrenCount == 0);
|
||||
Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount);
|
||||
// Unlink splitter
|
||||
var splitterParent = splitter.Parent;
|
||||
Assert.IsNotNull(splitterParent);
|
||||
splitter.Parent = null;
|
||||
|
||||
// Delete
|
||||
splitter.Dispose();
|
||||
// Move controls from second split panel to the split panel parent
|
||||
var scrPanel = Parent == splitter.Panel2 ? splitter.Panel1 : splitter.Panel2;
|
||||
var srcPanelChildrenCount = scrPanel.ChildrenCount;
|
||||
for (int i = srcPanelChildrenCount - 1; i >= 0 && scrPanel.ChildrenCount > 0; i--)
|
||||
{
|
||||
scrPanel.GetChild(i).Parent = splitterParent;
|
||||
}
|
||||
Assert.IsTrue(scrPanel.ChildrenCount == 0);
|
||||
Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount);
|
||||
|
||||
// Delete
|
||||
splitter.Dispose();
|
||||
}
|
||||
}
|
||||
else if (!IsMaster)
|
||||
{
|
||||
@@ -582,19 +593,17 @@ namespace FlaxEditor.GUI.Docking
|
||||
/// Adds the tab.
|
||||
/// </summary>
|
||||
/// <param name="window">The window to insert as a tab.</param>
|
||||
protected virtual void AddTab(DockWindow window)
|
||||
/// <param name="autoSelect">True if auto-select newly added tab.</param>
|
||||
protected virtual void AddTab(DockWindow window, bool autoSelect = true)
|
||||
{
|
||||
// Dock
|
||||
_tabs.Add(window);
|
||||
window.ParentDockPanel = this;
|
||||
|
||||
// Select tab
|
||||
SelectTab(window);
|
||||
if (autoSelect)
|
||||
SelectTab(window);
|
||||
}
|
||||
|
||||
private void CreateTabsProxy()
|
||||
{
|
||||
// Check if has no tabs proxy created
|
||||
if (_tabsProxy == null)
|
||||
{
|
||||
// Create proxy and make set simple full dock
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace FlaxEditor.GUI.Docking
|
||||
settings.Size = size;
|
||||
settings.Position = location;
|
||||
settings.MinimumSize = new Float2(1);
|
||||
settings.MaximumSize = new Float2(4096);
|
||||
settings.MaximumSize = Float2.Zero; // Unlimited size
|
||||
settings.Fullscreen = false;
|
||||
settings.HasBorder = true;
|
||||
settings.SupportsTransparency = false;
|
||||
|
||||
@@ -58,7 +58,6 @@ namespace FlaxEditor.GUI.Drag
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
return new DragDataText(DragPrefix + item.ID.ToString("N"));
|
||||
}
|
||||
|
||||
@@ -71,11 +70,9 @@ namespace FlaxEditor.GUI.Drag
|
||||
{
|
||||
if (items == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
string text = DragPrefix;
|
||||
foreach (var item in items)
|
||||
text += item.ID.ToString("N") + '\n';
|
||||
|
||||
return new DragDataText(text);
|
||||
}
|
||||
|
||||
@@ -83,9 +80,7 @@ namespace FlaxEditor.GUI.Drag
|
||||
/// Tries to parse the drag data.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>
|
||||
/// Gathered objects or empty IEnumerable if cannot get any valid.
|
||||
/// </returns>
|
||||
/// <returns>Gathered objects or empty IEnumerable if cannot get any valid.</returns>
|
||||
public override IEnumerable<Script> FromDragData(DragData data)
|
||||
{
|
||||
if (data is DragDataText dataText)
|
||||
@@ -97,12 +92,9 @@ namespace FlaxEditor.GUI.Drag
|
||||
var results = new List<Script>(ids.Length);
|
||||
for (int i = 0; i < ids.Length; i++)
|
||||
{
|
||||
// Find element
|
||||
if (Guid.TryParse(ids[i], out Guid id))
|
||||
{
|
||||
var obj = FlaxEngine.Object.Find<Script>(ref id);
|
||||
|
||||
// Check it
|
||||
if (obj != null)
|
||||
results.Add(obj);
|
||||
}
|
||||
@@ -111,11 +103,11 @@ namespace FlaxEditor.GUI.Drag
|
||||
return results.ToArray();
|
||||
}
|
||||
}
|
||||
return new Script[0];
|
||||
return Utils.GetEmptyArray<Script>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse the drag data to validate if it has valid scripts darg.
|
||||
/// Tries to parse the drag data to validate if it has valid scripts drag.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>True if drag data has valid scripts, otherwise false.</returns>
|
||||
@@ -138,7 +130,6 @@ namespace FlaxEditor.GUI.Drag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -182,6 +182,7 @@ namespace FlaxEditor.GUI.Input
|
||||
}
|
||||
SlidingEnd?.Invoke();
|
||||
Defocus();
|
||||
Parent?.Focus();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Utilities;
|
||||
|
||||
@@ -101,8 +101,11 @@ namespace FlaxEditor.GUI
|
||||
if (_isValid(type))
|
||||
{
|
||||
var attributes = type.GetAttributes(true);
|
||||
if (attributes.FirstOrDefault(x => x is HideInEditorAttribute) == null)
|
||||
if (attributes.FirstOrDefault(x => x is HideInEditorAttribute || x is System.Runtime.CompilerServices.CompilerGeneratedAttribute) == null)
|
||||
{
|
||||
var mType = type.Type;
|
||||
if (mType != null && mType.IsValueType && mType.ReflectedType != null && string.Equals(mType.ReflectedType.Name, "<PrivateImplementationDetails>", StringComparison.Ordinal))
|
||||
continue;
|
||||
AddItem(new TypeItemView(type, attributes));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ namespace FlaxEditor.GUI
|
||||
{
|
||||
DoubleClick?.Invoke();
|
||||
RowDoubleClick?.Invoke(this);
|
||||
|
||||
|
||||
return base.OnMouseDoubleClick(location, button);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace FlaxEditor.GUI.Tabs
|
||||
[HideInEditor]
|
||||
public class Tab : ContainerControl
|
||||
{
|
||||
internal Tabs _selectedInTabs;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text.
|
||||
/// </summary>
|
||||
@@ -86,5 +88,25 @@ namespace FlaxEditor.GUI.Tabs
|
||||
{
|
||||
return new Tabs.TabHeader((Tabs)Parent, this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnParentChangedInternal()
|
||||
{
|
||||
if (_selectedInTabs != null)
|
||||
_selectedInTabs.SelectedTab = null;
|
||||
|
||||
base.OnParentChangedInternal();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (IsDisposing)
|
||||
return;
|
||||
if (_selectedInTabs != null)
|
||||
_selectedInTabs.SelectedTab = null;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -261,7 +263,12 @@ namespace FlaxEditor.GUI.Tabs
|
||||
// Check if index will change
|
||||
if (_selectedIndex != index)
|
||||
{
|
||||
SelectedTab?.OnDeselected();
|
||||
var prev = SelectedTab;
|
||||
if (prev != null)
|
||||
{
|
||||
prev._selectedInTabs = null;
|
||||
prev.OnDeselected();
|
||||
}
|
||||
_selectedIndex = index;
|
||||
PerformLayout();
|
||||
OnSelectedTabChanged();
|
||||
@@ -340,8 +347,13 @@ namespace FlaxEditor.GUI.Tabs
|
||||
/// </summary>
|
||||
protected virtual void OnSelectedTabChanged()
|
||||
{
|
||||
var selectedTab = SelectedTab;
|
||||
SelectedTabChanged?.Invoke(this);
|
||||
SelectedTab?.OnSelected();
|
||||
if (selectedTab != null)
|
||||
{
|
||||
selectedTab._selectedInTabs = this;
|
||||
selectedTab.OnSelected();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -402,6 +414,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
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
if (AssetID == value?.ID)
|
||||
return;
|
||||
AssetID = value?.ID ?? Guid.Empty;
|
||||
_picker.SelectedAsset = value;
|
||||
_picker.Validator.SelectedAsset = value;
|
||||
OnAssetChanged();
|
||||
Timeline?.MarkAsEdited();
|
||||
}
|
||||
@@ -63,10 +63,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
|
||||
private void OnPickerSelectedItemChanged()
|
||||
{
|
||||
if (Asset == (TAsset)_picker.SelectedAsset)
|
||||
if (Asset == (TAsset)_picker.Validator.SelectedAsset)
|
||||
return;
|
||||
using (new TrackUndoBlock(this))
|
||||
Asset = (TAsset)_picker.SelectedAsset;
|
||||
Asset = (TAsset)_picker.Validator.SelectedAsset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Viewport.Modes;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Gizmo
|
||||
@@ -13,7 +14,10 @@ namespace FlaxEditor.Gizmo
|
||||
[HideInEditor]
|
||||
public class GizmosCollection : List<GizmoBase>
|
||||
{
|
||||
private IGizmoOwner _owner;
|
||||
private GizmoBase _active;
|
||||
private EditorGizmoMode _activeMode;
|
||||
private readonly List<EditorGizmoMode> _modes = new List<EditorGizmoMode>();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when active gizmo tool gets changed.
|
||||
@@ -31,7 +35,7 @@ namespace FlaxEditor.Gizmo
|
||||
if (_active == value)
|
||||
return;
|
||||
if (value != null && !Contains(value))
|
||||
throw new InvalidOperationException("Invalid Gizmo.");
|
||||
throw new ArgumentException("Not added.");
|
||||
|
||||
_active?.OnDeactivated();
|
||||
_active = value;
|
||||
@@ -40,6 +44,46 @@ namespace FlaxEditor.Gizmo
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the active gizmo mode.
|
||||
/// </summary>
|
||||
public EditorGizmoMode ActiveMode
|
||||
{
|
||||
get => _activeMode;
|
||||
set
|
||||
{
|
||||
if (_activeMode == value)
|
||||
return;
|
||||
if (value != null)
|
||||
{
|
||||
if (!_modes.Contains(value))
|
||||
throw new ArgumentException("Not added.");
|
||||
if (value.Owner != _owner)
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
_activeMode?.OnDeactivated();
|
||||
Active = null;
|
||||
_activeMode = value;
|
||||
_activeMode?.OnActivated();
|
||||
ActiveModeChanged?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when active mode gets changed.
|
||||
/// </summary>
|
||||
public event Action<EditorGizmoMode> ActiveModeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Init.
|
||||
/// </summary>
|
||||
/// <param name="owner">The gizmos owner interface.</param>
|
||||
public GizmosCollection(IGizmoOwner owner)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified item.
|
||||
/// </summary>
|
||||
@@ -57,7 +101,65 @@ namespace FlaxEditor.Gizmo
|
||||
public new void Clear()
|
||||
{
|
||||
Active = null;
|
||||
ActiveMode = null;
|
||||
foreach (var mode in _modes)
|
||||
mode.Dispose();
|
||||
_modes.Clear();
|
||||
|
||||
base.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the mode to the viewport.
|
||||
/// </summary>
|
||||
/// <param name="mode">The mode.</param>
|
||||
public void AddMode(EditorGizmoMode mode)
|
||||
{
|
||||
if (mode == null)
|
||||
throw new ArgumentNullException(nameof(mode));
|
||||
if (_modes.Contains(mode))
|
||||
throw new ArgumentException("Already added.");
|
||||
if (mode.Owner != null)
|
||||
throw new ArgumentException("Already added to other viewport.");
|
||||
|
||||
_modes.Add(mode);
|
||||
mode.Init(_owner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the mode from the viewport.
|
||||
/// </summary>
|
||||
/// <param name="mode">The mode.</param>
|
||||
public void RemoveMode(EditorGizmoMode mode)
|
||||
{
|
||||
if (mode == null)
|
||||
throw new ArgumentNullException(nameof(mode));
|
||||
if (!_modes.Contains(mode))
|
||||
throw new ArgumentException("Not added.");
|
||||
if (mode.Owner != _owner)
|
||||
throw new ArgumentException("Not added to this viewport.");
|
||||
|
||||
if (_activeMode == mode)
|
||||
ActiveMode = null;
|
||||
_modes.Remove(mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the active mode.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The mode type.</typeparam>
|
||||
/// <returns>The activated mode.</returns>
|
||||
public T SetActiveMode<T>() where T : EditorGizmoMode
|
||||
{
|
||||
for (int i = 0; i < _modes.Count; i++)
|
||||
{
|
||||
if (_modes[i] is T mode)
|
||||
{
|
||||
ActiveMode = mode;
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
throw new ArgumentException("Not added mode to activate.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Gizmo
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 '.'";
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -266,6 +266,19 @@ namespace FlaxEditor.Modules
|
||||
Editor.StateMachine.ChangingScenesState.LoadScene(sceneId, additive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reload all loaded scenes.
|
||||
/// </summary>
|
||||
public void ReloadScenes()
|
||||
{
|
||||
foreach (var scene in Level.Scenes)
|
||||
{
|
||||
var sceneId = scene.ID;
|
||||
if (!Level.UnloadScene(scene))
|
||||
Level.LoadScene(sceneId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes scene (async).
|
||||
/// </summary>
|
||||
@@ -332,7 +345,7 @@ namespace FlaxEditor.Modules
|
||||
continue;
|
||||
scenes.Add(s);
|
||||
}
|
||||
|
||||
|
||||
// In play-mode Editor mocks the level streaming script
|
||||
if (Editor.IsPlayMode)
|
||||
{
|
||||
|
||||
@@ -147,6 +147,17 @@ namespace FlaxEditor.Modules.SourceCodeEditing
|
||||
}
|
||||
if (key != null)
|
||||
xml.TryGetValue(key, out text);
|
||||
|
||||
// Customize tooltips for properties to be more human-readable in UI
|
||||
if (text != null && memberType.HasFlag(MemberTypes.Property) && text.StartsWith("Gets or sets ", StringComparison.Ordinal))
|
||||
{
|
||||
text = text.Substring(13);
|
||||
unsafe
|
||||
{
|
||||
fixed (char* e = text)
|
||||
e[0] = char.ToUpper(e[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -41,6 +39,7 @@ namespace FlaxEditor.Modules
|
||||
ContextMenuSingleSelectGroup<int> _numberOfClientsGroup = new ContextMenuSingleSelectGroup<int>();
|
||||
|
||||
private ContextMenuButton _menuFileSaveScenes;
|
||||
private ContextMenuButton _menuFileReloadScenes;
|
||||
private ContextMenuButton _menuFileCloseScenes;
|
||||
private ContextMenuButton _menuFileOpenScriptsProject;
|
||||
private ContextMenuButton _menuFileGenerateScriptsProjectFiles;
|
||||
@@ -208,6 +207,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 +299,7 @@ namespace FlaxEditor.Modules
|
||||
else
|
||||
text = "Ready";
|
||||
|
||||
if(ProgressVisible)
|
||||
if (ProgressVisible)
|
||||
{
|
||||
color = Style.Current.Statusbar.Loading;
|
||||
}
|
||||
@@ -402,7 +402,7 @@ namespace FlaxEditor.Modules
|
||||
{
|
||||
UpdateStatusBar();
|
||||
}
|
||||
else if(ProgressVisible)
|
||||
else if (ProgressVisible)
|
||||
{
|
||||
UpdateStatusBar();
|
||||
}
|
||||
@@ -471,13 +471,13 @@ namespace FlaxEditor.Modules
|
||||
// Place dialog nearby the target control
|
||||
var targetControlDesktopCenter = targetControl.PointToScreen(targetControl.Size * 0.5f);
|
||||
var desktopSize = Platform.GetMonitorBounds(targetControlDesktopCenter);
|
||||
var pos = targetControlDesktopCenter + new Float2(10.0f, -dialog.Height * 0.5f);
|
||||
var dialogEnd = pos + dialog.Size;
|
||||
var pos = targetControlDesktopCenter + new Float2(10.0f, -dialog.DialogSize.Y * 0.5f);
|
||||
var dialogEnd = pos + dialog.DialogSize;
|
||||
var desktopEnd = desktopSize.BottomRight - new Float2(10.0f);
|
||||
if (dialogEnd.X >= desktopEnd.X || dialogEnd.Y >= desktopEnd.Y)
|
||||
pos = targetControl.PointToScreen(Float2.Zero) - new Float2(10.0f + dialog.Width, dialog.Height);
|
||||
pos = targetControl.PointToScreen(Float2.Zero) - new Float2(10.0f + dialog.DialogSize.X, dialog.DialogSize.Y);
|
||||
var desktopBounds = Platform.VirtualDesktopBounds;
|
||||
pos = Float2.Clamp(pos, desktopBounds.UpperLeft, desktopBounds.BottomRight - dialog.Size);
|
||||
pos = Float2.Clamp(pos, desktopBounds.UpperLeft, desktopBounds.BottomRight - dialog.DialogSize);
|
||||
dialog.RootWindow.Window.Position = pos;
|
||||
|
||||
// Register for context menu (prevent auto-closing context menu when selecting color)
|
||||
@@ -528,6 +528,7 @@ namespace FlaxEditor.Modules
|
||||
_menuFileSaveAll = cm.AddButton("Save All", inputOptions.Save, Editor.SaveAll);
|
||||
_menuFileSaveScenes = cm.AddButton("Save scenes", inputOptions.SaveScenes, Editor.Scene.SaveScenes);
|
||||
_menuFileCloseScenes = cm.AddButton("Close scenes", inputOptions.CloseScenes, Editor.Scene.CloseAllScenes);
|
||||
_menuFileReloadScenes = cm.AddButton("Reload scenes", Editor.Scene.ReloadScenes);
|
||||
cm.AddSeparator();
|
||||
_menuFileOpenScriptsProject = cm.AddButton("Open scripts project", inputOptions.OpenScriptsProject, Editor.CodeEditing.OpenSolution);
|
||||
_menuFileGenerateScriptsProjectFiles = cm.AddButton("Generate scripts project files", inputOptions.GenerateScriptsProject, Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync);
|
||||
@@ -557,7 +558,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 +654,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 +689,8 @@ namespace FlaxEditor.Modules
|
||||
_menuToolsTakeScreenshot.ShortKeys = inputOptions.TakeScreenshot.ToString();
|
||||
|
||||
MainMenuShortcutKeysUpdated?.Invoke();
|
||||
|
||||
UpdateToolstrip();
|
||||
}
|
||||
|
||||
private void InitToolstrip(RootControl mainWindow)
|
||||
@@ -709,11 +712,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();
|
||||
@@ -829,6 +832,7 @@ namespace FlaxEditor.Modules
|
||||
|
||||
_menuFileSaveScenes.Enabled = hasOpenedScene;
|
||||
_menuFileCloseScenes.Enabled = hasOpenedScene;
|
||||
_menuFileReloadScenes.Enabled = hasOpenedScene;
|
||||
_menuFileGenerateScriptsProjectFiles.Enabled = !Editor.ProgressReporting.GenerateScriptsProjectFiles.IsActive;
|
||||
|
||||
c.PerformLayout();
|
||||
|
||||
@@ -171,9 +171,13 @@ namespace FlaxEditor.Modules
|
||||
var mainWindow = MainWindow;
|
||||
if (mainWindow)
|
||||
{
|
||||
var projectPath = Globals.ProjectFolder.Replace('/', '\\');
|
||||
var platformBit = Platform.Is64BitApp ? "64" : "32";
|
||||
var title = string.Format("Flax Editor - \'{0}\' ({1}-bit)", projectPath, platformBit);
|
||||
var projectPath = Globals.ProjectFolder;
|
||||
#if PLATFORM_WINDOWS
|
||||
projectPath = projectPath.Replace('/', '\\');
|
||||
#endif
|
||||
var engineVersion = Editor.EngineProject.Version;
|
||||
var engineVersionText = engineVersion.Revision > 0 ? $"{engineVersion.Major}.{engineVersion.Minor}.{engineVersion.Revision}" : $"{engineVersion.Major}.{engineVersion.Minor}";
|
||||
var title = $"Flax Editor {engineVersionText} - \'{projectPath}\'";
|
||||
mainWindow.Title = title;
|
||||
}
|
||||
}
|
||||
@@ -237,7 +241,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 +284,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 +293,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 +315,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 +478,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 +638,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 +652,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();
|
||||
@@ -724,7 +739,6 @@ namespace FlaxEditor.Modules
|
||||
settings.Size = Platform.DesktopSize * 0.75f;
|
||||
settings.StartPosition = WindowStartPosition.CenterScreen;
|
||||
settings.ShowAfterFirstPaint = true;
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
if (!Editor.Instance.Options.Options.Interface.UseNativeWindowSystem)
|
||||
{
|
||||
@@ -736,12 +750,9 @@ namespace FlaxEditor.Modules
|
||||
#elif PLATFORM_LINUX
|
||||
settings.HasBorder = false;
|
||||
#endif
|
||||
|
||||
MainWindow = Platform.CreateWindow(ref settings);
|
||||
|
||||
if (MainWindow == null)
|
||||
{
|
||||
// Error
|
||||
Editor.LogError("Failed to create editor main window!");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -259,10 +259,7 @@ namespace FlaxEditor.Options
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
@@ -270,9 +267,7 @@ namespace FlaxEditor.Options
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
@@ -284,7 +279,6 @@ namespace FlaxEditor.Options
|
||||
InputBinding.TryParse(str, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
|
||||
@@ -295,7 +289,6 @@ namespace FlaxEditor.Options
|
||||
{
|
||||
return ((InputBinding)value).ToString();
|
||||
}
|
||||
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,10 @@ namespace FlaxEditor.Options
|
||||
[EditorDisplay("Common"), EditorOrder(230)]
|
||||
public InputBinding RotateSelection = new InputBinding(KeyboardKeys.R);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "F11")]
|
||||
[EditorDisplay("Common"), EditorOrder(240)]
|
||||
public InputBinding ToggleFullscreen = new InputBinding(KeyboardKeys.F11);
|
||||
|
||||
#endregion
|
||||
|
||||
#region File
|
||||
@@ -208,16 +212,20 @@ namespace FlaxEditor.Options
|
||||
[EditorDisplay("Debugger", "Continue"), EditorOrder(810)]
|
||||
public InputBinding DebuggerContinue = new InputBinding(KeyboardKeys.F5);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Shift+F11")]
|
||||
[EditorDisplay("Debugger", "Unlock mouse in Play Mode"), EditorOrder(820)]
|
||||
public InputBinding DebuggerUnlockMouse = new InputBinding(KeyboardKeys.F11, KeyboardKeys.Shift);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "F10")]
|
||||
[EditorDisplay("Debugger", "Step Over"), EditorOrder(820)]
|
||||
[EditorDisplay("Debugger", "Step Over"), EditorOrder(830)]
|
||||
public InputBinding DebuggerStepOver = new InputBinding(KeyboardKeys.F10);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "F11")]
|
||||
[EditorDisplay("Debugger", "Step Into"), EditorOrder(830)]
|
||||
[EditorDisplay("Debugger", "Step Into"), EditorOrder(840)]
|
||||
public InputBinding DebuggerStepInto = new InputBinding(KeyboardKeys.F11);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Shift+F11")]
|
||||
[EditorDisplay("Debugger", "Step Out"), EditorOrder(840)]
|
||||
[EditorDisplay("Debugger", "Step Out"), EditorOrder(850)]
|
||||
public InputBinding DebuggerStepOut = new InputBinding(KeyboardKeys.F11, KeyboardKeys.Shift);
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -26,45 +26,108 @@ namespace FlaxEditor.Options
|
||||
public float MouseWheelSensitivity { get; set; } = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default movement speed for the viewport camera (must match the dropdown menu values in the viewport).
|
||||
/// Gets or sets the total amount of steps the camera needs to go from minimum to maximum speed.
|
||||
/// </summary>
|
||||
[DefaultValue(1.0f), Limit(0.01f, 100.0f)]
|
||||
[EditorDisplay("Defaults"), EditorOrder(110), Tooltip("The default movement speed for the viewport camera (must match the dropdown menu values in the viewport).")]
|
||||
public float DefaultMovementSpeed { get; set; } = 1.0f;
|
||||
[DefaultValue(64), Limit(1, 128)]
|
||||
[EditorDisplay("Camera"), EditorOrder(110), Tooltip("The total amount of steps the camera needs to go from minimum to maximum speed.")]
|
||||
public int TotalCameraSpeedSteps { get; set; } = 64;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the degree to which the camera will be eased when using camera flight in the editor window.
|
||||
/// </summary>
|
||||
[DefaultValue(3.0f), Limit(1.0f, 8.0f)]
|
||||
[EditorDisplay("Camera"), EditorOrder(111), Tooltip("The degree to which the camera will be eased when using camera flight in the editor window (ignored if camera easing degree is enabled).")]
|
||||
public float CameraEasingDegree { get; set; } = 3.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default movement speed for the viewport camera (must be in range between minimum and maximum movement speed values).
|
||||
/// </summary>
|
||||
[DefaultValue(1.0f), Limit(0.05f, 32.0f)]
|
||||
[EditorDisplay("Defaults"), EditorOrder(120), Tooltip("The default movement speed for the viewport camera (must be in range between minimum and maximum movement speed values).")]
|
||||
public float MovementSpeed { get; set; } = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default minimum camera movement speed.
|
||||
/// </summary>
|
||||
[DefaultValue(0.05f), Limit(0.05f, 32.0f)]
|
||||
[EditorDisplay("Defaults"), EditorOrder(121), Tooltip("The default minimum movement speed for the viewport camera.")]
|
||||
public float MinMovementSpeed { get; set; } = 0.05f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default maximum camera movement speed.
|
||||
/// </summary>
|
||||
[DefaultValue(32.0f), Limit(16.0f, 1000.0f)]
|
||||
[EditorDisplay("Defaults"), EditorOrder(122), Tooltip("The default maximum movement speed for the viewport camera.")]
|
||||
public float MaxMovementSpeed { get; set; } = 32f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default camera easing mode.
|
||||
/// </summary>
|
||||
[DefaultValue(true)]
|
||||
[EditorDisplay("Defaults"), EditorOrder(130), Tooltip("The default camera easing mode.")]
|
||||
public bool UseCameraEasing { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default near clipping plane distance for the viewport camera.
|
||||
/// </summary>
|
||||
[DefaultValue(10.0f), Limit(0.001f, 1000.0f)]
|
||||
[EditorDisplay("Defaults"), EditorOrder(120), Tooltip("The default near clipping plane distance for the viewport camera.")]
|
||||
public float DefaultNearPlane { get; set; } = 10.0f;
|
||||
[EditorDisplay("Defaults"), EditorOrder(140), Tooltip("The default near clipping plane distance for the viewport camera.")]
|
||||
public float NearPlane { get; set; } = 10.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default far clipping plane distance for the viewport camera.
|
||||
/// </summary>
|
||||
[DefaultValue(40000.0f), Limit(10.0f)]
|
||||
[EditorDisplay("Defaults"), EditorOrder(130), Tooltip("The default far clipping plane distance for the viewport camera.")]
|
||||
public float DefaultFarPlane { get; set; } = 40000.0f;
|
||||
[EditorDisplay("Defaults"), EditorOrder(150), Tooltip("The default far clipping plane distance for the viewport camera.")]
|
||||
public float FarPlane { get; set; } = 40000.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default field of view angle (in degrees) for the viewport camera.
|
||||
/// </summary>
|
||||
[DefaultValue(60.0f), Limit(35.0f, 160.0f, 0.1f)]
|
||||
[EditorDisplay("Defaults", "Default Field Of View"), EditorOrder(140), Tooltip("The default field of view angle (in degrees) for the viewport camera.")]
|
||||
public float DefaultFieldOfView { get; set; } = 60.0f;
|
||||
[EditorDisplay("Defaults"), EditorOrder(160), Tooltip("The default field of view angle (in degrees) for the viewport camera.")]
|
||||
public float FieldOfView { get; set; } = 60.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the panning direction is inverted for the viewport camera.
|
||||
/// Gets or sets the default camera orthographic mode.
|
||||
/// </summary>
|
||||
[DefaultValue(false)]
|
||||
[EditorDisplay("Defaults"), EditorOrder(150), Tooltip("Invert the panning direction for the viewport camera.")]
|
||||
public bool DefaultInvertPanning { get; set; } = false;
|
||||
[EditorDisplay("Defaults"), EditorOrder(170), Tooltip("The default camera orthographic mode.")]
|
||||
public bool UseOrthographicProjection { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Scales editor viewport grid.
|
||||
/// Gets or sets the default camera orthographic scale (if camera uses orthographic mode).
|
||||
/// </summary>
|
||||
[DefaultValue(5.0f), Limit(0.001f, 100000.0f, 0.1f)]
|
||||
[EditorDisplay("Defaults"), EditorOrder(180), Tooltip("The default camera orthographic scale (if camera uses orthographic mode).")]
|
||||
public float OrthographicScale { get; set; } = 5.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default panning direction for the viewport camera.
|
||||
/// </summary>
|
||||
[DefaultValue(false)]
|
||||
[EditorDisplay("Defaults"), EditorOrder(190), Tooltip("The default panning direction for the viewport camera.")]
|
||||
public bool InvertPanning { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default relative panning mode.
|
||||
/// </summary>
|
||||
[DefaultValue(true)]
|
||||
[EditorDisplay("Defaults"), EditorOrder(200), Tooltip("The default relative panning mode. Uses distance between camera and target to determine panning speed.")]
|
||||
public bool UseRelativePanning { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default panning speed (ignored if relative panning is speed enabled).
|
||||
/// </summary>
|
||||
[DefaultValue(0.8f), Limit(0.01f, 128.0f, 0.1f)]
|
||||
[EditorDisplay("Defaults"), EditorOrder(210), Tooltip("The default camera panning speed (ignored if relative panning is enabled).")]
|
||||
public float PanningSpeed { get; set; } = 0.8f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default editor viewport grid scale.
|
||||
/// </summary>
|
||||
[DefaultValue(50.0f), Limit(25.0f, 500.0f, 5.0f)]
|
||||
[EditorDisplay("Defaults"), EditorOrder(160), Tooltip("Scales editor viewport grid.")]
|
||||
[EditorDisplay("Defaults"), EditorOrder(220), Tooltip("The default editor viewport grid scale.")]
|
||||
public float ViewportGridScale { get; set; } = 50.0f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -154,7 +154,8 @@ bool ProjectInfo::LoadProject(const String& projectPath)
|
||||
Version = ::Version(
|
||||
JsonTools::GetInt(version, "Major", 0),
|
||||
JsonTools::GetInt(version, "Minor", 0),
|
||||
JsonTools::GetInt(version, "Build", 0));
|
||||
JsonTools::GetInt(version, "Build", -1),
|
||||
JsonTools::GetInt(version, "Revision", -1));
|
||||
}
|
||||
}
|
||||
if (Version.Revision() == 0)
|
||||
|
||||
@@ -23,17 +23,11 @@ namespace FlaxEditor
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
writer.WriteNull();
|
||||
}
|
||||
else if (value is Version)
|
||||
{
|
||||
writer.WriteValue(value.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonSerializationException("Expected Version object value");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -47,65 +41,60 @@ namespace FlaxEditor
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
|
||||
if (reader.TokenType == JsonToken.StartObject)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.StartObject)
|
||||
try
|
||||
{
|
||||
try
|
||||
reader.Read();
|
||||
var values = new Dictionary<string, int>();
|
||||
while (reader.TokenType == JsonToken.PropertyName)
|
||||
{
|
||||
var key = reader.Value as string;
|
||||
reader.Read();
|
||||
Dictionary<string, int> values = new Dictionary<string, int>();
|
||||
while (reader.TokenType == JsonToken.PropertyName)
|
||||
{
|
||||
var key = reader.Value as string;
|
||||
reader.Read();
|
||||
var val = (long)reader.Value;
|
||||
reader.Read();
|
||||
values.Add(key, (int)val);
|
||||
}
|
||||
var val = (long)reader.Value;
|
||||
reader.Read();
|
||||
values.Add(key, (int)val);
|
||||
}
|
||||
|
||||
int major = 0, minor = 0, build = 0;
|
||||
values.TryGetValue("Major", out major);
|
||||
values.TryGetValue("Minor", out minor);
|
||||
values.TryGetValue("Build", out build);
|
||||
values.TryGetValue("Major", out var major);
|
||||
values.TryGetValue("Minor", out var minor);
|
||||
if (!values.TryGetValue("Build", out var build))
|
||||
build = -1;
|
||||
if (!values.TryGetValue("Revision", out var revision))
|
||||
revision = -1;
|
||||
|
||||
Version v = new Version(major, minor, build);
|
||||
return v;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
|
||||
}
|
||||
if (build <= 0)
|
||||
return new Version(major, minor);
|
||||
if (revision <= 0)
|
||||
return new Version(major, minor, build);
|
||||
return new Version(major, minor, build, revision);
|
||||
}
|
||||
else if (reader.TokenType == JsonToken.String)
|
||||
catch (Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
Version v = new Version((string)reader.Value!);
|
||||
return v;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception(String.Format("Unexpected token or value when parsing version. Token: {0}, Value: {1}", reader.TokenType, reader.Value));
|
||||
throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
|
||||
}
|
||||
}
|
||||
if (reader.TokenType == JsonToken.String)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new Version((string)reader.Value!);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
|
||||
}
|
||||
}
|
||||
throw new Exception(String.Format("Unexpected token or value when parsing version. Token: {0}, Value: {1}", reader.TokenType, reader.Value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance can convert the specified object type.
|
||||
/// </summary>
|
||||
/// <param name="objectType">Type of the object.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <returns><c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.</returns>
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(Version);
|
||||
|
||||
@@ -7,6 +7,7 @@ using Real = System.Single;
|
||||
#endif
|
||||
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.SceneGraph.Actors
|
||||
{
|
||||
@@ -30,6 +31,13 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
|
||||
// Rotate to match the space (GUI uses upper left corner as a root)
|
||||
Actor.LocalOrientation = Quaternion.Euler(0, -180, -180);
|
||||
var uiControl = new UIControl
|
||||
{
|
||||
Name = "Canvas Scalar",
|
||||
Transform = Actor.Transform,
|
||||
Control = new CanvasScaler()
|
||||
};
|
||||
Root.Spawn(uiControl, Actor);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -264,7 +266,7 @@ namespace FlaxEditor.SceneGraph.GUI
|
||||
/// <summary>
|
||||
/// Starts the actor renaming action.
|
||||
/// </summary>
|
||||
public void StartRenaming(EditorWindow window)
|
||||
public void StartRenaming(EditorWindow window, Panel treePanel = null)
|
||||
{
|
||||
// Block renaming during scripts reload
|
||||
if (Editor.Instance.ProgressReporting.CompileScripts.IsActive)
|
||||
@@ -279,7 +281,13 @@ namespace FlaxEditor.SceneGraph.GUI
|
||||
(window as PrefabWindow).ScrollingOnTreeView(false);
|
||||
|
||||
// Start renaming the actor
|
||||
var dialog = RenamePopup.Show(this, TextRect, _actorNode.Name, false);
|
||||
var rect = TextRect;
|
||||
if (treePanel != null)
|
||||
{
|
||||
treePanel.ScrollViewTo(this, true);
|
||||
rect.Size = new Float2(treePanel.Width - TextRect.Location.X, TextRect.Height);
|
||||
}
|
||||
var dialog = RenamePopup.Show(this, rect, _actorNode.Name, false);
|
||||
dialog.Renamed += OnRenamed;
|
||||
dialog.Closed += popup =>
|
||||
{
|
||||
@@ -301,10 +309,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -56,12 +56,14 @@ namespace FlaxEditor.States
|
||||
else if (Editor.Options.Options.General.ForceScriptCompilationOnStartup && !skipCompile)
|
||||
{
|
||||
// Generate project files when Cache is missing or was cleared previously
|
||||
if (!Directory.Exists(Path.Combine(Editor.GameProject?.ProjectFolderPath, "Cache", "Intermediate")) ||
|
||||
!Directory.Exists(Path.Combine(Editor.GameProject?.ProjectFolderPath, "Cache", "Projects")))
|
||||
var projectFolderPath = Editor.GameProject?.ProjectFolderPath;
|
||||
if (!Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Intermediate")) ||
|
||||
!Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Projects")))
|
||||
{
|
||||
var customArgs = Editor.Instance.CodeEditing.SelectedEditor.GenerateProjectCustomArgs;
|
||||
var customArgs = Editor.CodeEditing.SelectedEditor?.GenerateProjectCustomArgs;
|
||||
ScriptsBuilder.GenerateProject(customArgs);
|
||||
}
|
||||
|
||||
// Compile scripts before loading any scenes, then we load them and can open scenes
|
||||
ScriptsBuilder.Compile();
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface.ContextMenu;
|
||||
@@ -89,188 +87,7 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
};
|
||||
|
||||
internal static class NodesCache
|
||||
{
|
||||
private static readonly object _locker = new object();
|
||||
private static int _version;
|
||||
private static Task _task;
|
||||
private static VisjectCM _taskContextMenu;
|
||||
private static Dictionary<KeyValuePair<string, ushort>, GroupArchetype> _cache;
|
||||
|
||||
public static void Wait()
|
||||
{
|
||||
_task?.Wait();
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
Wait();
|
||||
|
||||
if (_cache != null && _cache.Count != 0)
|
||||
{
|
||||
OnCodeEditingTypesCleared();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Get(VisjectCM contextMenu)
|
||||
{
|
||||
Wait();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
if (_cache == null)
|
||||
_cache = new Dictionary<KeyValuePair<string, ushort>, GroupArchetype>();
|
||||
contextMenu.LockChildrenRecursive();
|
||||
|
||||
// Check if has cached groups
|
||||
if (_cache.Count != 0)
|
||||
{
|
||||
// Check if context menu doesn't have the recent cached groups
|
||||
if (!contextMenu.Groups.Any(g => g.Archetypes[0].Tag is int asInt && asInt == _version))
|
||||
{
|
||||
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
|
||||
foreach (var g in groups)
|
||||
contextMenu.RemoveGroup(g);
|
||||
foreach (var g in _cache.Values)
|
||||
contextMenu.AddGroup(g);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove any old groups from context menu
|
||||
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
|
||||
foreach (var g in groups)
|
||||
contextMenu.RemoveGroup(g);
|
||||
|
||||
// Register for scripting types reload
|
||||
Editor.Instance.CodeEditing.TypesCleared += OnCodeEditingTypesCleared;
|
||||
|
||||
// Run caching on an async
|
||||
_task = Task.Run(OnActiveContextMenuShowAsync);
|
||||
_taskContextMenu = contextMenu;
|
||||
}
|
||||
|
||||
contextMenu.UnlockChildrenRecursive();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnActiveContextMenuShowAsync()
|
||||
{
|
||||
Profiler.BeginEvent("Setup Anim Graph Context Menu (async)");
|
||||
|
||||
foreach (var scriptType in Editor.Instance.CodeEditing.All.Get())
|
||||
{
|
||||
if (!SurfaceUtils.IsValidVisualScriptType(scriptType))
|
||||
continue;
|
||||
|
||||
// Skip Newtonsoft.Json stuff
|
||||
var scriptTypeTypeName = scriptType.TypeName;
|
||||
if (scriptTypeTypeName.StartsWith("Newtonsoft.Json."))
|
||||
continue;
|
||||
var scriptTypeName = scriptType.Name;
|
||||
|
||||
// Enum
|
||||
if (scriptType.IsEnum)
|
||||
{
|
||||
// Create node archetype
|
||||
var node = (NodeArchetype)Archetypes.Constants.Nodes[10].Clone();
|
||||
node.DefaultValues[0] = Activator.CreateInstance(scriptType.Type);
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = scriptTypeName;
|
||||
node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 2);
|
||||
if (!_cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(243, 156, 18),
|
||||
Tag = _version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
_cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Add node to the group
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Structure
|
||||
if (scriptType.IsValueType)
|
||||
{
|
||||
if (scriptType.IsVoid)
|
||||
continue;
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 4);
|
||||
if (!_cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(155, 89, 182),
|
||||
Tag = _version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
_cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
var tooltip = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
|
||||
// Create Pack node archetype
|
||||
var node = (NodeArchetype)Archetypes.Packing.Nodes[6].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Pack " + scriptTypeName;
|
||||
node.Description = tooltip;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
|
||||
// Create Unpack node archetype
|
||||
node = (NodeArchetype)Archetypes.Packing.Nodes[13].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Unpack " + scriptTypeName;
|
||||
node.Description = tooltip;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Add group to context menu (on a main thread)
|
||||
FlaxEngine.Scripting.InvokeOnUpdate(() =>
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
_taskContextMenu.AddGroups(_cache.Values);
|
||||
_taskContextMenu = null;
|
||||
}
|
||||
});
|
||||
|
||||
Profiler.EndEvent();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
_task = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnCodeEditingTypesCleared()
|
||||
{
|
||||
Wait();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
_cache.Clear();
|
||||
_version++;
|
||||
}
|
||||
|
||||
Editor.Instance.CodeEditing.TypesCleared -= OnCodeEditingTypesCleared;
|
||||
}
|
||||
}
|
||||
private static NodesCache _nodesCache = new NodesCache(IterateNodesCache);
|
||||
|
||||
/// <summary>
|
||||
/// The state machine editing context menu.
|
||||
@@ -345,7 +162,7 @@ namespace FlaxEditor.Surface
|
||||
_cmStateMachineMenu = new VisjectCM(new VisjectCM.InitInfo
|
||||
{
|
||||
Groups = StateMachineGroupArchetypes,
|
||||
CanSpawnNode = arch => true,
|
||||
CanSpawnNode = (_, _) => true,
|
||||
});
|
||||
_cmStateMachineMenu.ShowExpanded = true;
|
||||
}
|
||||
@@ -378,9 +195,7 @@ namespace FlaxEditor.Surface
|
||||
// Check if show additional nodes in the current surface context
|
||||
if (activeCM != _cmStateMachineMenu)
|
||||
{
|
||||
Profiler.BeginEvent("Setup Anim Graph Context Menu");
|
||||
NodesCache.Get(activeCM);
|
||||
Profiler.EndEvent();
|
||||
_nodesCache.Get(activeCM);
|
||||
|
||||
base.OnShowPrimaryMenu(activeCM, location, startBox);
|
||||
|
||||
@@ -394,7 +209,86 @@ namespace FlaxEditor.Surface
|
||||
|
||||
private void OnActiveContextMenuVisibleChanged(Control activeCM)
|
||||
{
|
||||
NodesCache.Wait();
|
||||
_nodesCache.Wait();
|
||||
}
|
||||
|
||||
private static void IterateNodesCache(ScriptType scriptType, Dictionary<KeyValuePair<string, ushort>, GroupArchetype> cache, int version)
|
||||
{
|
||||
// Skip Newtonsoft.Json stuff
|
||||
var scriptTypeTypeName = scriptType.TypeName;
|
||||
if (scriptTypeTypeName.StartsWith("Newtonsoft.Json."))
|
||||
return;
|
||||
var scriptTypeName = scriptType.Name;
|
||||
|
||||
// Enum
|
||||
if (scriptType.IsEnum)
|
||||
{
|
||||
// Create node archetype
|
||||
var node = (NodeArchetype)Archetypes.Constants.Nodes[10].Clone();
|
||||
node.DefaultValues[0] = Activator.CreateInstance(scriptType.Type);
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = scriptTypeName;
|
||||
node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 2);
|
||||
if (!cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(243, 156, 18),
|
||||
Tag = version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Add node to the group
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
return;
|
||||
}
|
||||
|
||||
// Structure
|
||||
if (scriptType.IsValueType)
|
||||
{
|
||||
if (scriptType.IsVoid)
|
||||
return;
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 4);
|
||||
if (!cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(155, 89, 182),
|
||||
Tag = version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
var tooltip = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
|
||||
// Create Pack node archetype
|
||||
var node = (NodeArchetype)Archetypes.Packing.Nodes[6].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Pack " + scriptTypeName;
|
||||
node.Description = tooltip;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
|
||||
// Create Unpack node archetype
|
||||
node = (NodeArchetype)Archetypes.Packing.Nodes[13].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Unpack " + scriptTypeName;
|
||||
node.Description = tooltip;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -406,9 +300,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUseNodeType(NodeArchetype nodeArchetype)
|
||||
public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
return (nodeArchetype.Flags & NodeFlags.AnimGraph) != 0 && base.CanUseNodeType(nodeArchetype);
|
||||
return (nodeArchetype.Flags & NodeFlags.AnimGraph) != 0 && base.CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -488,7 +382,7 @@ namespace FlaxEditor.Surface
|
||||
_cmStateMachineTransitionMenu = null;
|
||||
}
|
||||
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
|
||||
NodesCache.Wait();
|
||||
_nodesCache.Wait();
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUseNodeType(NodeArchetype nodeArchetype)
|
||||
public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
if (nodeArchetype.Title == "Function Input")
|
||||
return true;
|
||||
@@ -44,7 +44,7 @@ namespace FlaxEditor.Surface
|
||||
if (Context == RootContext && nodeArchetype.Title == "Function Output")
|
||||
return true;
|
||||
|
||||
return base.CanUseNodeType(nodeArchetype);
|
||||
return base.CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -465,7 +465,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
if (selectedIndex != -1)
|
||||
{
|
||||
var index = 5 + selectedIndex * 2;
|
||||
SetValue(index, _animationPicker.SelectedID);
|
||||
SetValue(index, _animationPicker.Validator.SelectedID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -495,7 +495,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
if (isValid)
|
||||
{
|
||||
_animationPicker.SelectedID = data1;
|
||||
_animationPicker.Validator.SelectedID = data1;
|
||||
_animationSpeed.Value = data0.W;
|
||||
|
||||
var path = string.Empty;
|
||||
@@ -505,7 +505,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
else
|
||||
{
|
||||
_animationPicker.SelectedID = Guid.Empty;
|
||||
_animationPicker.Validator.SelectedID = Guid.Empty;
|
||||
_animationSpeed.Value = 1.0f;
|
||||
}
|
||||
_animationPicker.Enabled = isValid;
|
||||
@@ -542,9 +542,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
@@ -174,17 +174,17 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
// Ensure to have unique name
|
||||
var title = StateMachineTitle;
|
||||
@@ -484,7 +484,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
var startPos = PointToParent(ref center);
|
||||
targetState.GetConnectionEndPoint(ref startPos, out var endPos);
|
||||
var color = style.Foreground;
|
||||
StateMachineState.DrawConnection(Surface, ref startPos, ref endPos, ref color);
|
||||
StateMachineState.DrawConnection(ref startPos, ref endPos, ref color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,7 +514,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <inheritdoc />
|
||||
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
{
|
||||
StateMachineState.DrawConnection(Surface, ref startPos, ref endPos, ref color);
|
||||
StateMachineState.DrawConnection(ref startPos, ref endPos, ref color);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -680,11 +680,10 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <summary>
|
||||
/// Draws the connection between two state machine nodes.
|
||||
/// </summary>
|
||||
/// <param name="surface">The surface.</param>
|
||||
/// <param name="startPos">The start position.</param>
|
||||
/// <param name="endPos">The end position.</param>
|
||||
/// <param name="color">The line color.</param>
|
||||
public static void DrawConnection(VisjectSurface surface, ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
public static void DrawConnection(ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
{
|
||||
var sub = endPos - startPos;
|
||||
var length = sub.Length;
|
||||
@@ -695,11 +694,14 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
float rotation = Float2.Dot(dir, Float2.UnitY);
|
||||
if (endPos.X < startPos.X)
|
||||
rotation = 2 - rotation;
|
||||
// TODO: make it look better (fix the math)
|
||||
var arrowTransform = Matrix3x3.Translation2D(new Float2(-16.0f, -8.0f)) * Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) * Matrix3x3.Translation2D(endPos);
|
||||
var sprite = Editor.Instance.Icons.VisjectArrowClosed32;
|
||||
var arrowTransform =
|
||||
Matrix3x3.Translation2D(-6.5f, -8) *
|
||||
Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) *
|
||||
Matrix3x3.Translation2D(endPos - dir * 8);
|
||||
|
||||
Render2D.PushTransform(ref arrowTransform);
|
||||
Render2D.DrawSprite(Editor.Instance.Icons.VisjectArrowClosed32, arrowRect, color);
|
||||
Render2D.DrawSprite(sprite, arrowRect, color);
|
||||
Render2D.PopTransform();
|
||||
|
||||
endPos -= dir * 4.0f;
|
||||
@@ -742,9 +744,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
LoadTransitions();
|
||||
|
||||
@@ -1293,7 +1295,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
isMouseOver = Float2.DistanceSquared(ref mousePosition, ref point) < 25.0f;
|
||||
}
|
||||
var color = isMouseOver ? Color.Wheat : t.LineColor;
|
||||
DrawConnection(Surface, ref t.StartPos, ref t.EndPos, ref color);
|
||||
DrawConnection(ref t.StartPos, ref t.EndPos, ref color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1322,7 +1324,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <inheritdoc />
|
||||
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
{
|
||||
DrawConnection(Surface, ref startPos, ref endPos, ref color);
|
||||
DrawConnection(ref startPos, ref endPos, ref color);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1433,9 +1435,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
public override int TransitionsDataIndex => 2;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
// Ensure to have unique name
|
||||
var title = StateTitle;
|
||||
@@ -1453,9 +1455,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
887
Source/Editor/Surface/Archetypes/BehaviorTree.cs
Normal file
887
Source/Editor/Surface/Archetypes/BehaviorTree.cs
Normal file
@@ -0,0 +1,887 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.CustomEditors.Dedicated;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Utilities;
|
||||
|
||||
namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains archetypes for nodes from the Behavior Tree group.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public static class BehaviorTree
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for Behavior Tree nodes wrapped inside <see cref="SurfaceNode" />.
|
||||
/// </summary>
|
||||
internal class NodeBase : SurfaceNode
|
||||
{
|
||||
protected const float ConnectionAreaMargin = 12.0f;
|
||||
protected const float ConnectionAreaHeight = 12.0f;
|
||||
protected const float DecoratorsMarginX = 5.0f;
|
||||
protected const float DecoratorsMarginY = 2.0f;
|
||||
|
||||
protected bool _debugRelevant;
|
||||
protected string _debugInfo;
|
||||
protected Float2 _debugInfoSize;
|
||||
protected ScriptType _type;
|
||||
internal bool _isValueEditing;
|
||||
|
||||
public BehaviorTreeNode Instance;
|
||||
|
||||
protected NodeBase(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
}
|
||||
|
||||
public static string GetTitle(ScriptType scriptType)
|
||||
{
|
||||
var title = scriptType.Name;
|
||||
if (title.StartsWith("BehaviorTree"))
|
||||
title = title.Substring(12);
|
||||
if (title.EndsWith("Node"))
|
||||
title = title.Substring(0, title.Length - 4);
|
||||
if (title.EndsWith("Decorator"))
|
||||
title = title.Substring(0, title.Length - 9);
|
||||
title = Utilities.Utils.GetPropertyNameUI(title);
|
||||
return title;
|
||||
}
|
||||
|
||||
public virtual void UpdateDebug(Behavior behavior)
|
||||
{
|
||||
BehaviorTreeNode instance = null;
|
||||
if (behavior)
|
||||
{
|
||||
// Try to use instance from the currently debugged behavior
|
||||
// TODO: support nodes from nested trees
|
||||
instance = behavior.Tree.GetNodeInstance(ID);
|
||||
}
|
||||
var size = _debugInfoSize;
|
||||
UpdateDebugInfo(instance, behavior);
|
||||
if (size != _debugInfoSize)
|
||||
ResizeAuto();
|
||||
}
|
||||
|
||||
protected virtual void UpdateTitle()
|
||||
{
|
||||
string title = null;
|
||||
if (Instance != null)
|
||||
{
|
||||
title = Instance.Name;
|
||||
if (string.IsNullOrEmpty(title))
|
||||
title = GetTitle(_type);
|
||||
}
|
||||
else
|
||||
{
|
||||
var typeName = (string)Values[0];
|
||||
title = "Missing Type " + typeName;
|
||||
}
|
||||
Title = title;
|
||||
}
|
||||
|
||||
protected virtual void UpdateDebugInfo(BehaviorTreeNode instance = null, Behavior behavior = null)
|
||||
{
|
||||
_debugRelevant = false;
|
||||
_debugInfo = null;
|
||||
_debugInfoSize = Float2.Zero;
|
||||
if (!instance)
|
||||
instance = Instance;
|
||||
if (instance)
|
||||
{
|
||||
// Get debug description for the node based on the current settings
|
||||
_debugRelevant = Behavior.GetNodeDebugRelevancy(instance, behavior);
|
||||
_debugInfo = Behavior.GetNodeDebugInfo(instance, behavior);
|
||||
if (!string.IsNullOrEmpty(_debugInfo))
|
||||
_debugInfoSize = Style.Current.FontSmall.MeasureText(_debugInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded(action);
|
||||
|
||||
// Setup node type and data
|
||||
var typeName = (string)Values[0];
|
||||
_type = TypeUtils.GetType(typeName);
|
||||
if (_type != null)
|
||||
{
|
||||
TooltipText = Editor.Instance.CodeDocs.GetTooltip(_type);
|
||||
try
|
||||
{
|
||||
// Load node instance from data
|
||||
Instance = (BehaviorTreeNode)_type.CreateInstance();
|
||||
var instanceData = (byte[])Values[1];
|
||||
FlaxEngine.Json.JsonSerializer.LoadFromBytes(Instance, instanceData, Globals.EngineBuildNumber);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Editor.LogError("Failed to load Behavior Tree node of type " + typeName);
|
||||
Editor.LogWarning(ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Instance = null;
|
||||
}
|
||||
|
||||
UpdateDebugInfo();
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
public override void OnValuesChanged()
|
||||
{
|
||||
base.OnValuesChanged();
|
||||
|
||||
// Skip updating instance when it's being edited by user via UI
|
||||
if (!_isValueEditing)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
// Reload node instance from data
|
||||
var instanceData = (byte[])Values[1];
|
||||
if (instanceData == null || instanceData.Length == 0)
|
||||
{
|
||||
// Recreate instance data to default state if previous state was empty
|
||||
var defaultInstance = (BehaviorTreeNode)_type.CreateInstance(); // TODO: use default instance from native ScriptingType
|
||||
instanceData = FlaxEngine.Json.JsonSerializer.SaveToBytes(defaultInstance);
|
||||
}
|
||||
FlaxEngine.Json.JsonSerializer.LoadFromBytes(Instance, instanceData, Globals.EngineBuildNumber);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Editor.LogError("Failed to load Behavior Tree node of type " + _type);
|
||||
Editor.LogWarning(ex);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateDebugInfo();
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned(action);
|
||||
|
||||
ResizeAuto();
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
// Debug Info
|
||||
if (!string.IsNullOrEmpty(_debugInfo))
|
||||
{
|
||||
var style = Style.Current;
|
||||
Render2D.DrawText(style.FontSmall, _debugInfo, new Rectangle(4, _headerRect.Bottom + 4, _debugInfoSize), style.Foreground);
|
||||
}
|
||||
|
||||
// Debug relevancy outline
|
||||
if (_debugRelevant)
|
||||
{
|
||||
var colorTop = Color.LightYellow;
|
||||
var colorBottom = Color.Yellow;
|
||||
var backgroundRect = new Rectangle(Float2.One, Size - new Float2(2.0f));
|
||||
Render2D.DrawRectangle(backgroundRect, colorTop, colorTop, colorBottom, colorBottom);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (IsDisposing)
|
||||
return;
|
||||
_debugInfo = null;
|
||||
_type = ScriptType.Null;
|
||||
FlaxEngine.Object.Destroy(ref Instance);
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Customized <see cref="SurfaceNode" /> for the Behavior Tree node.
|
||||
/// </summary>
|
||||
internal class Node : NodeBase
|
||||
{
|
||||
private InputBox _input;
|
||||
private OutputBox _output;
|
||||
internal List<Decorator> _decorators;
|
||||
|
||||
internal static SurfaceNode Create(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
{
|
||||
return new Node(id, context, nodeArch, groupArch);
|
||||
}
|
||||
|
||||
internal Node(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
}
|
||||
|
||||
public unsafe List<uint> DecoratorIds
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = new List<uint>();
|
||||
var ids = Values.Length >= 3 ? Values[2] as byte[] : null;
|
||||
if (ids != null)
|
||||
{
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
int count = ids.Length / sizeof(uint);
|
||||
for (int i = 0; i < count; i++)
|
||||
result.Add(ptr[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
set => SetDecoratorIds(value, true);
|
||||
}
|
||||
|
||||
public unsafe List<Decorator> Decorators
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_decorators == null)
|
||||
{
|
||||
_decorators = new List<Decorator>();
|
||||
var ids = Values.Length >= 3 ? Values[2] as byte[] : null;
|
||||
if (ids != null)
|
||||
{
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
int count = ids.Length / sizeof(uint);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var decorator = Surface.FindNode(ptr[i]) as Decorator;
|
||||
if (decorator != null)
|
||||
_decorators.Add(decorator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return _decorators;
|
||||
}
|
||||
set
|
||||
{
|
||||
_decorators = null;
|
||||
var ids = new byte[sizeof(uint) * value.Count];
|
||||
if (value != null)
|
||||
{
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
for (var i = 0; i < value.Count; i++)
|
||||
ptr[i] = value[i].ID;
|
||||
}
|
||||
}
|
||||
SetValue(2, ids);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void SetDecoratorIds(List<uint> value, bool withUndo)
|
||||
{
|
||||
var ids = new byte[sizeof(uint) * value.Count];
|
||||
if (value != null)
|
||||
{
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
for (var i = 0; i < value.Count; i++)
|
||||
ptr[i] = value[i];
|
||||
}
|
||||
}
|
||||
if (withUndo)
|
||||
SetValue(2, ids);
|
||||
else
|
||||
{
|
||||
Values[2] = ids;
|
||||
OnValuesChanged();
|
||||
Surface?.MarkAsEdited();
|
||||
}
|
||||
}
|
||||
|
||||
public override unsafe SurfaceNode[] SealedNodes
|
||||
{
|
||||
get
|
||||
{
|
||||
// Return decorator nodes attached to this node to be moved/copied/pasted as a one
|
||||
SurfaceNode[] result = null;
|
||||
var ids = Values.Length >= 3 ? Values[2] as byte[] : null;
|
||||
if (ids != null)
|
||||
{
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
int count = ids.Length / sizeof(uint);
|
||||
result = new SurfaceNode[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var decorator = Surface.FindNode(ptr[i]) as Decorator;
|
||||
if (decorator != null)
|
||||
result[i] = decorator;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnShowSecondaryContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu menu, Float2 location)
|
||||
{
|
||||
base.OnShowSecondaryContextMenu(menu, location);
|
||||
|
||||
if (!Surface.CanEdit)
|
||||
return;
|
||||
|
||||
menu.AddSeparator();
|
||||
|
||||
var nodeTypes = Editor.Instance.CodeEditing.BehaviorTreeNodes.Get();
|
||||
|
||||
if (_input.Enabled) // Root node cannot have decorators
|
||||
{
|
||||
var decorators = menu.AddChildMenu("Add Decorator");
|
||||
var decoratorType = new ScriptType(typeof(BehaviorTreeDecorator));
|
||||
foreach (var nodeType in nodeTypes)
|
||||
{
|
||||
if (nodeType != decoratorType && decoratorType.IsAssignableFrom(nodeType))
|
||||
{
|
||||
var button = decorators.ContextMenu.AddButton(GetTitle(nodeType));
|
||||
button.Tag = nodeType;
|
||||
button.TooltipText = Editor.Instance.CodeDocs.GetTooltip(nodeType);
|
||||
button.ButtonClicked += OnAddDecoratorButtonClicked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddDecoratorButtonClicked(ContextMenuButton button)
|
||||
{
|
||||
var nodeType = (ScriptType)button.Tag;
|
||||
|
||||
// Spawn decorator
|
||||
var decorator = Context.SpawnNode(19, 3, Location, new object[]
|
||||
{
|
||||
nodeType.TypeName,
|
||||
Utils.GetEmptyArray<byte>(),
|
||||
});
|
||||
|
||||
// Add decorator to the node
|
||||
var decorators = Decorators;
|
||||
decorators.Add((Decorator)decorator);
|
||||
Decorators = decorators;
|
||||
}
|
||||
|
||||
public override void OnValuesChanged()
|
||||
{
|
||||
// Reject cached value
|
||||
_decorators = null;
|
||||
|
||||
base.OnValuesChanged();
|
||||
|
||||
ResizeAuto();
|
||||
}
|
||||
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded(action);
|
||||
|
||||
// Setup boxes
|
||||
_input = (InputBox)GetBox(0);
|
||||
_output = (OutputBox)GetBox(1);
|
||||
_input.ConnectionOffset = new Float2(0, FlaxEditor.Surface.Constants.BoxSize * -0.5f);
|
||||
_output.ConnectionOffset = new Float2(0, FlaxEditor.Surface.Constants.BoxSize * 0.5f);
|
||||
|
||||
// Setup node type and data
|
||||
var flagsRoot = NodeFlags.NoRemove | NodeFlags.NoCloseButton | NodeFlags.NoSpawnViaPaste;
|
||||
var flags = Archetype.Flags & ~flagsRoot;
|
||||
if (_type != null)
|
||||
{
|
||||
bool isRoot = _type.Type == typeof(BehaviorTreeRootNode);
|
||||
_input.Enabled = _input.Visible = !isRoot;
|
||||
_output.Enabled = _output.Visible = new ScriptType(typeof(BehaviorTreeCompoundNode)).IsAssignableFrom(_type);
|
||||
if (isRoot)
|
||||
flags |= flagsRoot;
|
||||
}
|
||||
if (Archetype.Flags != flags)
|
||||
{
|
||||
// Apply custom flags
|
||||
Archetype = (NodeArchetype)Archetype.Clone();
|
||||
Archetype.Flags = flags;
|
||||
}
|
||||
|
||||
ResizeAuto();
|
||||
}
|
||||
|
||||
public override unsafe void OnPasted(Dictionary<uint, uint> idsMapping)
|
||||
{
|
||||
base.OnPasted(idsMapping);
|
||||
|
||||
// Update decorators
|
||||
var ids = Values.Length >= 3 ? Values[2] as byte[] : null;
|
||||
if (ids != null)
|
||||
{
|
||||
_decorators = null;
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
int count = ids.Length / sizeof(uint);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (idsMapping.TryGetValue(ptr[i], out var id))
|
||||
{
|
||||
// Fix previous parent node to re-apply layout (in case it was forced by spawned decorator)
|
||||
var decorator = Surface.FindNode(ptr[i]) as Decorator;
|
||||
var decoratorNode = decorator?.Node;
|
||||
if (decoratorNode != null)
|
||||
decoratorNode.ResizeAuto();
|
||||
|
||||
// Update mapping to the new node
|
||||
ptr[i] = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
Values[2] = ids;
|
||||
ResizeAuto();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
ResizeAuto();
|
||||
Surface.NodeDeleted += OnNodeDeleted;
|
||||
}
|
||||
|
||||
private void OnNodeDeleted(SurfaceNode node)
|
||||
{
|
||||
if (node is Decorator decorator && Decorators.Contains(decorator))
|
||||
{
|
||||
// Decorator was spawned (eg. via undo)
|
||||
_decorators = null;
|
||||
ResizeAuto();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ResizeAuto()
|
||||
{
|
||||
if (Surface == null)
|
||||
return;
|
||||
var width = 0.0f;
|
||||
var height = 0.0f;
|
||||
var titleLabelFont = Style.Current.FontLarge;
|
||||
width = Mathf.Max(width, 100.0f);
|
||||
width = Mathf.Max(width, titleLabelFont.MeasureText(Title).X + 30);
|
||||
if (_debugInfoSize.X > 0)
|
||||
{
|
||||
width = Mathf.Max(width, _debugInfoSize.X + 8.0f);
|
||||
height += _debugInfoSize.Y + 8.0f;
|
||||
}
|
||||
if (_input != null && _input.Visible)
|
||||
height += ConnectionAreaHeight;
|
||||
if (_output != null && _output.Visible)
|
||||
height += ConnectionAreaHeight;
|
||||
var decorators = Decorators;
|
||||
foreach (var decorator in decorators)
|
||||
{
|
||||
decorator.ResizeAuto();
|
||||
height += decorator.Height + DecoratorsMarginY;
|
||||
width = Mathf.Max(width, decorator.Width - FlaxEditor.Surface.Constants.NodeCloseButtonSize - 2 * DecoratorsMarginX);
|
||||
}
|
||||
Size = new Float2(width + FlaxEditor.Surface.Constants.NodeMarginX * 2 + FlaxEditor.Surface.Constants.NodeCloseButtonSize, height + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.NodeFooterSize);
|
||||
UpdateRectangles();
|
||||
}
|
||||
|
||||
protected override void UpdateRectangles()
|
||||
{
|
||||
Rectangle bounds = Bounds;
|
||||
if (_input != null && _input.Visible)
|
||||
{
|
||||
_input.Bounds = new Rectangle(ConnectionAreaMargin, 0, Width - ConnectionAreaMargin * 2, ConnectionAreaHeight);
|
||||
bounds.Location.Y += _input.Height;
|
||||
}
|
||||
var decorators = Decorators;
|
||||
var indexInParent = IndexInParent;
|
||||
foreach (var decorator in decorators)
|
||||
{
|
||||
decorator.Bounds = new Rectangle(bounds.Location.X + DecoratorsMarginX, bounds.Location.Y, bounds.Width - 2 * DecoratorsMarginX, decorator.Height);
|
||||
bounds.Location.Y += decorator.Height + DecoratorsMarginY;
|
||||
if (decorator.IndexInParent < indexInParent)
|
||||
decorator.IndexInParent = indexInParent + 1; // Push elements above the node
|
||||
}
|
||||
const float footerSize = FlaxEditor.Surface.Constants.NodeFooterSize;
|
||||
const float headerSize = FlaxEditor.Surface.Constants.NodeHeaderSize;
|
||||
const float closeButtonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
|
||||
const float closeButtonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
|
||||
_headerRect = new Rectangle(0, bounds.Y - Y, bounds.Width, headerSize);
|
||||
_closeButtonRect = new Rectangle(bounds.Width - closeButtonSize - closeButtonMargin, _headerRect.Y + closeButtonMargin, closeButtonSize, closeButtonSize);
|
||||
_footerRect = new Rectangle(0, bounds.Height - footerSize, bounds.Width, footerSize);
|
||||
if (_output != null && _output.Visible)
|
||||
{
|
||||
_footerRect.Y -= ConnectionAreaHeight;
|
||||
_output.Bounds = new Rectangle(ConnectionAreaMargin, bounds.Height - ConnectionAreaHeight, bounds.Width - ConnectionAreaMargin * 2, ConnectionAreaHeight);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnLocationChanged()
|
||||
{
|
||||
base.OnLocationChanged();
|
||||
|
||||
// Sync attached elements placement
|
||||
UpdateRectangles();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Customized <see cref="SurfaceNode" /> for the Behavior Tree decorator.
|
||||
/// </summary>
|
||||
internal class Decorator : NodeBase
|
||||
{
|
||||
private sealed class DragDecorator : DragHelper<uint, DragEventArgs>
|
||||
{
|
||||
public const string DragPrefix = "DECORATOR!?";
|
||||
|
||||
public DragDecorator(Func<uint, bool> validateFunction)
|
||||
: base(validateFunction)
|
||||
{
|
||||
}
|
||||
|
||||
public override DragData ToDragData(uint item) => new DragDataText(DragPrefix + item);
|
||||
|
||||
public override DragData ToDragData(IEnumerable<uint> items)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override IEnumerable<uint> FromDragData(DragData data)
|
||||
{
|
||||
if (data is DragDataText dataText)
|
||||
{
|
||||
if (dataText.Text.StartsWith(DragPrefix))
|
||||
{
|
||||
var id = dataText.Text.Remove(0, DragPrefix.Length).Split('\n');
|
||||
return new[] { uint.Parse(id[0]) };
|
||||
}
|
||||
}
|
||||
return Utils.GetEmptyArray<uint>();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ReorderDecoratorAction : IUndoAction
|
||||
{
|
||||
public VisjectSurface Surface;
|
||||
public uint DecoratorId, PrevNodeId, NewNodeId;
|
||||
public int PrevIndex, NewIndex;
|
||||
|
||||
public string ActionString => "Reorder decorator";
|
||||
|
||||
private void Do(uint nodeId, int index)
|
||||
{
|
||||
var decorator = Surface.FindNode(DecoratorId) as Decorator;
|
||||
if (decorator == null)
|
||||
throw new Exception("Missing decorator");
|
||||
var node = decorator.Node;
|
||||
var decorators = node.DecoratorIds;
|
||||
decorators.Remove(DecoratorId);
|
||||
if (node.ID != nodeId)
|
||||
{
|
||||
node.SetDecoratorIds(decorators, false);
|
||||
node = Surface.FindNode(nodeId) as Node;
|
||||
decorators = node.DecoratorIds;
|
||||
}
|
||||
if (index < 0 || index >= decorators.Count)
|
||||
decorators.Add(DecoratorId);
|
||||
else
|
||||
decorators.Insert(index, DecoratorId);
|
||||
node.SetDecoratorIds(decorators, false);
|
||||
}
|
||||
|
||||
public void Do()
|
||||
{
|
||||
Do(NewNodeId, NewIndex);
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
Do(PrevNodeId, PrevIndex);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Surface = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static SurfaceNode Create(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
{
|
||||
return new Decorator(id, context, nodeArch, groupArch);
|
||||
}
|
||||
|
||||
private DragImage _dragIcon;
|
||||
private DragDecorator _dragDecorator;
|
||||
private float _dragLocation = -1;
|
||||
|
||||
internal Decorator(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
_dragDecorator = new DragDecorator(ValidateDrag);
|
||||
}
|
||||
|
||||
public Node Node
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var node in Surface.Nodes)
|
||||
{
|
||||
if (node is Node n && n.DecoratorIds.Contains(ID))
|
||||
return n;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Color FooterColor => Color.Transparent;
|
||||
|
||||
protected override Float2 CalculateNodeSize(float width, float height)
|
||||
{
|
||||
if (_debugInfoSize.X > 0)
|
||||
{
|
||||
width = Mathf.Max(width, _debugInfoSize.X + 8.0f);
|
||||
height += _debugInfoSize.Y + 8.0f;
|
||||
}
|
||||
return new Float2(width + FlaxEditor.Surface.Constants.NodeCloseButtonSize * 2 + DecoratorsMarginX * 2, height + FlaxEditor.Surface.Constants.NodeHeaderSize);
|
||||
}
|
||||
|
||||
protected override void UpdateRectangles()
|
||||
{
|
||||
base.UpdateRectangles();
|
||||
|
||||
_footerRect = Rectangle.Empty;
|
||||
if (_dragIcon != null)
|
||||
_dragIcon.Bounds = new Rectangle(_closeButtonRect.X - _closeButtonRect.Width, _closeButtonRect.Y, _closeButtonRect.Size);
|
||||
}
|
||||
|
||||
protected override void UpdateTitle()
|
||||
{
|
||||
// Update parent node on title change
|
||||
var title = Title;
|
||||
base.UpdateTitle();
|
||||
if (title != Title)
|
||||
Node?.ResizeAuto();
|
||||
}
|
||||
|
||||
protected override void UpdateDebugInfo(BehaviorTreeNode instance, Behavior behavior)
|
||||
{
|
||||
// Update parent node on debug text change
|
||||
var debugInfoSize = _debugInfoSize;
|
||||
base.UpdateDebugInfo(instance, behavior);
|
||||
if (debugInfoSize != _debugInfoSize)
|
||||
Node?.ResizeAuto();
|
||||
}
|
||||
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
// Add drag button to reorder decorator
|
||||
_dragIcon = new DragImage
|
||||
{
|
||||
AnchorPreset = AnchorPresets.TopRight,
|
||||
Color = Style.Current.ForegroundGrey,
|
||||
Parent = this,
|
||||
Margin = new Margin(1),
|
||||
Visible = Surface.CanEdit,
|
||||
Brush = new SpriteBrush(Editor.Instance.Icons.DragBar12),
|
||||
Tag = this,
|
||||
Drag = img => { img.DoDragDrop(_dragDecorator.ToDragData(ID)); }
|
||||
};
|
||||
|
||||
base.OnLoaded(action);
|
||||
}
|
||||
|
||||
private bool ValidateDrag(uint id)
|
||||
{
|
||||
return Surface.FindNode(id) != null;
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
var style = Style.Current;
|
||||
|
||||
// Outline
|
||||
if (!_isSelected)
|
||||
{
|
||||
var rect = new Rectangle(Float2.Zero, Size);
|
||||
Render2D.DrawRectangle(rect, style.BorderHighlighted);
|
||||
}
|
||||
|
||||
// Drag hint
|
||||
if (IsDragOver && _dragDecorator.HasValidDrag)
|
||||
{
|
||||
var rect = new Rectangle(0, _dragLocation < Height * 0.5f ? 0 : Height - 6, Width, 6);
|
||||
Render2D.FillRectangle(rect, style.BackgroundSelected);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
if (action == SurfaceNodeActions.Undo)
|
||||
{
|
||||
// Update parent node layout when restoring decorator from undo
|
||||
var node = Node;
|
||||
if (node != null)
|
||||
{
|
||||
node._decorators = null;
|
||||
node.ResizeAuto();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSurfaceCanEditChanged(bool canEdit)
|
||||
{
|
||||
base.OnSurfaceCanEditChanged(canEdit);
|
||||
|
||||
if (_dragIcon != null)
|
||||
_dragIcon.Visible = canEdit;
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragEnter(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
if (_dragDecorator.OnDragEnter(data))
|
||||
{
|
||||
_dragLocation = location.Y;
|
||||
result = DragDropEffect.Move;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragMove(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
if (_dragDecorator.HasValidDrag)
|
||||
{
|
||||
_dragLocation = location.Y;
|
||||
result = DragDropEffect.Move;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void OnDragLeave()
|
||||
{
|
||||
_dragLocation = -1;
|
||||
_dragDecorator.OnDragLeave();
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragDrop(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
if (_dragDecorator.HasValidDrag)
|
||||
{
|
||||
// Reorder or reparent decorator
|
||||
var decorator = (Decorator)Surface.FindNode(_dragDecorator.Objects[0]);
|
||||
var prevNode = decorator.Node;
|
||||
var prevIndex = prevNode.Decorators.IndexOf(decorator);
|
||||
var newNode = Node;
|
||||
var newIndex = newNode.Decorators.IndexOf(this);
|
||||
if (_dragLocation >= Height * 0.5f)
|
||||
newIndex++;
|
||||
if (prevIndex != newIndex || prevNode != newNode)
|
||||
{
|
||||
var action = new ReorderDecoratorAction
|
||||
{
|
||||
Surface = Surface,
|
||||
DecoratorId = decorator.ID,
|
||||
PrevNodeId = prevNode.ID,
|
||||
PrevIndex = prevIndex,
|
||||
NewNodeId = newNode.ID,
|
||||
NewIndex = newIndex,
|
||||
};
|
||||
action.Do();
|
||||
Surface.Undo?.AddAction(action);
|
||||
}
|
||||
_dragLocation = -1;
|
||||
_dragDecorator.OnDragDrop();
|
||||
result = DragDropEffect.Move;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The nodes for that group.
|
||||
/// </summary>
|
||||
public static NodeArchetype[] Nodes =
|
||||
{
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 1, // Task Node
|
||||
Create = Node.Create,
|
||||
Flags = NodeFlags.BehaviorTreeGraph | NodeFlags.NoSpawnViaGUI,
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
string.Empty, // Type Name
|
||||
Utils.GetEmptyArray<byte>(), // Instance Data
|
||||
null, // List of Decorator Nodes IDs
|
||||
},
|
||||
Size = new Float2(100, 0),
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Input(0, string.Empty, true, ScriptType.Void, 0),
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, ScriptType.Void, 1),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 2, // Root Node
|
||||
Create = Node.Create,
|
||||
Flags = NodeFlags.BehaviorTreeGraph | NodeFlags.NoSpawnViaGUI,
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
typeof(BehaviorTreeRootNode).FullName, // Root node
|
||||
Utils.GetEmptyArray<byte>(), // Instance Data
|
||||
},
|
||||
Size = new Float2(100, 0),
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Input(0, string.Empty, true, ScriptType.Void, 0),
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, ScriptType.Void, 1),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 3, // Decorator Node
|
||||
Create = Decorator.Create,
|
||||
Flags = NodeFlags.BehaviorTreeGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoMove,
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
string.Empty, // Type Name
|
||||
Utils.GetEmptyArray<byte>(), // Instance Data
|
||||
},
|
||||
Size = new Float2(100, 0),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user