diff --git a/Source/Editor/Content/AssetPickerValidator.cs b/Source/Editor/Content/AssetPickerValidator.cs
new file mode 100644
index 000000000..f43ab6a29
--- /dev/null
+++ b/Source/Editor/Content/AssetPickerValidator.cs
@@ -0,0 +1,292 @@
+using System;
+using System.IO;
+using FlaxEditor.Scripting;
+using FlaxEngine;
+using FlaxEngine.Utilities;
+
+namespace FlaxEditor.Content;
+
+///
+/// Manages and converts the selected content item to the appropriate types. Useful for drag operations.
+///
+public class AssetPickerValidator : IContentItemOwner
+{
+ private Asset _selected;
+ private ContentItem _selectedItem;
+ private ScriptType _type;
+ private string _fileExtension;
+
+ ///
+ /// Gets or sets the selected item.
+ ///
+ 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();
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the selected asset identifier.
+ ///
+ 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);
+ }
+
+ ///
+ /// Gets or sets the selected content item path.
+ ///
+ 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);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the selected asset object.
+ ///
+ 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();
+ }
+ }
+
+ ///
+ /// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use for generic file picker.
+ ///
+ 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;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the content items extensions filter. Null if unused.
+ ///
+ 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;
+ }
+ }
+ }
+
+ ///
+ /// Occurs when selected item gets changed.
+ ///
+ public event Action SelectedItemChanged;
+
+ ///
+ /// The custom callback for assets validation. Cane be used to implement a rule for assets to pick.
+ ///
+ public Func CheckValid;
+
+ ///
+ /// Returns whether item is valid.
+ ///
+ ///
+ ///
+ 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;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public AssetPickerValidator()
+ : this(new ScriptType(typeof(Asset)))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The assets types that this picker accepts.
+ public AssetPickerValidator(ScriptType assetType)
+ {
+ _type = assetType;
+ }
+
+ ///
+ /// Called when selected item gets changed.
+ ///
+ protected virtual void OnSelectedItemChanged()
+ {
+ SelectedItemChanged?.Invoke();
+ }
+
+ ///
+ public void OnItemDeleted(ContentItem item)
+ {
+ // Deselect item
+ SelectedItem = null;
+ }
+
+ ///
+ public void OnItemRenamed(ContentItem item)
+ {
+ }
+
+ ///
+ public void OnItemReimported(ContentItem item)
+ {
+ }
+
+ ///
+ public void OnItemDispose(ContentItem item)
+ {
+ // Deselect item
+ SelectedItem = null;
+ }
+
+ ///
+ /// Call to remove reference from the selected item.
+ ///
+ public void OnDestroy()
+ {
+ _selectedItem?.RemoveReference(this);
+ _selectedItem = null;
+ _selected = null;
+ }
+}
diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs
index 03b21e12b..32111e51c 100644
--- a/Source/Editor/CustomEditors/CustomEditor.cs
+++ b/Source/Editor/CustomEditors/CustomEditor.cs
@@ -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;
diff --git a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs
index bf087feda..4829bd91a 100644
--- a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs
@@ -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;
diff --git a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
index cfba940c2..1f3359fd5 100644
--- a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
@@ -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);
}
///
@@ -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;
}
}
diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
index 8922e2d25..7d92c71c0 100644
--- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
@@ -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();
+ 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;
+ }
+
+ ///
+ 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));
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ 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;
+ }
+
+ ///
+ public override void OnDragLeave()
+ {
+ _dragHandlers.OnDragLeave();
+
+ base.OnDragLeave();
+ }
+
+ ///
+ 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;
+ }
+ }
}
}
diff --git a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs
index 9607680f2..c215a5ab7 100644
--- a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs
@@ -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)
diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs
index 8d6b0f9e2..1476832c4 100644
--- a/Source/Editor/GUI/AssetPicker.cs
+++ b/Source/Editor/GUI/AssetPicker.cs
@@ -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
///
///
[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;
///
- /// Gets or sets the selected item.
+ /// The asset validator. Used to ensure only appropriate items can be picked.
///
- 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();
- }
- }
- }
-
- ///
- /// Gets or sets the selected asset identifier.
- ///
- 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);
- }
-
- ///
- /// Gets or sets the selected content item path.
- ///
- 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);
- }
- }
- }
-
- ///
- /// Gets or sets the selected asset object.
- ///
- 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();
- }
- }
-
- ///
- /// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use for generic file picker.
- ///
- 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;
- }
- }
- }
-
- ///
- /// Gets or sets the content items extensions filter. Null if unused.
- ///
- 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; }
///
/// Occurs when selected item gets changed.
@@ -216,38 +49,6 @@ namespace FlaxEditor.GUI
///
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;
- }
-
///
/// Initializes a new instance of the class.
///
@@ -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));
}
}
- ///
- public void OnItemDeleted(ContentItem item)
- {
- // Deselect item
- SelectedItem = null;
- }
-
- ///
- public void OnItemRenamed(ContentItem item)
- {
- }
-
- ///
- public void OnItemReimported(ContentItem item)
- {
- }
-
- ///
- 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
///
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)
+ if (Validator.SelectedAsset != null)
{
- var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path);
+ 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
diff --git a/Source/Editor/GUI/Dialogs/Dialog.cs b/Source/Editor/GUI/Dialogs/Dialog.cs
index 5054aee98..86e24e3b3 100644
--- a/Source/Editor/GUI/Dialogs/Dialog.cs
+++ b/Source/Editor/GUI/Dialogs/Dialog.cs
@@ -39,6 +39,11 @@ namespace FlaxEditor.GUI.Dialogs
///
public DialogResult Result => _result;
+ ///
+ /// Returns the size of the dialog.
+ ///
+ public Float2 DialogSize => _dialogSize;
+
///
/// Initializes a new instance of the class.
///
diff --git a/Source/Editor/GUI/Docking/DockPanel.cs b/Source/Editor/GUI/Docking/DockPanel.cs
index e544285a5..b04aad08c 100644
--- a/Source/Editor/GUI/Docking/DockPanel.cs
+++ b/Source/Editor/GUI/Docking/DockPanel.cs
@@ -465,36 +465,47 @@ namespace FlaxEditor.GUI.Docking
{
if (Parent.Parent is SplitPanel splitter)
{
- // Check if has any child panels
- var childPanel = new List(_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.
///
/// The window to insert as a tab.
- protected virtual void AddTab(DockWindow window)
+ /// True if auto-select newly added tab.
+ 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
diff --git a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
index f8ffe62ad..7bb85751a 100644
--- a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
+++ b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
@@ -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;
diff --git a/Source/Editor/GUI/Tabs/Tab.cs b/Source/Editor/GUI/Tabs/Tab.cs
index 3968cc17d..85f805af0 100644
--- a/Source/Editor/GUI/Tabs/Tab.cs
+++ b/Source/Editor/GUI/Tabs/Tab.cs
@@ -13,6 +13,8 @@ namespace FlaxEditor.GUI.Tabs
[HideInEditor]
public class Tab : ContainerControl
{
+ internal Tabs _selectedInTabs;
+
///
/// Gets or sets the text.
///
@@ -86,5 +88,25 @@ namespace FlaxEditor.GUI.Tabs
{
return new Tabs.TabHeader((Tabs)Parent, this);
}
+
+ ///
+ protected override void OnParentChangedInternal()
+ {
+ if (_selectedInTabs != null)
+ _selectedInTabs.SelectedTab = null;
+
+ base.OnParentChangedInternal();
+ }
+
+ ///
+ public override void OnDestroy()
+ {
+ if (IsDisposing)
+ return;
+ if (_selectedInTabs != null)
+ _selectedInTabs.SelectedTab = null;
+
+ base.OnDestroy();
+ }
}
}
diff --git a/Source/Editor/GUI/Tabs/Tabs.cs b/Source/Editor/GUI/Tabs/Tabs.cs
index d830b9e0f..b5e2cfe39 100644
--- a/Source/Editor/GUI/Tabs/Tabs.cs
+++ b/Source/Editor/GUI/Tabs/Tabs.cs
@@ -263,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();
@@ -342,8 +347,13 @@ namespace FlaxEditor.GUI.Tabs
///
protected virtual void OnSelectedTabChanged()
{
+ var selectedTab = SelectedTab;
SelectedTabChanged?.Invoke(this);
- SelectedTab?.OnSelected();
+ if (selectedTab != null)
+ {
+ selectedTab._selectedInTabs = this;
+ selectedTab.OnSelected();
+ }
}
///
diff --git a/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs b/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs
index b7c87cb01..3bbca15ef 100644
--- a/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs
@@ -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;
}
///
diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs
index 7789eb7c4..4fb28ac76 100644
--- a/Source/Editor/Modules/SceneModule.cs
+++ b/Source/Editor/Modules/SceneModule.cs
@@ -266,6 +266,19 @@ namespace FlaxEditor.Modules
Editor.StateMachine.ChangingScenesState.LoadScene(sceneId, additive);
}
+ ///
+ /// Reload all loaded scenes.
+ ///
+ public void ReloadScenes()
+ {
+ foreach (var scene in Level.Scenes)
+ {
+ var sceneId = scene.ID;
+ if (!Level.UnloadScene(scene))
+ Level.LoadScene(sceneId);
+ }
+ }
+
///
/// Closes scene (async).
///
diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs
index 225bad082..082439402 100644
--- a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs
+++ b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs
@@ -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]);
+ }
+ }
}
}
diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs
index 1369ecd1e..3386d411c 100644
--- a/Source/Editor/Modules/UIModule.cs
+++ b/Source/Editor/Modules/UIModule.cs
@@ -39,6 +39,7 @@ namespace FlaxEditor.Modules
ContextMenuSingleSelectGroup _numberOfClientsGroup = new ContextMenuSingleSelectGroup();
private ContextMenuButton _menuFileSaveScenes;
+ private ContextMenuButton _menuFileReloadScenes;
private ContextMenuButton _menuFileCloseScenes;
private ContextMenuButton _menuFileOpenScriptsProject;
private ContextMenuButton _menuFileGenerateScriptsProjectFiles;
@@ -470,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)
@@ -527,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);
@@ -830,6 +832,7 @@ namespace FlaxEditor.Modules
_menuFileSaveScenes.Enabled = hasOpenedScene;
_menuFileCloseScenes.Enabled = hasOpenedScene;
+ _menuFileReloadScenes.Enabled = hasOpenedScene;
_menuFileGenerateScriptsProjectFiles.Enabled = !Editor.ProgressReporting.GenerateScriptsProjectFiles.IsActive;
c.PerformLayout();
diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs
index 05d72598f..cda83b56e 100644
--- a/Source/Editor/Modules/WindowsModule.cs
+++ b/Source/Editor/Modules/WindowsModule.cs
@@ -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;
}
}
@@ -735,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)
{
@@ -747,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;
}
diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs
index 90d098bb6..e2e7d0e71 100644
--- a/Source/Editor/Options/InputOptions.cs
+++ b/Source/Editor/Options/InputOptions.cs
@@ -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
diff --git a/Source/Editor/SceneGraph/Actors/UICanvasNode.cs b/Source/Editor/SceneGraph/Actors/UICanvasNode.cs
index c09e1a246..ed6fc08d6 100644
--- a/Source/Editor/SceneGraph/Actors/UICanvasNode.cs
+++ b/Source/Editor/SceneGraph/Actors/UICanvasNode.cs
@@ -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);
}
///
diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
index 163397768..f64e46385 100644
--- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
+++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
@@ -266,7 +266,7 @@ namespace FlaxEditor.SceneGraph.GUI
///
/// Starts the actor renaming action.
///
- public void StartRenaming(EditorWindow window)
+ public void StartRenaming(EditorWindow window, Panel treePanel = null)
{
// Block renaming during scripts reload
if (Editor.Instance.ProgressReporting.CompileScripts.IsActive)
@@ -281,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 =>
{
diff --git a/Source/Editor/States/LoadingState.cs b/Source/Editor/States/LoadingState.cs
index 698dc192f..d48918e1b 100644
--- a/Source/Editor/States/LoadingState.cs
+++ b/Source/Editor/States/LoadingState.cs
@@ -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();
}
diff --git a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs
index 9773e9695..7c3aa4f13 100644
--- a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs
+++ b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs
@@ -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;
diff --git a/Source/Editor/Surface/Archetypes/Bitwise.cs b/Source/Editor/Surface/Archetypes/Bitwise.cs
index 06f719adc..43dcb91b5 100644
--- a/Source/Editor/Surface/Archetypes/Bitwise.cs
+++ b/Source/Editor/Surface/Archetypes/Bitwise.cs
@@ -60,7 +60,7 @@ namespace FlaxEditor.Surface.Archetypes
Op1(1, "Bitwise NOT", "Negates the value using bitwise operation", new[] { "!", "~" }),
Op2(2, "Bitwise AND", "Performs a bitwise conjunction on two values", new[] { "&" }),
Op2(3, "Bitwise OR", "", new[] { "|" }),
- Op2(4, "Bitwise XOR", ""),
+ Op2(4, "Bitwise XOR", "", new[] { "^" }),
};
}
}
diff --git a/Source/Editor/Surface/Archetypes/Boolean.cs b/Source/Editor/Surface/Archetypes/Boolean.cs
index 153b2fead..ed97a9642 100644
--- a/Source/Editor/Surface/Archetypes/Boolean.cs
+++ b/Source/Editor/Surface/Archetypes/Boolean.cs
@@ -60,7 +60,7 @@ namespace FlaxEditor.Surface.Archetypes
Op1(1, "Boolean NOT", "Negates the boolean value", new[] { "!", "~" }),
Op2(2, "Boolean AND", "Performs a logical conjunction on two values", new[] { "&&" }),
Op2(3, "Boolean OR", "Returns true if either (or both) of its operands is true", new[] { "||" }),
- Op2(4, "Boolean XOR", ""),
+ Op2(4, "Boolean XOR", "", new [] { "^" } ),
Op2(5, "Boolean NOR", ""),
Op2(6, "Boolean NAND", ""),
};
diff --git a/Source/Editor/Surface/Archetypes/Comparisons.cs b/Source/Editor/Surface/Archetypes/Comparisons.cs
index a0efbd92f..b72111c46 100644
--- a/Source/Editor/Surface/Archetypes/Comparisons.cs
+++ b/Source/Editor/Surface/Archetypes/Comparisons.cs
@@ -14,7 +14,7 @@ namespace FlaxEditor.Surface.Archetypes
[HideInEditor]
public static class Comparisons
{
- private static NodeArchetype Op(ushort id, string title, string desc)
+ private static NodeArchetype Op(ushort id, string title, string desc, string[] altTitles = null)
{
return new NodeArchetype
{
@@ -22,6 +22,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = title,
Description = desc,
Flags = NodeFlags.AllGraphs,
+ AlternativeTitles = altTitles,
ConnectionsHints = ConnectionsHint.Value,
Size = new Float2(100, 40),
IndependentBoxes = new[]
@@ -170,12 +171,12 @@ namespace FlaxEditor.Surface.Archetypes
///
public static NodeArchetype[] Nodes =
{
- Op(1, "==", "Determines whether two values are equal"),
- Op(2, "!=", "Determines whether two values are not equal"),
- Op(3, ">", "Determines whether the first value is greater than the other"),
- Op(4, "<", "Determines whether the first value is less than the other"),
- Op(5, "<=", "Determines whether the first value is less or equal to the other"),
- Op(6, ">=", "Determines whether the first value is greater or equal to the other"),
+ Op(1, "==", "Determines whether two values are equal", new[] { "equals" }),
+ Op(2, "!=", "Determines whether two values are not equal", new[] { "not equals" }),
+ Op(3, ">", "Determines whether the first value is greater than the other", new[] { "greater than", "larger than", "bigger than" }),
+ Op(4, "<", "Determines whether the first value is less than the other", new[] { "less than", "smaller than" }),
+ Op(5, "<=", "Determines whether the first value is less or equal to the other", new[] { "less equals than", "smaller equals than" }),
+ Op(6, ">=", "Determines whether the first value is greater or equal to the other", new[] { "greater equals than", "larger equals than", "bigger equals than" }),
new NodeArchetype
{
TypeID = 7,
diff --git a/Source/Editor/Surface/Archetypes/Constants.cs b/Source/Editor/Surface/Archetypes/Constants.cs
index cbe7822e3..e58d917d1 100644
--- a/Source/Editor/Surface/Archetypes/Constants.cs
+++ b/Source/Editor/Surface/Archetypes/Constants.cs
@@ -7,11 +7,12 @@ using Real = System.Single;
#endif
using System;
-using System.Reflection;
+using System.Linq;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.GUI;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
+using FlaxEditor.Surface.Undo;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -24,6 +25,109 @@ namespace FlaxEditor.Surface.Archetypes
[HideInEditor]
public static class Constants
{
+ ///
+ /// A special type of node that adds the functionality to convert nodes to parameters.
+ ///
+ internal class ConvertToParameterNode : SurfaceNode
+ {
+ private readonly ScriptType _type;
+ private readonly Func