Merge branch '1.5' into dotnet7

# Conflicts:
#	Source/Editor/Managed/ManagedEditor.Internal.cpp
#	Source/Engine/Core/Config/LayersAndTagsSettings.cs
This commit is contained in:
Wojtek Figat
2022-12-28 18:49:14 +01:00
97 changed files with 1945 additions and 421 deletions

View File

@@ -95,19 +95,17 @@ namespace FlaxEditor.Content
{
if (!_folder.CanRename)
return;
Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(false);
// Start renaming the folder
var dialog = RenamePopup.Show(this, HeaderRect, _folder.ShortName, false);
Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(false);
var dialog = RenamePopup.Show(this, TextRect, _folder.ShortName, false);
dialog.Tag = _folder;
dialog.Renamed += popup =>
{
Editor.Instance.Windows.ContentWin.Rename((ContentFolder)popup.Tag, popup.Text);
Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(true);
};
dialog.Closed += popup =>
{
Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(true);
};
dialog.Closed += popup => { Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(true); };
}
/// <summary>

View File

@@ -67,19 +67,21 @@ namespace FlaxEditor.CustomEditors
{
if (targetType.Type.GetArrayRank() != 1)
return new GenericEditor(); // Not-supported multidimensional array
// Allow using custom editor for array of custom type
var customEditorType = Internal_GetCustomEditor(targetType.Type);
if (customEditorType != null)
return (CustomEditor)Activator.CreateInstance(customEditorType);
return new ArrayEditor();
}
var targetTypeType = TypeUtils.GetType(targetType);
if (canUseRefPicker)
{
if (typeof(Asset).IsAssignableFrom(targetTypeType))
{
return new AssetRefEditor();
}
if (typeof(FlaxEngine.Object).IsAssignableFrom(targetTypeType))
{
return new FlaxObjectRefEditor();
}
}
// Use custom editor
@@ -87,11 +89,9 @@ namespace FlaxEditor.CustomEditors
var checkType = targetTypeType;
do
{
var type = Internal_GetCustomEditor(checkType);
if (type != null)
{
return (CustomEditor)Activator.CreateInstance(type);
}
var customEditorType = Internal_GetCustomEditor(checkType);
if (customEditorType != null)
return (CustomEditor)Activator.CreateInstance(customEditorType);
checkType = checkType.BaseType;
// Skip if cannot use ref editors
@@ -108,23 +108,17 @@ namespace FlaxEditor.CustomEditors
// Select default editor (based on type)
if (targetType.IsEnum)
{
return new EnumEditor();
}
if (targetType.IsGenericType)
if (targetType.IsGenericType)
{
if (targetTypeType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
return new DictionaryEditor();
}
// Use custom editor
var genericTypeDefinition = targetType.GetGenericTypeDefinition();
var type = Internal_GetCustomEditor(genericTypeDefinition);
if (type != null)
{
return (CustomEditor)Activator.CreateInstance(type);
}
var customEditorType = Internal_GetCustomEditor(genericTypeDefinition);
if (customEditorType != null)
return (CustomEditor)Activator.CreateInstance(customEditorType);
}
// The most generic editor

View File

@@ -72,7 +72,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
cm.ItemClicked += item => AddScript((ScriptType)item.Tag);
cm.SortItems();
cm.Show(this, button.BottomLeft);
cm.Show(this, button.BottomLeft - new Float2((cm.Width - button.Width) / 2, 0));
}
/// <inheritdoc />

View File

@@ -407,6 +407,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
public class UIControlControlEditor : GenericEditor
{
private Type _cachedType;
private bool _anchorDropDownClosed = true;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
@@ -498,7 +499,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
ItemInfo maxItem = new ItemInfo(maxInfo);
GroupElement ng = main.Group("Anchors", true);
ng.Panel.Close(false);
ng.Panel.IsClosed = _anchorDropDownClosed;
ng.Panel.IsClosedChanged += panel => _anchorDropDownClosed = panel.IsClosed;
ng.Property("Min", minItem.GetValues(Values));
ng.Property("Max", maxItem.GetValues(Values));
}

View File

@@ -1,57 +0,0 @@
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
using FlaxEditor.Content.Settings;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.GUI;
namespace FlaxEditor.CustomEditors.Editors
{
/// <summary>
/// Custom editor for picking actor tag. Instead of choosing tag index or entering tag text it shows a combo box with simple tag picking by name.
/// </summary>
public sealed class ActorTagEditor : CustomEditor
{
private ComboBoxElement element;
private const string NoTagText = "Untagged";
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
element = layout.ComboBox();
element.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged;
// Set tag names
element.ComboBox.AddItem(NoTagText);
element.ComboBox.AddItems(LayersAndTagsSettings.GetCurrentTags());
}
private void OnSelectedIndexChanged(ComboBox comboBox)
{
string value = comboBox.SelectedItem;
if (value == NoTagText)
value = string.Empty;
SetValue(value);
}
/// <inheritdoc />
public override void Refresh()
{
base.Refresh();
if (HasDifferentValues)
{
// TODO: support different values on many actor selected
}
else
{
string value = (string)Values[0];
if (string.IsNullOrEmpty(value))
value = NoTagText;
element.ComboBox.SelectedItem = value;
}
}
}
}

View File

@@ -32,6 +32,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
Width = 16.0f,
Text = "...",
TooltipText = "Edit...",
Parent = _label,
};
button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
@@ -81,30 +82,6 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
private static void UpdateFilter(TreeNode node, string filterText)
{
// Update children
bool isAnyChildVisible = false;
for (int i = 0; i < node.Children.Count; i++)
{
if (node.Children[i] is TreeNode child)
{
UpdateFilter(child, filterText);
isAnyChildVisible |= child.Visible;
}
}
// Update itself
bool noFilter = string.IsNullOrWhiteSpace(filterText);
bool isThisVisible = noFilter || QueryFilterHelper.Match(filterText, node.Text);
bool isExpanded = isAnyChildVisible;
if (isExpanded)
node.Expand(true);
else
node.Collapse(true);
node.Visible = isThisVisible | isAnyChildVisible;
}
private void ShowPicker()
{
var menu = CreatePicker(Culture, value => { Culture = value; });
@@ -147,8 +124,7 @@ namespace FlaxEditor.CustomEditors.Editors
if (tree.IsLayoutLocked)
return;
root.LockChildrenRecursive();
var query = searchBox.Text;
UpdateFilter(root, query);
Utilities.Utils.UpdateSearchPopupFilter(root, searchBox.Text);
root.UnlockChildrenRecursive();
menu.PerformLayout();
};

View File

@@ -0,0 +1,497 @@
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Tree;
using FlaxEngine;
using FlaxEngine.GUI;
using System.Collections.Generic;
using System;
using System.Linq;
using System.Text;
using FlaxEditor.Content.Settings;
namespace FlaxEditor.CustomEditors.Editors
{
/// <summary>
/// Custom editor for <see cref="Tag"/>.
/// </summary>
[CustomEditor(typeof(Tag)), DefaultEditor]
public sealed class TagEditor : CustomEditor
{
private ClickableLabel _label;
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
_label = layout.ClickableLabel(Tag.ToString()).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 = Tag.ToString();
}
private Tag Tag
{
get
{
if (Values[0] is Tag asTag)
return asTag;
if (Values[0] is int asInt)
return new Tag(asInt);
if (Values[0] is string asString)
return Tags.Get(asString);
return Tag.Default;
}
set
{
if (Values[0] is Tag)
SetValue(value);
if (Values[0] is int)
SetValue(value.Index);
else if (Values[0] is string)
SetValue(value.ToString());
}
}
private void ShowPicker()
{
var menu = CreatePicker(Tag, null, new PickerData
{
IsSingle = true,
SetValue = value => { Tag = value; },
});
menu.Show(_label, new Float2(0, _label.Height));
}
internal class PickerData
{
public bool IsEditing;
public bool IsSingle;
public Action<Tag> SetValue;
public Action<Tag[]> SetValues;
public List<Tag> CachedTags;
}
private static void UncheckAll(TreeNode n, TreeNode except = null)
{
foreach (var child in n.Children)
{
if (child is CheckBox checkBox && except != n)
checkBox.Checked = false;
else if (child is TreeNode treeNode)
UncheckAll(treeNode, except);
}
}
private static void UncheckAll(Tree n, TreeNode except = null)
{
foreach (var child in n.Children)
{
if (child is TreeNode treeNode)
UncheckAll(treeNode, except);
}
}
private static void GetTags(TreeNode n, PickerData pickerData)
{
if (n is TreeNodeWithAddons a && a.Addons.Count != 0 && a.Addons[0] is CheckBox c && c.Checked)
pickerData.CachedTags.Add(new Tag((int)n.Tag));
foreach (var child in n.Children)
{
if (child is TreeNode treeNode)
GetTags(treeNode, pickerData);
}
}
private static void GetTags(Tree n, PickerData pickerData)
{
foreach (var child in n.Children)
{
if (child is TreeNode treeNode)
GetTags(treeNode, pickerData);
}
}
private static void OnCheckboxEdited(TreeNode node, CheckBox c, PickerData pickerData)
{
if (pickerData.IsEditing)
return;
pickerData.IsEditing = true;
if (pickerData.IsSingle)
{
UncheckAll(node.ParentTree, node);
var value = new Tag(c.Checked ? (int)node.Tag : -1);
pickerData.SetValue?.Invoke(value);
pickerData.SetValues?.Invoke(new[] { value });
}
else
{
if (pickerData.CachedTags == null)
pickerData.CachedTags = new List<Tag>();
else
pickerData.CachedTags.Clear();
GetTags(node.ParentTree, pickerData);
pickerData.SetValue?.Invoke(pickerData.CachedTags.Count != 0 ? pickerData.CachedTags[0] : Tag.Default);
pickerData.SetValues?.Invoke(pickerData.CachedTags.ToArray());
}
pickerData.IsEditing = false;
}
private static void OnAddButtonClicked(Tree tree, TreeNode parentNode, PickerData pickerData)
{
// Create new node
var nodeIndent = 16.0f;
var indentation = 0;
var parentTag = string.Empty;
if (parentNode.CustomArrowRect.HasValue)
{
indentation = (int)((parentNode.CustomArrowRect.Value.Location.X - 18) / nodeIndent) + 1;
var parentIndex = (int)parentNode.Tag;
parentTag = Tags.List[parentIndex];
}
var node = new TreeNodeWithAddons
{
ChildrenIndent = nodeIndent,
CullChildren = false,
ClipChildren = false,
TextMargin = new Margin(22.0f, 2.0f, 2.0f, 2.0f),
CustomArrowRect = new Rectangle(18 + indentation * nodeIndent, 2, 12, 12),
BackgroundColorSelected = Color.Transparent,
BackgroundColorHighlighted = parentNode.BackgroundColorHighlighted,
};
var checkbox = new CheckBox(32.0f + indentation * nodeIndent, 0)
{
Height = 16.0f,
IsScrollable = false,
Parent = node,
};
node.Addons.Add(checkbox);
checkbox.StateChanged += c => OnCheckboxEdited(node, c, pickerData);
var addButton = new Button(tree.Width - 16, 0, 14, 14)
{
Text = "+",
TooltipText = "Add subtag within this tag namespace",
IsScrollable = false,
BorderColor = Color.Transparent,
BackgroundColor = Color.Transparent,
Parent = node,
AnchorPreset = AnchorPresets.TopRight,
};
node.Addons.Add(addButton);
addButton.ButtonClicked += button => OnAddButtonClicked(tree, node, pickerData);
// Link
node.Parent = parentNode;
node.IndexInParent = 0;
parentNode.Expand(true);
((Panel)tree.Parent.Parent).ScrollViewTo(node);
// Start renaming the tag
var prefix = parentTag.Length != 0 ? parentTag + '.' : string.Empty;
var renameArea = node.HeaderRect;
if (renameArea.Location.X < nodeIndent)
{
// Fix root node's child renaming
renameArea.Location.X += nodeIndent;
renameArea.Size.X -= nodeIndent;
}
var dialog = RenamePopup.Show(node, renameArea, prefix, false);
var cursor = dialog.InputField.TextLength;
dialog.InputField.SelectionRange = new TextRange(cursor, cursor);
dialog.Validate = (popup, value) =>
{
// Ensure that name is unique
if (Tags.List.Contains(value))
return false;
// Ensure user entered direct subtag of the parent node
if (value.StartsWith(popup.InitialValue))
{
var name = value.Substring(popup.InitialValue.Length);
if (name.Length > 0 && name.IndexOf('.') == -1)
return true;
}
return false;
};
dialog.Renamed += popup =>
{
// Get tag name
var tagName = popup.Text;
var name = tagName.Substring(popup.InitialValue.Length);
if (name.Length == 0)
return;
// Add tag
var tag = Tags.Get(tagName);
node.Text = name;
node.Tag = tag.Index;
var settingsAsset = GameSettings.LoadAsset<LayersAndTagsSettings>();
if (settingsAsset && !settingsAsset.WaitForLoaded())
{
// Save any local changes to the tags asset in Editor
var settingsAssetItem = Editor.Instance.ContentDatabase.FindAsset(settingsAsset.ID) as Content.JsonAssetItem;
var assetWindow = Editor.Instance.Windows.FindEditor(settingsAssetItem) as Windows.Assets.JsonAssetWindow;
assetWindow?.Save();
// Update asset
var settingsObj = (LayersAndTagsSettings)settingsAsset.Instance;
settingsObj.Tags.Add(tagName);
settingsAsset.SetInstance(settingsObj);
}
};
dialog.Closed += popup =>
{
// Remove temporary node if renaming was canceled
if (popup.InitialValue == popup.Text || popup.Text.Length == 0)
node.Dispose();
};
}
internal static ContextMenuBase CreatePicker(Tag value, Tag[] values, PickerData pickerData)
{
// Initialize search popup
var menu = Utilities.Utils.CreateSearchPopup(out var searchBox, out var tree, 20.0f);
// Create tree with tags hierarchy
tree.Margin = new Margin(-16.0f, 0.0f, -16.0f, -0.0f); // Hide root node
var tags = Tags.List;
var nameToNode = new Dictionary<string, ContainerControl>();
var style = FlaxEngine.GUI.Style.Current;
var nodeBackgroundColorHighlighted = style.BackgroundHighlighted * 0.5f;
var nodeIndent = 16.0f;
var root = tree.AddChild<TreeNode>();
for (var i = 0; i < tags.Length; i++)
{
var tag = tags[i];
bool isSelected = pickerData.IsSingle ? value.Index == i : values.Contains(new Tag(i));
// Count parent tags count
int indentation = 0;
for (int j = 0; j < tag.Length; j++)
{
if (tag[j] == '.')
indentation++;
}
// Create node
var node = new TreeNodeWithAddons
{
Tag = i,
Text = tag,
ChildrenIndent = nodeIndent,
CullChildren = false,
ClipChildren = false,
TextMargin = new Margin(22.0f, 2.0f, 2.0f, 2.0f),
CustomArrowRect = new Rectangle(18 + indentation * nodeIndent, 2, 12, 12),
BackgroundColorSelected = Color.Transparent,
BackgroundColorHighlighted = nodeBackgroundColorHighlighted,
};
var checkbox = new CheckBox(32.0f + indentation * nodeIndent, 0, isSelected)
{
Height = 16.0f,
IsScrollable = false,
Parent = node,
};
node.Addons.Add(checkbox);
checkbox.StateChanged += c => OnCheckboxEdited(node, c, pickerData);
var addButton = new Button(menu.Width - 16, 0, 14, 14)
{
Text = "+",
TooltipText = "Add subtag within this tag namespace",
IsScrollable = false,
BorderColor = Color.Transparent,
BackgroundColor = Color.Transparent,
Parent = node,
AnchorPreset = AnchorPresets.TopRight,
};
node.Addons.Add(addButton);
addButton.ButtonClicked += button => OnAddButtonClicked(tree, node, pickerData);
// Link to parent
{
var lastDotIndex = tag.LastIndexOf('.');
var parentTagName = lastDotIndex != -1 ? tag.Substring(0, lastDotIndex) : string.Empty;
if (!nameToNode.TryGetValue(parentTagName, out ContainerControl parent))
parent = root;
node.Parent = parent;
}
nameToNode[tag] = node;
// Expand selected nodes to be visible in hierarchy
if (isSelected)
node.ExpandAllParents(true);
}
nameToNode.Clear();
// Adds top panel with utility buttons
var buttonsPanel = new HorizontalPanel
{
Margin = new Margin(1.0f),
AutoSize = false,
Bounds = new Rectangle(0, 0, menu.Width, 20.0f),
Parent = menu,
};
var buttonsSize = new Float2((menu.Width - buttonsPanel.Margin.Width) / 4.0f - buttonsPanel.Spacing, 18.0f);
var buttonExpandAll = new Button
{
Size = buttonsSize,
Parent = buttonsPanel,
Text = "Expand all",
};
buttonExpandAll.Clicked += () => root.ExpandAll(true);
var buttonCollapseAll = new Button
{
Size = buttonsSize,
Parent = buttonsPanel,
Text = "Collapse all",
};
buttonCollapseAll.Clicked += () =>
{
root.CollapseAll(true);
root.Expand(true);
};
var buttonAddTag = new Button
{
Size = buttonsSize,
Parent = buttonsPanel,
Text = "Add Tag",
};
buttonAddTag.Clicked += () => OnAddButtonClicked(tree, root, pickerData);
var buttonReset = new Button
{
Size = buttonsSize,
Parent = buttonsPanel,
Text = "Reset",
};
buttonReset.Clicked += () =>
{
pickerData.IsEditing = true;
UncheckAll(root);
pickerData.IsEditing = false;
pickerData.SetValue?.Invoke(Tag.Default);
pickerData.SetValues?.Invoke(null);
};
// Setup search filter
searchBox.TextChanged += delegate
{
if (tree.IsLayoutLocked)
return;
root.LockChildrenRecursive();
Utilities.Utils.UpdateSearchPopupFilter(root, searchBox.Text);
root.UnlockChildrenRecursive();
menu.PerformLayout();
};
// Prepare for display
root.SortChildrenRecursive();
root.Expand(true);
return menu;
}
}
/// <summary>
/// Custom editor for array of <see cref="Tag"/>.
/// </summary>
[CustomEditor(typeof(Tag[])), DefaultEditor]
public sealed class TagsEditor : CustomEditor
{
private ClickableLabel _label;
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
_label = layout.ClickableLabel(GetText(out _)).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 = GetText(out var tags);
_label.Height = Math.Max(tags.Length, 1) * 18.0f;
}
private string GetText(out Tag[] tags)
{
tags = Tags;
if (tags.Length == 0)
return string.Empty;
if (tags.Length == 1)
return tags[0].ToString();
var sb = new StringBuilder();
for (var i = 0; i < tags.Length; i++)
{
var tag = tags[i];
if (i != 0)
sb.AppendLine();
sb.Append(tag.ToString());
}
return sb.ToString();
}
private Tag[] Tags
{
get
{
if (Values[0] is Tag[] asArray)
return asArray;
if (Values[0] is List<Tag> asList)
return asList.ToArray();
return Utils.GetEmptyArray<Tag>();
}
set
{
if (Values[0] is Tag[])
SetValue(value);
if (Values[0] is List<Tag>)
SetValue(new List<Tag>(value));
}
}
private void ShowPicker()
{
var menu = TagEditor.CreatePicker(Tag.Default, Tags, new TagEditor.PickerData
{
SetValues = value => { Tags = value; },
});
menu.Show(_label, new Float2(0, _label.Height));
}
}
}

View File

@@ -196,6 +196,7 @@ namespace FlaxEditor.GUI.ContextMenu
desc.HasSizingFrame = false;
OnWindowCreating(ref desc);
_window = Platform.CreateWindow(ref desc);
_window.GotFocus += OnWindowGotFocus;
_window.LostFocus += OnWindowLostFocus;
// Attach to the window
@@ -353,6 +354,15 @@ namespace FlaxEditor.GUI.ContextMenu
}
}
private void OnWindowGotFocus()
{
if (_childCM != null && _window && _window.IsForegroundWindow)
{
// Hide child if user clicked over parent (do it next frame to process other events before - eg. child windows focus loss)
FlaxEngine.Scripting.InvokeOnUpdate(HideChild);
}
}
private void OnWindowLostFocus()
{
// Skip for parent menus (child should handle lost of focus)
@@ -428,6 +438,21 @@ namespace FlaxEditor.GUI.ContextMenu
return true;
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
if (base.OnKeyDown(key))
return true;
switch (key)
{
case KeyboardKeys.Escape:
Hide();
return true;
}
return false;
}
/// <inheritdoc />
public override void OnDestroy()
{

View File

@@ -0,0 +1,56 @@
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.GUI.Input
{
/// <summary>
/// Search box control which can gather text search input from the user.
/// </summary>
public class SearchBox : TextBox
{
/// <summary>
/// A button that clears the search bar.
/// </summary>
public Button ClearSearchButton { get; }
/// <summary>
/// Init search box
/// </summary>
public SearchBox()
: this(false, 0, 0)
{
}
/// <summary>
/// Init search box
/// </summary>
public SearchBox(bool isMultiline, float x, float y, float width = 120)
: base(isMultiline, x, y, width)
{
WatermarkText = "Search...";
ClearSearchButton = new Button
{
Parent = this,
Width = 14.0f,
Height = 14.0f,
AnchorPreset = AnchorPresets.TopRight,
Text = "",
TooltipText = "Cancel Search.",
BackgroundColor = TextColor,
BorderColor = Color.Transparent,
BackgroundColorHighlighted = Style.Current.ForegroundGrey,
BorderColorHighlighted = Color.Transparent,
BackgroundColorSelected = Style.Current.ForegroundGrey,
BorderColorSelected = Color.Transparent,
BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Cross12),
Visible = false,
};
ClearSearchButton.LocalY += 2;
ClearSearchButton.LocalX -= 2;
ClearSearchButton.Clicked += Clear;
TextChanged += () => ClearSearchButton.Visible = !string.IsNullOrEmpty(Text);
}
}
}

View File

@@ -173,6 +173,7 @@ namespace FlaxEditor.GUI.Input
private void EndSliding()
{
_isSliding = false;
EndEditOnClick = true;
EndMouseCapture();
if (_cursorChanged)
{
@@ -245,6 +246,7 @@ namespace FlaxEditor.GUI.Input
_startSlideLocation = location;
_startSlideValue = _value;
StartMouseCapture(true);
EndEditOnClick = false;
// Hide cursor and cache location
Cursor = CursorType.Hidden;

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -204,11 +205,10 @@ namespace FlaxEditor.GUI
Size = new Float2(width, height);
// Search box
_searchBox = new TextBox(false, 1, 1)
_searchBox = new SearchBox(false, 1, 1)
{
Parent = this,
Width = Width - 3,
WatermarkText = "Search...",
};
_searchBox.TextChanged += OnSearchFilterChanged;

View File

@@ -57,6 +57,11 @@ namespace FlaxEditor.GUI
set => _inputField.Text = value;
}
/// <summary>
/// Gets the text input field control.
/// </summary>
public TextBox InputField => _inputField;
/// <summary>
/// Initializes a new instance of the <see cref="RenamePopup"/> class.
/// </summary>

View File

@@ -217,7 +217,7 @@ namespace FlaxEditor.GUI.Tree
/// <summary>
/// Gets the arrow rectangle.
/// </summary>
public Rectangle ArrowRect => new Rectangle(_xOffset + 2 + _margin.Left, 2, 12, 12);
public Rectangle ArrowRect => CustomArrowRect.HasValue ? CustomArrowRect.Value : new Rectangle(_xOffset + 2 + _margin.Left, 2, 12, 12);
/// <summary>
/// Gets the header rectangle.
@@ -248,6 +248,12 @@ namespace FlaxEditor.GUI.Tree
}
}
/// <summary>
/// Custom arrow rectangle within node.
/// </summary>
[HideInEditor, NoSerialize]
public Rectangle? CustomArrowRect;
/// <summary>
/// Gets the drag over action type.
/// </summary>
@@ -277,7 +283,7 @@ namespace FlaxEditor.GUI.Tree
/// Initializes a new instance of the <see cref="TreeNode"/> class.
/// </summary>
public TreeNode()
: this(false)
: this(false, SpriteHandle.Invalid, SpriteHandle.Invalid)
{
}
@@ -486,11 +492,14 @@ namespace FlaxEditor.GUI.Tree
/// <returns>True if event has been handled.</returns>
protected virtual bool OnMouseDoubleClickHeader(ref Float2 location, MouseButton button)
{
// Toggle open state
if (_opened)
Collapse();
else
Expand();
if (HasAnyVisibleChild)
{
// Toggle open state
if (_opened)
Collapse();
else
Expand();
}
// Handled
return true;
@@ -754,7 +763,7 @@ namespace FlaxEditor.GUI.Tree
}
// Check if mouse hits arrow
if (HasAnyVisibleChild && _mouseOverArrow)
if (_mouseOverArrow && HasAnyVisibleChild)
{
// Toggle open state
if (_opened)

View File

@@ -0,0 +1,121 @@
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
using FlaxEngine;
using FlaxEngine.GUI;
using System.Collections.Generic;
namespace FlaxEditor.GUI.Tree
{
/// <summary>
/// Tree node control with in-built checkbox.
/// </summary>
[HideInEditor]
public class TreeNodeWithAddons : TreeNode
{
/// <summary>
/// The additional controls (eg. added to the header).
/// </summary>
public List<Control> Addons = new List<Control>();
/// <inheritdoc />
public override void Draw()
{
base.Draw();
foreach (var child in Addons)
{
Render2D.PushTransform(ref child._cachedTransform);
child.Draw();
Render2D.PopTransform();
}
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
foreach (var child in Addons)
{
if (child.Visible && child.Enabled)
{
if (IntersectsChildContent(child, location, out var childLocation))
{
if (child.OnMouseDown(childLocation, button))
return true;
}
}
}
return base.OnMouseDown(location, button);
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
foreach (var child in Addons)
{
if (child.Visible && child.Enabled)
{
if (IntersectsChildContent(child, location, out var childLocation))
{
if (child.OnMouseUp(childLocation, button))
return true;
}
}
}
return base.OnMouseUp(location, button);
}
/// <inheritdoc />
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
foreach (var child in Addons)
{
if (child.Visible && child.Enabled)
{
if (IntersectsChildContent(child, location, out var childLocation))
{
if (child.OnMouseDoubleClick(childLocation, button))
return true;
}
}
}
return base.OnMouseDoubleClick(location, button);
}
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
base.OnMouseMove(location);
if (IsCollapsed)
{
foreach (var child in Addons)
{
if (child.Visible && child.Enabled)
{
if (IntersectsChildContent(child, location, out var childLocation))
{
if (child.IsMouseOver)
{
// Move
child.OnMouseMove(childLocation);
}
else
{
// Enter
child.OnMouseEnter(childLocation);
}
}
else if (child.IsMouseOver)
{
// Leave
child.OnMouseLeave();
}
}
}
}
}
}
}

View File

@@ -328,15 +328,8 @@ namespace CustomEditorsUtilInternal
}
}
namespace LayersAndTagsSettingsInternal1
namespace LayersAndTagsSettingsInternal
{
MonoArray* GetCurrentTags(int* tagsCount)
{
SCRIPTING_EXPORT("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentTags")
*tagsCount = Level::Tags.Count();
return MUtils::ToArray(Level::Tags);
}
MonoArray* GetCurrentLayers(int* layersCount)
{
SCRIPTING_EXPORT("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers")
@@ -1180,8 +1173,7 @@ public:
ADD_INTERNAL_CALL("FlaxEditor.Content.Import.TextureImportEntry::Internal_GetTextureImportOptions", &GetTextureImportOptions);
ADD_INTERNAL_CALL("FlaxEditor.Content.Import.ModelImportEntry::Internal_GetModelImportOptions", &GetModelImportOptions);
ADD_INTERNAL_CALL("FlaxEditor.Content.Import.AudioImportEntry::Internal_GetAudioImportOptions", &GetAudioImportOptions);
ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentTags", &LayersAndTagsSettingsInternal1::GetCurrentTags);
ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers", &LayersAndTagsSettingsInternal1::GetCurrentLayers);
ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers", &LayersAndTagsSettingsInternal::GetCurrentLayers);
ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.GameSettings::Apply", &GameSettingsInternal1::Apply);
ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CloseSplashScreen", &CloseSplashScreen);
ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CreateAsset", &CreateAsset);

View File

@@ -379,7 +379,7 @@ namespace FlaxEditor.Modules
actor.StaticFlags = old.StaticFlags;
actor.HideFlags = old.HideFlags;
actor.Layer = old.Layer;
actor.Tag = old.Tag;
actor.Tags = old.Tags;
actor.Name = old.Name;
actor.IsActive = old.IsActive;

View File

@@ -741,7 +741,9 @@ namespace FlaxEditor.Modules
return;
// Find layout to use
var searchFolder = Globals.ProjectCacheFolder;
var searchFolder = StringUtils.CombinePaths(Editor.LocalCachePath, "LayoutsCache");
if (!Directory.Exists(searchFolder))
Directory.CreateDirectory(searchFolder);
var files = Directory.GetFiles(searchFolder, "Layout_*.xml", SearchOption.TopDirectoryOnly);
var layouts = _menuWindowApplyWindowLayout.ContextMenu;
layouts.DisposeAllItems();
@@ -749,8 +751,11 @@ namespace FlaxEditor.Modules
{
var file = files[i];
var name = file.Substring(searchFolder.Length + 8, file.Length - searchFolder.Length - 12);
var button = layouts.AddButton(name, OnApplyLayoutButtonClicked);
button.Tag = file;
var nameCM = layouts.AddChildMenu(name);
var applyButton = nameCM.ContextMenu.AddButton("Apply", OnApplyLayoutButtonClicked);
applyButton.TooltipText = "Applies the selected layout.";
nameCM.ContextMenu.AddButton("Delete", () => File.Delete(file)).TooltipText = "Permanently deletes the selected layout.";
applyButton.Tag = file;
}
_menuWindowApplyWindowLayout.Enabled = files.Length > 0;
}

View File

@@ -193,6 +193,8 @@ namespace FlaxEditor.Modules
/// <returns>Editor window or null if cannot find any window.</returns>
public EditorWindow FindEditor(ContentItem item)
{
if (item == null)
return null;
for (int i = 0; i < Windows.Count; i++)
{
var win = Windows[i];
@@ -556,7 +558,7 @@ namespace FlaxEditor.Modules
base.OnSubmit();
var path = StringUtils.CombinePaths(Globals.ProjectCacheFolder, "Layout_" + name + ".xml");
var path = StringUtils.CombinePaths(Editor.LocalCachePath, "LayoutsCache", "Layout_" + name + ".xml");
Editor.Instance.Windows.SaveLayout(path);
}
}

View File

@@ -283,7 +283,7 @@ namespace FlaxEditor.SceneGraph.GUI
(window as PrefabWindow).ScrollingOnTreeView(false);
// Start renaming the actor
var dialog = RenamePopup.Show(this, HeaderRect, _actorNode.Name, false);
var dialog = RenamePopup.Show(this, TextRect, _actorNode.Name, false);
dialog.Renamed += OnRenamed;
dialog.Closed += popup =>
{

View File

@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -128,10 +129,9 @@ namespace FlaxEditor.Surface.ContextMenu
Size = new Float2(320, 220);
// Search box
_searchBox = new TextBox(false, 1, 1)
_searchBox = new SearchBox(false, 1, 1)
{
Width = Width - 3,
WatermarkText = "Search...",
Parent = this
};
_searchBox.TextChanged += OnSearchFilterChanged;

View File

@@ -14,6 +14,7 @@ using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEditor.GUI.Tree;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
@@ -949,17 +950,17 @@ namespace FlaxEditor.Utilities
/// </summary>
/// <param name="searchBox">The search box.</param>
/// <param name="tree">The tree control.</param>
/// <param name="headerHeight">Amount of additional space above the search box to put custom UI.</param>
/// <returns>The created menu to setup and show.</returns>
public static ContextMenuBase CreateSearchPopup(out TextBox searchBox, out Tree tree)
public static ContextMenuBase CreateSearchPopup(out TextBox searchBox, out Tree tree, float headerHeight = 0)
{
var menu = new ContextMenuBase
{
Size = new Float2(320, 220),
Size = new Float2(320, 220 + headerHeight),
};
searchBox = new TextBox(false, 1, 1)
searchBox = new SearchBox(false, 1, headerHeight + 1)
{
Width = menu.Width - 3,
WatermarkText = "Search...",
Parent = menu,
};
var panel1 = new Panel(ScrollBars.Vertical)
@@ -980,6 +981,33 @@ namespace FlaxEditor.Utilities
return menu;
}
/// <summary>
/// Updates (recursivly) search popup tree structures based on the filter text.
/// </summary>
public static void UpdateSearchPopupFilter(TreeNode node, string filterText)
{
// Update children
bool isAnyChildVisible = false;
for (int i = 0; i < node.Children.Count; i++)
{
if (node.Children[i] is TreeNode child)
{
UpdateSearchPopupFilter(child, filterText);
isAnyChildVisible |= child.Visible;
}
}
// Update itself
bool noFilter = string.IsNullOrWhiteSpace(filterText);
bool isThisVisible = noFilter || QueryFilterHelper.Match(filterText, node.Text);
bool isExpanded = isAnyChildVisible;
if (isExpanded)
node.Expand(true);
else
node.Collapse(true);
node.Visible = isThisVisible | isAnyChildVisible;
}
/// <summary>
/// Gets the asset name relative to the project root folder (without asset file extension)
/// </summary>

View File

@@ -93,6 +93,7 @@ namespace FlaxEditor.Windows
"Stefan Brandmair",
"Lukáš Jech",
"Jean-Baptiste Perrier",
"Chandler Cox",
});
authors.Sort();
var authorsLabel = new Label(4, topParentControl.Bottom + 20, Width - 8, 70)

View File

@@ -6,6 +6,7 @@ using FlaxEditor.Content;
using FlaxEditor.CustomEditors;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI;
using FlaxEditor.GUI.Input;
using FlaxEditor.SceneGraph;
using FlaxEditor.Viewport;
using FlaxEngine;
@@ -124,10 +125,9 @@ namespace FlaxEditor.Windows.Assets
IsScrollable = false,
Offsets = new Margin(0, 0, 0, 18 + 6),
};
_searchBox = new TextBox
_searchBox = new SearchBox()
{
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
WatermarkText = "Search...",
Parent = headerPanel,
Bounds = new Rectangle(4, 4, headerPanel.Width - 8, 18),
};

View File

@@ -114,10 +114,9 @@ namespace FlaxEditor.Windows
IsScrollable = false,
Offsets = new Margin(0, 0, 0, 18 + 6),
};
_foldersSearchBox = new TextBox
_foldersSearchBox = new SearchBox
{
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
WatermarkText = "Search...",
Parent = headerPanel,
Bounds = new Rectangle(4, 4, headerPanel.Width - 8, 18),
};
@@ -150,10 +149,9 @@ namespace FlaxEditor.Windows
Parent = _split.Panel2,
};
const float viewDropdownWidth = 50.0f;
_itemsSearchBox = new TextBox
_itemsSearchBox = new SearchBox
{
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
WatermarkText = "Search...",
Parent = contentItemsSearchPanel,
Bounds = new Rectangle(viewDropdownWidth + 8, 4, contentItemsSearchPanel.Width - 12 - viewDropdownWidth, 18),
};
@@ -838,13 +836,20 @@ namespace FlaxEditor.Windows
};
_root.Expand(true);
// Add game project on top, plugins in the middle and engine at bottom
_root.AddChild(Editor.ContentDatabase.Game);
foreach (var project in Editor.ContentDatabase.Projects)
{
project.SortChildrenRecursive();
if (project == Editor.ContentDatabase.Game || project == Editor.ContentDatabase.Engine)
continue;
_root.AddChild(project);
}
_root.AddChild(Editor.ContentDatabase.Engine);
Editor.ContentDatabase.Game?.Expand(true);
_tree.Margin = new Margin(0.0f, 0.0f, -16.0f, 2.0f); // Hide root node
_tree.AddChild(_root);
_root.SortChildrenRecursive();
// Setup navigation
_navigationUnlocked = true;

View File

@@ -8,6 +8,7 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEditor.Options;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -158,9 +159,8 @@ namespace FlaxEditor.Windows
Parent = this,
};
_viewDropdown.Clicked += OnViewButtonClicked;
_searchBox = new TextBox(false, _viewDropdown.Right + 2, 2, Width - _viewDropdown.Right - 2 - _scrollSize)
_searchBox = new SearchBox(false, _viewDropdown.Right + 2, 2, Width - _viewDropdown.Right - 2 - _scrollSize)
{
WatermarkText = "Search...",
Parent = this,
};
_searchBox.TextChanged += Refresh;

View File

@@ -133,7 +133,9 @@ namespace FlaxEditor.Windows.Profiler
{
resource = new Resource
{
#if !BUILD_RELEASE
Name = gpuResource.Name,
#endif
Type = gpuResource.ResourceType,
};

View File

@@ -6,6 +6,7 @@ using FlaxEditor.Gizmo;
using FlaxEditor.Content;
using FlaxEditor.GUI.Tree;
using FlaxEditor.GUI.Drag;
using FlaxEditor.GUI.Input;
using FlaxEditor.SceneGraph;
using FlaxEditor.SceneGraph.GUI;
using FlaxEditor.Scripting;
@@ -49,10 +50,9 @@ namespace FlaxEditor.Windows
IsScrollable = false,
Offsets = new Margin(0, 0, 0, 18 + 6),
};
_searchBox = new TextBox
_searchBox = new SearchBox
{
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
WatermarkText = "Search...",
Parent = headerPanel,
Bounds = new Rectangle(4, 4, headerPanel.Width - 8, 18),
};

View File

@@ -10,6 +10,7 @@ using FlaxEditor;
using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Docking;
using FlaxEditor.GUI.Input;
using FlaxEditor.GUI.Tree;
using FlaxEditor.Scripting;
using FlaxEditor.Surface;
@@ -224,10 +225,9 @@ namespace FlaxEngine.Windows.Search
Parent = topPanel,
};
optionsButton.ButtonClicked += OnOptionsDropdownClicked;
_searchBox = new TextBox
_searchBox = new SearchBox
{
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
WatermarkText = "Search...",
Parent = topPanel,
Bounds = new Rectangle(optionsButton.Right + 2.0f, 2, topPanel.Width - 4.0f - optionsButton.Width, 18.0f),
};

View File

@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using FlaxEditor.GUI.Input;
using FlaxEditor.GUI.Tabs;
using FlaxEditor.GUI.Tree;
using FlaxEditor.Scripting;
@@ -130,10 +131,9 @@ namespace FlaxEditor.Windows
};
_groupSearch = CreateGroupWithList(_actorGroups, "Search", 26);
_searchBox = new TextBox
_searchBox = new SearchBox
{
AnchorPreset = AnchorPresets.HorizontalStretchTop,
WatermarkText = "Search...",
Parent = _groupSearch.Parent.Parent,
Bounds = new Rectangle(4, 4, _actorGroups.Width - 8, 18),
};