diff --git a/.github/ISSUE_TEMPLATE/1-bug.yaml b/.github/ISSUE_TEMPLATE/1-bug.yaml
index a75003f63..7e6150557 100644
--- a/.github/ISSUE_TEMPLATE/1-bug.yaml
+++ b/.github/ISSUE_TEMPLATE/1-bug.yaml
@@ -5,7 +5,7 @@ body:
- type: markdown
attributes:
value: |
- Thanks for taking the time to fill out this bug report! Please attach any minimal reproduction projects!
+ Thanks for taking the time to fill out this bug report! Please attach a minimal reproduction project if available!
- type: textarea
id: description-area
attributes:
@@ -17,19 +17,19 @@ body:
id: steps-area
attributes:
label: Steps to reproduce
- description: Please provide reproduction steps if possible.
+ description: Please provide reproduction steps if available.
validations:
required: true
- type: dropdown
id: version
attributes:
label: Version
- description: What version of Flax are you running?
+ description: What version of Flax did you experience the bug in?
options:
- - '1.8'
- '1.9'
- '1.10'
- '1.11'
+ - '1.12'
- master branch
default: 3
validations:
diff --git a/.github/ISSUE_TEMPLATE/2-feature-request.yaml b/.github/ISSUE_TEMPLATE/2-feature-request.yaml
index 338c9aea0..7ad43bf5b 100644
--- a/.github/ISSUE_TEMPLATE/2-feature-request.yaml
+++ b/.github/ISSUE_TEMPLATE/2-feature-request.yaml
@@ -5,7 +5,7 @@ body:
- type: markdown
attributes:
value: |
- Thanks for taking the time to fill out a feature request!
+ Thank you for taking the time to submit this feature request!
- type: textarea
id: description-area
attributes:
@@ -17,6 +17,6 @@ body:
id: benefits-area
attributes:
label: Benefits
- description: Please provide what benefits this feature would provide to the engine!
+ description: Please list what benefits this feature would provide to the engine!
validations:
required: true
\ No newline at end of file
diff --git a/Content/Editor/MaterialTemplates/Deformable.shader b/Content/Editor/MaterialTemplates/Deformable.shader
index 688170e73..86a9200f3 100644
--- a/Content/Editor/MaterialTemplates/Deformable.shader
+++ b/Content/Editor/MaterialTemplates/Deformable.shader
@@ -337,7 +337,6 @@ VertexOutput VS_SplineModel(ModelInput input)
// Apply world position offset per-vertex
#if USE_POSITION_OFFSET
output.Geometry.WorldPosition += material.PositionOffset;
- output.Geometry.PrevWorldPosition += material.PositionOffset;
output.Position = PROJECT_POINT(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix);
#endif
diff --git a/Flax.flaxproj b/Flax.flaxproj
index d6cf594a8..ca7272f0c 100644
--- a/Flax.flaxproj
+++ b/Flax.flaxproj
@@ -4,7 +4,7 @@
"Major": 1,
"Minor": 12,
"Revision": 0,
- "Build": 6909
+ "Build": 6910
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.",
diff --git a/Source/Editor/Content/GUI/ContentNavigation.cs b/Source/Editor/Content/GUI/ContentNavigation.cs
index a8581142b..c84b98746 100644
--- a/Source/Editor/Content/GUI/ContentNavigation.cs
+++ b/Source/Editor/Content/GUI/ContentNavigation.cs
@@ -19,7 +19,7 @@ namespace FlaxEditor.Content.GUI
///
/// Gets the target node.
///
- public ContentTreeNode TargetNode { get; }
+ public ContentFolderTreeNode TargetNode { get; }
///
/// Initializes a new instance of the class.
@@ -28,7 +28,7 @@ namespace FlaxEditor.Content.GUI
/// The x position.
/// The y position.
/// The height.
- public ContentNavigationButton(ContentTreeNode targetNode, float x, float y, float height)
+ public ContentNavigationButton(ContentFolderTreeNode targetNode, float x, float y, float height)
: base(x, y, height)
{
TargetNode = targetNode;
@@ -147,7 +147,7 @@ namespace FlaxEditor.Content.GUI
ClearItems();
foreach (var child in Target.TargetNode.Children)
{
- if (child is ContentTreeNode node)
+ if (child is ContentFolderTreeNode node)
{
if (node.Folder.VisibleInHierarchy) // Respect the filter set by ContentFilterConfig.Filter(...)
AddItem(node.Folder.ShortName);
@@ -180,7 +180,7 @@ namespace FlaxEditor.Content.GUI
var item = _items[index];
foreach (var child in Target.TargetNode.Children)
{
- if (child is ContentTreeNode node && node.Folder.ShortName == item)
+ if (child is ContentFolderTreeNode node && node.Folder.ShortName == item)
{
Editor.Instance.Windows.ContentWin.Navigate(node);
return;
diff --git a/Source/Editor/Content/Items/ContentFolder.cs b/Source/Editor/Content/Items/ContentFolder.cs
index 462d0c1c1..94dbed103 100644
--- a/Source/Editor/Content/Items/ContentFolder.cs
+++ b/Source/Editor/Content/Items/ContentFolder.cs
@@ -59,7 +59,7 @@ namespace FlaxEditor.Content
///
/// Gets the content node.
///
- public ContentTreeNode Node { get; }
+ public ContentFolderTreeNode Node { get; }
///
/// The subitems of this folder.
@@ -72,7 +72,7 @@ namespace FlaxEditor.Content
/// The folder type.
/// The path to the item.
/// The folder parent node.
- internal ContentFolder(ContentFolderType type, string path, ContentTreeNode node)
+ internal ContentFolder(ContentFolderType type, string path, ContentFolderTreeNode node)
: base(path)
{
FolderType = type;
@@ -118,7 +118,7 @@ namespace FlaxEditor.Content
get
{
var hasParentFolder = ParentFolder != null;
- var isContentFolder = Node is MainContentTreeNode;
+ var isContentFolder = Node is MainContentFolderTreeNode;
return hasParentFolder && !isContentFolder;
}
}
diff --git a/Source/Editor/Content/Tree/ContentFolderTreeNode.cs b/Source/Editor/Content/Tree/ContentFolderTreeNode.cs
new file mode 100644
index 000000000..6741910cf
--- /dev/null
+++ b/Source/Editor/Content/Tree/ContentFolderTreeNode.cs
@@ -0,0 +1,433 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using FlaxEditor.GUI;
+using FlaxEditor.GUI.Drag;
+using FlaxEditor.GUI.Tree;
+using FlaxEditor.SceneGraph;
+using FlaxEditor.Utilities;
+using FlaxEngine;
+using FlaxEngine.GUI;
+
+namespace FlaxEditor.Content;
+
+///
+/// Content folder tree node.
+///
+///
+public class ContentFolderTreeNode : TreeNode
+{
+ private DragItems _dragOverItems;
+ private DragActors _dragActors;
+ private List _highlights;
+
+ ///
+ /// The folder.
+ ///
+ protected ContentFolder _folder;
+
+ ///
+ /// Whether this node can be deleted.
+ ///
+ public virtual bool CanDelete => true;
+
+ ///
+ /// Whether this node can be duplicated.
+ ///
+ public virtual bool CanDuplicate => true;
+
+ ///
+ /// Gets the content folder item.
+ ///
+ public ContentFolder Folder => _folder;
+
+ ///
+ /// Gets the type of the folder.
+ ///
+ public ContentFolderType FolderType => _folder.FolderType;
+
+ ///
+ /// Returns true if that folder can import/manage scripts.
+ ///
+ public bool CanHaveScripts => _folder.CanHaveScripts;
+
+ ///
+ /// Returns true if that folder can import/manage assets.
+ ///
+ /// True if can contain assets for project, otherwise false
+ public bool CanHaveAssets => _folder.CanHaveAssets;
+
+ ///
+ /// Gets the parent node.
+ ///
+ public ContentFolderTreeNode ParentNode => Parent as ContentFolderTreeNode;
+
+ ///
+ /// Gets the folder path.
+ ///
+ public string Path => _folder.Path;
+
+ ///
+ /// Gets the navigation button label.
+ ///
+ public virtual string NavButtonLabel => _folder.ShortName;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The parent node.
+ /// The folder path.
+ public ContentFolderTreeNode(ContentFolderTreeNode parent, string path)
+ : this(parent, parent?.FolderType ?? ContentFolderType.Other, path)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The parent node.
+ /// The folder type.
+ /// The folder path.
+ protected ContentFolderTreeNode(ContentFolderTreeNode parent, ContentFolderType type, string path)
+ : base(false, Editor.Instance.Icons.FolderClosed32, Editor.Instance.Icons.FolderOpen32)
+ {
+ _folder = new ContentFolder(type, path, this);
+ Text = _folder.ShortName;
+ if (parent != null)
+ {
+ Folder.ParentFolder = parent.Folder;
+ Parent = parent;
+ }
+ IconColor = Color.Transparent; // Hide default icon, we draw scaled icon manually
+ UpdateCustomArrowRect();
+ Editor.Instance?.Windows?.ContentWin?.TryAutoExpandContentNode(this);
+ }
+
+ ///
+ /// Updates the custom arrow rectangle so it stays aligned with the current layout.
+ ///
+ private void UpdateCustomArrowRect()
+ {
+ var contentWindow = Editor.Instance?.Windows?.ContentWin;
+ var scale = contentWindow != null && contentWindow.IsTreeOnlyMode ? contentWindow.View.ViewScale : 1.0f;
+ var arrowSize = Mathf.Clamp(12.0f * scale, 10.0f, 20.0f);
+ var iconSize = Mathf.Clamp(16.0f * scale, 12.0f, 28.0f);
+
+ // Use the current text layout, not just cached values.
+ var textRect = TextRect;
+ var iconLeft = textRect.Left - iconSize - 2.0f;
+ var x = Mathf.Max(iconLeft - arrowSize - 2.0f, 0.0f);
+ var y = Mathf.Max((HeaderHeight - arrowSize) * 0.5f, 0.0f);
+
+ CustomArrowRect = new Rectangle(x, y, arrowSize, arrowSize);
+ }
+
+ ///
+ public override void PerformLayout(bool force = false)
+ {
+ base.PerformLayout(force);
+ UpdateCustomArrowRect();
+ }
+
+ ///
+ /// Shows the rename popup for the item.
+ ///
+ public void StartRenaming()
+ {
+ if (!_folder.CanRename)
+ return;
+
+ // Start renaming the folder
+ 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); };
+ }
+
+ ///
+ /// Updates the query search filter.
+ ///
+ /// The filter text.
+ public void UpdateFilter(string filterText)
+ {
+ bool noFilter = string.IsNullOrWhiteSpace(filterText);
+
+ // Update itself
+ bool isThisVisible;
+ if (noFilter)
+ {
+ // Clear filter
+ _highlights?.Clear();
+ isThisVisible = true;
+ }
+ else
+ {
+ var text = Text;
+ if (QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges))
+ {
+ // Update highlights
+ if (_highlights == null)
+ _highlights = new List(ranges.Length);
+ else
+ _highlights.Clear();
+ var style = Style.Current;
+ var font = style.FontSmall;
+ var textRect = TextRect;
+ for (int i = 0; i < ranges.Length; i++)
+ {
+ var start = font.GetCharPosition(text, ranges[i].StartIndex);
+ var end = font.GetCharPosition(text, ranges[i].EndIndex);
+ _highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height));
+ }
+ isThisVisible = true;
+ }
+ else
+ {
+ // Hide
+ _highlights?.Clear();
+ isThisVisible = false;
+ }
+ }
+
+ // Update children
+ bool isAnyChildVisible = false;
+ for (int i = 0; i < _children.Count; i++)
+ {
+ if (_children[i] is ContentFolderTreeNode child)
+ {
+ child.UpdateFilter(filterText);
+ isAnyChildVisible |= child.Visible;
+ }
+ else if (_children[i] is ContentItemTreeNode itemNode)
+ {
+ itemNode.UpdateFilter(filterText);
+ isAnyChildVisible |= itemNode.Visible;
+ }
+ }
+
+ if (!noFilter)
+ {
+ bool isExpanded = isAnyChildVisible;
+ if (isExpanded)
+ Expand(true);
+ else
+ Collapse(true);
+ }
+
+ Visible = isThisVisible | isAnyChildVisible;
+ }
+
+ ///
+ public override int Compare(Control other)
+ {
+ if (other is ContentItemTreeNode)
+ return -1;
+ if (other is ContentFolderTreeNode otherNode)
+ return string.Compare(Text, otherNode.Text, StringComparison.Ordinal);
+ return base.Compare(other);
+ }
+
+ ///
+ public override void Draw()
+ {
+ base.Draw();
+
+ // Draw all highlights
+ if (_highlights != null)
+ {
+ var style = Style.Current;
+ var color = style.ProgressNormal * 0.6f;
+ for (int i = 0; i < _highlights.Count; i++)
+ Render2D.FillRectangle(_highlights[i], color);
+ }
+
+ var contentWindow = Editor.Instance.Windows.ContentWin;
+ var scale = contentWindow != null && contentWindow.IsTreeOnlyMode ? contentWindow.View.ViewScale : 1.0f;
+ var icon = IsExpanded ? Editor.Instance.Icons.FolderOpen32 : Editor.Instance.Icons.FolderClosed32;
+ var iconSize = Mathf.Clamp(16.0f * scale, 12.0f, 28.0f);
+ var iconRect = new Rectangle(TextRect.Left - iconSize - 2.0f, (HeaderHeight - iconSize) * 0.5f, iconSize, iconSize);
+ Render2D.DrawSprite(icon, iconRect);
+ }
+
+ ///
+ public override void OnDestroy()
+ {
+ // Delete folder item
+ _folder.Dispose();
+
+ base.OnDestroy();
+ }
+
+ ///
+ protected override void OnExpandedChanged()
+ {
+ base.OnExpandedChanged();
+
+ Editor.Instance?.Windows?.ContentWin?.OnContentTreeNodeExpandedChanged(this, IsExpanded);
+ }
+
+ private DragDropEffect GetDragEffect(DragData data)
+ {
+ if (_dragActors != null && _dragActors.HasValidDrag)
+ return DragDropEffect.Move;
+ if (data is DragDataFiles)
+ {
+ if (_folder.CanHaveAssets)
+ return DragDropEffect.Copy;
+ }
+ else
+ {
+ if (_dragOverItems != null && _dragOverItems.HasValidDrag)
+ return DragDropEffect.Move;
+ }
+
+ return DragDropEffect.None;
+ }
+
+ private bool ValidateDragItem(ContentItem item)
+ {
+ // Reject itself and any parent
+ return item != _folder && !item.Find(_folder);
+ }
+
+ private bool ValidateDragActors(ActorNode actor)
+ {
+ return actor.CanCreatePrefab && _folder.CanHaveAssets;
+ }
+
+ private void ImportActors(DragActors actors)
+ {
+ Select();
+ foreach (var actorNode in actors.Objects)
+ {
+ var actor = actorNode.Actor;
+ if (actors.Objects.Contains(actorNode.ParentNode as ActorNode))
+ continue;
+
+ Editor.Instance.Prefabs.CreatePrefab(actor, false);
+ }
+ Editor.Instance.Windows.ContentWin.RefreshView();
+ }
+
+ ///
+ protected override DragDropEffect OnDragEnterHeader(DragData data)
+ {
+ if (data is DragDataFiles)
+ return _folder.CanHaveAssets ? DragDropEffect.Copy : DragDropEffect.None;
+
+ if (_dragActors == null)
+ _dragActors = new DragActors(ValidateDragActors);
+ if (_dragActors.OnDragEnter(data))
+ return DragDropEffect.Move;
+
+ if (_dragOverItems == null)
+ _dragOverItems = new DragItems(ValidateDragItem);
+
+ _dragOverItems.OnDragEnter(data);
+ return GetDragEffect(data);
+ }
+
+ ///
+ protected override DragDropEffect OnDragMoveHeader(DragData data)
+ {
+ if (data is DragDataFiles)
+ return _folder.CanHaveAssets ? DragDropEffect.Copy : DragDropEffect.None;
+ if (_dragActors != null && _dragActors.HasValidDrag)
+ return DragDropEffect.Move;
+ return GetDragEffect(data);
+ }
+
+ ///
+ protected override void OnDragLeaveHeader()
+ {
+ _dragOverItems?.OnDragLeave();
+ _dragActors?.OnDragLeave();
+ base.OnDragLeaveHeader();
+ }
+
+ ///
+ protected override DragDropEffect OnDragDropHeader(DragData data)
+ {
+ var result = DragDropEffect.None;
+
+ // Check if drop element or files
+ if (data is DragDataFiles files)
+ {
+ // Import files
+ Editor.Instance.ContentImporting.Import(files.Files, _folder);
+ result = DragDropEffect.Copy;
+
+ Expand();
+ }
+ else if (_dragActors != null && _dragActors.HasValidDrag)
+ {
+ ImportActors(_dragActors);
+ _dragActors.OnDragDrop();
+ result = DragDropEffect.Move;
+
+ Expand();
+ }
+ else if (_dragOverItems != null && _dragOverItems.HasValidDrag)
+ {
+ // Move items
+ Editor.Instance.ContentDatabase.Move(_dragOverItems.Objects, _folder);
+ result = DragDropEffect.Move;
+
+ Expand();
+ }
+
+ _dragOverItems?.OnDragDrop();
+
+ return result;
+ }
+
+ ///
+ protected override void DoDragDrop()
+ {
+ DoDragDrop(DragItems.GetDragData(_folder));
+ }
+
+ ///
+ protected override void OnLongPress()
+ {
+ Select();
+
+ StartRenaming();
+ }
+
+ ///
+ public override bool OnKeyDown(KeyboardKeys key)
+ {
+ if (IsFocused)
+ {
+ switch (key)
+ {
+ case KeyboardKeys.F2:
+ StartRenaming();
+ return true;
+ case KeyboardKeys.Delete:
+ if (Folder.Exists && CanDelete)
+ Editor.Instance.Windows.ContentWin.Delete(Folder);
+ return true;
+ }
+ if (RootWindow.GetKey(KeyboardKeys.Control))
+ {
+ switch (key)
+ {
+ case KeyboardKeys.D:
+ if (Folder.Exists && CanDuplicate)
+ Editor.Instance.Windows.ContentWin.Duplicate(Folder);
+ return true;
+ }
+ }
+ }
+
+ return base.OnKeyDown(key);
+ }
+}
diff --git a/Source/Editor/Content/Tree/ContentItemTreeNode.cs b/Source/Editor/Content/Tree/ContentItemTreeNode.cs
new file mode 100644
index 000000000..09d1ec01b
--- /dev/null
+++ b/Source/Editor/Content/Tree/ContentItemTreeNode.cs
@@ -0,0 +1,224 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using FlaxEditor.Content.GUI;
+using FlaxEditor.GUI.Drag;
+using FlaxEditor.GUI.Tree;
+using FlaxEditor.Utilities;
+using FlaxEngine;
+using FlaxEngine.GUI;
+
+namespace FlaxEditor.Content;
+
+///
+/// Tree node for non-folder content items.
+///
+public sealed class ContentItemTreeNode : TreeNode, IContentItemOwner
+{
+ private List _highlights;
+
+ ///
+ /// The content item.
+ ///
+ public ContentItem Item { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The content item.
+ public ContentItemTreeNode(ContentItem item)
+ : base(false, Editor.Instance.Icons.Document128, Editor.Instance.Icons.Document128)
+ {
+ Item = item ?? throw new ArgumentNullException(nameof(item));
+ UpdateDisplayedName();
+ IconColor = Color.Transparent; // Reserve icon space but draw custom thumbnail.
+ Item.AddReference(this);
+ }
+
+ private static SpriteHandle GetIcon(ContentItem item)
+ {
+ if (item == null)
+ return SpriteHandle.Invalid;
+ var icon = item.Thumbnail;
+ if (!icon.IsValid)
+ icon = item.DefaultThumbnail;
+ if (!icon.IsValid)
+ icon = Editor.Instance.Icons.Document128;
+ return icon;
+ }
+
+ ///
+ /// Updates the query search filter.
+ ///
+ /// The filter text.
+ public void UpdateFilter(string filterText)
+ {
+ bool noFilter = string.IsNullOrWhiteSpace(filterText);
+ bool isVisible;
+ if (noFilter)
+ {
+ _highlights?.Clear();
+ isVisible = true;
+ }
+ else
+ {
+ var text = Text;
+ if (QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges))
+ {
+ if (_highlights == null)
+ _highlights = new List(ranges.Length);
+ else
+ _highlights.Clear();
+ var style = Style.Current;
+ var font = style.FontSmall;
+ var textRect = TextRect;
+ for (int i = 0; i < ranges.Length; i++)
+ {
+ var start = font.GetCharPosition(text, ranges[i].StartIndex);
+ var end = font.GetCharPosition(text, ranges[i].EndIndex);
+ _highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height));
+ }
+ isVisible = true;
+ }
+ else
+ {
+ _highlights?.Clear();
+ isVisible = false;
+ }
+ }
+
+ Visible = isVisible;
+ }
+
+ ///
+ public override void Draw()
+ {
+ base.Draw();
+
+ var icon = GetIcon(Item);
+ if (icon.IsValid)
+ {
+ var contentWindow = Editor.Instance.Windows.ContentWin;
+ var scale = contentWindow != null && contentWindow.IsTreeOnlyMode ? contentWindow.View.ViewScale : 1.0f;
+ var iconSize = Mathf.Clamp(16.0f * scale, 12.0f, 28.0f);
+ var textRect = TextRect;
+ var iconRect = new Rectangle(textRect.Left - iconSize - 2.0f, (HeaderHeight - iconSize) * 0.5f, iconSize, iconSize);
+ Render2D.DrawSprite(icon, iconRect);
+ }
+
+ if (_highlights != null)
+ {
+ var style = Style.Current;
+ var color = style.ProgressNormal * 0.6f;
+ for (int i = 0; i < _highlights.Count; i++)
+ Render2D.FillRectangle(_highlights[i], color);
+ }
+ }
+
+ ///
+ protected override bool OnMouseDoubleClickHeader(ref Float2 location, MouseButton button)
+ {
+ if (button == MouseButton.Left)
+ {
+ Editor.Instance.Windows.ContentWin.Open(Item);
+ return true;
+ }
+
+ return base.OnMouseDoubleClickHeader(ref location, button);
+ }
+
+ ///
+ public override bool OnKeyDown(KeyboardKeys key)
+ {
+ if (IsFocused)
+ {
+ switch (key)
+ {
+ case KeyboardKeys.Return:
+ Editor.Instance.Windows.ContentWin.Open(Item);
+ return true;
+ case KeyboardKeys.F2:
+ Editor.Instance.Windows.ContentWin.Rename(Item);
+ return true;
+ case KeyboardKeys.Delete:
+ Editor.Instance.Windows.ContentWin.Delete(Item);
+ return true;
+ }
+ }
+
+ return base.OnKeyDown(key);
+ }
+
+ ///
+ protected override void DoDragDrop()
+ {
+ DoDragDrop(DragItems.GetDragData(Item));
+ }
+
+ ///
+ public override bool OnShowTooltip(out string text, out Float2 location, out Rectangle area)
+ {
+ Item.UpdateTooltipText();
+ TooltipText = Item.TooltipText;
+ return base.OnShowTooltip(out text, out location, out area);
+ }
+
+ ///
+ void IContentItemOwner.OnItemDeleted(ContentItem item)
+ {
+ }
+
+ ///
+ void IContentItemOwner.OnItemRenamed(ContentItem item)
+ {
+ UpdateDisplayedName();
+ }
+
+ ///
+ void IContentItemOwner.OnItemReimported(ContentItem item)
+ {
+ }
+
+ ///
+ void IContentItemOwner.OnItemDispose(ContentItem item)
+ {
+ }
+
+ ///
+ public override int Compare(Control other)
+ {
+ if (other is ContentFolderTreeNode)
+ return 1;
+ if (other is ContentItemTreeNode otherItem)
+ return ApplySortOrder(string.Compare(Text, otherItem.Text, StringComparison.InvariantCulture));
+ return base.Compare(other);
+ }
+
+ ///
+ public override void OnDestroy()
+ {
+ Item.RemoveReference(this);
+ base.OnDestroy();
+ }
+
+ ///
+ /// Updates the text of the node.
+ ///
+ public void UpdateDisplayedName()
+ {
+ var contentWindow = Editor.Instance?.Windows?.ContentWin;
+ var showExtensions = contentWindow?.View?.ShowFileExtensions ?? true;
+ Text = Item.ShowFileExtension || showExtensions ? Item.FileName : Item.ShortName;
+ }
+
+ private static SortType GetSortType()
+ {
+ return Editor.Instance?.Windows?.ContentWin?.CurrentSortType ?? SortType.AlphabeticOrder;
+ }
+
+ private static int ApplySortOrder(int result)
+ {
+ return GetSortType() == SortType.AlphabeticReverse ? -result : result;
+ }
+}
diff --git a/Source/Editor/Content/Tree/ContentTreeNode.cs b/Source/Editor/Content/Tree/ContentTreeNode.cs
deleted file mode 100644
index ca629000d..000000000
--- a/Source/Editor/Content/Tree/ContentTreeNode.cs
+++ /dev/null
@@ -1,331 +0,0 @@
-// Copyright (c) Wojciech Figat. All rights reserved.
-
-using System.Collections.Generic;
-using FlaxEditor.GUI;
-using FlaxEditor.GUI.Drag;
-using FlaxEditor.GUI.Tree;
-using FlaxEditor.Utilities;
-using FlaxEngine;
-using FlaxEngine.GUI;
-
-namespace FlaxEditor.Content
-{
- ///
- /// Content folder tree node.
- ///
- ///
- public class ContentTreeNode : TreeNode
- {
- private DragItems _dragOverItems;
- private List _highlights;
-
- ///
- /// The folder.
- ///
- protected ContentFolder _folder;
-
- ///
- /// Whether this node can be deleted.
- ///
- public virtual bool CanDelete => true;
-
- ///
- /// Whether this node can be duplicated.
- ///
- public virtual bool CanDuplicate => true;
-
- ///
- /// Gets the content folder item.
- ///
- public ContentFolder Folder => _folder;
-
- ///
- /// Gets the type of the folder.
- ///
- public ContentFolderType FolderType => _folder.FolderType;
-
- ///
- /// Returns true if that folder can import/manage scripts.
- ///
- public bool CanHaveScripts => _folder.CanHaveScripts;
-
- ///
- /// Returns true if that folder can import/manage assets.
- ///
- /// True if can contain assets for project, otherwise false
- public bool CanHaveAssets => _folder.CanHaveAssets;
-
- ///
- /// Gets the parent node.
- ///
- public ContentTreeNode ParentNode => Parent as ContentTreeNode;
-
- ///
- /// Gets the folder path.
- ///
- public string Path => _folder.Path;
-
- ///
- /// Gets the navigation button label.
- ///
- public virtual string NavButtonLabel => _folder.ShortName;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The parent node.
- /// The folder path.
- public ContentTreeNode(ContentTreeNode parent, string path)
- : this(parent, parent?.FolderType ?? ContentFolderType.Other, path)
- {
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The parent node.
- /// The folder type.
- /// The folder path.
- protected ContentTreeNode(ContentTreeNode parent, ContentFolderType type, string path)
- : base(false, Editor.Instance.Icons.FolderClosed32, Editor.Instance.Icons.FolderOpen32)
- {
- _folder = new ContentFolder(type, path, this);
- Text = _folder.ShortName;
- if (parent != null)
- {
- Folder.ParentFolder = parent.Folder;
- Parent = parent;
- }
- IconColor = Style.Current.Foreground;
- }
-
- ///
- /// Shows the rename popup for the item.
- ///
- public void StartRenaming()
- {
- if (!_folder.CanRename)
- return;
-
- // Start renaming the folder
- 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); };
- }
-
- ///
- /// Updates the query search filter.
- ///
- /// The filter text.
- public void UpdateFilter(string filterText)
- {
- bool noFilter = string.IsNullOrWhiteSpace(filterText);
-
- // Update itself
- bool isThisVisible;
- if (noFilter)
- {
- // Clear filter
- _highlights?.Clear();
- isThisVisible = true;
- }
- else
- {
- var text = Text;
- if (QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges))
- {
- // Update highlights
- if (_highlights == null)
- _highlights = new List(ranges.Length);
- else
- _highlights.Clear();
- var style = Style.Current;
- var font = style.FontSmall;
- var textRect = TextRect;
- for (int i = 0; i < ranges.Length; i++)
- {
- var start = font.GetCharPosition(text, ranges[i].StartIndex);
- var end = font.GetCharPosition(text, ranges[i].EndIndex);
- _highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height));
- }
- isThisVisible = true;
- }
- else
- {
- // Hide
- _highlights?.Clear();
- isThisVisible = false;
- }
- }
-
- // Update children
- bool isAnyChildVisible = false;
- for (int i = 0; i < _children.Count; i++)
- {
- if (_children[i] is ContentTreeNode child)
- {
- child.UpdateFilter(filterText);
- isAnyChildVisible |= child.Visible;
- }
- }
-
- if (!noFilter)
- {
- bool isExpanded = isAnyChildVisible;
- if (isExpanded)
- Expand(true);
- else
- Collapse(true);
- }
-
- Visible = isThisVisible | isAnyChildVisible;
- }
-
- ///
- public override void Draw()
- {
- base.Draw();
-
- // Draw all highlights
- if (_highlights != null)
- {
- var style = Style.Current;
- var color = style.ProgressNormal * 0.6f;
- for (int i = 0; i < _highlights.Count; i++)
- Render2D.FillRectangle(_highlights[i], color);
- }
- }
-
- ///
- public override void OnDestroy()
- {
- // Delete folder item
- _folder.Dispose();
-
- base.OnDestroy();
- }
-
- private DragDropEffect GetDragEffect(DragData data)
- {
- if (data is DragDataFiles)
- {
- if (_folder.CanHaveAssets)
- return DragDropEffect.Copy;
- }
- else
- {
- if (_dragOverItems.HasValidDrag)
- return DragDropEffect.Move;
- }
-
- return DragDropEffect.None;
- }
-
- private bool ValidateDragItem(ContentItem item)
- {
- // Reject itself and any parent
- return item != _folder && !item.Find(_folder);
- }
-
- ///
- protected override DragDropEffect OnDragEnterHeader(DragData data)
- {
- if (_dragOverItems == null)
- _dragOverItems = new DragItems(ValidateDragItem);
-
- _dragOverItems.OnDragEnter(data);
- return GetDragEffect(data);
- }
-
- ///
- protected override DragDropEffect OnDragMoveHeader(DragData data)
- {
- return GetDragEffect(data);
- }
-
- ///
- protected override void OnDragLeaveHeader()
- {
- _dragOverItems.OnDragLeave();
- base.OnDragLeaveHeader();
- }
-
- ///
- protected override DragDropEffect OnDragDropHeader(DragData data)
- {
- var result = DragDropEffect.None;
-
- // Check if drop element or files
- if (data is DragDataFiles files)
- {
- // Import files
- Editor.Instance.ContentImporting.Import(files.Files, _folder);
- result = DragDropEffect.Copy;
-
- Expand();
- }
- else if (_dragOverItems.HasValidDrag)
- {
- // Move items
- Editor.Instance.ContentDatabase.Move(_dragOverItems.Objects, _folder);
- result = DragDropEffect.Move;
-
- Expand();
- }
-
- _dragOverItems.OnDragDrop();
-
- return result;
- }
-
- ///
- protected override void DoDragDrop()
- {
- DoDragDrop(DragItems.GetDragData(_folder));
- }
-
- ///
- protected override void OnLongPress()
- {
- Select();
-
- StartRenaming();
- }
-
- ///
- public override bool OnKeyDown(KeyboardKeys key)
- {
- if (IsFocused)
- {
- switch (key)
- {
- case KeyboardKeys.F2:
- StartRenaming();
- return true;
- case KeyboardKeys.Delete:
- if (Folder.Exists && CanDelete)
- Editor.Instance.Windows.ContentWin.Delete(Folder);
- return true;
- }
- if (RootWindow.GetKey(KeyboardKeys.Control))
- {
- switch (key)
- {
- case KeyboardKeys.D:
- if (Folder.Exists && CanDuplicate)
- Editor.Instance.Windows.ContentWin.Duplicate(Folder);
- return true;
- }
- }
- }
-
- return base.OnKeyDown(key);
- }
- }
-}
diff --git a/Source/Editor/Content/Tree/MainContentTreeNode.cs b/Source/Editor/Content/Tree/MainContentTreeNode.cs
index fc147f5c9..aaeed0eda 100644
--- a/Source/Editor/Content/Tree/MainContentTreeNode.cs
+++ b/Source/Editor/Content/Tree/MainContentTreeNode.cs
@@ -7,8 +7,8 @@ namespace FlaxEditor.Content
///
/// Content tree node used for main directories.
///
- ///
- public class MainContentTreeNode : ContentTreeNode
+ ///
+ public class MainContentFolderTreeNode : ContentFolderTreeNode
{
private FileSystemWatcher _watcher;
@@ -19,12 +19,12 @@ namespace FlaxEditor.Content
public override bool CanDuplicate => false;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The parent project.
/// The folder type.
/// The folder path.
- public MainContentTreeNode(ProjectTreeNode parent, ContentFolderType type, string path)
+ public MainContentFolderTreeNode(ProjectFolderTreeNode parent, ContentFolderType type, string path)
: base(parent, type, path)
{
_watcher = new FileSystemWatcher(path)
diff --git a/Source/Editor/Content/Tree/ProjectTreeNode.cs b/Source/Editor/Content/Tree/ProjectTreeNode.cs
index 61002d4fa..4f921e371 100644
--- a/Source/Editor/Content/Tree/ProjectTreeNode.cs
+++ b/Source/Editor/Content/Tree/ProjectTreeNode.cs
@@ -1,5 +1,6 @@
// Copyright (c) Wojciech Figat. All rights reserved.
+using System;
using FlaxEngine.GUI;
namespace FlaxEditor.Content
@@ -7,8 +8,8 @@ namespace FlaxEditor.Content
///
/// Root tree node for the project workspace.
///
- ///
- public sealed class ProjectTreeNode : ContentTreeNode
+ ///
+ public sealed class ProjectFolderTreeNode : ContentFolderTreeNode
{
///
/// The project/
@@ -18,18 +19,18 @@ namespace FlaxEditor.Content
///
/// The project content directory.
///
- public MainContentTreeNode Content;
+ public MainContentFolderTreeNode Content;
///
/// The project source code directory.
///
- public MainContentTreeNode Source;
+ public MainContentFolderTreeNode Source;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The project.
- public ProjectTreeNode(ProjectInfo project)
+ public ProjectFolderTreeNode(ProjectInfo project)
: base(null, project.ProjectFolderPath)
{
Project = project;
@@ -48,9 +49,29 @@ namespace FlaxEditor.Content
///
public override int Compare(Control other)
{
- // Move the main game project to the top
- if (Project.Name == Editor.Instance.GameProject.Name)
- return -1;
+ if (other is ProjectFolderTreeNode otherProject)
+ {
+ var gameProject = Editor.Instance.GameProject;
+ var engineProject = Editor.Instance.EngineProject;
+ bool isGame = Project == gameProject;
+ bool isEngine = Project == engineProject;
+ bool otherIsGame = otherProject.Project == gameProject;
+ bool otherIsEngine = otherProject.Project == engineProject;
+
+ // Main game project at the top
+ if (isGame && !otherIsGame)
+ return -1;
+ if (!isGame && otherIsGame)
+ return 1;
+
+ // Engine project at the bottom (when distinct)
+ if (isEngine && !otherIsEngine)
+ return 1;
+ if (!isEngine && otherIsEngine)
+ return -1;
+
+ return string.CompareOrdinal(Project.Name, otherProject.Project.Name);
+ }
return base.Compare(other);
}
}
diff --git a/Source/Editor/Content/Tree/RootContentTreeNode.cs b/Source/Editor/Content/Tree/RootContentTreeNode.cs
index d9c1ba761..d1f816c14 100644
--- a/Source/Editor/Content/Tree/RootContentTreeNode.cs
+++ b/Source/Editor/Content/Tree/RootContentTreeNode.cs
@@ -5,13 +5,13 @@ namespace FlaxEditor.Content
///
/// Root tree node for the content workspace.
///
- ///
- public sealed class RootContentTreeNode : ContentTreeNode
+ ///
+ public sealed class RootContentFolderTreeNode : ContentFolderTreeNode
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- public RootContentTreeNode()
+ public RootContentFolderTreeNode()
: base(null, string.Empty)
{
}
diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs
index d312f64b2..def57b332 100644
--- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs
@@ -55,9 +55,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
// TODO: consider editing more than one instance of the same prefab asset at once
- var prefab = FlaxEngine.Content.LoadAsync(actor.PrefabID);
- // TODO: don't stall here?
- if (prefab && !prefab.WaitForLoaded())
+ var prefab = FlaxEngine.Content.Load(actor.PrefabID);
+ if (prefab)
{
var prefabObjectId = actor.PrefabObjectID;
var prefabInstance = prefab.GetDefaultInstance(ref prefabObjectId);
@@ -203,7 +202,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
//Presenter.BuildLayoutOnUpdate();
// Better way is to just update the reference value using the new default instance of the prefab, created after changes apply
- if (Values != null && prefab && !prefab.WaitForLoaded())
+ if (Values != null && (Actor)Values[0] && prefab && !prefab.WaitForLoaded())
{
var actor = (Actor)Values[0];
var prefabObjectId = actor.PrefabObjectID;
diff --git a/Source/Editor/GUI/NavigationBar.cs b/Source/Editor/GUI/NavigationBar.cs
index eb87eaafc..7f43d09ac 100644
--- a/Source/Editor/GUI/NavigationBar.cs
+++ b/Source/Editor/GUI/NavigationBar.cs
@@ -76,9 +76,23 @@ namespace FlaxEditor.GUI
toolstrip.IsLayoutLocked = toolstripLocked;
toolstrip.ItemsMargin = toolstripMargin;
- var lastToolstripButton = toolstrip.LastButton;
+ var margin = toolstrip.ItemsMargin;
+ float xOffset = margin.Left;
+ bool hadChild = false;
+ for (int i = 0; i < toolstrip.ChildrenCount; i++)
+ {
+ var child = toolstrip.GetChild(i);
+ if (child == this || !child.Visible)
+ continue;
+ hadChild = true;
+ xOffset += child.Width + margin.Width;
+ }
+
+ var right = hadChild ? xOffset - margin.Width : margin.Left;
var parentSize = Parent.Size;
- Bounds = new Rectangle(lastToolstripButton.Right + 8.0f, 0, parentSize.X - X - 8.0f, toolstrip.Height);
+ var x = right + 8.0f;
+ var width = Mathf.Max(parentSize.X - x - 8.0f, 0.0f);
+ Bounds = new Rectangle(x, 0, width, toolstrip.Height);
}
///
diff --git a/Source/Editor/Gizmo/DirectionGizmo.cs b/Source/Editor/Gizmo/DirectionGizmo.cs
index babebbd5b..b76cd0607 100644
--- a/Source/Editor/Gizmo/DirectionGizmo.cs
+++ b/Source/Editor/Gizmo/DirectionGizmo.cs
@@ -1,6 +1,7 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System.Collections.Generic;
+using FlaxEditor.Options;
using FlaxEditor.Viewport;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -10,13 +11,20 @@ namespace FlaxEditor.Gizmo;
[HideInEditor]
internal class DirectionGizmo : ContainerControl
{
+ public const float DefaultGizmoSize = 112.5f;
+
+ private const float AxisLength = 71.25f;
+ private const float SpriteRadius = 10.85f;
+
private IGizmoOwner _owner;
private ViewportProjection _viewportProjection;
private EditorViewport _viewport;
private Vector3 _gizmoCenter;
- private float _axisLength = 75.0f;
- private float _textAxisLength = 95.0f;
- private float _spriteRadius = 12.0f;
+ private float _gizmoBrightness;
+ private float _gizmoOpacity;
+ private float _backgroundOpacity;
+ private float _axisLength;
+ private float _spriteRadius;
private AxisData _xAxisData;
private AxisData _yAxisData;
@@ -92,19 +100,34 @@ internal class DirectionGizmo : ContainerControl
_xAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "X", AxisColor = new Color(1.0f, 0.0f, 0.02745f, 1.0f), Negative = false, Direction = AxisDirection.PosX };
_yAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "Y", AxisColor = new Color(0.239215f, 1.0f, 0.047058f, 1.0f), Negative = false, Direction = AxisDirection.PosY };
- _zAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "Z", AxisColor = new Color(0.0f, 0.0235294f, 1.0f, 1.0f), Negative = false, Direction = AxisDirection.PosZ };
+ _zAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "Z", AxisColor = new Color(0.0f, 0.3607f, 0.9f, 1.0f), Negative = false, Direction = AxisDirection.PosZ };
_negXAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "-X", AxisColor = new Color(1.0f, 0.0f, 0.02745f, 1.0f), Negative = true, Direction = AxisDirection.NegX };
_negYAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "-Y", AxisColor = new Color(0.239215f, 1.0f, 0.047058f, 1.0f), Negative = true, Direction = AxisDirection.NegY };
- _negZAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "-Z", AxisColor = new Color(0.0f, 0.0235294f, 1.0f, 1.0f), Negative = true, Direction = AxisDirection.NegZ };
+ _negZAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "-Z", AxisColor = new Color(0.0f, 0.3607f, 0.9f, 1.0f), Negative = true, Direction = AxisDirection.NegZ };
_axisData.EnsureCapacity(6);
_spritePositions.EnsureCapacity(6);
- _posHandle = Editor.Instance.Icons.VisjectBoxClosed32;
- _negHandle = Editor.Instance.Icons.VisjectBoxOpen32;
+ var editor = Editor.Instance;
+ _posHandle = editor.Icons.VisjectBoxClosed32;
+ _negHandle = editor.Icons.VisjectBoxOpen32;
_fontReference = new FontReference(Style.Current.FontSmall);
- _fontReference.Size = 8;
+
+ editor.Options.OptionsChanged += OnEditorOptionsChanged;
+ OnEditorOptionsChanged(editor.Options.Options);
+ }
+
+ private void OnEditorOptionsChanged(EditorOptions options)
+ {
+ float gizmoScale = options.Viewport.DirectionGizmoScale;
+ _axisLength = AxisLength * gizmoScale;
+ _spriteRadius = SpriteRadius * gizmoScale;
+ _gizmoBrightness = options.Viewport.DirectionGizmoBrightness;
+ _gizmoOpacity = options.Viewport.DirectionGizmoOpacity;
+ _backgroundOpacity = options.Viewport.DirectionGizmoBackgroundOpacity;
+
+ _fontReference.Size = 8.25f * gizmoScale;
}
private bool IsPointInSprite(Float2 point, Float2 spriteCenter)
@@ -134,9 +157,9 @@ internal class DirectionGizmo : ContainerControl
}
///
- public override bool OnMouseDown(Float2 location, MouseButton button)
+ public override bool OnMouseUp(Float2 location, MouseButton button)
{
- if (base.OnMouseDown(location, button))
+ if (base.OnMouseUp(location, button))
return true;
// Check which axis is being clicked - check from closest to farthest for proper layering
@@ -156,12 +179,12 @@ internal class DirectionGizmo : ContainerControl
{
Quaternion orientation = direction switch
{
- AxisDirection.PosX => Quaternion.Euler(0, 90, 0),
- AxisDirection.NegX => Quaternion.Euler(0, -90, 0),
- AxisDirection.PosY => Quaternion.Euler(-90, 0, 0),
- AxisDirection.NegY => Quaternion.Euler(90, 0, 0),
- AxisDirection.PosZ => Quaternion.Euler(0, 0, 0),
- AxisDirection.NegZ => Quaternion.Euler(0, 180, 0),
+ AxisDirection.PosX => Quaternion.Euler(0, -90, 0),
+ AxisDirection.NegX => Quaternion.Euler(0, 90, 0),
+ AxisDirection.PosY => Quaternion.Euler(90, 0, 0),
+ AxisDirection.NegY => Quaternion.Euler(-90, 0, 0),
+ AxisDirection.PosZ => Quaternion.Euler(0, 180, 0),
+ AxisDirection.NegZ => Quaternion.Euler(0, 0, 0),
_ => Quaternion.Identity
};
_viewport.OrientViewport(ref orientation);
@@ -192,8 +215,19 @@ internal class DirectionGizmo : ContainerControl
// Normalize by viewport height to keep size independent of FOV and viewport dimensions
float heightNormalization = _viewport.Height / 720.0f; // 720 = reference height
+
+ // Fix in axes distance no matter FOV/OrthoScale to keep consistent size regardless of zoom level
if (_owner.Viewport.UseOrthographicProjection)
- heightNormalization /= _owner.Viewport.OrthographicScale * 0.5f; // Fix in ortho view to keep consistent size regardless of zoom level
+ heightNormalization /= _owner.Viewport.OrthographicScale * 0.5f;
+ else
+ {
+ // This could be some actual math expression, not that hack
+ var fov = _owner.Viewport.FieldOfView / 60.0f;
+ float scaleAt30 = 0.1f, scaleAt60 = 1.0f, scaleAt120 = 1.5f, scaleAt180 = 3.0f;
+ heightNormalization /= Mathf.Lerp(scaleAt30, scaleAt60, fov);
+ heightNormalization /= Mathf.Lerp(scaleAt60, scaleAt120, Mathf.Saturate(fov - 1));
+ heightNormalization /= Mathf.Lerp(scaleAt60, scaleAt180, Mathf.Saturate(fov - 2));
+ }
Float2 xDelta = (xProjected - gizmoCenterScreen) / heightNormalization;
Float2 yDelta = (yProjected - gizmoCenterScreen) / heightNormalization;
@@ -232,33 +266,32 @@ internal class DirectionGizmo : ContainerControl
// Rebuild sprite positions list for hover detection
_spritePositions.Clear();
- Render2D.DrawSprite(_posHandle, new Rectangle(0, 0, Size), Color.Black.AlphaMultiplied(0.1f));
+ Render2D.DrawSprite(_posHandle, new Rectangle(0, 0, Size), Color.Black.AlphaMultiplied(_backgroundOpacity));
// Draw in order from farthest to closest
for (int i = 0; i < _axisData.Count; i++)
{
var axis = _axisData[i];
Float2 tipScreen = relativeCenter + axis.Delta * _axisLength;
- Float2 tipTextScreen = relativeCenter + axis.Delta * _textAxisLength;
bool isHovered = _hoveredAxisIndex == i;
// Store sprite position for hover detection
- _spritePositions.Add((tipTextScreen, axis.Direction));
+ _spritePositions.Add((tipScreen, axis.Direction));
var axisColor = isHovered ? new Color(1.0f, 0.8980392f, 0.039215688f) : axis.AxisColor;
+ axisColor = axisColor.RGBMultiplied(_gizmoBrightness).AlphaMultiplied(_gizmoOpacity);
var font = _fontReference.GetFont();
if (!axis.Negative)
{
- Render2D.DrawLine(relativeCenter, tipScreen, axisColor, 2.0f);
- Render2D.DrawSprite(_posHandle, new Rectangle(tipTextScreen - new Float2(_spriteRadius), new Float2(_spriteRadius * 2)), axisColor);
- Render2D.DrawText(font, axis.Label, isHovered ? Color.Gray : Color.Black, tipTextScreen - font.MeasureText(axis.Label) * 0.5f);
+ Render2D.DrawLine(relativeCenter, tipScreen, axisColor, 1.5f);
+ Render2D.DrawSprite(_posHandle, new Rectangle(tipScreen - new Float2(_spriteRadius), new Float2(_spriteRadius * 2)), axisColor);
+ Render2D.DrawText(font, axis.Label, Color.Black, tipScreen - font.MeasureText(axis.Label) * 0.5f);
}
else
{
- Render2D.DrawSprite(_posHandle, new Rectangle(tipTextScreen - new Float2(_spriteRadius), new Float2(_spriteRadius * 2)), axisColor.RGBMultiplied(0.65f));
- Render2D.DrawSprite(_negHandle, new Rectangle(tipTextScreen - new Float2(_spriteRadius), new Float2(_spriteRadius * 2)), axisColor);
+ Render2D.DrawSprite(_posHandle, new Rectangle(tipScreen - new Float2(_spriteRadius), new Float2(_spriteRadius * 2)), axisColor.RGBMultiplied(0.85f).AlphaMultiplied(0.8f));
if (isHovered)
- Render2D.DrawText(font, axis.Label, Color.Black, tipTextScreen - font.MeasureText(axis.Label) * 0.5f);
+ Render2D.DrawText(font, axis.Label, Color.Black, tipScreen - font.MeasureText(axis.Label) * 0.5f);
}
}
diff --git a/Source/Editor/Gizmo/UIEditorGizmo.cs b/Source/Editor/Gizmo/UIEditorGizmo.cs
index 61109556c..77e6fff9d 100644
--- a/Source/Editor/Gizmo/UIEditorGizmo.cs
+++ b/Source/Editor/Gizmo/UIEditorGizmo.cs
@@ -529,7 +529,7 @@ namespace FlaxEditor
if (EnableBackground && _view != null)
{
// Draw background
- Surface.VisjectSurface.DrawBackgroundDefault(Editor.Instance.UI.VisjectSurfaceBackground, Width, Height);
+ Surface.VisjectSurface.DrawBackgroundDefault(Editor.Instance.UI.VisjectSurfaceBackground, Size, _view.Location);
if (ShowGrid)
{
diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs
index 57982f8c3..6e4dd7c25 100644
--- a/Source/Editor/Modules/ContentDatabaseModule.cs
+++ b/Source/Editor/Modules/ContentDatabaseModule.cs
@@ -24,22 +24,22 @@ namespace FlaxEditor.Modules
private bool _rebuildInitFlag;
private int _itemsCreated;
private int _itemsDeleted;
- private readonly HashSet _dirtyNodes = new HashSet();
+ private readonly HashSet _dirtyNodes = new HashSet();
///
/// The project directory.
///
- public ProjectTreeNode Game { get; private set; }
+ public ProjectFolderTreeNode Game { get; private set; }
///
/// The engine directory.
///
- public ProjectTreeNode Engine { get; private set; }
+ public ProjectFolderTreeNode Engine { get; private set; }
///
/// The list of all projects workspace directories (including game, engine and plugins projects).
///
- public readonly List Projects = new List();
+ public readonly List Projects = new List();
///
/// The list with all content items proxy objects. Use and to modify this or to refresh database when adding new item proxy types.
@@ -116,7 +116,7 @@ namespace FlaxEditor.Modules
///
/// The project.
/// The project workspace or null if not loaded into database.
- public ProjectTreeNode GetProjectWorkspace(ProjectInfo project)
+ public ProjectFolderTreeNode GetProjectWorkspace(ProjectInfo project)
{
return Projects.FirstOrDefault(x => x.Project == project);
}
@@ -878,7 +878,7 @@ namespace FlaxEditor.Modules
}
}
- private void LoadFolder(ContentTreeNode node, bool checkSubDirs)
+ private void LoadFolder(ContentFolderTreeNode node, bool checkSubDirs)
{
if (node == null)
return;
@@ -957,7 +957,7 @@ namespace FlaxEditor.Modules
if (childFolderNode == null)
{
// Create node
- ContentTreeNode n = new ContentTreeNode(node, childPath);
+ ContentFolderTreeNode n = new ContentFolderTreeNode(node, childPath);
if (!_isDuringFastSetup)
sortChildren = true;
@@ -982,7 +982,7 @@ namespace FlaxEditor.Modules
node.SortChildren();
// Ignore some special folders
- if (node is MainContentTreeNode mainNode && mainNode.Folder.ShortName == "Source")
+ if (node is MainContentFolderTreeNode mainNode && mainNode.Folder.ShortName == "Source")
{
var mainNodeChild = mainNode.Folder.Find(StringUtils.CombinePaths(mainNode.Path, "obj")) as ContentFolder;
if (mainNodeChild != null)
@@ -999,7 +999,7 @@ namespace FlaxEditor.Modules
}
}
- private void LoadScripts(ContentTreeNode parent, string[] files)
+ private void LoadScripts(ContentFolderTreeNode parent, string[] files)
{
for (int i = 0; i < files.Length; i++)
{
@@ -1045,7 +1045,7 @@ namespace FlaxEditor.Modules
}
}
- private void LoadAssets(ContentTreeNode parent, string[] files)
+ private void LoadAssets(ContentFolderTreeNode parent, string[] files)
{
for (int i = 0; i < files.Length; i++)
{
@@ -1097,20 +1097,20 @@ namespace FlaxEditor.Modules
var workspace = GetProjectWorkspace(project);
if (workspace == null)
{
- workspace = new ProjectTreeNode(project);
+ workspace = new ProjectFolderTreeNode(project);
Projects.Add(workspace);
var contentFolder = StringUtils.CombinePaths(project.ProjectFolderPath, "Content");
if (Directory.Exists(contentFolder))
{
- workspace.Content = new MainContentTreeNode(workspace, ContentFolderType.Content, contentFolder);
+ workspace.Content = new MainContentFolderTreeNode(workspace, ContentFolderType.Content, contentFolder);
workspace.Content.Folder.ParentFolder = workspace.Folder;
}
var sourceFolder = StringUtils.CombinePaths(project.ProjectFolderPath, "Source");
if (Directory.Exists(sourceFolder))
{
- workspace.Source = new MainContentTreeNode(workspace, ContentFolderType.Source, sourceFolder);
+ workspace.Source = new MainContentFolderTreeNode(workspace, ContentFolderType.Source, sourceFolder);
workspace.Source.Folder.ParentFolder = workspace.Folder;
}
}
@@ -1218,16 +1218,16 @@ namespace FlaxEditor.Modules
Proxy.Add(new GenericJsonAssetProxy());
// Create content folders nodes
- Engine = new ProjectTreeNode(Editor.EngineProject)
+ Engine = new ProjectFolderTreeNode(Editor.EngineProject)
{
- Content = new MainContentTreeNode(Engine, ContentFolderType.Content, Globals.EngineContentFolder),
+ Content = new MainContentFolderTreeNode(Engine, ContentFolderType.Content, Globals.EngineContentFolder),
};
if (Editor.GameProject != Editor.EngineProject)
{
- Game = new ProjectTreeNode(Editor.GameProject)
+ Game = new ProjectFolderTreeNode(Editor.GameProject)
{
- Content = new MainContentTreeNode(Game, ContentFolderType.Content, Globals.ProjectContentFolder),
- Source = new MainContentTreeNode(Game, ContentFolderType.Source, Globals.ProjectSourceFolder),
+ Content = new MainContentFolderTreeNode(Game, ContentFolderType.Content, Globals.ProjectContentFolder),
+ Source = new MainContentFolderTreeNode(Game, ContentFolderType.Source, Globals.ProjectSourceFolder),
};
// TODO: why it's required? the code above should work for linking the nodes hierarchy
Game.Content.Folder.ParentFolder = Game.Folder;
@@ -1307,7 +1307,7 @@ namespace FlaxEditor.Modules
}
}
- internal void OnDirectoryEvent(MainContentTreeNode node, FileSystemEventArgs e)
+ internal void OnDirectoryEvent(MainContentFolderTreeNode node, FileSystemEventArgs e)
{
// Ensure to be ready for external events
if (_isDuringFastSetup)
diff --git a/Source/Editor/Options/ViewportOptions.cs b/Source/Editor/Options/ViewportOptions.cs
index aed633672..c7f6ba544 100644
--- a/Source/Editor/Options/ViewportOptions.cs
+++ b/Source/Editor/Options/ViewportOptions.cs
@@ -171,5 +171,40 @@ namespace FlaxEditor.Options
[DefaultValue(1000.0f), Limit(0.0f, 20000.0f, 5.0f)]
[EditorDisplay("Viewport Icons"), EditorOrder(410)]
public float MaxSizeDistance { get; set; } = 1000.0f;
+
+ ///
+ /// Gets or sets a value that indicates whether the main viewports is visible.
+ ///
+ [DefaultValue(true)]
+ [EditorDisplay("Direction Gizmo"), EditorOrder(500), Tooltip("Sets the visibility of the direction gizmo in the main editor viewport.")]
+ public bool ShowDirectionGizmo { get; set; } = true;
+
+ ///
+ /// Gets or sets a value by which the main viewports size is multiplied with.
+ ///
+ [DefaultValue(1f), Limit(0.0f, 2.0f)]
+ [EditorDisplay("Direction Gizmo"), EditorOrder(501), Tooltip("The scale of the direction gizmo in the main viewport.")]
+ public float DirectionGizmoScale { get; set; } = 1f;
+
+ ///
+ /// Gets or sets a value for the opacity of the main viewports background.
+ ///
+ [DefaultValue(0.1f), Limit(0.0f, 1.0f)]
+ [EditorDisplay("Direction Gizmo"), EditorOrder(502), Tooltip("The background opacity of the of the direction gizmo in the main viewport.")]
+ public float DirectionGizmoBackgroundOpacity { get; set; } = 0.1f;
+
+ ///
+ /// Gets or sets a value for the opacity of the main viewports .
+ ///
+ [DefaultValue(0.6f), Limit(0.0f, 1.0f)]
+ [EditorDisplay("Direction Gizmo"), EditorOrder(503), Tooltip("The opacity of the of the direction gizmo in the main viewport.")]
+ public float DirectionGizmoOpacity { get; set; } = 0.6f;
+
+ ///
+ /// Gets or sets a value for the opacity of the main viewports .
+ ///
+ [DefaultValue(1f), Limit(0.0f, 2.0f)]
+ [EditorDisplay("Direction Gizmo"), EditorOrder(504), Tooltip("The brightness of the of the direction gizmo in the main viewport.")]
+ public float DirectionGizmoBrightness{ get; set; } = 1f;
}
}
diff --git a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs
index 450960af7..cb8681be0 100644
--- a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs
+++ b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs
@@ -102,8 +102,14 @@ namespace FlaxEditor.Surface.Archetypes
outline = style.BorderHighlighted;
else if (_editor._node.SelectedAnimationIndex == _index)
outline = style.BackgroundSelected;
+
+ var features = Render2D.Features;
+ Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping;
+
Render2D.DrawSprite(icon, rect.MakeExpanded(4.0f), outline);
Render2D.DrawSprite(icon, rect, style.Foreground);
+
+ Render2D.Features = features;
}
///
@@ -563,6 +569,9 @@ namespace FlaxEditor.Surface.Archetypes
// Grid
_node.DrawEditorGrid(ref rect);
+ var features = Render2D.Features;
+ Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping;
+
base.Draw();
// Draw debug position
@@ -578,6 +587,8 @@ namespace FlaxEditor.Surface.Archetypes
Render2D.DrawSprite(icon, debugRect, style.ProgressNormal);
}
+ Render2D.Features = features;
+
// Frame
var frameColor = containsFocus ? style.BackgroundSelected : (IsMouseOver ? style.ForegroundGrey : style.ForegroundDisabled);
Render2D.DrawRectangle(new Rectangle(1, 1, rect.Width - 2, rect.Height - 2), frameColor);
diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs
index e66f38398..b4afff091 100644
--- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs
+++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs
@@ -79,7 +79,7 @@ namespace FlaxEditor.Surface.Archetypes
: base(id, context, nodeArch, groupArch)
{
var marginX = FlaxEditor.Surface.Constants.NodeMarginX;
- var uiStartPosY = FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize;
+ var uiStartPosY = FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight;
var editButton = new Button(marginX, uiStartPosY, 246, 20)
{
diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs
index 9f3112905..410f6d33f 100644
--- a/Source/Editor/Surface/Archetypes/Animation.cs
+++ b/Source/Editor/Surface/Archetypes/Animation.cs
@@ -70,6 +70,9 @@ namespace FlaxEditor.Surface.Archetypes
if (box.ID != _assetBox.ID)
return;
_assetSelect.Visible = !box.HasAnyConnection;
+
+ if (!Archetype.Flags.HasFlag(NodeFlags.FixedSize))
+ ResizeAuto();
}
}
@@ -243,8 +246,8 @@ namespace FlaxEditor.Surface.Archetypes
{
Type = NodeElementType.Input,
Position = new Float2(
- FlaxEditor.Surface.Constants.NodeMarginX - FlaxEditor.Surface.Constants.BoxOffsetX,
- FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize + ylevel * FlaxEditor.Surface.Constants.LayoutOffsetY),
+ FlaxEditor.Surface.Constants.NodeMarginX,
+ FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight + ylevel * FlaxEditor.Surface.Constants.LayoutOffsetY),
Text = "Pose " + _blendPoses.Count,
Single = true,
ValueIndex = -1,
@@ -263,7 +266,7 @@ namespace FlaxEditor.Surface.Archetypes
private void UpdateHeight()
{
float nodeHeight = 10 + (Mathf.Max(_blendPoses.Count, 1) + 3) * FlaxEditor.Surface.Constants.LayoutOffsetY;
- Height = nodeHeight + FlaxEditor.Surface.Constants.NodeMarginY * 2 + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.NodeFooterSize;
+ Height = nodeHeight + FlaxEditor.Surface.Constants.NodeMarginY * 2 + FlaxEditor.Surface.Constants.NodeHeaderHeight + FlaxEditor.Surface.Constants.NodeFooterSize;
}
///
@@ -621,8 +624,8 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new MultiBlend1D(id, context, arch, groupArch),
Title = "Multi Blend 1D",
Description = "Animation blending in 1D",
- Flags = NodeFlags.AnimGraph | NodeFlags.VariableValuesSize,
- Size = new Float2(420, 300),
+ Flags = NodeFlags.AnimGraph | NodeFlags.VariableValuesSize | NodeFlags.FixedSize,
+ Size = new Float2(420, 320),
DefaultValues = new object[]
{
// Node data
@@ -646,9 +649,9 @@ namespace FlaxEditor.Surface.Archetypes
// Axis X
NodeElementArchetype.Factory.Input(3, "X", true, typeof(float), 4),
- NodeElementArchetype.Factory.Text(30, 3 * Surface.Constants.LayoutOffsetY + 2, "(min: max: )"),
- NodeElementArchetype.Factory.Vector_X(60, 3 * Surface.Constants.LayoutOffsetY + 2, 0),
- NodeElementArchetype.Factory.Vector_Y(145, 3 * Surface.Constants.LayoutOffsetY + 2, 0),
+ NodeElementArchetype.Factory.Text(30, 3 * Surface.Constants.LayoutOffsetY, "(min: max: )"),
+ NodeElementArchetype.Factory.Vector_X(60, 3 * Surface.Constants.LayoutOffsetY, 0),
+ NodeElementArchetype.Factory.Vector_Y(145, 3 * Surface.Constants.LayoutOffsetY, 0),
}
},
new NodeArchetype
@@ -657,8 +660,8 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new MultiBlend2D(id, context, arch, groupArch),
Title = "Multi Blend 2D",
Description = "Animation blending in 2D",
- Flags = NodeFlags.AnimGraph | NodeFlags.VariableValuesSize,
- Size = new Float2(420, 620),
+ Flags = NodeFlags.AnimGraph | NodeFlags.VariableValuesSize | NodeFlags.FixedSize,
+ Size = new Float2(420, 640),
DefaultValues = new object[]
{
// Node data
@@ -682,15 +685,15 @@ namespace FlaxEditor.Surface.Archetypes
// Axis X
NodeElementArchetype.Factory.Input(3, "X", true, typeof(float), 4),
- NodeElementArchetype.Factory.Text(30, 3 * Surface.Constants.LayoutOffsetY + 2, "(min: max: )"),
- NodeElementArchetype.Factory.Vector_X(60, 3 * Surface.Constants.LayoutOffsetY + 2, 0),
- NodeElementArchetype.Factory.Vector_Y(145, 3 * Surface.Constants.LayoutOffsetY + 2, 0),
+ NodeElementArchetype.Factory.Text(30, 3 * Surface.Constants.LayoutOffsetY, "(min: max: )"),
+ NodeElementArchetype.Factory.Vector_X(60, 3 * Surface.Constants.LayoutOffsetY, 0),
+ NodeElementArchetype.Factory.Vector_Y(145, 3 * Surface.Constants.LayoutOffsetY, 0),
// Axis Y
NodeElementArchetype.Factory.Input(4, "Y", true, typeof(float), 5),
- NodeElementArchetype.Factory.Text(30, 4 * Surface.Constants.LayoutOffsetY + 2, "(min: max: )"),
- NodeElementArchetype.Factory.Vector_Z(60, 4 * Surface.Constants.LayoutOffsetY + 2, 0),
- NodeElementArchetype.Factory.Vector_W(145, 4 * Surface.Constants.LayoutOffsetY + 2, 0),
+ NodeElementArchetype.Factory.Text(30, 4 * Surface.Constants.LayoutOffsetY, "(min: max: )"),
+ NodeElementArchetype.Factory.Vector_Z(60, 4 * Surface.Constants.LayoutOffsetY, 0),
+ NodeElementArchetype.Factory.Vector_W(145, 4 * Surface.Constants.LayoutOffsetY, 0),
}
},
new NodeArchetype
@@ -931,7 +934,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 27,
Title = "Copy Node",
Description = "Copies the skeleton node transformation data (in local space)",
- Flags = NodeFlags.AnimGraph,
+ Flags = NodeFlags.AnimGraph | NodeFlags.FixedSize,
Size = new Float2(260, 140),
DefaultValues = new object[]
{
diff --git a/Source/Editor/Surface/Archetypes/BehaviorTree.cs b/Source/Editor/Surface/Archetypes/BehaviorTree.cs
index 0b4b3f713..cdc223a8d 100644
--- a/Source/Editor/Surface/Archetypes/BehaviorTree.cs
+++ b/Source/Editor/Surface/Archetypes/BehaviorTree.cs
@@ -189,13 +189,69 @@ namespace FlaxEditor.Surface.Archetypes
public override void Draw()
{
- base.Draw();
+ var style = Style.Current;
+ var backgroundRect = new Rectangle(Float2.Zero, Size);
+
+ // Shadow
+ if (DrawBasicShadow)
+ {
+ var shadowRect = backgroundRect.MakeOffsetted(ShadowOffset);
+ Render2D.FillRectangle(shadowRect, Color.Black.AlphaMultiplied(0.125f));
+ }
+
+ // Background
+ Render2D.FillRectangle(backgroundRect, BackgroundColor);
+
+ // Breakpoint hit
+ if (Breakpoint.Hit)
+ {
+ var colorTop = Color.OrangeRed;
+ var colorBottom = Color.Red;
+ var time = DateTime.Now - Engine.StartupTime;
+ Render2D.DrawRectangle(backgroundRect.MakeExpanded(Mathf.Lerp(3.0f, 12.0f, Mathf.Sin((float)time.TotalSeconds * 10.0f) * 0.5f + 0.5f)), colorTop, colorTop, colorBottom, colorBottom, 2.0f);
+ }
+
+ // Header
+ var headerColor = style.BackgroundHighlighted;
+ if (_headerRect.Contains(ref _mousePosition) && !Surface.IsConnecting && !Surface.IsSelecting)
+ headerColor *= 1.07f;
+ Render2D.FillRectangle(_headerRect, style.BackgroundHighlighted);
+ Render2D.DrawText(style.FontLarge, Title, _headerTextRect, style.Foreground, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1f, FlaxEditor.Surface.Constants.NodeHeaderTextScale);
+
+ // Close button
+ if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0 && Surface.CanEdit)
+ {
+ bool highlightClose = _closeButtonRect.Contains(_mousePosition) && !Surface.IsConnecting && !Surface.IsSelecting;
+ DrawCloseButton(_closeButtonRect, highlightClose ? style.Foreground : style.ForegroundGrey);
+ }
+
+ DrawChildren();
+
+ // Selection outline
+ if (_isSelected)
+ {
+ var colorTop = Color.Orange;
+ var colorBottom = Color.OrangeRed;
+ Render2D.DrawRectangle(backgroundRect, colorTop, colorTop, colorBottom, colorBottom, 2.5f);
+ }
+
+ // Breakpoint dot
+ if (Breakpoint.Set)
+ {
+ var icon = Breakpoint.Enabled ? Surface.Style.Icons.BoxClose : Surface.Style.Icons.BoxOpen;
+ Render2D.DrawSprite(icon, new Rectangle(-7, -7, 16, 16), new Color(0.9f, 0.9f, 0.9f));
+ Render2D.DrawSprite(icon, new Rectangle(-6, -6, 14, 14), new Color(0.894117647f, 0.0784313725f, 0.0f));
+ }
+
+ if (highlightBox != null)
+ Render2D.DrawRectangle(highlightBox.Bounds, style.BorderHighlighted, 2f);
// Debug Info
if (!string.IsNullOrEmpty(_debugInfo))
{
- var style = Style.Current;
- Render2D.DrawText(style.FontSmall, _debugInfo, new Rectangle(4, _headerRect.Bottom + 4, _debugInfoSize), style.Foreground);
+ // Draw an extra background to cover the archetype color colored node background and make text more legible
+ Render2D.FillRectangle(new Rectangle(0, _headerRect.Bottom + 4, Width, Height - _headerRect.Bottom - 4), style.BackgroundHighlighted);
+ Render2D.DrawText(style.FontSmall, _debugInfo, new Rectangle(4, _headerRect.Bottom + 7, _debugInfoSize), style.Foreground, scale: 0.8f);
}
// Debug relevancy outline
@@ -203,7 +259,7 @@ namespace FlaxEditor.Surface.Archetypes
{
var colorTop = Color.LightYellow;
var colorBottom = Color.Yellow;
- var backgroundRect = new Rectangle(Float2.One, Size - new Float2(2.0f));
+ backgroundRect = new Rectangle(Float2.One, Size - new Float2(2.0f));
Render2D.DrawRectangle(backgroundRect, colorTop, colorTop, colorBottom, colorBottom);
}
}
@@ -415,8 +471,8 @@ namespace FlaxEditor.Surface.Archetypes
// 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);
+ _input.ConnectionOffset = new Float2(0, FlaxEditor.Surface.Constants.BoxRowHeight * -0.5f);
+ _output.ConnectionOffset = new Float2(0, FlaxEditor.Surface.Constants.BoxRowHeight * 0.5f);
// Setup node type and data
var flagsRoot = NodeFlags.NoRemove | NodeFlags.NoCloseButton | NodeFlags.NoSpawnViaPaste;
@@ -515,7 +571,7 @@ namespace FlaxEditor.Surface.Archetypes
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);
+ Size = new Float2(width + FlaxEditor.Surface.Constants.NodeMarginX * 2 + FlaxEditor.Surface.Constants.NodeCloseButtonSize, height + FlaxEditor.Surface.Constants.NodeHeaderHeight);
UpdateRectangles();
}
@@ -536,13 +592,13 @@ namespace FlaxEditor.Surface.Archetypes
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 headerHeight = FlaxEditor.Surface.Constants.NodeHeaderHeight;
const float closeButtonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
- const float closeButtonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
- _headerRect = new Rectangle(0, bounds.Y - Y, bounds.Width, headerSize);
+ float closeButtonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize * 0.75f;
+ _headerRect = new Rectangle(0, bounds.Y - Y, bounds.Width, headerHeight);
+ _headerTextRect = _headerRect with { X = 5f, Width = Width - closeButtonSize - closeButtonMargin * 4f };
_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;
@@ -648,6 +704,8 @@ namespace FlaxEditor.Surface.Archetypes
private DragDecorator _dragDecorator;
private float _dragLocation = -1;
+ internal override bool DrawBasicShadow => false;
+
internal Decorator(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
@@ -667,7 +725,7 @@ namespace FlaxEditor.Surface.Archetypes
}
}
- protected override Color FooterColor => Color.Transparent;
+ protected override Color ArchetypeColor => Color.Transparent;
protected override Float2 CalculateNodeSize(float width, float height)
{
@@ -676,7 +734,7 @@ namespace FlaxEditor.Surface.Archetypes
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);
+ return new Float2(width + FlaxEditor.Surface.Constants.NodeCloseButtonSize * 2 + DecoratorsMarginX * 2, height + FlaxEditor.Surface.Constants.NodeHeaderHeight);
}
protected override void UpdateRectangles()
@@ -684,8 +742,14 @@ namespace FlaxEditor.Surface.Archetypes
base.UpdateRectangles();
_footerRect = Rectangle.Empty;
+ const float closeButtonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
+ float closeButtonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize * 0.75f;
+ _closeButtonRect = new Rectangle(Bounds.Width - closeButtonSize - closeButtonMargin, _headerRect.Y + closeButtonMargin, closeButtonSize, closeButtonSize);
if (_dragIcon != null)
- _dragIcon.Bounds = new Rectangle(_closeButtonRect.X - _closeButtonRect.Width, _closeButtonRect.Y, _closeButtonRect.Size);
+ {
+ var dragIconRect = _closeButtonRect.MakeExpanded(5f);
+ _dragIcon.Bounds = new Rectangle(dragIconRect.X - dragIconRect.Width, dragIconRect.Y, dragIconRect.Size);
+ }
}
protected override void UpdateTitle()
@@ -754,16 +818,21 @@ namespace FlaxEditor.Surface.Archetypes
{
base.OnSurfaceLoaded(action);
+ var node = Node;
if (action == SurfaceNodeActions.Undo)
{
// Update parent node layout when restoring decorator from undo
- var node = Node;
if (node != null)
{
node._decorators = null;
node.ResizeAuto();
}
}
+ else
+ {
+ // Correctly size decorators when surface is loaded
+ node?.ResizeAuto();
+ }
}
///
diff --git a/Source/Editor/Surface/Archetypes/Comparisons.cs b/Source/Editor/Surface/Archetypes/Comparisons.cs
index bcf0159d2..32baadc1c 100644
--- a/Source/Editor/Surface/Archetypes/Comparisons.cs
+++ b/Source/Editor/Surface/Archetypes/Comparisons.cs
@@ -173,10 +173,10 @@ namespace FlaxEditor.Surface.Archetypes
{
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" }),
+ Op(3, ">", "Determines whether the first value is greater than the other", new[] { "greater than", "larger than", "bigger than", "more than" }),
+ Op(4, "<", "Determines whether the first value is less than the other", new[] { "less than", "smaller than", "tinier than" }),
+ Op(5, "<=", "Determines whether the first value is less or equal to the other", new[] { "less equals than", "smaller equals than", "tinier 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", "more equals than" }),
new NodeArchetype
{
TypeID = 7,
diff --git a/Source/Editor/Surface/Archetypes/Constants.cs b/Source/Editor/Surface/Archetypes/Constants.cs
index ad88fa56a..19bb767d4 100644
--- a/Source/Editor/Surface/Archetypes/Constants.cs
+++ b/Source/Editor/Surface/Archetypes/Constants.cs
@@ -166,7 +166,7 @@ namespace FlaxEditor.Surface.Archetypes
_picker = new EnumComboBox(type)
{
EnumTypeValue = Values[0],
- Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize, 160, 16),
+ Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight, 160, FlaxEditor.Surface.Constants.BoxRowHeight),
Parent = this,
};
_picker.ValueChanged += () => SetValue(0, _picker.EnumTypeValue);
@@ -218,7 +218,7 @@ namespace FlaxEditor.Surface.Archetypes
_output = (OutputBox)Elements[0];
_typePicker = new TypePickerControl
{
- Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize, 160, 16),
+ Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight, 160, 16),
Parent = this,
};
_typePicker.ValueChanged += () => Set(3);
@@ -362,7 +362,7 @@ namespace FlaxEditor.Surface.Archetypes
_output = (OutputBox)Elements[0];
_keyTypePicker = new TypePickerControl
{
- Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize, 160, 16),
+ Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight, 160, 16),
Parent = this,
};
_keyTypePicker.ValueChanged += OnKeyTypeChanged;
@@ -482,7 +482,7 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(bool))),
Description = "Constant boolean value",
Flags = NodeFlags.AllGraphs,
- Size = new Float2(110, 20),
+ Size = new Float2(90, 20),
DefaultValues = new object[]
{
false
@@ -515,7 +515,7 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(int))),
Description = "Constant integer value",
Flags = NodeFlags.AllGraphs,
- Size = new Float2(110, 20),
+ Size = new Float2(120, 20),
DefaultValues = new object[]
{
0
@@ -543,7 +543,7 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(float))),
Description = "Constant floating point",
Flags = NodeFlags.AllGraphs,
- Size = new Float2(110, 20),
+ Size = new Float2(120, 20),
DefaultValues = new object[]
{
0.0f
@@ -750,7 +750,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = "PI",
Description = "A value specifying the approximation of π which is 180 degrees",
Flags = NodeFlags.AllGraphs,
- Size = new Float2(50, 20),
+ Size = new Float2(45, 20),
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, "Ï€", typeof(float), 0),
@@ -782,7 +782,7 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(uint))),
Description = "Constant unsigned integer value",
Flags = NodeFlags.AllGraphs,
- Size = new Float2(170, 20),
+ Size = new Float2(130, 20),
DefaultValues = new object[]
{
0u
@@ -824,7 +824,7 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(double))),
Description = "Constant floating point",
Flags = NodeFlags.AllGraphs,
- Size = new Float2(110, 20),
+ Size = new Float2(120, 20),
DefaultValues = new object[]
{
0.0d
diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs
index 79d3948bf..e3deef368 100644
--- a/Source/Editor/Surface/Archetypes/Function.cs
+++ b/Source/Editor/Surface/Archetypes/Function.cs
@@ -267,7 +267,7 @@ namespace FlaxEditor.Surface.Archetypes
{
_nameField = new Label
{
- Width = 140.0f,
+ Size = new Float2(140, FlaxEditor.Surface.Constants.BoxRowHeight),
TextColorHighlighted = Style.Current.ForegroundGrey,
HorizontalAlignment = TextAlignment.Near,
Parent = this,
@@ -386,8 +386,7 @@ namespace FlaxEditor.Surface.Archetypes
_types = surface.FunctionTypes;
_typePicker = new ComboBox
{
- Location = new Float2(4, 32),
- Width = 80.0f,
+ Bounds = new Rectangle(4, 34, 80, FlaxEditor.Surface.Constants.BoxRowHeight),
Parent = this,
};
for (int i = 0; i < _types.Length; i++)
@@ -455,8 +454,7 @@ namespace FlaxEditor.Surface.Archetypes
_types = surface.FunctionTypes;
_typePicker = new ComboBox
{
- Location = new Float2(24, 32),
- Width = 80.0f,
+ Bounds = new Rectangle(24, 34, 80, FlaxEditor.Surface.Constants.BoxRowHeight),
Parent = this,
};
for (int i = 0; i < _types.Length; i++)
@@ -631,7 +629,7 @@ namespace FlaxEditor.Surface.Archetypes
}
///
- protected override Color FooterColor => new Color(200, 11, 112);
+ protected override Color ArchetypeColor => new Color(200, 11, 112);
///
public override void OnLoaded(SurfaceNodeActions action)
diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs
index 56f594586..5b3aaf310 100644
--- a/Source/Editor/Surface/Archetypes/Material.cs
+++ b/Source/Editor/Surface/Archetypes/Material.cs
@@ -312,16 +312,17 @@ namespace FlaxEditor.Surface.Archetypes
: base(id, context, nodeArch, groupArch)
{
_sizeValueIndex = Archetype.TypeID == 8 ? 1 : 3; // Index of the Size stored in Values array
- Float2 pos = new Float2(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize), size;
+ Float2 pos = new Float2(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight), size;
if (nodeArch.TypeID == 8)
{
- pos += new Float2(60, 0);
- size = new Float2(172, 200);
+ pos += new Float2(65, 0);
+ size = new Float2(160, 185);
+ _sizeMin = new Float2(240, 185);
}
else
{
- pos += new Float2(0, 40);
- size = new Float2(300, 200);
+ pos += new Float2(0, 40 + FlaxEditor.Utilities.Constants.UIMargin * 2f);
+ size = new Float2(300, 180);
}
_textBox = new CustomCodeTextBox
{
@@ -506,8 +507,8 @@ namespace FlaxEditor.Surface.Archetypes
Size = new Float2(300, 200),
DefaultValues = new object[]
{
- "// Here you can add HLSL code\nOutput0 = Input0;",
- new Float2(300, 200),
+ "// You can add HLSL code here\nOutput0 = Input0;",
+ new Float2(350, 200),
},
Elements = new[]
{
@@ -931,7 +932,7 @@ namespace FlaxEditor.Surface.Archetypes
new NodeArchetype
{
TypeID = 36,
- Title = "HSVToRGB",
+ Title = "HSV To RGB",
Description = "Converts a HSV value to linear RGB [X = 0/360, Y = 0/1, Z = 0/1]",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(160, 25),
@@ -948,7 +949,7 @@ namespace FlaxEditor.Surface.Archetypes
new NodeArchetype
{
TypeID = 37,
- Title = "RGBToHSV",
+ Title = "RGB To HSV",
Description = "Converts a linear RGB value to HSV [X = 0/360, Y = 0/1, Z = 0/1]",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(160, 25),
@@ -972,7 +973,7 @@ namespace FlaxEditor.Surface.Archetypes
Size = new Float2(300, 240),
DefaultValues = new object[]
{
- "// Here you can add HLSL code\nfloat4 GetCustomColor()\n{\n\treturn float4(1, 0, 0, 1);\n}",
+ "// You can add HLSL code here\nfloat4 GetCustomColor()\n{\n\treturn float4(1, 0, 0, 1);\n}",
true,
(int)MaterialTemplateInputsMapping.Utilities,
new Float2(300, 240),
diff --git a/Source/Editor/Surface/Archetypes/Math.cs b/Source/Editor/Surface/Archetypes/Math.cs
index 26d271264..9d2ae3bfd 100644
--- a/Source/Editor/Surface/Archetypes/Math.cs
+++ b/Source/Editor/Surface/Archetypes/Math.cs
@@ -213,7 +213,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 30,
Title = "Vector Transform",
Description = "Transform vector from source space to destination space",
- Flags = NodeFlags.MaterialGraph,
+ Flags = NodeFlags.MaterialGraph | NodeFlags.FixedSize,
Size = new Float2(170, 40),
DefaultValues = new object[]
{
diff --git a/Source/Editor/Surface/Archetypes/Parameters.cs b/Source/Editor/Surface/Archetypes/Parameters.cs
index 905f793c9..61d3b3617 100644
--- a/Source/Editor/Surface/Archetypes/Parameters.cs
+++ b/Source/Editor/Surface/Archetypes/Parameters.cs
@@ -13,6 +13,7 @@ using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEngine;
using FlaxEngine.Utilities;
+using FlaxEditor.Surface.Undo;
namespace FlaxEditor.Surface.Archetypes
{
@@ -92,7 +93,17 @@ namespace FlaxEditor.Surface.Archetypes
var selected = GetSelected();
var selectedID = selected?.ID ?? Guid.Empty;
if (selectedID != (Guid)Values[0])
+ {
+ if (Surface.Undo != null && Surface.Undo.Enabled)
+ {
+ // Capture node connections to support undo
+ var action = new EditNodeConnections(Context, this);
+ RemoveConnections();
+ action.End();
+ Surface.AddBatchedUndoAction(action);
+ }
Set(selected, ref selectedID);
+ }
}
///
diff --git a/Source/Editor/Surface/Archetypes/ParticleModules.cs b/Source/Editor/Surface/Archetypes/ParticleModules.cs
index 4a68c31e0..e85c87898 100644
--- a/Source/Editor/Surface/Archetypes/ParticleModules.cs
+++ b/Source/Editor/Surface/Archetypes/ParticleModules.cs
@@ -121,7 +121,7 @@ namespace FlaxEditor.Surface.Archetypes
Render2D.DrawRectangle(new Rectangle(1, 0, Width - 2, Height - 1), Colors[idx]);
// Close button
- Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
+ DrawCloseButton(_closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
// Arrange button
var dragBarColor = _arrangeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey;
@@ -138,6 +138,12 @@ namespace FlaxEditor.Surface.Archetypes
}
}
+ ///
+ public override void Resize(float width, float height)
+ {
+ // Do nothing so module does not change size
+ }
+
private bool ArrangeAreaCheck(out int index, out Rectangle rect)
{
var barSidesExtend = 20.0f;
@@ -261,9 +267,9 @@ namespace FlaxEditor.Surface.Archetypes
const float closeButtonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
const float closeButtonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
_headerRect = new Rectangle(0, 0, Width, headerSize);
- _closeButtonRect = new Rectangle(Width - closeButtonSize - closeButtonMargin, closeButtonMargin, closeButtonSize, closeButtonSize);
+ _closeButtonRect = new Rectangle(Width - closeButtonSize * 0.75f - closeButtonMargin, closeButtonMargin + 0.25f, closeButtonSize * 0.75f, closeButtonSize * 0.75f);
_footerRect = Rectangle.Empty;
- _enabled.Location = new Float2(_closeButtonRect.X - _enabled.Width - 2, _closeButtonRect.Y);
+ _enabled.Location = new Float2(_closeButtonRect.X - _enabled.Width - 2, _closeButtonRect.Y - 0.25f);
_arrangeButtonRect = new Rectangle(_enabled.X - closeButtonSize - closeButtonMargin, closeButtonMargin, closeButtonSize, closeButtonSize);
}
@@ -461,7 +467,7 @@ namespace FlaxEditor.Surface.Archetypes
///
/// The particle module node elements offset applied to controls to reduce default surface node header thickness.
///
- private const float NodeElementsOffset = 16.0f - Surface.Constants.NodeHeaderSize;
+ private const float NodeElementsOffset = 16.0f - Surface.Constants.NodeHeaderHeight;
private const NodeFlags DefaultModuleFlags = NodeFlags.ParticleEmitterGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoMove;
diff --git a/Source/Editor/Surface/Archetypes/Particles.cs b/Source/Editor/Surface/Archetypes/Particles.cs
index 59af7af5e..e0d7f2eb3 100644
--- a/Source/Editor/Surface/Archetypes/Particles.cs
+++ b/Source/Editor/Surface/Archetypes/Particles.cs
@@ -74,7 +74,7 @@ namespace FlaxEditor.Surface.Archetypes
///
/// The header height.
///
- public const float HeaderHeight = FlaxEditor.Surface.Constants.NodeHeaderSize;
+ public const float HeaderHeight = FlaxEditor.Surface.Constants.NodeHeaderHeight;
///
/// Gets the type of the module.
@@ -199,7 +199,7 @@ namespace FlaxEditor.Surface.Archetypes
DrawChildren();
// Options border
- var optionsAreaStart = FlaxEditor.Surface.Constants.NodeHeaderSize + 3.0f;
+ var optionsAreaStart = FlaxEditor.Surface.Constants.NodeHeaderHeight + 3.0f;
var optionsAreaHeight = 7 * FlaxEditor.Surface.Constants.LayoutOffsetY + 6.0f;
Render2D.DrawRectangle(new Rectangle(1, optionsAreaStart, Width - 2, optionsAreaHeight), style.BackgroundSelected);
@@ -340,7 +340,7 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new ParticleEmitterNode(id, context, arch, groupArch),
Title = "Particle Emitter",
Description = "Main particle emitter node. Contains a set of modules per emitter context. Modules are executed in order from top to bottom of the stack.",
- Flags = NodeFlags.ParticleEmitterGraph | NodeFlags.NoRemove | NodeFlags.NoSpawnViaGUI | NodeFlags.NoSpawnViaPaste | NodeFlags.NoCloseButton,
+ Flags = NodeFlags.ParticleEmitterGraph | NodeFlags.NoRemove | NodeFlags.NoSpawnViaGUI | NodeFlags.NoSpawnViaPaste | NodeFlags.NoCloseButton | NodeFlags.FixedSize,
Size = new Float2(300, 600),
DefaultValues = new object[]
{
diff --git a/Source/Editor/Surface/Archetypes/Textures.cs b/Source/Editor/Surface/Archetypes/Textures.cs
index f09ee5015..929cb153b 100644
--- a/Source/Editor/Surface/Archetypes/Textures.cs
+++ b/Source/Editor/Surface/Archetypes/Textures.cs
@@ -57,7 +57,7 @@ namespace FlaxEditor.Surface.Archetypes
{
_textureGroupPicker = new ComboBox
{
- Location = new Float2(FlaxEditor.Surface.Constants.NodeMarginX + 50, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.LayoutOffsetY * _level),
+ Location = new Float2(FlaxEditor.Surface.Constants.NodeMarginX + 50, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight + FlaxEditor.Surface.Constants.LayoutOffsetY * _level),
Width = 100,
Parent = this,
};
@@ -133,8 +133,8 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Texture",
Create = (id, context, arch, groupArch) => new Constants.ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Texture))),
Description = "Two dimensional texture object",
- Flags = NodeFlags.MaterialGraph,
- Size = new Float2(140, 120),
+ Flags = NodeFlags.MaterialGraph | NodeFlags.FixedSize,
+ Size = new Float2(140, 140),
DefaultValues = new object[]
{
Guid.Empty
@@ -158,7 +158,7 @@ namespace FlaxEditor.Surface.Archetypes
AlternativeTitles = new string[] { "UV", "UVs" },
Description = "Texture coordinates",
Flags = NodeFlags.MaterialGraph,
- Size = new Float2(150, 30),
+ Size = new Float2(160, 20),
DefaultValues = new object[]
{
0u
@@ -176,8 +176,8 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Cube Texture",
Create = (id, context, arch, groupArch) => new Constants.ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(CubeTexture))),
Description = "Set of 6 textures arranged in a cube",
- Flags = NodeFlags.MaterialGraph,
- Size = new Float2(140, 120),
+ Flags = NodeFlags.MaterialGraph | NodeFlags.FixedSize,
+ Size = new Float2(140, 140),
DefaultValues = new object[]
{
Guid.Empty
@@ -239,7 +239,7 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(3, "Max Steps", true, typeof(float), 3, 2),
NodeElementArchetype.Factory.Input(4, "Heightmap Texture", true, typeof(FlaxEngine.Object), 4),
NodeElementArchetype.Factory.Output(0, "Parallax UVs", typeof(Float2), 5),
- NodeElementArchetype.Factory.Text(Surface.Constants.BoxSize + 4, 5 * Surface.Constants.LayoutOffsetY, "Channel"),
+ NodeElementArchetype.Factory.Text(Surface.Constants.BoxRowHeight + 4, 5 * Surface.Constants.LayoutOffsetY, "Channel"),
NodeElementArchetype.Factory.ComboBox(70, 5 * Surface.Constants.LayoutOffsetY, 50, 3, new[]
{
"R",
@@ -493,7 +493,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 18,
Title = "Lightmap UV",
- AlternativeTitles = new string[] { "Lightmap TexCoord" },
+ AlternativeTitles = new string[] { "Lightmap TexCoord" },
Description = "Lightmap UVs",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(110, 20),
diff --git a/Source/Editor/Surface/Archetypes/Tools.cs b/Source/Editor/Surface/Archetypes/Tools.cs
index 60ecfaba6..24a0730df 100644
--- a/Source/Editor/Surface/Archetypes/Tools.cs
+++ b/Source/Editor/Surface/Archetypes/Tools.cs
@@ -467,7 +467,7 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new CurveNode(id, context, arch, groupArch),
Description = "An animation spline represented by a set of keyframes, each representing an endpoint of a Bezier curve.",
Flags = NodeFlags.AllGraphs,
- Size = new Float2(400, 180),
+ Size = new Float2(400, 180.0f),
DefaultValues = new object[]
{
// Keyframes count
@@ -485,7 +485,7 @@ namespace FlaxEditor.Surface.Archetypes
zero, // Tangent In
zero, // Tangent Out
- // Empty keys zero-6
+ // Empty keys 0-6
0.0f, zero, zero, zero,
0.0f, zero, zero, zero,
0.0f, zero, zero, zero,
@@ -515,13 +515,12 @@ namespace FlaxEditor.Surface.Archetypes
base.OnLoaded(action);
// Create curve editor
- var upperLeft = GetBox(0).BottomLeft;
- var upperRight = GetBox(1).BottomRight;
- float curveMargin = 20.0f;
+ var upperLeft = GetBox(0).BottomRight;
+ var upperRight = GetBox(1).BottomLeft;
_curve = new BezierCurveEditor
{
MaxKeyframes = 7,
- Bounds = new Rectangle(upperLeft + new Float2(curveMargin, 10.0f), upperRight.X - upperLeft.X - curveMargin * 2.0f, 140.0f),
+ Bounds = new Rectangle(upperLeft + new Float2(0f, 10.0f), upperRight.X - upperLeft.X - 8.0f, 135.0f),
Parent = this,
AnchorMax = Float2.One,
};
@@ -841,7 +840,7 @@ namespace FlaxEditor.Surface.Archetypes
_picker = new TypePickerControl
{
Type = ScriptType.FlaxObject,
- Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX + 20, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize, 160, 16),
+ Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX + 20, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight, 160, 16),
Parent = this,
};
_picker.ValueChanged += () => SetValue(0, _picker.ValueTypeName);
@@ -910,7 +909,7 @@ namespace FlaxEditor.Surface.Archetypes
_picker = new TypePickerControl
{
Type = ScriptType.Object,
- Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize, 140, 16),
+ Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight, 140, 16),
Parent = this,
};
_picker.ValueChanged += () => SetValue(0, _picker.ValueTypeName);
@@ -961,7 +960,7 @@ namespace FlaxEditor.Surface.Archetypes
_picker = new TypePickerControl
{
Type = ScriptType.FlaxObject,
- Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX + 20, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize, 160, 16),
+ Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX + 20, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight, 160, 16),
Parent = this,
};
_picker.ValueChanged += () => SetValue(0, _picker.ValueTypeName);
@@ -1012,7 +1011,7 @@ namespace FlaxEditor.Surface.Archetypes
_picker = new TypePickerControl
{
Type = type,
- Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX + 20, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize, 160, 16),
+ Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX + 20, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderHeight, 160, 16),
Parent = this,
};
_picker.ValueChanged += () => SetValue(0, _picker.ValueTypeName);
@@ -1071,7 +1070,11 @@ namespace FlaxEditor.Surface.Archetypes
internal class RerouteNode : SurfaceNode, IConnectionInstigator
{
- internal static readonly Float2 DefaultSize = new Float2(FlaxEditor.Surface.Constants.BoxSize);
+ internal static readonly Float2 DefaultSize = new Float2(FlaxEditor.Surface.Constants.BoxRowHeight);
+
+ internal bool DrawDisabled => _input.AllConnectionsDisabled || _output.AllConnectionsDisabled;
+ internal override bool DrawBasicShadow => false;
+
private Rectangle _localBounds;
private InputBox _input;
private OutputBox _output;
@@ -1174,9 +1177,18 @@ namespace FlaxEditor.Surface.Archetypes
_footerRect = Rectangle.Empty;
}
+ ///
+ public override void Resize(float width, float height)
+ {
+ // Do nothing so the input and output boxes do not change position
+ }
+
///
public override void Draw()
{
+ // Update active state of input
+ _input.IsActive = !_output.AllConnectionsDisabled;
+
var style = Surface.Style;
var connectionColor = style.Colors.Default;
var type = ScriptType.Null;
@@ -1190,6 +1202,10 @@ namespace FlaxEditor.Surface.Archetypes
Surface.Style.GetConnectionColor(type, hints, out connectionColor);
}
+ // Draw the box as disabled if needed
+ if (DrawDisabled)
+ connectionColor = connectionColor * 0.6f;
+
if (!_input.HasAnyConnection)
Render2D.FillRectangle(new Rectangle(-barHorizontalOffset - barHeight * 2, (DefaultSize.Y - barHeight) / 2, barHeight * 2, barHeight), connectionColor);
if (!_output.HasAnyConnection)
@@ -1200,6 +1216,11 @@ namespace FlaxEditor.Surface.Archetypes
icon = type.IsVoid ? style.Icons.ArrowClose : style.Icons.BoxClose;
else
icon = type.IsVoid ? style.Icons.ArrowOpen : style.Icons.BoxOpen;
+
+ // Shadow
+ var shadowRect = _localBounds.MakeOffsetted(ShadowOffset);
+ Render2D.DrawSprite(icon, shadowRect, Color.Black.AlphaMultiplied(0.125f));
+
Render2D.DrawSprite(icon, _localBounds, connectionColor);
base.Draw();
@@ -1444,8 +1465,8 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 6,
Title = "Panner",
Description = "Animates UVs over time",
- Flags = NodeFlags.MaterialGraph,
- Size = new Float2(170, 80),
+ Flags = NodeFlags.MaterialGraph | NodeFlags.FixedSize,
+ Size = new Float2(170, 96),
DefaultValues = new object[]
{
false
@@ -1455,7 +1476,7 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
NodeElementArchetype.Factory.Input(1, "Time", true, typeof(float), 1),
NodeElementArchetype.Factory.Input(2, "Speed", true, typeof(Float2), 2),
- NodeElementArchetype.Factory.Text(18, Surface.Constants.LayoutOffsetY * 3 + 5, "Fractional Part"),
+ NodeElementArchetype.Factory.Text(20, Surface.Constants.LayoutOffsetY * 3 + 5, "Fractional Part"),
NodeElementArchetype.Factory.Bool(0, Surface.Constants.LayoutOffsetY * 3 + 5, 0),
NodeElementArchetype.Factory.Output(0, "", typeof(Float2), 3)
}
@@ -1505,7 +1526,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Color Gradient",
Create = (id, context, arch, groupArch) => new ColorGradientNode(id, context, arch, groupArch),
Description = "Linear color gradient sampler",
- Flags = NodeFlags.AllGraphs,
+ Flags = NodeFlags.AllGraphs | NodeFlags.FixedSize,
Size = new Float2(400, 150.0f),
DefaultValues = new object[]
{
@@ -1825,7 +1846,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Reroute",
Create = (id, context, arch, groupArch) => new RerouteNode(id, context, arch, groupArch),
Description = "Reroute a connection.",
- Flags = NodeFlags.NoCloseButton | NodeFlags.NoSpawnViaGUI | NodeFlags.AllGraphs,
+ Flags = NodeFlags.NoCloseButton | NodeFlags.NoSpawnViaGUI | NodeFlags.AllGraphs | NodeFlags.FixedSize,
Size = RerouteNode.DefaultSize,
ConnectionsHints = ConnectionsHint.All,
IndependentBoxes = new int[] { 0 },
diff --git a/Source/Editor/Surface/Constants.cs b/Source/Editor/Surface/Constants.cs
index 0f3dca783..eba53cc69 100644
--- a/Source/Editor/Surface/Constants.cs
+++ b/Source/Editor/Surface/Constants.cs
@@ -13,46 +13,61 @@ namespace FlaxEditor.Surface
///
/// The node close button size.
///
- public const float NodeCloseButtonSize = 12.0f;
+ public const float NodeCloseButtonSize = 10.0f;
///
/// The node close button margin from the edges.
///
- public const float NodeCloseButtonMargin = 2.0f;
+ public const float NodeCloseButtonMargin = 5.0f;
///
/// The node header height.
///
- public const float NodeHeaderSize = 28.0f;
+ public const float NodeHeaderHeight = 25.0f;
+
+ ///
+ /// The scale of the header text.
+ ///
+ public const float NodeHeaderTextScale = 0.8f;
///
/// The node footer height.
///
- public const float NodeFooterSize = 4.0f;
+ public const float NodeFooterSize = 2.0f;
///
- /// The node left margin.
+ /// The horizontal node margin.
///
- public const float NodeMarginX = 5.0f;
+ public const float NodeMarginX = 6.0f;
///
- /// The node right margin.
+ /// The vertical node right margin.
///
- public const float NodeMarginY = 5.0f;
+ public const float NodeMarginY = 8.0f;
///
- /// The box position offset on the x axis.
+ /// The size of the row that is started by a box.
///
- public const float BoxOffsetX = 2.0f;
+ public const float BoxRowHeight = 19.0f;
///
/// The box size (with and height).
///
- public const float BoxSize = 20.0f;
+ public const float BoxSize = 15.0f;
///
/// The node layout offset on the y axis (height of the boxes rows, etc.). It's used to make the design more consistent.
///
- public const float LayoutOffsetY = 20.0f;
+ public const float LayoutOffsetY = 24.0f;
+
+ ///
+ /// The offset between the box text and the box
+ ///
+ public const float BoxTextOffset = 1.65f;
+
+ ///
+ /// The width of the rectangle used to draw the box text.
+ ///
+ public const float BoxTextRectWidth = 500.0f;
}
}
diff --git a/Source/Editor/Surface/Elements/BoolValue.cs b/Source/Editor/Surface/Elements/BoolValue.cs
index 70ded4dec..457e25f26 100644
--- a/Source/Editor/Surface/Elements/BoolValue.cs
+++ b/Source/Editor/Surface/Elements/BoolValue.cs
@@ -33,7 +33,7 @@ namespace FlaxEditor.Surface.Elements
///
public BoolValue(SurfaceNode parentNode, NodeElementArchetype archetype)
- : base(parentNode, archetype, archetype.ActualPosition, new Float2(16), true)
+ : base(parentNode, archetype, archetype.ActualPosition, new Float2(Constants.BoxRowHeight), true)
{
}
diff --git a/Source/Editor/Surface/Elements/Box.cs b/Source/Editor/Surface/Elements/Box.cs
index 964b3cc69..a110192cf 100644
--- a/Source/Editor/Surface/Elements/Box.cs
+++ b/Source/Editor/Surface/Elements/Box.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Undo;
using FlaxEngine;
@@ -194,9 +195,19 @@ namespace FlaxEditor.Surface.Elements
set => _isActive = value;
}
+ ///
+ /// Gets if the box is disabled (user can still connect, but connections will be ignored).
+ ///
+ public bool IsDisabled => !(Enabled && IsActive);
+
+ ///
+ /// Gets a value indicating whether all connections are disabled.
+ ///
+ public bool AllConnectionsDisabled => Connections.All(c => c.IsDisabled);
+
///
protected Box(SurfaceNode parentNode, NodeElementArchetype archetype, Float2 location)
- : base(parentNode, archetype, location, new Float2(Constants.BoxSize), false)
+ : base(parentNode, archetype, location, new Float2(Constants.BoxRowHeight), false)
{
_currentType = DefaultType;
_isSingle = Archetype.Single;
@@ -352,10 +363,10 @@ namespace FlaxEditor.Surface.Elements
Assert.AreEqual(r1, r2);
// Update
- ConnectionTick();
- box.ConnectionTick();
OnConnectionsChanged();
box.OnConnectionsChanged();
+ ConnectionTick();
+ box.ConnectionTick();
Surface?.OnNodesDisconnected(this, box);
}
@@ -379,10 +390,10 @@ namespace FlaxEditor.Surface.Elements
Assert.IsTrue(AreConnected(box));
// Update
- ConnectionTick();
- box.ConnectionTick();
OnConnectionsChanged();
box.OnConnectionsChanged();
+ ConnectionTick();
+ box.ConnectionTick();
Surface?.OnNodesConnected(this, box);
}
diff --git a/Source/Editor/Surface/Elements/ColorValue.cs b/Source/Editor/Surface/Elements/ColorValue.cs
index 671eb74f8..45af92273 100644
--- a/Source/Editor/Surface/Elements/ColorValue.cs
+++ b/Source/Editor/Surface/Elements/ColorValue.cs
@@ -28,6 +28,7 @@ namespace FlaxEditor.Surface.Elements
public ColorValue(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(Get(parentNode, archetype), archetype.Position.X, archetype.Position.Y)
{
+ Height = Constants.BoxRowHeight;
ParentNode = parentNode;
Archetype = archetype;
UseDynamicEditing = false;
diff --git a/Source/Editor/Surface/Elements/ComboBoxElement.cs b/Source/Editor/Surface/Elements/ComboBoxElement.cs
index cb1ed79a5..0f06274a4 100644
--- a/Source/Editor/Surface/Elements/ComboBoxElement.cs
+++ b/Source/Editor/Surface/Elements/ComboBoxElement.cs
@@ -33,6 +33,7 @@ namespace FlaxEditor.Surface.Elements
public ComboBoxElement(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(archetype.ActualPositionX, archetype.ActualPositionY, archetype.Size.X)
{
+ Height = Constants.BoxRowHeight;
ParentNode = parentNode;
Archetype = archetype;
diff --git a/Source/Editor/Surface/Elements/EnumValue.cs b/Source/Editor/Surface/Elements/EnumValue.cs
index 9aceaeff4..1fe87fef0 100644
--- a/Source/Editor/Surface/Elements/EnumValue.cs
+++ b/Source/Editor/Surface/Elements/EnumValue.cs
@@ -2,6 +2,7 @@
using System;
using FlaxEditor.GUI;
+using FlaxEngine;
using FlaxEngine.Utilities;
namespace FlaxEditor.Surface.Elements
@@ -29,9 +30,7 @@ namespace FlaxEditor.Surface.Elements
public EnumValue(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(TypeUtils.GetType(archetype.Text).Type)
{
- X = archetype.ActualPositionX;
- Y = archetype.ActualPositionY;
- Width = archetype.Size.X;
+ Bounds = new Rectangle(archetype.ActualPositionX, archetype.ActualPositionY, archetype.Size.X, Constants.BoxRowHeight);
ParentNode = parentNode;
Archetype = archetype;
Value = Convert.ToInt32(ParentNode.Values[Archetype.ValueIndex]);
diff --git a/Source/Editor/Surface/Elements/FloatValue.cs b/Source/Editor/Surface/Elements/FloatValue.cs
index a90ebb8b4..393145169 100644
--- a/Source/Editor/Surface/Elements/FloatValue.cs
+++ b/Source/Editor/Surface/Elements/FloatValue.cs
@@ -29,6 +29,7 @@ namespace FlaxEditor.Surface.Elements
public FloatValue(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(Get(parentNode, archetype), archetype.Position.X, archetype.Position.Y, 50, archetype.ValueMin, archetype.ValueMax, 0.01f)
{
+ Height = Constants.BoxRowHeight;
ParentNode = parentNode;
Archetype = archetype;
diff --git a/Source/Editor/Surface/Elements/InputBox.cs b/Source/Editor/Surface/Elements/InputBox.cs
index 9861ebacd..15548b9d9 100644
--- a/Source/Editor/Surface/Elements/InputBox.cs
+++ b/Source/Editor/Surface/Elements/InputBox.cs
@@ -120,7 +120,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = IntegerValue.Get(box.ParentNode, box.Archetype, box.Value);
- var width = 40;
+ var width = 50;
var control = new IntValueBox(value, bounds.X, bounds.Y, width + 12, int.MinValue, int.MaxValue, 0.01f)
{
Height = bounds.Height,
@@ -166,7 +166,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = UnsignedIntegerValue.Get(box.ParentNode, box.Archetype, box.Value);
- var width = 40;
+ var width = 50;
var control = new UIntValueBox(value, bounds.X, bounds.Y, width + 12, uint.MinValue, uint.MaxValue, 0.01f)
{
Height = bounds.Height,
@@ -212,7 +212,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = FloatValue.Get(box.ParentNode, box.Archetype, box.Value);
- var width = 40;
+ var width = 50;
var control = new FloatValueBox(value, bounds.X, bounds.Y, width + 12, float.MinValue, float.MaxValue, 0.01f)
{
Height = bounds.Height,
@@ -303,7 +303,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var width = 30;
+ var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 2 - 2, bounds.Height)
{
ClipChildren = false,
@@ -377,7 +377,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var width = 30;
+ var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 3 - 2, bounds.Height)
{
ClipChildren = false,
@@ -460,7 +460,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var width = 20;
+ var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 4 - 2, bounds.Height)
{
ClipChildren = false,
@@ -553,7 +553,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var width = 30;
+ var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 2 - 2, bounds.Height)
{
ClipChildren = false,
@@ -627,7 +627,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var width = 30;
+ var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 3 - 2, bounds.Height)
{
ClipChildren = false,
@@ -710,7 +710,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var width = 20;
+ var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 4 - 2, bounds.Height)
{
ClipChildren = false,
@@ -803,7 +803,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var width = 30;
+ var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 2 - 2, bounds.Height)
{
ClipChildren = false,
@@ -877,7 +877,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var width = 30;
+ var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 3 - 2, bounds.Height)
{
ClipChildren = false,
@@ -960,7 +960,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var width = 20;
+ var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 4 - 2, bounds.Height)
{
ClipChildren = false,
@@ -1053,7 +1053,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box).EulerAngles;
- var width = 20;
+ var width = 50;
var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 3 - 2, bounds.Height)
{
ClipChildren = false,
@@ -1442,8 +1442,8 @@ namespace FlaxEditor.Surface.Elements
// Draw text
var style = Style.Current;
- var rect = new Rectangle(Width + 4, 0, 1410, Height);
- Render2D.DrawText(style.FontSmall, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center);
+ var rect = new Rectangle(Width + Constants.BoxTextOffset, 0, Constants.BoxTextRectWidth, Height);
+ Render2D.DrawText(style.FontMedium, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center);
}
///
diff --git a/Source/Editor/Surface/Elements/IntegerValue.cs b/Source/Editor/Surface/Elements/IntegerValue.cs
index dde284211..77f4297ca 100644
--- a/Source/Editor/Surface/Elements/IntegerValue.cs
+++ b/Source/Editor/Surface/Elements/IntegerValue.cs
@@ -32,6 +32,7 @@ namespace FlaxEditor.Surface.Elements
public IntegerValue(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(Get(parentNode, archetype), archetype.Position.X, archetype.Position.Y, 50, (int)archetype.ValueMin, (int)archetype.ValueMax, 0.05f)
{
+ Height = Constants.BoxRowHeight;
ParentNode = parentNode;
Archetype = archetype;
diff --git a/Source/Editor/Surface/Elements/OutputBox.cs b/Source/Editor/Surface/Elements/OutputBox.cs
index 8836dc0dc..e1ec56a36 100644
--- a/Source/Editor/Surface/Elements/OutputBox.cs
+++ b/Source/Editor/Surface/Elements/OutputBox.cs
@@ -186,7 +186,7 @@ namespace FlaxEditor.Surface.Elements
Box targetBox = Connections[i];
var endPos = targetBox.ConnectionOrigin;
var highlight = DefaultConnectionThickness + Mathf.Max(startHighlight, targetBox.ConnectionsHighlightIntensity);
- var alpha = targetBox.Enabled && targetBox.IsActive ? 1.0f : 0.6f;
+ var alpha = targetBox.IsDisabled ? 0.6f : 1.0f;
// We have to calculate an offset here to preserve the original color for when the default connection thickness is larger than 1
var highlightOffset = (highlight - (DefaultConnectionThickness - 1));
@@ -212,7 +212,7 @@ namespace FlaxEditor.Surface.Elements
// Draw all the connections
var startPos = ConnectionOrigin;
var endPos = targetBox.ConnectionOrigin;
- var alpha = targetBox.Enabled && targetBox.IsActive ? 1.0f : 0.6f;
+ var alpha = targetBox.IsDisabled ? 0.6f : 1.0f;
var color = _currentTypeColor * alpha;
DrawConnection(Surface.Style, ref startPos, ref endPos, ref color, SelectedConnectionThickness);
}
@@ -230,8 +230,8 @@ namespace FlaxEditor.Surface.Elements
// Draw text
var style = Style.Current;
- var rect = new Rectangle(-100, 0, 100 - 2, Height);
- Render2D.DrawText(style.FontSmall, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Far, TextAlignment.Center);
+ var rect = new Rectangle(-Constants.BoxTextRectWidth - Constants.BoxTextOffset * 2f, 0f, Constants.BoxTextRectWidth, Height);
+ Render2D.DrawText(style.FontMedium, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Far, TextAlignment.Center);
}
}
}
diff --git a/Source/Editor/Surface/Elements/UnsignedIntegerValue.cs b/Source/Editor/Surface/Elements/UnsignedIntegerValue.cs
index b05a27286..621629819 100644
--- a/Source/Editor/Surface/Elements/UnsignedIntegerValue.cs
+++ b/Source/Editor/Surface/Elements/UnsignedIntegerValue.cs
@@ -24,6 +24,7 @@ namespace FlaxEditor.Surface.Elements
public UnsignedIntegerValue(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(Get(parentNode, archetype), archetype.Position.X, archetype.Position.Y, 50, (uint)archetype.ValueMin, (uint)archetype.ValueMax, 0.05f)
{
+ Height = Constants.BoxRowHeight;
ParentNode = parentNode;
Archetype = archetype;
diff --git a/Source/Editor/Surface/NodeElementArchetype.cs b/Source/Editor/Surface/NodeElementArchetype.cs
index f5a23d411..6db191347 100644
--- a/Source/Editor/Surface/NodeElementArchetype.cs
+++ b/Source/Editor/Surface/NodeElementArchetype.cs
@@ -3,8 +3,9 @@
using System;
using System.Collections.Generic;
using System.Reflection;
-using FlaxEditor.CustomEditors;
+using FlaxEditor.GUI.Input;
using FlaxEditor.Scripting;
+using FlaxEditor.Surface.Elements;
using FlaxEngine;
namespace FlaxEditor.Surface
@@ -78,12 +79,12 @@ namespace FlaxEditor.Surface
///
/// Gets the actual element position on the y axis.
///
- public float ActualPositionY => Position.Y + Constants.NodeMarginY + Constants.NodeHeaderSize;
+ public float ActualPositionY => Position.Y + Constants.NodeMarginY + Constants.NodeHeaderHeight;
///
/// Gets the actual element position.
///
- public Float2 ActualPosition => new Float2(Position.X + Constants.NodeMarginX, Position.Y + Constants.NodeMarginY + Constants.NodeHeaderSize);
+ public Float2 ActualPosition => new Float2(Position.X + Constants.NodeMarginX, Position.Y + Constants.NodeMarginY + Constants.NodeHeaderHeight);
///
/// Node element archetypes factory object. Helps to build surface nodes archetypes.
@@ -106,8 +107,8 @@ namespace FlaxEditor.Surface
{
Type = NodeElementType.Input,
Position = new Float2(
- Constants.NodeMarginX - Constants.BoxOffsetX,
- Constants.NodeMarginY + Constants.NodeHeaderSize + yLevel * Constants.LayoutOffsetY),
+ Constants.NodeMarginX,
+ Constants.NodeMarginY + Constants.NodeHeaderHeight + yLevel * Constants.LayoutOffsetY),
Text = text,
Single = single,
ValueIndex = valueIndex,
@@ -132,8 +133,8 @@ namespace FlaxEditor.Surface
{
Type = NodeElementType.Input,
Position = new Float2(
- Constants.NodeMarginX - Constants.BoxOffsetX,
- Constants.NodeMarginY + Constants.NodeHeaderSize + yLevel * Constants.LayoutOffsetY),
+ Constants.NodeMarginX,
+ Constants.NodeMarginY + Constants.NodeHeaderHeight + yLevel * Constants.LayoutOffsetY),
Text = text,
Single = single,
ValueIndex = valueIndex,
@@ -157,8 +158,8 @@ namespace FlaxEditor.Surface
{
Type = NodeElementType.Output,
Position = new Float2(
- Constants.NodeMarginX - Constants.BoxSize + Constants.BoxOffsetX,
- Constants.NodeMarginY + Constants.NodeHeaderSize + yLevel * Constants.LayoutOffsetY),
+ -Constants.NodeMarginX,
+ Constants.NodeMarginY + Constants.NodeHeaderHeight + yLevel * Constants.LayoutOffsetY),
Text = text,
Single = single,
ValueIndex = -1,
@@ -182,8 +183,8 @@ namespace FlaxEditor.Surface
{
Type = NodeElementType.Output,
Position = new Float2(
- Constants.NodeMarginX - Constants.BoxSize + Constants.BoxOffsetX,
- Constants.NodeMarginY + Constants.NodeHeaderSize + yLevel * Constants.LayoutOffsetY),
+ -Constants.NodeMarginX,
+ Constants.NodeMarginY + Constants.NodeHeaderHeight + yLevel * Constants.LayoutOffsetY),
Text = text,
Single = single,
ValueIndex = -1,
@@ -205,6 +206,7 @@ namespace FlaxEditor.Surface
{
Type = NodeElementType.BoolValue,
Position = new Float2(x, y),
+ Size = new Float2(16f),
Text = null,
Single = false,
ValueIndex = valueIndex,
@@ -228,7 +230,8 @@ namespace FlaxEditor.Surface
return new NodeElementArchetype
{
Type = NodeElementType.IntegerValue,
- Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderSize + y),
+ Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderHeight + y),
+ Size = new Float2(50f, IntegerValue.DefaultHeight),
Text = null,
Single = false,
ValueIndex = valueIndex,
@@ -254,7 +257,8 @@ namespace FlaxEditor.Surface
return new NodeElementArchetype
{
Type = NodeElementType.UnsignedIntegerValue,
- Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderSize + y),
+ Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderHeight + y),
+ Size = new Float2(50f, UnsignedIntegerValue.DefaultHeight),
Text = null,
Single = false,
ValueIndex = valueIndex,
@@ -280,7 +284,8 @@ namespace FlaxEditor.Surface
return new NodeElementArchetype
{
Type = NodeElementType.FloatValue,
- Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderSize + y),
+ Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderHeight + y),
+ Size = new Float2(50f, FloatValueBox.DefaultHeight),
Text = null,
Single = false,
ValueIndex = valueIndex,
@@ -359,7 +364,8 @@ namespace FlaxEditor.Surface
return new NodeElementArchetype
{
Type = NodeElementType.ColorValue,
- Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderSize + y),
+ Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderHeight + y),
+ Size = new Float2(32, 18),
Text = null,
Single = false,
ValueIndex = valueIndex,
@@ -382,6 +388,7 @@ namespace FlaxEditor.Surface
{
Type = NodeElementType.Asset,
Position = new Float2(x, y),
+ Size = new Float2(78f, 90f),
Text = type.FullName,
Single = false,
ValueIndex = valueIndex,
@@ -499,7 +506,7 @@ namespace FlaxEditor.Surface
/// The control height.
/// The control tooltip text.
/// The archetype.
- public static NodeElementArchetype Text(float x, float y, string text, float width = 100.0f, float height = 16.0f, string tooltip = null)
+ public static NodeElementArchetype Text(float x, float y, string text, float width = 100.0f, float height = 19.0f, string tooltip = null)
{
return new NodeElementArchetype
{
@@ -530,7 +537,7 @@ namespace FlaxEditor.Surface
return new NodeElementArchetype
{
Type = NodeElementType.TextBox,
- Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderSize + y),
+ Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderHeight + y),
Size = new Float2(width, height),
Single = false,
ValueIndex = valueIndex,
@@ -595,7 +602,7 @@ namespace FlaxEditor.Surface
return new NodeElementArchetype
{
Type = NodeElementType.BoxValue,
- Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderSize + y),
+ Position = new Float2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderHeight + y),
Text = null,
Single = false,
ValueIndex = valueIndex,
diff --git a/Source/Editor/Surface/NodeFactory.cs b/Source/Editor/Surface/NodeFactory.cs
index 7c85cb449..f787899dc 100644
--- a/Source/Editor/Surface/NodeFactory.cs
+++ b/Source/Editor/Surface/NodeFactory.cs
@@ -60,84 +60,84 @@ namespace FlaxEditor.Surface
{
GroupID = 1,
Name = "Material",
- Color = new Color(231, 76, 60),
+ Color = new Color(181, 89, 49),
Archetypes = Archetypes.Material.Nodes
},
new GroupArchetype
{
GroupID = 2,
Name = "Constants",
- Color = new Color(243, 156, 18),
+ Color = new Color(163, 106, 21),
Archetypes = Archetypes.Constants.Nodes
},
new GroupArchetype
{
GroupID = 3,
Name = "Math",
- Color = new Color(52, 152, 219),
+ Color = new Color(45, 126, 181),
Archetypes = Archetypes.Math.Nodes
},
new GroupArchetype
{
GroupID = 4,
Name = "Packing",
- Color = new Color(155, 89, 182),
+ Color = new Color(124, 66, 143),
Archetypes = Archetypes.Packing.Nodes
},
new GroupArchetype
{
GroupID = 5,
Name = "Textures",
- Color = new Color(46, 204, 113),
+ Color = new Color(43, 130, 83),
Archetypes = Archetypes.Textures.Nodes
},
new GroupArchetype
{
GroupID = 6,
Name = "Parameters",
- Color = new Color(52, 73, 94),
+ Color = new Color(55, 78, 99),
Archetypes = Archetypes.Parameters.Nodes
},
new GroupArchetype
{
GroupID = 7,
Name = "Tools",
- Color = new Color(149, 165, 166),
+ Color = new Color(88, 96, 97),
Archetypes = Archetypes.Tools.Nodes
},
new GroupArchetype
{
GroupID = 8,
Name = "Layers",
- Color = new Color(249, 105, 116),
+ Color = new Color(189, 75, 81),
Archetypes = Archetypes.Layers.Nodes
},
new GroupArchetype
{
GroupID = 9,
Name = "Animations",
- Color = new Color(105, 179, 160),
+ Color = new Color(72, 125, 107),
Archetypes = Archetypes.Animation.Nodes
},
new GroupArchetype
{
GroupID = 10,
Name = "Boolean",
- Color = new Color(237, 28, 36),
+ Color = new Color(166, 27, 32),
Archetypes = Archetypes.Boolean.Nodes
},
new GroupArchetype
{
GroupID = 11,
Name = "Bitwise",
- Color = new Color(181, 230, 29),
+ Color = new Color(96, 125, 34),
Archetypes = Archetypes.Bitwise.Nodes
},
new GroupArchetype
{
GroupID = 12,
Name = "Comparisons",
- Color = new Color(148, 30, 34),
+ Color = new Color(166, 33, 57),
Archetypes = Archetypes.Comparisons.Nodes
},
// GroupID = 13 -> Custom Nodes provided externally
@@ -145,7 +145,7 @@ namespace FlaxEditor.Surface
{
GroupID = 14,
Name = "Particles",
- Color = new Color(121, 210, 176),
+ Color = new Color(72, 125, 107),
Archetypes = Archetypes.Particles.Nodes
},
new GroupArchetype
@@ -166,7 +166,7 @@ namespace FlaxEditor.Surface
{
GroupID = 17,
Name = "Flow",
- Color = new Color(237, 136, 64),
+ Color = new Color(181, 91, 33),
Archetypes = Archetypes.Flow.Nodes
},
new GroupArchetype
diff --git a/Source/Editor/Surface/NodeFlags.cs b/Source/Editor/Surface/NodeFlags.cs
index 27900bdb3..4201d9da1 100644
--- a/Source/Editor/Surface/NodeFlags.cs
+++ b/Source/Editor/Surface/NodeFlags.cs
@@ -78,6 +78,11 @@ namespace FlaxEditor.Surface
///
VariableValuesSize = 2048,
+ ///
+ /// Node has fixed size defined and should not use automatic layout.
+ ///
+ FixedSize = 4096,
+
///
/// Node can be used in the all visual graphs.
///
diff --git a/Source/Editor/Surface/ParticleEmitterSurface.cs b/Source/Editor/Surface/ParticleEmitterSurface.cs
index f332471fb..c61d4365d 100644
--- a/Source/Editor/Surface/ParticleEmitterSurface.cs
+++ b/Source/Editor/Surface/ParticleEmitterSurface.cs
@@ -59,7 +59,7 @@ namespace FlaxEditor.Surface
var width = _rootNode.Width;
var rootPos = _rootNode.Location;
var pos = rootPos;
- pos.Y += Constants.NodeHeaderSize + 1.0f + 7 * Constants.LayoutOffsetY + 6.0f + 4.0f;
+ pos.Y += Constants.NodeHeaderHeight + 1.0f + 7 * Constants.LayoutOffsetY + 6.0f + 4.0f;
for (int i = 0; i < _rootNode.Headers.Length; i++)
{
@@ -67,7 +67,7 @@ namespace FlaxEditor.Surface
var modulesStart = pos - rootPos;
var modules = modulesGroups.FirstOrDefault(x => x.Key == header.ModuleType);
- pos.Y += Constants.NodeHeaderSize + 2.0f;
+ pos.Y += Constants.NodeHeaderHeight + 2.0f;
if (modules != null)
{
foreach (var module in modules)
diff --git a/Source/Editor/Surface/ResizableSurfaceNode.cs b/Source/Editor/Surface/ResizableSurfaceNode.cs
index 259c29836..ee6560a84 100644
--- a/Source/Editor/Surface/ResizableSurfaceNode.cs
+++ b/Source/Editor/Surface/ResizableSurfaceNode.cs
@@ -74,6 +74,12 @@ namespace FlaxEditor.Surface
Resize(size.X, size.Y);
}
+ ///
+ public override void ResizeAuto()
+ {
+ // Do nothing, we want to put full control of node size into the users hands
+ }
+
///
public override void Draw()
{
diff --git a/Source/Editor/Surface/SurfaceComment.cs b/Source/Editor/Surface/SurfaceComment.cs
index a76fa245d..79a285bd8 100644
--- a/Source/Editor/Surface/SurfaceComment.cs
+++ b/Source/Editor/Surface/SurfaceComment.cs
@@ -56,10 +56,10 @@ namespace FlaxEditor.Surface
: base(id, context, nodeArch, groupArch)
{
_sizeValueIndex = 2; // Index of the Size stored in Values array
- _sizeMin = new Float2(140.0f, Constants.NodeHeaderSize);
+ _sizeMin = new Float2(140.0f, Constants.NodeHeaderHeight);
_renameTextBox = new TextBox(false, 0, 0, Width)
{
- Height = Constants.NodeHeaderSize,
+ Height = Constants.NodeHeaderHeight,
Visible = false,
Parent = this,
EndEditOnClick = false, // We have to handle this ourselves, otherwise the textbox instantly loses focus when double-clicking the header
@@ -124,11 +124,11 @@ namespace FlaxEditor.Surface
{
base.UpdateRectangles();
- const float headerSize = Constants.NodeHeaderSize;
+ const float headerSize = Constants.NodeHeaderHeight;
const float buttonMargin = Constants.NodeCloseButtonMargin;
const float buttonSize = Constants.NodeCloseButtonSize;
_headerRect = new Rectangle(0, 0, Width, headerSize);
- _closeButtonRect = new Rectangle(Width - buttonSize - buttonMargin, buttonMargin, buttonSize, buttonSize);
+ _closeButtonRect = new Rectangle(Width - buttonSize * 0.75f - buttonMargin, buttonMargin, buttonSize * 0.75f, buttonSize * 0.75f);
_colorButtonRect = new Rectangle(_closeButtonRect.Left - buttonSize - buttonMargin, buttonMargin, buttonSize, buttonSize);
_resizeButtonRect = new Rectangle(_closeButtonRect.Left, Height - buttonSize - buttonMargin, buttonSize, buttonSize);
_renameTextBox.Width = Width;
@@ -183,7 +183,7 @@ namespace FlaxEditor.Surface
if (Surface.CanEdit)
{
// Close button
- Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
+ DrawCloseButton(_closeButtonRect, _closeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
// Color button
Render2D.DrawSprite(style.Settings, _colorButtonRect, _colorButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs
index 6312bd68d..d28d04e1e 100644
--- a/Source/Editor/Surface/SurfaceNode.cs
+++ b/Source/Editor/Surface/SurfaceNode.cs
@@ -40,6 +40,13 @@ namespace FlaxEditor.Surface
[HideInEditor]
public class SurfaceNode : SurfaceControl
{
+ internal const float ShadowOffset = 2.25f;
+
+ ///
+ /// If true, draws a basic rectangle shadow behind the node. Disable to hide shadow or if the node is drawing a custom shadow.
+ ///
+ internal virtual bool DrawBasicShadow => true;
+
///
/// The box to draw a highlight around. Drawing will be skipped if null.
///
@@ -55,6 +62,11 @@ namespace FlaxEditor.Surface
///
protected Rectangle _headerRect;
+ ///
+ /// The header text rectangle (local space).
+ ///
+ protected Rectangle _headerTextRect;
+
///
/// The close button rectangle (local space).
///
@@ -123,7 +135,7 @@ namespace FlaxEditor.Surface
/// The node archetype.
/// The group archetype.
public SurfaceNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
- : base(context, nodeArch.Size.X + Constants.NodeMarginX * 2, nodeArch.Size.Y + Constants.NodeMarginY * 2 + Constants.NodeHeaderSize + Constants.NodeFooterSize)
+ : base(context, nodeArch.Size.X + Constants.NodeMarginX * 2, nodeArch.Size.Y + Constants.NodeMarginY * 2 + Constants.NodeHeaderHeight + Constants.NodeFooterSize)
{
Title = nodeArch.Title;
ID = id;
@@ -132,7 +144,7 @@ namespace FlaxEditor.Surface
AutoFocus = false;
TooltipText = GetTooltip();
CullChildren = false;
- BackgroundColor = Style.Current.BackgroundNormal;
+ BackgroundColor = Color.Lerp(Style.Current.Background, Style.Current.BackgroundHighlighted, 0.55f);
if (Archetype.DefaultValues != null)
{
@@ -147,9 +159,9 @@ namespace FlaxEditor.Surface
public virtual string ContentSearchText => null;
///
- /// Gets the color of the footer of the node.
+ /// Gets the color of the header of the node.
///
- protected virtual Color FooterColor => GroupArchetype.Color;
+ protected virtual Color ArchetypeColor => GroupArchetype.Color;
private Float2 mouseDownMousePosition;
@@ -161,7 +173,7 @@ namespace FlaxEditor.Surface
/// The node control total size.
protected virtual Float2 CalculateNodeSize(float width, float height)
{
- return new Float2(width + Constants.NodeMarginX * 2, height + Constants.NodeMarginY * 2 + Constants.NodeHeaderSize + Constants.NodeFooterSize);
+ return new Float2(width + Constants.NodeMarginX * 2, height + Constants.NodeMarginY * 2 + Constants.NodeHeaderHeight + Constants.NodeFooterSize);
}
///
@@ -169,7 +181,7 @@ namespace FlaxEditor.Surface
///
/// The width.
/// The height.
- public void Resize(float width, float height)
+ public virtual void Resize(float width, float height)
{
if (Surface == null)
return;
@@ -187,7 +199,7 @@ namespace FlaxEditor.Surface
{
if (Elements[i] is OutputBox box)
{
- box.Location = box.Archetype.Position + new Float2(width, 0);
+ box.Location = box.Archetype.Position + new Float2(width - Constants.NodeMarginX, 0);
}
}
@@ -215,30 +227,41 @@ namespace FlaxEditor.Surface
var child = Children[i];
if (!child.Visible)
continue;
+
+ // Input boxes
if (child is InputBox inputBox)
{
- var boxWidth = boxLabelFont.MeasureText(inputBox.Text).X + 20;
- if (inputBox.DefaultValueEditor != null)
+ var boxWidth = boxLabelFont.MeasureText(inputBox.Text).X + 25;
+ if (inputBox.DefaultValueEditor != null && inputBox.DefaultValueEditor.Visible)
boxWidth += inputBox.DefaultValueEditor.Width + 4;
leftWidth = Mathf.Max(leftWidth, boxWidth);
- leftHeight = Mathf.Max(leftHeight, inputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f);
+ leftHeight = Mathf.Max(leftHeight, inputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderHeight + Constants.BoxRowHeight);
}
+ // Output boxes
else if (child is OutputBox outputBox)
{
- rightWidth = Mathf.Max(rightWidth, boxLabelFont.MeasureText(outputBox.Text).X + 20);
- rightHeight = Mathf.Max(rightHeight, outputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f);
+ rightWidth = Mathf.Max(rightWidth, boxLabelFont.MeasureText(outputBox.Text).X + 25);
+ rightHeight = Mathf.Max(rightHeight, outputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderHeight + Constants.BoxRowHeight);
}
+ // Elements (Float-, int-, uint- value boxes, asset pickers, etc.)
+ // These will only ever be on the left side of the node, so we only adjust left width and height
+ else if (child is SurfaceNodeElementControl elementControl)
+ {
+ leftWidth = Mathf.Max(leftWidth, elementControl.Width + 8f);
+ leftHeight = Mathf.Max(leftHeight, elementControl.Height);
+ }
+ // Other controls in the node
else if (child is Control control)
{
if (control.AnchorPreset == AnchorPresets.TopLeft)
{
- width = Mathf.Max(width, control.Right + 4 - Constants.NodeMarginX);
- height = Mathf.Max(height, control.Bottom + 4 - Constants.NodeMarginY - Constants.NodeHeaderSize);
+ width = Mathf.Max(width, control.Right + 15 + Constants.NodeMarginX);
+ height = Mathf.Max(height, control.Bottom - Constants.NodeMarginY - Constants.NodeHeaderHeight);
}
else if (!_headerRect.Intersects(control.Bounds))
{
- width = Mathf.Max(width, control.Width + 4);
- height = Mathf.Max(height, control.Height + 4);
+ width = Mathf.Max(width, control.Width + 15 + Constants.NodeMarginX);
+ height = Mathf.Max(height, control.Height);
}
}
}
@@ -325,6 +348,9 @@ namespace FlaxEditor.Surface
Elements.Add(element);
if (element is Control control)
AddChild(control);
+
+ if (!IsLayoutLocked)
+ UpdateSize();
}
///
@@ -365,7 +391,7 @@ namespace FlaxEditor.Surface
// Sync properties for exiting box
box.Text = text;
box.CurrentType = type;
- box.Y = Constants.NodeMarginY + Constants.NodeHeaderSize + yLevel * Constants.LayoutOffsetY;
+ box.Y = Constants.NodeMarginY + Constants.NodeHeaderHeight + yLevel * Constants.LayoutOffsetY;
}
// Update box
@@ -434,7 +460,7 @@ namespace FlaxEditor.Surface
private static readonly List UpdateStack = new List();
///
- /// Updates dependant/independent boxes types.
+ /// Updates dependent/independent boxes types.
///
public void UpdateBoxesTypes()
{
@@ -776,6 +802,24 @@ namespace FlaxEditor.Surface
return output;
}
+ ///
+ /// Draws the close button inside of the .
+ ///
+ /// The rectangle to draw the close button in.
+ /// The color of the close button.
+ public void DrawCloseButton(Rectangle rect, Color color)
+ {
+ // Disable vertex snapping to reduce artefacts at the line ends
+ var features = Render2D.Features;
+ Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping;
+
+ rect.Expand(-2f); // Don't overshoot the rectangle because of the thickness
+ Render2D.DrawLine(rect.TopLeft, rect.BottomRight, color, 2f);
+ Render2D.DrawLine(rect.BottomLeft, rect.TopRight, color, 2f);
+
+ Render2D.Features = features;
+ }
+
///
/// Draws all the connections between surface objects related to this node.
///
@@ -867,6 +911,14 @@ namespace FlaxEditor.Surface
return sb.ToString();
}
+ private void UpdateSize()
+ {
+ if (Archetype.Flags.HasFlag(NodeFlags.FixedSize))
+ Resize(Archetype.Size.X, Archetype.Size.Y);
+ else
+ ResizeAuto();
+ }
+
///
protected override bool ShowTooltip => base.ShowTooltip && _headerRect.Contains(ref _mousePosition) && !Surface.IsLeftMouseButtonDown && !Surface.IsRightMouseButtonDown && !Surface.IsPrimaryMenuOpened;
@@ -919,6 +971,8 @@ namespace FlaxEditor.Surface
if (Elements[i] is Box box)
box.OnConnectionsChanged();
}
+
+ UpdateSize();
}
///
@@ -956,6 +1010,8 @@ namespace FlaxEditor.Surface
Surface.AddBatchedUndoAction(new EditNodeValuesAction(this, before, graphEdited));
_isDuringValuesEditing = false;
+
+ UpdateSize();
}
///
@@ -990,6 +1046,8 @@ namespace FlaxEditor.Surface
}
_isDuringValuesEditing = false;
+
+ UpdateSize();
}
internal void SetIsDuringValuesEditing(bool value)
@@ -998,7 +1056,7 @@ namespace FlaxEditor.Surface
}
///
- /// Sets teh node values from the given pasted source. Can be overriden to perform validation or custom values processing.
+ /// Sets teh node values from the given pasted source. Can be overridden to perform validation or custom values processing.
///
/// The input values array.
public virtual void SetValuesPaste(object[] values)
@@ -1022,16 +1080,18 @@ namespace FlaxEditor.Surface
public virtual void ConnectionTick(Box box)
{
UpdateBoxesTypes();
+ UpdateSize();
}
///
protected override void UpdateRectangles()
{
const float footerSize = Constants.NodeFooterSize;
- const float headerSize = Constants.NodeHeaderSize;
+ const float headerSize = Constants.NodeHeaderHeight;
const float closeButtonMargin = Constants.NodeCloseButtonMargin;
const float closeButtonSize = Constants.NodeCloseButtonSize;
_headerRect = new Rectangle(0, 0, Width, headerSize);
+ _headerTextRect = _headerRect with { X = 5f, Width = Width - closeButtonSize - closeButtonMargin * 4f };
_closeButtonRect = new Rectangle(Width - closeButtonSize - closeButtonMargin, closeButtonMargin, closeButtonSize, closeButtonSize);
_footerRect = new Rectangle(0, Height - footerSize, Width, footerSize);
}
@@ -1040,9 +1100,16 @@ namespace FlaxEditor.Surface
public override void Draw()
{
var style = Style.Current;
+ var backgroundRect = new Rectangle(Float2.Zero, Size);
+
+ // Shadow
+ if (DrawBasicShadow)
+ {
+ var shadowRect = backgroundRect.MakeOffsetted(ShadowOffset);
+ Render2D.FillRectangle(shadowRect, Color.Black.AlphaMultiplied(0.125f));
+ }
// Background
- var backgroundRect = new Rectangle(Float2.Zero, Size);
Render2D.FillRectangle(backgroundRect, BackgroundColor);
// Breakpoint hit
@@ -1058,18 +1125,18 @@ namespace FlaxEditor.Surface
var headerColor = style.BackgroundHighlighted;
if (_headerRect.Contains(ref _mousePosition) && !Surface.IsConnecting && !Surface.IsSelecting)
headerColor *= 1.07f;
- Render2D.FillRectangle(_headerRect, headerColor);
- Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center);
+ Render2D.FillRectangle(_headerRect, ArchetypeColor);
+ Render2D.DrawText(style.FontLarge, Title, _headerTextRect, style.Foreground, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1f, Constants.NodeHeaderTextScale);
// Close button
if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0 && Surface.CanEdit)
{
bool highlightClose = _closeButtonRect.Contains(_mousePosition) && !Surface.IsConnecting && !Surface.IsSelecting;
- Render2D.DrawSprite(style.Cross, _closeButtonRect, highlightClose ? style.Foreground : style.ForegroundGrey);
+ DrawCloseButton(_closeButtonRect, highlightClose ? style.Foreground : style.ForegroundGrey);
}
// Footer
- Render2D.FillRectangle(_footerRect, FooterColor);
+ Render2D.FillRectangle(_footerRect, ArchetypeColor);
DrawChildren();
@@ -1078,7 +1145,7 @@ namespace FlaxEditor.Surface
{
var colorTop = Color.Orange;
var colorBottom = Color.OrangeRed;
- Render2D.DrawRectangle(backgroundRect, colorTop, colorTop, colorBottom, colorBottom);
+ Render2D.DrawRectangle(backgroundRect, colorTop, colorTop, colorBottom, colorBottom, 2.5f);
}
// Breakpoint dot
diff --git a/Source/Editor/Surface/SurfaceStyle.cs b/Source/Editor/Surface/SurfaceStyle.cs
index be09f51be..2997e1d21 100644
--- a/Source/Editor/Surface/SurfaceStyle.cs
+++ b/Source/Editor/Surface/SurfaceStyle.cs
@@ -2,6 +2,7 @@
using System;
using FlaxEditor.Scripting;
+using FlaxEditor.Surface.Elements;
using FlaxEngine;
using FlaxEngine.Utilities;
@@ -140,6 +141,11 @@ namespace FlaxEditor.Surface
///
public Texture Background;
+ ///
+ /// The color used as a surface background.
+ ///
+ public Color BackgroundColor;
+
///
/// Boxes drawing callback.
///
@@ -216,19 +222,20 @@ namespace FlaxEditor.Surface
private static void DefaultDrawBox(Elements.Box box)
{
- var rect = new Rectangle(Float2.Zero, box.Size);
+ var rect = new Rectangle(box.Width * 0.5f - Constants.BoxSize * 0.5f, box.Height * 0.5f - Constants.BoxSize * 0.5f, new Float2(Constants.BoxSize));
// Size culling
const float minBoxSize = 5.0f;
if (rect.Size.LengthSquared < minBoxSize * minBoxSize)
return;
- // Debugging boxes size
+ // Debugging boxes size and bounds
//Render2D.DrawRectangle(rect, Color.Orange); return;
+ //Render2D.DrawRectangle(box.Bounds, Color.Green);
// Draw icon
bool hasConnections = box.HasAnyConnection;
- float alpha = box.Enabled && box.IsActive ? 1.0f : 0.6f;
+ float alpha = box.IsDisabled ? 0.6f : 1.0f;
Color color = box.CurrentTypeColor * alpha;
var style = box.Surface.Style;
SpriteHandle icon;
@@ -237,20 +244,42 @@ namespace FlaxEditor.Surface
else
icon = hasConnections ? style.Icons.BoxClose : style.Icons.BoxOpen;
color *= box.ConnectionsHighlightIntensity + 1;
+ if (box.IsMouseOver)
+ {
+ color *= 1.3f;
+ rect = rect.MakeExpanded(1.0f);
+ }
+
+ // Disable vertex snapping to prevent position jitter/snapping artefacts for the boxes when zooming the surface
+ var features = Render2D.Features;
+ Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping;
+
Render2D.DrawSprite(icon, rect, color);
+ // Draw connected hint with color from connected output box
+ if (hasConnections && box.Connections[0] is OutputBox connectedOutputBox)
+ {
+ bool connectedSameColor = connectedOutputBox.CurrentTypeColor == box.CurrentTypeColor;
+ Color innerColor = connectedSameColor ? color.RGBMultiplied(0.4f) : connectedOutputBox.CurrentTypeColor;
+ innerColor = innerColor * alpha;
+ Render2D.DrawSprite(icon, rect.MakeExpanded(-5.0f), innerColor);
+ }
+
// Draw selection hint
if (box.IsSelected)
{
float outlineAlpha = Mathf.Sin(Time.TimeSinceStartup * 4.0f) * 0.5f + 0.5f;
float outlineWidth = Mathf.Lerp(1.5f, 4.0f, outlineAlpha);
var outlineRect = new Rectangle(rect.X - outlineWidth, rect.Y - outlineWidth, rect.Width + outlineWidth * 2, rect.Height + outlineWidth * 2);
- Render2D.DrawSprite(icon, outlineRect, FlaxEngine.GUI.Style.Current.BorderSelected.RGBMultiplied(1.0f + outlineAlpha * 0.4f));
+ Color selectionColor = FlaxEngine.GUI.Style.Current.BorderSelected.RGBMultiplied(1.0f + outlineAlpha * 0.4f);
+ Render2D.DrawSprite(icon, outlineRect, selectionColor.AlphaMultiplied(0.4f));
}
+
+ Render2D.Features = features;
}
///
- /// Function used to create style for the given surface type. Can be overriden to provide some customization via user plugin.
+ /// Function used to create style for the given surface type. Can be overridden to provide some customization via user plugin.
///
public static Func CreateStyleHandler = CreateDefault;
@@ -293,6 +322,7 @@ namespace FlaxEditor.Surface
ArrowClose = editor.Icons.VisjectArrowClosed32,
},
Background = editor.UI.VisjectSurfaceBackground,
+ BackgroundColor = new Color(31, 31, 31),
};
}
@@ -311,13 +341,11 @@ namespace FlaxEditor.Surface
{
var dir = sub / length;
var arrowRect = new Rectangle(0, 0, 16.0f, 16.0f);
- float rotation = Float2.Dot(dir, Float2.UnitY);
- if (endPos.X < startPos.X)
- rotation = 2 - rotation;
+ float rotation = Mathf.Atan2(dir.Y, dir.X);
var sprite = Editor.Instance.Icons.VisjectArrowClosed32;
var arrowTransform =
Matrix3x3.Translation2D(-6.5f, -8) *
- Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) *
+ Matrix3x3.RotationZ(rotation) *
Matrix3x3.Translation2D(endPos - dir * 8);
Render2D.PushTransform(ref arrowTransform);
diff --git a/Source/Editor/Surface/VisjectSurface.Draw.cs b/Source/Editor/Surface/VisjectSurface.Draw.cs
index a7d390132..5f5078d5f 100644
--- a/Source/Editor/Surface/VisjectSurface.Draw.cs
+++ b/Source/Editor/Surface/VisjectSurface.Draw.cs
@@ -65,31 +65,68 @@ namespace FlaxEditor.Surface
///
protected virtual void DrawBackground()
{
- DrawBackgroundDefault(Style.Background, Width, Height);
+ DrawBackgroundDefault(Style.Background, Size, _rootControl.Location);
+ //DrawBackgroundSolidColor(Style.BackgroundColor, Width, Height);
+ DrawGridBackground();
}
- internal static void DrawBackgroundDefault(Texture background, float width, float height)
+ internal static void DrawBackgroundSolidColor(Color color, float width, float height)
+ {
+ Rectangle backgroundRect = new Rectangle(0f, 0f, width, height);
+ Render2D.FillRectangle(backgroundRect, color);
+ }
+
+ internal void DrawGridBackground()
+ {
+ var viewRect = GetClientArea();
+ var upperLeft = _rootControl.PointFromParent(viewRect.Location);
+ var bottomRight = _rootControl.PointFromParent(viewRect.Size);
+ var min = Float2.Min(upperLeft, bottomRight);
+ var max = Float2.Max(upperLeft, bottomRight);
+ var pixelRange = (max - min) * ViewScale * 2.75f;
+ Render2D.PushClip(ref viewRect);
+ DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X);
+ DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y);
+ Render2D.PopClip();
+ }
+
+ private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange)
+ {
+ var linesColor = Style.BackgroundColor.RGBMultiplied(1.2f);
+ float[] gridTickStrengths = null;
+ Utilities.Utils.DrawCurveTicks((decimal tick, double step, float strength) =>
+ {
+ var p = _rootControl.PointToParent(axis * (float)tick); ;
+
+ // Draw line
+ var lineRect = new Rectangle
+ (
+ viewRect.Location + (p - 0.5f) * axis,
+ Float2.Lerp(viewRect.Size, Float2.One, axis)
+ );
+ Render2D.FillRectangle(lineRect, linesColor.AlphaMultiplied(strength));
+
+ }, Utilities.Utils.CurveTickSteps, ref gridTickStrengths, min, max, pixelRange);
+ }
+
+ internal static void DrawBackgroundDefault(Texture background, Float2 size, Float2 offset)
{
if (background && background.ResidentMipLevels > 0)
{
var bSize = background.Size;
- float bw = bSize.X;
- float bh = bSize.Y;
- var pos = Float2.Mod(bSize);
+ var pos = Float2.Mod(offset / bSize) * bSize;
+ var max = Float2.Ceil(size / bSize + 1.0f);
if (pos.X > 0)
- pos.X -= bw;
+ pos.X -= bSize.X;
if (pos.Y > 0)
- pos.Y -= bh;
+ pos.Y -= bSize.Y;
- int maxI = Mathf.CeilToInt(width / bw + 1.0f);
- int maxJ = Mathf.CeilToInt(height / bh + 1.0f);
-
- for (int i = 0; i < maxI; i++)
+ for (int i = 0; i < max.X; i++)
{
- for (int j = 0; j < maxJ; j++)
+ for (int j = 0; j < max.Y; j++)
{
- Render2D.DrawTexture(background, new Rectangle(pos.X + i * bw, pos.Y + j * bh, bw, bh), Color.White);
+ Render2D.DrawTexture(background, new Rectangle(pos.X + i * bSize.X, pos.Y + j * bSize.Y, bSize), Color.White);
}
}
}
diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs
index 9f326f36c..55c643f12 100644
--- a/Source/Editor/Surface/VisjectSurface.cs
+++ b/Source/Editor/Surface/VisjectSurface.cs
@@ -217,7 +217,7 @@ namespace FlaxEditor.Surface
set
{
// Clamp
- value = Mathf.Clamp(value, 0.05f, 1.6f);
+ value = Mathf.Clamp(value, 0.05f, 1.85f);
// Check if value will change
if (Mathf.Abs(value - _targetScale) > 0.0001f)
@@ -511,7 +511,7 @@ namespace FlaxEditor.Surface
{
GroupID = Custom.GroupID,
Name = "Custom",
- Color = Color.Wheat
+ Color = Color.Wheat.RGBMultiplied(0.4f),
};
}
else
diff --git a/Source/Editor/Undo/Actions/PasteActorsAction.cs b/Source/Editor/Undo/Actions/PasteActorsAction.cs
index 2fe4e6235..f4dc7b318 100644
--- a/Source/Editor/Undo/Actions/PasteActorsAction.cs
+++ b/Source/Editor/Undo/Actions/PasteActorsAction.cs
@@ -41,7 +41,7 @@ namespace FlaxEditor.Actions
ActionString = name;
_pasteParent = pasteParent;
- _idsMapping = new Dictionary(objectIds.Length * 4);
+ _idsMapping = new Dictionary(objectIds.Length);
for (int i = 0; i < objectIds.Length; i++)
{
_idsMapping[objectIds[i]] = Guid.NewGuid();
@@ -72,13 +72,24 @@ namespace FlaxEditor.Actions
///
/// Links the broken parent reference (missing parent). By default links the actor to the first scene.
///
- /// The actor.
- protected virtual void LinkBrokenParentReference(Actor actor)
+ /// The actor node.
+ protected virtual void LinkBrokenParentReference(ActorNode actorNode)
{
// Link to the first scene root
if (Level.ScenesCount == 0)
throw new Exception("Failed to paste actor with a broken reference. No loaded scenes.");
- actor.SetParent(Level.GetScene(0), false);
+ actorNode.Actor.SetParent(Level.GetScene(0), false);
+ }
+
+ ///
+ /// Checks if actor has a broken parent reference. For example, it's linked to the parent that is indie prefab editor while it should be pasted into scene.
+ ///
+ /// The actor node.
+ protected virtual void CheckBrokenParentReference(ActorNode actorNode)
+ {
+ // Ensure pasted object ends up on a scene
+ if (actorNode.Actor.Scene == null)
+ LinkBrokenParentReference(actorNode);
}
///
@@ -103,16 +114,13 @@ namespace FlaxEditor.Actions
for (int i = 0; i < actors.Length; i++)
{
var actor = actors[i];
-
- // Check if has no parent linked (broken reference eg. old parent not existing)
- if (actor.Parent == null)
- {
- LinkBrokenParentReference(actor);
- }
-
var node = GetNode(actor.ID);
if (node is ActorNode actorNode)
{
+ // Check if has no parent linked (broken reference eg. old parent not existing)
+ if (actor.Parent == null)
+ LinkBrokenParentReference(actorNode);
+
nodes.Add(actorNode);
}
}
@@ -136,6 +144,12 @@ namespace FlaxEditor.Actions
nodeParents[i].Actor.SetParent(pasteParentNode.Actor, false);
}
}
+ else
+ {
+ // Sanity check on pasted actor to ensure they end up i na proper context (scene editor or specific prefab editor)
+ foreach (var node in nodeParents)
+ CheckBrokenParentReference(node);
+ }
// Store previously looked up names and the results
Dictionary foundNamesResults = new();
diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs
index 78ccedd60..fa5724780 100644
--- a/Source/Editor/Viewport/EditorViewport.cs
+++ b/Source/Editor/Viewport/EditorViewport.cs
@@ -166,7 +166,6 @@ namespace FlaxEditor.Viewport
#endif
// Input
-
internal bool _disableInputUpdate;
private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown;
private Float2 _startPos;
diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
index df10024cc..f171840b5 100644
--- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs
+++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
@@ -1,18 +1,19 @@
// Copyright (c) Wojciech Figat. All rights reserved.
-using System;
using System.Collections.Generic;
+using Object = FlaxEngine.Object;
using FlaxEditor.Content;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu;
+using FlaxEditor.Options;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEditor.Viewport.Modes;
+using FlaxEditor.Viewport.Widgets;
using FlaxEditor.Windows;
using FlaxEngine;
using FlaxEngine.Gizmo;
using FlaxEngine.GUI;
-using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport
{
@@ -26,6 +27,7 @@ namespace FlaxEditor.Viewport
private readonly ContextMenuButton _showGridButton;
private readonly ContextMenuButton _showNavigationButton;
private readonly ContextMenuButton _toggleGameViewButton;
+ private readonly ContextMenuButton _showDirectionGizmoButton;
private SelectionOutline _customSelectionOutline;
///
@@ -226,12 +228,13 @@ namespace FlaxEditor.Viewport
// Add rubber band selector
_rubberBandSelector = new ViewportRubberBandSelector(this);
- _directionGizmo = new DirectionGizmo(this);
- _directionGizmo.AnchorPreset = AnchorPresets.TopRight;
- _directionGizmo.Parent = this;
- _directionGizmo.LocalY += 25;
- _directionGizmo.LocalX -= 150;
- _directionGizmo.Size = new Float2(150, 150);
+
+ // Add direction gizmo
+ _directionGizmo = new DirectionGizmo(this)
+ {
+ AnchorPreset = AnchorPresets.TopRight,
+ Parent = this,
+ };
// Add grid
Grid = new GridGizmo(this);
@@ -252,11 +255,10 @@ namespace FlaxEditor.Viewport
_showNavigationButton.CloseMenuOnClick = false;
// Show direction gizmo widget
- var showDirectionGizmoButton = ViewWidgetShowMenu.AddButton("Direction Gizmo", () => _directionGizmo.Visible = !_directionGizmo.Visible);
- showDirectionGizmoButton.AutoCheck = true;
- showDirectionGizmoButton.CloseMenuOnClick = false;
- showDirectionGizmoButton.Checked = _directionGizmo.Visible;
-
+ _showDirectionGizmoButton = ViewWidgetShowMenu.AddButton("Direction Gizmo", () => _directionGizmo.Visible = !_directionGizmo.Visible);
+ _showDirectionGizmoButton.AutoCheck = true;
+ _showDirectionGizmoButton.CloseMenuOnClick = false;
+
// Game View
ViewWidgetButtonMenu.AddSeparator();
_toggleGameViewButton = ViewWidgetButtonMenu.AddButton("Game View", inputOptions.ToggleGameView, ToggleGameView);
@@ -290,6 +292,18 @@ namespace FlaxEditor.Viewport
// Game View
InputActions.Add(options => options.ToggleGameView, ToggleGameView);
+
+ editor.Options.OptionsChanged += OnEditorOptionsChanged;
+ OnEditorOptionsChanged(editor.Options.Options);
+ }
+
+ private void OnEditorOptionsChanged(EditorOptions options)
+ {
+ _directionGizmo.Visible = options.Viewport.ShowDirectionGizmo;
+ _showDirectionGizmoButton.Checked = _directionGizmo.Visible;
+ _directionGizmo.Size = new Float2(DirectionGizmo.DefaultGizmoSize * options.Viewport.DirectionGizmoScale);
+ _directionGizmo.LocalX = -_directionGizmo.Size.X * 0.5f;
+ _directionGizmo.LocalY = _directionGizmo.Size.Y * 0.5f + ViewportWidgetsContainer.WidgetsHeight;
}
///
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs
index e6b71d5e9..32bf18b19 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs
@@ -264,10 +264,17 @@ namespace FlaxEditor.Windows.Assets
}
///
- protected override void LinkBrokenParentReference(Actor actor)
+ protected override void LinkBrokenParentReference(ActorNode actorNode)
{
// Link to prefab root
- actor.SetParent(_window.Graph.MainActor, false);
+ actorNode.Actor.SetParent(_window.Graph.MainActor, false);
+ }
+
+ ///
+ protected override void CheckBrokenParentReference(ActorNode actorNode)
+ {
+ if (actorNode.Actor.Scene != null || actorNode.Root != _window.Graph.Root)
+ LinkBrokenParentReference(actorNode);
}
///
diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
index 40dd9131b..ad5a8caf2 100644
--- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
@@ -58,7 +58,7 @@ namespace FlaxEditor.Windows
cm.AddSeparator();
}
- if (item is ContentFolder contentFolder && contentFolder.Node is ProjectTreeNode)
+ if (item is ContentFolder contentFolder && contentFolder.Node is ProjectFolderTreeNode)
{
cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(CurrentViewFolder.Path));
}
@@ -76,8 +76,8 @@ namespace FlaxEditor.Windows
cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(item.Path)));
- if (!String.IsNullOrEmpty(Editor.Instance.Windows.ContentWin._itemsSearchBox.Text))
- {
+ if (!_showAllContentInTree && !String.IsNullOrEmpty(Editor.Instance.Windows.ContentWin._itemsSearchBox.Text))
+ {
cm.AddButton("Show in Content Panel", () =>
{
Editor.Instance.Windows.ContentWin.ClearItemsSearch();
@@ -129,7 +129,7 @@ namespace FlaxEditor.Windows
}
}
- if (isFolder && folder.Node is MainContentTreeNode)
+ if (isFolder && folder.Node is MainContentFolderTreeNode)
{
cm.AddSeparator();
}
@@ -145,7 +145,7 @@ namespace FlaxEditor.Windows
b = cm.AddButton("Paste", _view.Paste);
b.Enabled = _view.CanPaste();
- if (isFolder && folder.Node is MainContentTreeNode)
+ if (isFolder && folder.Node is MainContentFolderTreeNode)
{
// Do nothing
}
@@ -201,7 +201,7 @@ namespace FlaxEditor.Windows
private void CreateNewModuleMenu(ContextMenu menu, ContentFolder folder, bool disableUncreatable = false)
{
// Check if is source folder to add new module
- if (folder?.ParentFolder?.Node is ProjectTreeNode parentFolderNode && folder.Node == parentFolderNode.Source)
+ if (folder?.ParentFolder?.Node is ProjectFolderTreeNode parentFolderNode && folder.Node == parentFolderNode.Source)
{
var button = menu.AddButton("New module");
button.CloseMenuOnClick = false;
@@ -216,7 +216,7 @@ namespace FlaxEditor.Windows
private bool CanCreateFolder(ContentItem item = null)
{
- bool canCreateFolder = CurrentViewFolder != _root.Folder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectTreeNode);
+ bool canCreateFolder = CurrentViewFolder != _root.Folder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectFolderTreeNode);
return canCreateFolder;
}
diff --git a/Source/Editor/Windows/ContentWindow.Navigation.cs b/Source/Editor/Windows/ContentWindow.Navigation.cs
index 53dd6447b..4c7373aa6 100644
--- a/Source/Editor/Windows/ContentWindow.Navigation.cs
+++ b/Source/Editor/Windows/ContentWindow.Navigation.cs
@@ -10,15 +10,47 @@ namespace FlaxEditor.Windows
{
public partial class ContentWindow
{
- private static readonly List NavUpdateCache = new List(8);
+ private static readonly List NavUpdateCache = new List(8);
private void OnTreeSelectionChanged(List from, List to)
{
+ bool setLastViewFolder = !IsLayoutLocked;
+ if (!_showAllContentInTree && to.Count > 1)
+ {
+ _tree.Select(to[^1]);
+ return;
+ }
+ if (_showAllContentInTree && to.Count > 1)
+ {
+ if (setLastViewFolder)
+ {
+ var activeNode = GetActiveTreeSelection(to);
+ if (activeNode is ContentItemTreeNode itemNode)
+ SaveLastViewedFolder(itemNode.Item?.ParentFolder?.Node);
+ else
+ SaveLastViewedFolder(activeNode as ContentFolderTreeNode);
+ }
+ UpdateUI();
+ return;
+ }
+
// Navigate
- var source = from.Count > 0 ? from[0] as ContentTreeNode : null;
- var target = to.Count > 0 ? to[0] as ContentTreeNode : null;
+ var source = from.Count > 0 ? from[0] as ContentFolderTreeNode : null;
+ var targetNode = GetActiveTreeSelection(to);
+ if (targetNode is ContentItemTreeNode itemNode2)
+ {
+ if (setLastViewFolder)
+ SaveLastViewedFolder(itemNode2.Item?.ParentFolder?.Node);
+ UpdateUI();
+ itemNode2.Focus();
+ return;
+ }
+
+ var target = targetNode as ContentFolderTreeNode;
Navigate(source, target);
+ if (setLastViewFolder)
+ SaveLastViewedFolder(target);
target?.Focus();
}
@@ -26,12 +58,12 @@ namespace FlaxEditor.Windows
/// Navigates to the specified target content location.
///
/// The target.
- public void Navigate(ContentTreeNode target)
+ public void Navigate(ContentFolderTreeNode target)
{
Navigate(SelectedNode, target);
}
- private void Navigate(ContentTreeNode source, ContentTreeNode target)
+ private void Navigate(ContentFolderTreeNode source, ContentFolderTreeNode target)
{
if (target == null)
target = _root;
@@ -50,7 +82,8 @@ namespace FlaxEditor.Windows
}
// Show folder contents and select tree node
- RefreshView(target);
+ if (!_showAllContentInTree)
+ RefreshView(target);
_tree.Select(target);
target.ExpandAllParents();
@@ -62,7 +95,8 @@ namespace FlaxEditor.Windows
//UndoList.SetSize(32);
// Update search
- UpdateItemsSearch();
+ if (!_showAllContentInTree)
+ UpdateItemsSearch();
// Unlock navigation
_navigationUnlocked = true;
@@ -81,7 +115,7 @@ namespace FlaxEditor.Windows
if (_navigationUnlocked && _navigationUndo.Count > 0)
{
// Pop node
- ContentTreeNode node = _navigationUndo.Pop();
+ ContentFolderTreeNode node = _navigationUndo.Pop();
// Lock navigation
_navigationUnlocked = false;
@@ -90,7 +124,8 @@ namespace FlaxEditor.Windows
_navigationRedo.Push(SelectedNode);
// Select node
- RefreshView(node);
+ if (!_showAllContentInTree)
+ RefreshView(node);
_tree.Select(node);
node.ExpandAllParents();
@@ -99,14 +134,16 @@ namespace FlaxEditor.Windows
//UndoList.SetSize(32);
// Update search
- UpdateItemsSearch();
+ if (!_showAllContentInTree)
+ UpdateItemsSearch();
// Unlock navigation
_navigationUnlocked = true;
// Update UI
UpdateUI();
- _view.SelectFirstItem();
+ if (!_showAllContentInTree)
+ _view.SelectFirstItem();
}
}
@@ -119,7 +156,7 @@ namespace FlaxEditor.Windows
if (_navigationUnlocked && _navigationRedo.Count > 0)
{
// Pop node
- ContentTreeNode node = _navigationRedo.Pop();
+ ContentFolderTreeNode node = _navigationRedo.Pop();
// Lock navigation
_navigationUnlocked = false;
@@ -128,7 +165,8 @@ namespace FlaxEditor.Windows
_navigationUndo.Push(SelectedNode);
// Select node
- RefreshView(node);
+ if (!_showAllContentInTree)
+ RefreshView(node);
_tree.Select(node);
node.ExpandAllParents();
@@ -137,14 +175,16 @@ namespace FlaxEditor.Windows
//UndoList.SetSize(32);
// Update search
- UpdateItemsSearch();
+ if (!_showAllContentInTree)
+ UpdateItemsSearch();
// Unlock navigation
_navigationUnlocked = true;
// Update UI
UpdateUI();
- _view.SelectFirstItem();
+ if (!_showAllContentInTree)
+ _view.SelectFirstItem();
}
}
@@ -153,8 +193,8 @@ namespace FlaxEditor.Windows
///
public void NavigateUp()
{
- ContentTreeNode target = _root;
- ContentTreeNode current = SelectedNode;
+ ContentFolderTreeNode target = _root;
+ ContentFolderTreeNode current = SelectedNode;
if (current?.Folder.ParentFolder != null)
{
@@ -188,7 +228,7 @@ namespace FlaxEditor.Windows
// Spawn buttons
var nodes = NavUpdateCache;
nodes.Clear();
- ContentTreeNode node = SelectedNode;
+ ContentFolderTreeNode node = SelectedNode;
while (node != null)
{
nodes.Add(node);
@@ -222,13 +262,31 @@ namespace FlaxEditor.Windows
///
/// Gets the selected tree node.
///
- public ContentTreeNode SelectedNode => _tree.SelectedNode as ContentTreeNode;
+ public ContentFolderTreeNode SelectedNode
+ {
+ get
+ {
+ var selected = GetActiveTreeSelection(_tree.Selection);
+ if (selected is ContentItemTreeNode itemNode)
+ return itemNode.Parent as ContentFolderTreeNode;
+ return selected as ContentFolderTreeNode;
+ }
+ }
///
/// Gets the current view folder.
///
public ContentFolder CurrentViewFolder => SelectedNode?.Folder;
+ private TreeNode GetActiveTreeSelection(List selection)
+ {
+ if (selection == null || selection.Count == 0)
+ return null;
+ return _showAllContentInTree && selection.Count > 1
+ ? selection[^1]
+ : selection[0];
+ }
+
///
/// Shows the root folder.
///
@@ -236,5 +294,10 @@ namespace FlaxEditor.Windows
{
_tree.Select(_root);
}
+
+ private void SaveLastViewedFolder(ContentFolderTreeNode node)
+ {
+ Editor.ProjectCache.SetCustomData(ProjectDataLastViewedFolder, node?.Path ?? string.Empty);
+ }
}
}
diff --git a/Source/Editor/Windows/ContentWindow.Search.cs b/Source/Editor/Windows/ContentWindow.Search.cs
index 5a0ed63aa..af6cc67ef 100644
--- a/Source/Editor/Windows/ContentWindow.Search.cs
+++ b/Source/Editor/Windows/ContentWindow.Search.cs
@@ -115,12 +115,14 @@ namespace FlaxEditor.Windows
var root = _root;
root.LockChildrenRecursive();
+ _suppressExpandedStateSave = true;
PerformLayout();
// Update tree
var query = _foldersSearchBox.Text;
root.UpdateFilter(query);
+ _suppressExpandedStateSave = false;
root.UnlockChildrenRecursive();
PerformLayout();
PerformLayout();
@@ -161,6 +163,11 @@ namespace FlaxEditor.Windows
// Skip events during setup or init stuff
if (IsLayoutLocked)
return;
+ if (_showAllContentInTree)
+ {
+ RefreshTreeItems();
+ return;
+ }
// Check if clear filters
if (_itemsSearchBox.TextLength == 0 && !_viewDropdown.HasSelection)
@@ -200,7 +207,7 @@ namespace FlaxEditor.Windows
// Special case for root folder
for (int i = 0; i < _root.ChildrenCount; i++)
{
- if (_root.GetChild(i) is ContentTreeNode node)
+ if (_root.GetChild(i) is ContentFolderTreeNode node)
UpdateItemsSearchFilter(node.Folder, items, filters, showAllFiles);
}
}
@@ -222,7 +229,7 @@ namespace FlaxEditor.Windows
// Special case for root folder
for (int i = 0; i < _root.ChildrenCount; i++)
{
- if (_root.GetChild(i) is ContentTreeNode node)
+ if (_root.GetChild(i) is ContentFolderTreeNode node)
UpdateItemsSearchFilter(node.Folder, items, filters, showAllFiles, query);
}
}
diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs
index 6313a91b0..2141b283e 100644
--- a/Source/Editor/Windows/ContentWindow.cs
+++ b/Source/Editor/Windows/ContentWindow.cs
@@ -27,10 +27,15 @@ namespace FlaxEditor.Windows
public sealed partial class ContentWindow : EditorWindow
{
private const string ProjectDataLastViewedFolder = "LastViewedFolder";
+ private const string ProjectDataExpandedFolders = "ExpandedFolders";
private bool _isWorkspaceDirty;
private string _workspaceRebuildLocation;
private string _lastViewedFolderBeforeReload;
private SplitPanel _split;
+ private Panel _treeOnlyPanel;
+ private ContainerControl _treePanelRoot;
+ private ContainerControl _treeHeaderPanel;
+ private Panel _contentItemsSearchPanel;
private Panel _contentViewPanel;
private Panel _contentTreePanel;
private ContentView _view;
@@ -43,18 +48,23 @@ namespace FlaxEditor.Windows
private readonly ToolStripButton _navigateUpButton;
private NavigationBar _navigationBar;
+ private Panel _viewDropdownPanel;
private Tree _tree;
private TextBox _foldersSearchBox;
private TextBox _itemsSearchBox;
private ViewDropdown _viewDropdown;
private SortType _sortType;
private bool _showEngineFiles = true, _showPluginsFiles = true, _showAllFiles = true, _showGeneratedFiles = false;
+ private bool _showAllContentInTree;
+ private bool _suppressExpandedStateSave;
+ private readonly HashSet _expandedFolderPaths = new HashSet(StringComparer.OrdinalIgnoreCase);
+ private bool _renameInTree;
- private RootContentTreeNode _root;
+ private RootContentFolderTreeNode _root;
private bool _navigationUnlocked;
- private readonly Stack _navigationUndo = new Stack(32);
- private readonly Stack _navigationRedo = new Stack(32);
+ private readonly Stack _navigationUndo = new Stack(32);
+ private readonly Stack _navigationRedo = new Stack(32);
private NewItem _newElement;
@@ -134,6 +144,9 @@ namespace FlaxEditor.Windows
}
}
+ internal bool IsTreeOnlyMode => _showAllContentInTree;
+ internal SortType CurrentSortType => _sortType;
+
///
/// Initializes a new instance of the class.
///
@@ -163,14 +176,6 @@ namespace FlaxEditor.Windows
_navigateUpButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Up64, NavigateUp).LinkTooltip("Navigate up.");
_toolStrip.AddSeparator();
- // Navigation bar
- _navigationBar = new NavigationBar
- {
- Parent = _toolStrip,
- ScrollbarTrackColor = style.Background,
- ScrollbarThumbColor = style.ForegroundGrey,
- };
-
// Split panel
_split = new SplitPanel(options.Options.Interface.ContentWindowOrientation, ScrollBars.None, ScrollBars.None)
{
@@ -180,19 +185,38 @@ namespace FlaxEditor.Windows
Parent = this,
};
+ // Tree-only panel (used when showing all content in the tree)
+ _treeOnlyPanel = new Panel(ScrollBars.None)
+ {
+ AnchorPreset = AnchorPresets.StretchAll,
+ Offsets = new Margin(0, 0, _toolStrip.Bottom, 0),
+ Visible = false,
+ Parent = this,
+ };
+
+ // Tree host panel
+ _treePanelRoot = new ContainerControl
+ {
+ AnchorPreset = AnchorPresets.StretchAll,
+ Offsets = Margin.Zero,
+ Parent = _split.Panel1,
+ };
+
// Content structure tree searching query input box
- var headerPanel = new ContainerControl
+ _treeHeaderPanel = new ContainerControl
{
AnchorPreset = AnchorPresets.HorizontalStretchTop,
BackgroundColor = style.Background,
IsScrollable = false,
Offsets = new Margin(0, 0, 0, 18 + 6),
+ Parent = _treePanelRoot,
};
+
_foldersSearchBox = new SearchBox
{
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
- Parent = headerPanel,
- Bounds = new Rectangle(4, 4, headerPanel.Width - 8, 18),
+ Parent = _treeHeaderPanel,
+ Bounds = new Rectangle(4, 4, _treeHeaderPanel.Width - 8, 18),
};
_foldersSearchBox.TextChanged += OnFoldersSearchBoxTextChanged;
@@ -200,55 +224,74 @@ namespace FlaxEditor.Windows
_contentTreePanel = new Panel
{
AnchorPreset = AnchorPresets.StretchAll,
- Offsets = new Margin(0, 0, headerPanel.Bottom, 0),
+ Offsets = new Margin(0, 0, _treeHeaderPanel.Bottom, 0),
IsScrollable = true,
ScrollBars = ScrollBars.Both,
- Parent = _split.Panel1,
+ Parent = _treePanelRoot,
};
// Content structure tree
- _tree = new Tree(false)
+ _tree = new Tree(true)
{
DrawRootTreeLine = false,
Parent = _contentTreePanel,
};
_tree.SelectedChanged += OnTreeSelectionChanged;
- headerPanel.Parent = _split.Panel1;
// Content items searching query input box and filters selector
- var contentItemsSearchPanel = new ContainerControl
+ _contentItemsSearchPanel = new Panel
{
AnchorPreset = AnchorPresets.HorizontalStretchTop,
IsScrollable = true,
Offsets = new Margin(0, 0, 0, 18 + 8),
Parent = _split.Panel2,
};
- const float viewDropdownWidth = 50.0f;
+
_itemsSearchBox = new SearchBox
{
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
- Parent = contentItemsSearchPanel,
- Bounds = new Rectangle(viewDropdownWidth + 8, 4, contentItemsSearchPanel.Width - 12 - viewDropdownWidth, 18),
+ Parent = _contentItemsSearchPanel,
+ Bounds = new Rectangle(4, 4, _contentItemsSearchPanel.Width - 8, 18),
};
_itemsSearchBox.TextChanged += UpdateItemsSearch;
+
+ _viewDropdownPanel = new Panel
+ {
+ Width = 50.0f,
+ Parent = this,
+ AnchorPreset = AnchorPresets.TopLeft,
+ BackgroundColor = Color.Transparent,
+ };
+
_viewDropdown = new ViewDropdown
{
- AnchorPreset = AnchorPresets.MiddleLeft,
SupportMultiSelect = true,
TooltipText = "Change content view and filter options",
- Parent = contentItemsSearchPanel,
- Offsets = new Margin(4, viewDropdownWidth, -9, 18),
+ Offsets = Margin.Zero,
+ Width = 46.0f,
+ Height = 18.0f,
+ Parent = _viewDropdownPanel,
};
+ _viewDropdown.LocalX += 2.0f;
+ _viewDropdown.LocalY += _toolStrip.ItemsHeight * 0.5f - 9.0f;
_viewDropdown.SelectedIndexChanged += e => UpdateItemsSearch();
for (int i = 0; i <= (int)ContentItemSearchFilter.Other; i++)
_viewDropdown.Items.Add(((ContentItemSearchFilter)i).ToString());
_viewDropdown.PopupCreate += OnViewDropdownPopupCreate;
+ // Navigation bar (after view dropdown so layout order stays stable)
+ _navigationBar = new NavigationBar
+ {
+ Parent = _toolStrip,
+ ScrollbarTrackColor = style.Background,
+ ScrollbarThumbColor = style.ForegroundGrey,
+ };
+
// Content view panel
_contentViewPanel = new Panel
{
AnchorPreset = AnchorPresets.StretchAll,
- Offsets = new Margin(0, 0, contentItemsSearchPanel.Bottom + 4, 0),
+ Offsets = new Margin(0, 0, _contentItemsSearchPanel.Bottom + 4, 0),
IsScrollable = true,
ScrollBars = ScrollBars.Vertical,
Parent = _split.Panel2,
@@ -268,9 +311,14 @@ namespace FlaxEditor.Windows
_view.OnDelete += Delete;
_view.OnDuplicate += Duplicate;
_view.OnPaste += Paste;
+ _view.ViewScaleChanged += ApplyTreeViewScale;
_view.InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus());
InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus());
+
+ LoadExpandedFolders();
+ UpdateViewDropdownBounds();
+ ApplyTreeViewScale();
}
private void OnCreateNewItemButtonClicked()
@@ -325,6 +373,7 @@ namespace FlaxEditor.Windows
var viewType = menu.AddChildMenu("View Type");
viewType.ContextMenu.AddButton("Tiles", OnViewTypeButtonClicked).Tag = ContentViewType.Tiles;
viewType.ContextMenu.AddButton("List", OnViewTypeButtonClicked).Tag = ContentViewType.List;
+ viewType.ContextMenu.AddButton("Tree View", OnViewTypeButtonClicked).Tag = "Tree";
viewType.ContextMenu.VisibleChanged += control =>
{
if (!control.Visible)
@@ -332,13 +381,23 @@ namespace FlaxEditor.Windows
foreach (var item in ((ContextMenu)control).Items)
{
if (item is ContextMenuButton button)
- button.Checked = View.ViewType == (ContentViewType)button.Tag;
+ {
+ if (button.Tag is ContentViewType type)
+ button.Checked = View.ViewType == type && !_showAllContentInTree;
+ else
+ button.Checked = _showAllContentInTree;
+ }
}
};
var show = menu.AddChildMenu("Show");
{
- var b = show.ContextMenu.AddButton("File extensions", () => View.ShowFileExtensions = !View.ShowFileExtensions);
+ var b = show.ContextMenu.AddButton("File extensions", () =>
+ {
+ View.ShowFileExtensions = !View.ShowFileExtensions;
+ if (_showAllContentInTree)
+ UpdateTreeItemNames(_root);
+ });
b.TooltipText = "Shows all files with extensions";
b.Checked = View.ShowFileExtensions;
b.CloseMenuOnClick = false;
@@ -419,9 +478,63 @@ namespace FlaxEditor.Windows
RefreshView();
}
+ private void SetShowAllContentInTree(bool value)
+ {
+ if (_showAllContentInTree == value)
+ return;
+
+ _showAllContentInTree = value;
+ ApplyTreeViewMode();
+ }
+
+ private void ApplyTreeViewMode()
+ {
+ if (_treeOnlyPanel == null || _split == null || _treePanelRoot == null)
+ return;
+
+ if (_showAllContentInTree)
+ {
+ _split.Visible = false;
+ _treeOnlyPanel.Visible = true;
+ _treePanelRoot.Parent = _treeOnlyPanel;
+ _treePanelRoot.Offsets = Margin.Zero;
+ _contentItemsSearchPanel.Visible = false;
+ _itemsSearchBox.Visible = false;
+ _contentViewPanel.Visible = false;
+ RefreshTreeItems();
+ }
+ else
+ {
+ _treeOnlyPanel.Visible = false;
+ _split.Visible = true;
+ _treePanelRoot.Parent = _split.Panel1;
+ _treePanelRoot.Offsets = Margin.Zero;
+ _contentItemsSearchPanel.Visible = true;
+ _itemsSearchBox.Visible = true;
+ _contentViewPanel.Visible = true;
+ if (_tree.SelectedNode is ContentItemTreeNode itemNode && itemNode.Parent is TreeNode parentNode)
+ _tree.Select(parentNode);
+ if (_root != null)
+ RemoveTreeAssetNodes(_root);
+ RefreshView(SelectedNode);
+ }
+
+ PerformLayout();
+ ApplyTreeViewScale();
+ _tree.PerformLayout();
+ }
+
private void OnViewTypeButtonClicked(ContextMenuButton button)
{
- View.ViewType = (ContentViewType)button.Tag;
+ if (button.Tag is ContentViewType viewType)
+ {
+ SetShowAllContentInTree(false);
+ View.ViewType = viewType;
+ }
+ else
+ {
+ SetShowAllContentInTree(true);
+ }
}
private void OnFilterClicked(ContextMenuButton filterButton)
@@ -481,15 +594,58 @@ namespace FlaxEditor.Windows
// Show element in the view
Select(item, true);
- // Disable scrolling in content view
- if (_contentViewPanel.VScrollBar != null)
- _contentViewPanel.VScrollBar.ThumbEnabled = false;
- if (_contentViewPanel.HScrollBar != null)
- _contentViewPanel.HScrollBar.ThumbEnabled = false;
- ScrollingOnContentView(false);
+ // Disable scrolling in proper view
+ _renameInTree = _showAllContentInTree;
+ if (_renameInTree)
+ {
+ if (_contentTreePanel.VScrollBar != null)
+ _contentTreePanel.VScrollBar.ThumbEnabled = false;
+ if (_contentTreePanel.HScrollBar != null)
+ _contentTreePanel.HScrollBar.ThumbEnabled = false;
+ ScrollingOnTreeView(false);
+ }
+ else
+ {
+ if (_contentViewPanel.VScrollBar != null)
+ _contentViewPanel.VScrollBar.ThumbEnabled = false;
+ if (_contentViewPanel.HScrollBar != null)
+ _contentViewPanel.HScrollBar.ThumbEnabled = false;
+ ScrollingOnContentView(false);
+ }
// Show rename popup
- var popup = RenamePopup.Show(item, item.TextRectangle, item.ShortName, true);
+ RenamePopup popup;
+ if (_renameInTree)
+ {
+ TreeNode node = null;
+ if (item is ContentFolder folder)
+ node = folder.Node;
+ else if (item.ParentFolder != null)
+ node = FindTreeItemNode(item.ParentFolder.Node, item);
+ if (node == null)
+ {
+ // Fallback to content view rename
+ popup = RenamePopup.Show(item, item.TextRectangle, item.ShortName, true);
+ }
+ else
+ {
+ var area = node.TextRect;
+ const float minRenameWidth = 220.0f;
+ if (area.Width < minRenameWidth)
+ {
+ float expand = minRenameWidth - area.Width;
+ area.X -= expand * 0.5f;
+ area.Width = minRenameWidth;
+ }
+ area.Y -= 2;
+ area.Height += 4.0f;
+ popup = RenamePopup.Show(node, area, item.ShortName, true);
+ }
+ }
+ else
+ {
+ popup = RenamePopup.Show(item, item.TextRectangle, item.ShortName, true);
+ }
popup.Tag = item;
popup.Validate += OnRenameValidate;
popup.Renamed += renamePopup => Rename((ContentItem)renamePopup.Tag, renamePopup.Text);
@@ -509,12 +665,24 @@ namespace FlaxEditor.Windows
private void OnRenameClosed(RenamePopup popup)
{
- // Restore scrolling in content view
- if (_contentViewPanel.VScrollBar != null)
- _contentViewPanel.VScrollBar.ThumbEnabled = true;
- if (_contentViewPanel.HScrollBar != null)
- _contentViewPanel.HScrollBar.ThumbEnabled = true;
- ScrollingOnContentView(true);
+ // Restore scrolling in proper view
+ if (_renameInTree)
+ {
+ if (_contentTreePanel.VScrollBar != null)
+ _contentTreePanel.VScrollBar.ThumbEnabled = true;
+ if (_contentTreePanel.HScrollBar != null)
+ _contentTreePanel.HScrollBar.ThumbEnabled = true;
+ ScrollingOnTreeView(true);
+ }
+ else
+ {
+ if (_contentViewPanel.VScrollBar != null)
+ _contentViewPanel.VScrollBar.ThumbEnabled = true;
+ if (_contentViewPanel.HScrollBar != null)
+ _contentViewPanel.HScrollBar.ThumbEnabled = true;
+ ScrollingOnContentView(true);
+ }
+ _renameInTree = false;
// Check if was creating new element
if (_newElement != null)
@@ -927,6 +1095,16 @@ namespace FlaxEditor.Windows
}
}
+ private void OnContentDatabaseItemAdded(ContentItem contentItem)
+ {
+ if (contentItem is ContentFolder folder && _expandedFolderPaths.Contains(StringUtils.NormalizePath(folder.Path)))
+ {
+ _suppressExpandedStateSave = true;
+ folder.Node?.Expand(true);
+ _suppressExpandedStateSave = false;
+ }
+ }
+
///
/// Opens the specified content item.
///
@@ -943,7 +1121,8 @@ namespace FlaxEditor.Windows
var folder = (ContentFolder)item;
folder.Node.Expand();
_tree.Select(folder.Node);
- _view.SelectFirstItem();
+ if (!_showAllContentInTree)
+ _view.SelectFirstItem();
return;
}
@@ -984,6 +1163,36 @@ namespace FlaxEditor.Windows
// Ensure that window is visible
FocusOrShow();
+ if (_showAllContentInTree)
+ {
+ var targetNode = item is ContentFolder folder ? folder.Node : parent.Node;
+ if (targetNode != null)
+ {
+ targetNode.ExpandAllParents();
+ if (item is ContentFolder)
+ {
+ _tree.Select(targetNode);
+ _contentTreePanel.ScrollViewTo(targetNode, fastScroll);
+ targetNode.Focus();
+ }
+ else
+ {
+ var itemNode = FindTreeItemNode(targetNode, item);
+ if (itemNode != null)
+ {
+ _tree.Select(itemNode);
+ _contentTreePanel.ScrollViewTo(itemNode, fastScroll);
+ itemNode.Focus();
+ }
+ else
+ {
+ _tree.Select(targetNode);
+ }
+ }
+ }
+ return;
+ }
+
// Navigate to the parent directory
Navigate(parent.Node);
@@ -995,12 +1204,26 @@ namespace FlaxEditor.Windows
_view.Focus();
}
+ private ContentItemTreeNode FindTreeItemNode(ContentFolderTreeNode parentNode, ContentItem item)
+ {
+ if (parentNode == null || item == null)
+ return null;
+ for (int i = 0; i < parentNode.ChildrenCount; i++)
+ {
+ if (parentNode.GetChild(i) is ContentItemTreeNode itemNode && itemNode.Item == item)
+ return itemNode;
+ }
+ return null;
+ }
+
///
/// Refreshes the current view items collection.
///
public void RefreshView()
{
- if (_view.IsSearching)
+ if (_showAllContentInTree)
+ RefreshTreeItems();
+ else if (_view.IsSearching)
UpdateItemsSearch();
else
RefreshView(SelectedNode);
@@ -1010,8 +1233,14 @@ namespace FlaxEditor.Windows
/// Refreshes the view.
///
/// The target location.
- public void RefreshView(ContentTreeNode target)
+ public void RefreshView(ContentFolderTreeNode target)
{
+ if (_showAllContentInTree)
+ {
+ RefreshTreeItems();
+ return;
+ }
+
_view.IsSearching = false;
if (target == _root)
{
@@ -1019,7 +1248,7 @@ namespace FlaxEditor.Windows
var items = new List(8);
for (int i = 0; i < _root.ChildrenCount; i++)
{
- if (_root.GetChild(i) is ContentTreeNode node)
+ if (_root.GetChild(i) is ContentFolderTreeNode node)
{
items.Add(node.Folder);
}
@@ -1038,12 +1267,263 @@ namespace FlaxEditor.Windows
}
}
+ private void RefreshTreeItems()
+ {
+ if (!_showAllContentInTree || _root == null)
+ return;
+
+ _root.LockChildrenRecursive();
+ RemoveTreeAssetNodes(_root);
+ AddTreeAssetNodes(_root);
+ _root.UnlockChildrenRecursive();
+ _tree.PerformLayout();
+ }
+
+ private void UpdateTreeItemNames(ContentFolderTreeNode node)
+ {
+ if (node == null)
+ return;
+
+ for (int i = 0; i < node.ChildrenCount; i++)
+ {
+ if (node.GetChild(i) is ContentFolderTreeNode childFolder)
+ {
+ UpdateTreeItemNames(childFolder);
+ }
+ else if (node.GetChild(i) is ContentItemTreeNode itemNode)
+ {
+ itemNode.UpdateDisplayedName();
+ }
+ }
+ }
+
+ internal void OnContentTreeNodeExpandedChanged(ContentFolderTreeNode node, bool isExpanded)
+ {
+ if (_suppressExpandedStateSave || node == null || node == _root)
+ return;
+
+ var path = node.Path;
+ if (string.IsNullOrEmpty(path))
+ return;
+ path = StringUtils.NormalizePath(path);
+
+ if (isExpanded)
+ _expandedFolderPaths.Add(path);
+ else
+ // Remove all sub paths if parent folder is closed.
+ _expandedFolderPaths.RemoveWhere(x => x.Contains(path));
+
+ SaveExpandedFolders();
+ }
+
+ internal void TryAutoExpandContentNode(ContentFolderTreeNode node)
+ {
+ if (node == null || node == _root)
+ return;
+
+ var path = node.Path;
+ if (string.IsNullOrEmpty(path))
+ return;
+ path = StringUtils.NormalizePath(path);
+
+ if (!_expandedFolderPaths.Contains(path))
+ return;
+
+ _suppressExpandedStateSave = true;
+ node.Expand(true);
+ _suppressExpandedStateSave = false;
+ }
+
+ private void LoadExpandedFolders()
+ {
+ _expandedFolderPaths.Clear();
+ if (Editor.ProjectCache.TryGetCustomData(ProjectDataExpandedFolders, out string data) && !string.IsNullOrWhiteSpace(data))
+ {
+ var entries = data.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var path = entries[i].Trim();
+ if (path.Length == 0)
+ continue;
+ _expandedFolderPaths.Add(StringUtils.NormalizePath(path));
+ }
+ }
+ }
+
+ private void SaveExpandedFolders()
+ {
+ if (_expandedFolderPaths.Count == 0)
+ {
+ Editor.ProjectCache.RemoveCustomData(ProjectDataExpandedFolders);
+ return;
+ }
+
+ var data = string.Join("\n", _expandedFolderPaths);
+ Editor.ProjectCache.SetCustomData(ProjectDataExpandedFolders, data);
+ }
+
+ private void ApplyExpandedFolders()
+ {
+ if (_root == null || _expandedFolderPaths.Count == 0)
+ return;
+
+ _suppressExpandedStateSave = true;
+ foreach (var path in _expandedFolderPaths)
+ {
+ if (Editor.ContentDatabase.Find(path) is ContentFolder folder)
+ {
+ folder.Node.ExpandAllParents(true);
+ folder.Node.Expand(true);
+ }
+ }
+ _suppressExpandedStateSave = false;
+ }
+
+ private void RemoveTreeAssetNodes(ContentFolderTreeNode node)
+ {
+ for (int i = node.ChildrenCount - 1; i >= 0; i--)
+ {
+ if (node.GetChild(i) is ContentItemTreeNode itemNode)
+ {
+ node.RemoveChild(itemNode);
+ itemNode.Dispose();
+ }
+ else if (node.GetChild(i) is ContentFolderTreeNode childFolder)
+ {
+ RemoveTreeAssetNodes(childFolder);
+ }
+ }
+ }
+
+ private void AddTreeAssetNodes(ContentFolderTreeNode node)
+ {
+ if (node.Folder != null)
+ {
+ var children = node.Folder.Children;
+ for (int i = 0; i < children.Count; i++)
+ {
+ var child = children[i];
+ if (child is ContentFolder)
+ continue;
+ if (!ShouldShowTreeItem(child))
+ continue;
+
+ var itemNode = new ContentItemTreeNode(child)
+ {
+ Parent = node,
+ };
+ }
+ }
+
+ for (int i = 0; i < node.ChildrenCount; i++)
+ {
+ if (node.GetChild(i) is ContentFolderTreeNode childFolder)
+ {
+ AddTreeAssetNodes(childFolder);
+ }
+ }
+
+ node.SortChildren();
+ }
+
+ private bool ShouldShowTreeItem(ContentItem item)
+ {
+ if (item == null || !item.Visible)
+ return false;
+ if (_viewDropdown != null && _viewDropdown.HasSelection)
+ {
+ var filterIndex = (int)item.SearchFilter;
+ if (!_viewDropdown.Selection.Contains(filterIndex))
+ return false;
+ }
+ if (!_showAllFiles && item is FileItem)
+ return false;
+ if (!_showGeneratedFiles && IsGeneratedFile(item.Path))
+ return false;
+ return true;
+ }
+
+ private static bool IsGeneratedFile(string path)
+ {
+ return path.EndsWith(".Gen.cs", StringComparison.Ordinal) ||
+ path.EndsWith(".Gen.h", StringComparison.Ordinal) ||
+ path.EndsWith(".Gen.cpp", StringComparison.Ordinal) ||
+ path.EndsWith(".csproj", StringComparison.Ordinal) ||
+ path.Contains(".CSharp");
+ }
+
private void UpdateUI()
{
UpdateToolstrip();
UpdateNavigationBar();
}
+ private void ApplyTreeViewScale()
+ {
+ if (_tree == null)
+ return;
+
+ var scale = _showAllContentInTree ? View.ViewScale : 1.0f;
+ var headerHeight = Mathf.Clamp(16.0f * scale, 12.0f, 28.0f);
+ var style = Style.Current;
+ var fontSize = Mathf.Clamp(style.FontSmall.Size * scale, 8.0f, 28.0f);
+ var fontRef = new FontReference(style.FontSmall.Asset, fontSize);
+ var iconSize = Mathf.Clamp(16.0f * scale, 12.0f, 28.0f);
+ var textMarginLeft = 2.0f + Mathf.Max(0.0f, iconSize - 16.0f);
+ ApplyTreeNodeScale(_root, headerHeight, fontRef, textMarginLeft);
+ _root?.PerformLayout(true);
+ _tree.PerformLayout();
+ }
+
+ private void ApplyTreeNodeScale(ContentFolderTreeNode node, float headerHeight, FontReference fontRef, float textMarginLeft)
+ {
+ if (node == null)
+ return;
+
+ var margin = node.TextMargin;
+ margin.Left = textMarginLeft;
+ margin.Top = 2.0f;
+ margin.Right = 2.0f;
+ margin.Bottom = 2.0f;
+ node.TextMargin = margin;
+ node.CustomArrowRect = GetTreeArrowRect(node, headerHeight);
+ node.HeaderHeight = headerHeight;
+ node.TextFont = fontRef;
+ for (int i = 0; i < node.ChildrenCount; i++)
+ {
+ if (node.GetChild(i) is ContentFolderTreeNode child)
+ ApplyTreeNodeScale(child, headerHeight, fontRef, textMarginLeft);
+ else if (node.GetChild(i) is ContentItemTreeNode itemNode)
+ {
+ var itemMargin = itemNode.TextMargin;
+ itemMargin.Left = textMarginLeft;
+ itemMargin.Top = 2.0f;
+ itemMargin.Right = 2.0f;
+ itemMargin.Bottom = 2.0f;
+ itemNode.TextMargin = itemMargin;
+ itemNode.HeaderHeight = headerHeight;
+ itemNode.TextFont = fontRef;
+ }
+ }
+ }
+
+ private static Rectangle GetTreeArrowRect(ContentFolderTreeNode node, float headerHeight)
+ {
+ if (node == null)
+ return Rectangle.Empty;
+
+ var scale = Editor.Instance?.Windows?.ContentWin?.IsTreeOnlyMode == true
+ ? Editor.Instance.Windows.ContentWin.View.ViewScale
+ : 1.0f;
+ var arrowSize = Mathf.Clamp(12.0f * scale, 10.0f, 20.0f);
+ var iconSize = Mathf.Clamp(16.0f * scale, 12.0f, 28.0f);
+ var textRect = node.TextRect;
+ var iconLeft = textRect.Left - iconSize - 2.0f;
+ var x = iconLeft - arrowSize - 2.0f;
+ var y = (headerHeight - arrowSize) * 0.5f;
+ return new Rectangle(Mathf.Max(x, 0.0f), Mathf.Max(y, 0.0f), arrowSize, arrowSize);
+ }
+
private void UpdateToolstrip()
{
if (_toolStrip == null)
@@ -1063,20 +1543,42 @@ namespace FlaxEditor.Windows
{
var bottomPrev = _toolStrip.Bottom;
_navigationBar.UpdateBounds(_toolStrip);
+ if (_viewDropdownPanel != null && _viewDropdownPanel.Visible)
+ {
+ var reserved = _viewDropdownPanel.Width + 8.0f;
+ _navigationBar.Width = Mathf.Max(_navigationBar.Width - reserved, 0.0f);
+ }
if (bottomPrev != _toolStrip.Bottom)
{
// Navigation bar changed toolstrip height
_split.Offsets = new Margin(0, 0, _toolStrip.Bottom, 0);
+ if (_treeOnlyPanel != null)
+ _treeOnlyPanel.Offsets = new Margin(0, 0, _toolStrip.Bottom, 0);
PerformLayout();
}
+ UpdateViewDropdownBounds();
}
}
+ private void UpdateViewDropdownBounds()
+ {
+ if (_viewDropdownPanel == null || _toolStrip == null)
+ return;
+
+ var margin = _toolStrip.ItemsMargin;
+ var height = _toolStrip.ItemsHeight;
+ var y = _toolStrip.Y + (_toolStrip.Height - height) * 0.5f;
+ var width = _viewDropdownPanel.Width;
+ var x = _toolStrip.Right - width - margin.Right;
+ _viewDropdownPanel.Bounds = new Rectangle(x, y, width, height);
+ }
+
///
public override void OnInit()
{
// Content database events
Editor.ContentDatabase.WorkspaceModified += () => _isWorkspaceDirty = true;
+ Editor.ContentDatabase.ItemAdded += OnContentDatabaseItemAdded;
Editor.ContentDatabase.ItemRemoved += OnContentDatabaseItemRemoved;
Editor.ContentDatabase.WorkspaceRebuilding += () => { _workspaceRebuildLocation = SelectedNode?.Path; };
Editor.ContentDatabase.WorkspaceRebuilt += () =>
@@ -1095,6 +1597,7 @@ namespace FlaxEditor.Windows
ShowRoot();
};
+ LoadExpandedFolders();
Refresh();
// Load last viewed folder
@@ -1110,7 +1613,7 @@ namespace FlaxEditor.Windows
private void OnScriptsReloadBegin()
{
- var lastViewedFolder = _tree.Selection.Count == 1 ? _tree.SelectedNode as ContentTreeNode : null;
+ var lastViewedFolder = _tree.Selection.Count == 1 ? _tree.SelectedNode as ContentFolderTreeNode : null;
_lastViewedFolderBeforeReload = lastViewedFolder?.Path ?? string.Empty;
_tree.RemoveChild(_root);
@@ -1133,7 +1636,7 @@ namespace FlaxEditor.Windows
private void Refresh()
{
// Setup content root node
- _root = new RootContentTreeNode
+ _root = new RootContentFolderTreeNode
{
ChildrenIndent = 0
};
@@ -1156,7 +1659,7 @@ namespace FlaxEditor.Windows
_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.Margin = new Margin(0.0f, 0.0f, -16.0f, ScrollBar.DefaultSize + 2); // Hide root node
_tree.AddChild(_root);
// Setup navigation
@@ -1167,6 +1670,8 @@ namespace FlaxEditor.Windows
// Update UI layout
_isLayoutLocked = false;
PerformLayout();
+ ApplyExpandedFolders();
+ ApplyTreeViewMode();
}
///
@@ -1176,7 +1681,10 @@ namespace FlaxEditor.Windows
if (_isWorkspaceDirty)
{
_isWorkspaceDirty = false;
- RefreshView();
+ if (_showAllContentInTree)
+ RefreshTreeItems();
+ else
+ RefreshView();
}
base.Update(deltaTime);
@@ -1186,7 +1694,15 @@ namespace FlaxEditor.Windows
public override void OnExit()
{
// Save last viewed folder
- var lastViewedFolder = _tree.Selection.Count == 1 ? _tree.SelectedNode as ContentTreeNode : null;
+ ContentFolderTreeNode lastViewedFolder = null;
+ if (_tree.Selection.Count == 1)
+ {
+ var selectedNode = _tree.SelectedNode;
+ if (selectedNode is ContentItemTreeNode itemNode)
+ lastViewedFolder = itemNode.Item?.ParentFolder?.Node;
+ else
+ lastViewedFolder = selectedNode as ContentFolderTreeNode;
+ }
Editor.ProjectCache.SetCustomData(ProjectDataLastViewedFolder, lastViewedFolder?.Path ?? string.Empty);
// Clear view
@@ -1197,7 +1713,7 @@ namespace FlaxEditor.Windows
{
while (_root.HasChildren)
{
- _root.RemoveChild((ContentTreeNode)_root.GetChild(0));
+ _root.RemoveChild((ContentFolderTreeNode)_root.GetChild(0));
}
}
}
@@ -1232,7 +1748,12 @@ namespace FlaxEditor.Windows
{
ShowContextMenuForItem(null, ref location, false);
}
- else if (c is ContentTreeNode node)
+ else if (c is ContentItemTreeNode itemNode)
+ {
+ _tree.Select(itemNode);
+ ShowContextMenuForItem(itemNode.Item, ref location, false);
+ }
+ else if (c is ContentFolderTreeNode node)
{
_tree.Select(node);
ShowContextMenuForItem(node.Folder, ref location, true);
@@ -1258,11 +1779,16 @@ namespace FlaxEditor.Windows
///
protected override void PerformLayoutBeforeChildren()
{
- UpdateNavigationBarBounds();
-
base.PerformLayoutBeforeChildren();
}
+ ///
+ protected override void PerformLayoutAfterChildren()
+ {
+ base.PerformLayoutAfterChildren();
+ UpdateNavigationBarBounds();
+ }
+
///
public override bool UseLayoutData => true;
@@ -1277,6 +1803,7 @@ namespace FlaxEditor.Windows
writer.WriteAttributeString("ShowAllFiles", ShowAllFiles.ToString());
writer.WriteAttributeString("ShowGeneratedFiles", ShowGeneratedFiles.ToString());
writer.WriteAttributeString("ViewType", _view.ViewType.ToString());
+ writer.WriteAttributeString("TreeViewAllContent", _showAllContentInTree.ToString());
}
///
@@ -1297,6 +1824,9 @@ namespace FlaxEditor.Windows
ShowGeneratedFiles = value2;
if (Enum.TryParse(node.GetAttribute("ViewType"), out ContentViewType viewType))
_view.ViewType = viewType;
+ if (bool.TryParse(node.GetAttribute("TreeViewAllContent"), out value2))
+ _showAllContentInTree = value2;
+ ApplyTreeViewMode();
}
///
@@ -1304,6 +1834,7 @@ namespace FlaxEditor.Windows
{
_split.SplitterValue = 0.2f;
_view.ViewScale = 1.0f;
+ _showAllContentInTree = false;
}
///
@@ -1312,10 +1843,17 @@ namespace FlaxEditor.Windows
_foldersSearchBox = null;
_itemsSearchBox = null;
_viewDropdown = null;
+ _viewDropdownPanel = null;
+ _treePanelRoot = null;
+ _treeHeaderPanel = null;
+ _treeOnlyPanel = null;
+ _contentItemsSearchPanel = null;
Editor.Options.OptionsChanged -= OnOptionsChanged;
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd;
+ if (Editor?.ContentDatabase != null)
+ Editor.ContentDatabase.ItemAdded -= OnContentDatabaseItemAdded;
base.OnDestroy();
}
diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp
index 455b6ae3d..f583ab32a 100644
--- a/Source/Engine/Content/BinaryAsset.cpp
+++ b/Source/Engine/Content/BinaryAsset.cpp
@@ -84,6 +84,11 @@ bool BinaryAsset::Init(AssetInitData& initData)
{
asset->_dependantAssets.Add(this);
}
+ else
+ {
+ // Dependency is not yet loaded to keep track this link to act when it's loaded
+ Content::onAssetDepend(this, e.First);
+ }
}
#endif
@@ -336,9 +341,7 @@ bool BinaryAsset::SaveToAsset(const StringView& path, AssetInitData& data, bool
// Force-resolve storage (asset at that path could be not yet loaded into registry)
storage = ContentStorageManager::GetStorage(filePath);
}
-
- // Check if can perform write operation to the asset container
- if (storage && !storage->AllowDataModifications())
+ if (storage && storage->IsReadOnly())
{
LOG(Warning, "Cannot write to the asset storage container.");
return true;
@@ -635,7 +638,7 @@ void BinaryAsset::onRename(const StringView& newPath)
ScopeLock lock(Locker);
// We don't support packages now
- ASSERT(!Storage->IsPackage() && Storage->AllowDataModifications() && Storage->GetEntriesCount() == 1);
+ ASSERT(!Storage->IsPackage() && !Storage->IsReadOnly() && Storage->GetEntriesCount() == 1);
// Rename storage
Storage->OnRename(newPath);
diff --git a/Source/Engine/Content/BinaryAsset.h b/Source/Engine/Content/BinaryAsset.h
index 39c34588e..e52e9dcff 100644
--- a/Source/Engine/Content/BinaryAsset.h
+++ b/Source/Engine/Content/BinaryAsset.h
@@ -22,6 +22,7 @@
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API BinaryAsset : public Asset
{
DECLARE_ASSET_HEADER(BinaryAsset);
+ friend Content;
protected:
AssetHeader _header;
FlaxStorageReference _storageRef; // Allow asset to have missing storage reference but only before asset is loaded or if it's virtual
diff --git a/Source/Engine/Content/Cache/AssetsCache.cpp b/Source/Engine/Content/Cache/AssetsCache.cpp
index 2a2d252b6..96083509e 100644
--- a/Source/Engine/Content/Cache/AssetsCache.cpp
+++ b/Source/Engine/Content/Cache/AssetsCache.cpp
@@ -415,9 +415,7 @@ void AssetsCache::RegisterAssets(FlaxStorage* storage)
// Check if need to resolve any collisions
if (duplicatedEntries.HasItems())
{
- // Check if cannot resolve collision for that container (it must allow to write to)
- // TODO: we could support packages as well but don't have to do it now, maybe in future
- if (storage->AllowDataModifications() == false)
+ if (storage->IsReadOnly())
{
LOG(Error, "Cannot register \'{0}\'. Founded duplicated asset at \'{1}\' but storage container doesn't allow data modifications.", storagePath, info.Path);
return;
diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp
index 47ec97c0a..39a8ceca6 100644
--- a/Source/Engine/Content/Content.cpp
+++ b/Source/Engine/Content/Content.cpp
@@ -98,6 +98,9 @@ namespace
DateTime LastWorkspaceDiscovery;
CriticalSection WorkspaceDiscoveryLocker;
#endif
+#if USE_EDITOR
+ Dictionary> PendingDependencies;
+#endif
}
#if ENABLE_ASSETS_DISCOVERY
@@ -184,6 +187,9 @@ void ContentService::Update()
{
auto asset = LoadedAssetsToInvoke.Dequeue();
asset->onLoaded_MainThread();
+#if USE_EDITOR
+ Content::onAddDependencies(asset);
+#endif
}
LoadedAssetsToInvokeLocker.Unlock();
}
@@ -1091,10 +1097,17 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
FileSystem::DeleteFile(tmpPath);
// Reload storage
- if (auto storage = ContentStorageManager::GetStorage(dstPath, false))
+ auto storage = ContentStorageManager::GetStorage(dstPath, false);
+ if (storage && storage->IsLoaded())
{
storage->Reload();
}
+ else if (auto dependencies = PendingDependencies.TryGet(dstId))
+ {
+ // Destination storage is not loaded but there are other assets that depend on it so update them
+ for (const auto& e : *dependencies)
+ e.Item->OnDependencyModified(nullptr);
+ }
}
}
else
@@ -1311,6 +1324,9 @@ void Content::tryCallOnLoaded(Asset* asset)
{
LoadedAssetsToInvoke.RemoveAtKeepOrder(index);
asset->onLoaded_MainThread();
+#if USE_EDITOR
+ onAddDependencies(asset);
+#endif
}
}
@@ -1328,6 +1344,10 @@ void Content::onAssetUnload(Asset* asset)
Assets.Remove(asset->GetID());
UnloadQueue.Remove(asset);
LoadedAssetsToInvoke.Remove(asset);
+#if USE_EDITOR
+ for (auto& e : PendingDependencies)
+ e.Value.Remove(asset);
+#endif
}
void Content::onAssetChangeId(Asset* asset, const Guid& oldId, const Guid& newId)
@@ -1335,8 +1355,42 @@ void Content::onAssetChangeId(Asset* asset, const Guid& oldId, const Guid& newId
ScopeLock locker(AssetsLocker);
Assets.Remove(oldId);
Assets.Add(newId, asset);
+#if USE_EDITOR
+ if (PendingDependencies.ContainsKey(oldId))
+ {
+ auto deps = MoveTemp(PendingDependencies[oldId]);
+ PendingDependencies.Remove(oldId);
+ PendingDependencies.Add(newId, MoveTemp(deps));
+ }
+#endif
}
+#if USE_EDITOR
+
+void Content::onAssetDepend(BinaryAsset* asset, const Guid& otherId)
+{
+ ScopeLock locker(AssetsLocker);
+ PendingDependencies[otherId].Add(asset);
+}
+
+void Content::onAddDependencies(Asset* asset)
+{
+ auto it = PendingDependencies.Find(asset->GetID());
+ if (it.IsNotEnd())
+ {
+ auto& dependencies = it->Value;
+ auto binaryAsset = Asset::Cast(asset);
+ if (binaryAsset)
+ {
+ for (const auto& e : dependencies)
+ binaryAsset->_dependantAssets.Add(e.Item);
+ }
+ PendingDependencies.Remove(it);
+ }
+}
+
+#endif
+
bool Content::IsAssetTypeIdInvalid(const ScriptingTypeHandle& type, const ScriptingTypeHandle& assetType)
{
// Skip if no restrictions for the type
diff --git a/Source/Engine/Content/Content.h b/Source/Engine/Content/Content.h
index 107b51270..50886c04b 100644
--- a/Source/Engine/Content/Content.h
+++ b/Source/Engine/Content/Content.h
@@ -12,6 +12,7 @@
class Engine;
class FlaxFile;
+class BinaryAsset;
class IAssetFactory;
class AssetsCache;
@@ -395,6 +396,12 @@ private:
static void onAssetLoaded(Asset* asset);
static void onAssetUnload(Asset* asset);
static void onAssetChangeId(Asset* asset, const Guid& oldId, const Guid& newId);
+#if USE_EDITOR
+ friend BinaryAsset;
+ friend class ContentService;
+ static void onAssetDepend(BinaryAsset* asset, const Guid& otherId);
+ static void onAddDependencies(Asset* asset);
+#endif
static void deleteFileSafety(const StringView& path, const Guid* id = nullptr);
// Internal bindings
diff --git a/Source/Engine/Content/Factories/BinaryAssetFactory.cpp b/Source/Engine/Content/Factories/BinaryAssetFactory.cpp
index c492bd5b0..9486c6478 100644
--- a/Source/Engine/Content/Factories/BinaryAssetFactory.cpp
+++ b/Source/Engine/Content/Factories/BinaryAssetFactory.cpp
@@ -28,7 +28,7 @@ bool BinaryAssetFactoryBase::Init(BinaryAsset* asset)
#if USE_EDITOR
// Check if need to perform data conversion to the newer version (only in Editor)
const auto upgrader = GetUpgrader();
- if (storage->AllowDataModifications() && upgrader && upgrader->ShouldUpgrade(initData.SerializedVersion))
+ if (!storage->IsReadOnly() && upgrader && upgrader->ShouldUpgrade(initData.SerializedVersion))
{
const auto startTime = DateTime::NowUTC();
const AssetInfo info(asset->GetID(), asset->GetTypeName(), storage->GetPath());
diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h
index 4eae2829b..e7d4077fd 100644
--- a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h
+++ b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h
@@ -80,7 +80,6 @@ private:
auto asset = Asset.Get();
if (asset)
{
- asset->Locker.Lock();
Task* task = (Task*)Platform::AtomicRead(&asset->_loadingTask);
if (task)
{
@@ -99,7 +98,6 @@ private:
task = task->GetContinueWithTask();
} while (task);
}
- asset->Locker.Unlock();
}
}
};
diff --git a/Source/Engine/Content/Storage/FlaxFile.h b/Source/Engine/Content/Storage/FlaxFile.h
index 1fefd5ae4..387b1b43f 100644
--- a/Source/Engine/Content/Storage/FlaxFile.h
+++ b/Source/Engine/Content/Storage/FlaxFile.h
@@ -23,7 +23,7 @@ public:
// [FlaxStorage]
String ToString() const override;
bool IsPackage() const override;
- bool AllowDataModifications() const override;
+ bool IsReadOnly() const override;
bool HasAsset(const Guid& id) const override;
bool HasAsset(const AssetInfo& info) const override;
int32 GetEntriesCount() const override;
diff --git a/Source/Engine/Content/Storage/FlaxPackage.h b/Source/Engine/Content/Storage/FlaxPackage.h
index dee505b74..615a2e8e4 100644
--- a/Source/Engine/Content/Storage/FlaxPackage.h
+++ b/Source/Engine/Content/Storage/FlaxPackage.h
@@ -24,7 +24,7 @@ public:
// [FlaxStorage]
String ToString() const override;
bool IsPackage() const override;
- bool AllowDataModifications() const override;
+ bool IsReadOnly() const override;
bool HasAsset(const Guid& id) const override;
bool HasAsset(const AssetInfo& info) const override;
int32 GetEntriesCount() const override;
diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp
index 0cdaa6a6e..8dee911e4 100644
--- a/Source/Engine/Content/Storage/FlaxStorage.cpp
+++ b/Source/Engine/Content/Storage/FlaxStorage.cpp
@@ -811,7 +811,7 @@ bool FlaxStorage::ChangeAssetID(Entry& e, const Guid& newId)
// TODO: validate entry
ASSERT(newId.IsValid());
- ASSERT(AllowDataModifications());
+ ASSERT(!IsReadOnly());
LOG(Info, "Changing asset \'{0}\' id to \'{1}\' (storage: \'{2}\')", e.ID, newId, _path);
@@ -885,7 +885,7 @@ bool FlaxStorage::ChangeAssetID(Entry& e, const Guid& newId)
FlaxChunk* FlaxStorage::AllocateChunk()
{
- if (AllowDataModifications())
+ if (!IsReadOnly())
{
PROFILE_MEM(ContentFiles);
auto chunk = New();
@@ -1135,7 +1135,7 @@ bool FlaxStorage::Create(WriteStream* stream, Span assets, const
bool FlaxStorage::Save(const AssetInitData& data, bool silentMode)
{
// Check if can modify the storage
- if (!AllowDataModifications())
+ if (IsReadOnly())
return true;
// Note: we support saving only single asset, to save more assets in single package use FlaxStorage::Create(..)
@@ -1546,7 +1546,7 @@ void FlaxStorage::Tick(double time)
void FlaxStorage::OnRename(const StringView& newPath)
{
- ASSERT(AllowDataModifications());
+ ASSERT(!IsReadOnly());
_path = newPath;
}
@@ -1568,9 +1568,9 @@ bool FlaxFile::IsPackage() const
return false;
}
-bool FlaxFile::AllowDataModifications() const
+bool FlaxFile::IsReadOnly() const
{
- return true;
+ return false;
}
bool FlaxFile::HasAsset(const Guid& id) const
@@ -1648,9 +1648,9 @@ bool FlaxPackage::IsPackage() const
return true;
}
-bool FlaxPackage::AllowDataModifications() const
+bool FlaxPackage::IsReadOnly() const
{
- return false;
+ return true;
}
bool FlaxPackage::HasAsset(const Guid& id) const
diff --git a/Source/Engine/Content/Storage/FlaxStorage.h b/Source/Engine/Content/Storage/FlaxStorage.h
index 6acc91981..7ac3b3b43 100644
--- a/Source/Engine/Content/Storage/FlaxStorage.h
+++ b/Source/Engine/Content/Storage/FlaxStorage.h
@@ -276,9 +276,9 @@ public:
virtual bool IsPackage() const = 0;
///
- /// Checks whenever storage container allows the data modifications.
+ /// Checks whenever storage container doesn't allow modifications.
///
- virtual bool AllowDataModifications() const = 0;
+ virtual bool IsReadOnly() const = 0;
///
/// Determines whether the specified asset exists in this container.
diff --git a/Source/Engine/Core/Math/Rectangle.cs b/Source/Engine/Core/Math/Rectangle.cs
index 8e3c2b6c4..fcded50ce 100644
--- a/Source/Engine/Core/Math/Rectangle.cs
+++ b/Source/Engine/Core/Math/Rectangle.cs
@@ -253,6 +253,16 @@ namespace FlaxEngine
return new Rectangle(Location + new Float2(x, y), Size);
}
+ ///
+ /// Make offseted rectangle
+ ///
+ /// Offset (will be applied to X- and Y- axis).
+ /// Offseted rectangle.
+ public Rectangle MakeOffsetted(float offset)
+ {
+ return new Rectangle(Location + offset, Size);
+ }
+
///
/// Make offseted rectangle
///
diff --git a/Source/Engine/Core/Types/TimeSpan.h b/Source/Engine/Core/Types/TimeSpan.h
index 7704007a5..7455b7582 100644
--- a/Source/Engine/Core/Types/TimeSpan.h
+++ b/Source/Engine/Core/Types/TimeSpan.h
@@ -253,7 +253,7 @@ public:
///
FORCE_INLINE float GetTotalSeconds() const
{
- return static_cast(Ticks) / TicksPerSecond;
+ return (float)((double)Ticks / TicksPerSecond);
}
public:
diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp
index 285b9ec72..aecc292dc 100644
--- a/Source/Engine/Core/Types/Variant.cpp
+++ b/Source/Engine/Core/Types/Variant.cpp
@@ -124,7 +124,7 @@ VariantType::VariantType(Types type, const StringAnsiView& typeName, bool static
VariantType::VariantType(Types type, const ScriptingType& sType)
: VariantType(type)
{
- SetTypeName(sType);
+ SetTypeName(sType.Fullname, sType.Module->CanReload);
}
VariantType::VariantType(Types type, const MClass* klass)
@@ -171,7 +171,7 @@ VariantType::VariantType(const StringAnsiView& typeName)
// Check case for array
if (typeName.EndsWith(StringAnsiView("[]"), StringSearchCase::CaseSensitive))
{
- new(this) VariantType(Array, StringAnsiView(typeName.Get(), typeName.Length() - 2));
+ new(this) VariantType(Array, typeName);
return;
}
diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h
index 7b60907f7..13b928171 100644
--- a/Source/Engine/Core/Types/Variant.h
+++ b/Source/Engine/Core/Types/Variant.h
@@ -68,10 +68,12 @@ API_STRUCT(InBuild) struct FLAXENGINE_API VariantType
MAX,
#if USE_LARGE_WORLDS
+ Real = Double,
Vector2 = Double2,
Vector3 = Double3,
Vector4 = Double4,
#else
+ Real = Float,
Vector2 = Float2,
Vector3 = Float3,
Vector4 = Float4,
diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs
index 7af32fc9b..33e102089 100644
--- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs
+++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs
@@ -1197,11 +1197,17 @@ namespace FlaxEngine.Interop
}
[UnmanagedCallersOnly]
- internal static byte GetMethodParameterIsOut(ManagedHandle methodHandle, int parameterNum)
+ internal static UInt64 GetMethodParameterIsOut(ManagedHandle methodHandle)
{
MethodHolder methodHolder = Unsafe.As(methodHandle.Target);
- ParameterInfo parameterInfo = methodHolder.method.GetParameters()[parameterNum];
- return (byte)(parameterInfo.IsOut ? 1 : 0);
+ var parameters = methodHolder.method.GetParameters();
+ UInt64 result = 0;
+ for (int i = 0; i < parameters.Length; i++)
+ {
+ if (parameters[i].IsOut)
+ result |= 1ul << i;
+ }
+ return result;
}
[UnmanagedCallersOnly]
diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp
index 459f1b662..1fe9e39a9 100644
--- a/Source/Engine/Foliage/Foliage.cpp
+++ b/Source/Engine/Foliage/Foliage.cpp
@@ -198,7 +198,7 @@ void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, DrawCal
sphere.Center -= context.ViewOrigin;
if (Float3::Distance(context.LodView.Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance &&
context.RenderContext.View.CullingFrustum.Intersects(sphere) &&
- RenderTools::ComputeBoundsScreenRadiusSquared(sphere.Center, sphere.Radius, context.RenderContext.View) * context.ViewScreenSizeSq >= context.MinObjectPixelSizeSq)
+ RenderTools::ComputeBoundsScreenRadiusSquared(sphere.Center, (float)sphere.Radius, context.RenderContext.View) * context.ViewScreenSizeSq >= context.MinObjectPixelSizeSq)
{
const auto modelFrame = instance.DrawState.PrevFrame + 1;
diff --git a/Source/Engine/Graphics/GPUBufferDescription.cs b/Source/Engine/Graphics/GPUBufferDescription.cs
index 107d17b3e..43fcf94bf 100644
--- a/Source/Engine/Graphics/GPUBufferDescription.cs
+++ b/Source/Engine/Graphics/GPUBufferDescription.cs
@@ -428,6 +428,20 @@ namespace FlaxEngine
partial struct VertexElement : IEquatable
{
+ ///
+ /// Creates the vertex element description.
+ ///
+ /// Element type.
+ /// Data format.
+ public VertexElement(Types type, PixelFormat format)
+ {
+ Type = type;
+ Slot = 0;
+ Offset = 0;
+ PerInstance = 0;
+ Format = format;
+ }
+
///
/// Creates the vertex element description.
///
diff --git a/Source/Engine/Graphics/Materials/MaterialShader.cpp b/Source/Engine/Graphics/Materials/MaterialShader.cpp
index 2c8b02acc..7a39cd991 100644
--- a/Source/Engine/Graphics/Materials/MaterialShader.cpp
+++ b/Source/Engine/Graphics/Materials/MaterialShader.cpp
@@ -52,13 +52,10 @@ IMaterial::BindParameters::BindParameters(::GPUContext* context, const ::RenderC
}
IMaterial::BindParameters::BindParameters(::GPUContext* context, const ::RenderContext& renderContext, const ::DrawCall& drawCall, bool instanced)
- : GPUContext(context)
- , RenderContext(renderContext)
- , DrawCall(&drawCall)
- , Time(Time::Draw.UnscaledTime.GetTotalSeconds())
- , ScaledTime(Time::Draw.Time.GetTotalSeconds())
- , Instanced(instanced)
+ : BindParameters(context, renderContext)
{
+ DrawCall = &drawCall;
+ Instanced = instanced;
}
GPUConstantBuffer* IMaterial::BindParameters::PerViewConstants = nullptr;
diff --git a/Source/Engine/Graphics/Models/MeshAccessor.cs b/Source/Engine/Graphics/Models/MeshAccessor.cs
index ffe509476..cd216cdf5 100644
--- a/Source/Engine/Graphics/Models/MeshAccessor.cs
+++ b/Source/Engine/Graphics/Models/MeshAccessor.cs
@@ -1,6 +1,7 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace FlaxEngine
@@ -17,13 +18,14 @@ namespace FlaxEngine
{
private Span _data;
private PixelFormat _format;
- private int _stride;
+ private int _stride, _count;
private readonly PixelFormatSampler _sampler;
- internal Stream(Span data, PixelFormat format, int stride)
+ internal Stream(Span data, PixelFormat format, int stride, int count)
{
_data = data;
_stride = stride;
+ _count = count;
if (PixelFormatSampler.Get(format, out _sampler))
{
_format = format;
@@ -52,7 +54,7 @@ namespace FlaxEngine
///
/// Gets the count of the items in the stride.
///
- public int Count => _data.Length / _stride;
+ public int Count => _count;
///
/// Returns true if stream is valid.
@@ -76,6 +78,10 @@ namespace FlaxEngine
/// Loaded value.
public int GetInt(int index)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
fixed (byte* data = _data)
return (int)_sampler.Read(data + index * _stride).X;
}
@@ -87,6 +93,10 @@ namespace FlaxEngine
/// Loaded value.
public float GetFloat(int index)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
fixed (byte* data = _data)
return _sampler.Read(data + index * _stride).X;
}
@@ -98,6 +108,10 @@ namespace FlaxEngine
/// Loaded value.
public Float2 GetFloat2(int index)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
fixed (byte* data = _data)
return new Float2(_sampler.Read(data + index * _stride));
}
@@ -109,6 +123,10 @@ namespace FlaxEngine
/// Loaded value.
public Float3 GetFloat3(int index)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
fixed (byte* data = _data)
return new Float3(_sampler.Read(data + index * _stride));
}
@@ -120,6 +138,10 @@ namespace FlaxEngine
/// Loaded value.
public Float4 GetFloat4(int index)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
fixed (byte* data = _data)
return _sampler.Read(data + index * _stride);
}
@@ -131,9 +153,13 @@ namespace FlaxEngine
/// Value to assign.
public void SetInt(int index, int value)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
var v = new Float4(value);
fixed (byte* data = _data)
- _sampler.Write(data + index * _stride, ref v);
+ _sampler.Write(data + index * _stride, &v);
}
///
@@ -143,9 +169,13 @@ namespace FlaxEngine
/// Value to assign.
public void SetFloat(int index, float value)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
var v = new Float4(value);
fixed (byte* data = _data)
- _sampler.Write(data + index * _stride, ref v);
+ _sampler.Write(data + index * _stride, &v);
}
///
@@ -155,9 +185,13 @@ namespace FlaxEngine
/// Value to assign.
public void SetFloat2(int index, Float2 value)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
var v = new Float4(value, 0.0f, 0.0f);
fixed (byte* data = _data)
- _sampler.Write(data + index * _stride, ref v);
+ _sampler.Write(data + index * _stride, &v);
}
///
@@ -167,9 +201,13 @@ namespace FlaxEngine
/// Value to assign.
public void SetFloat3(int index, Float3 value)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
var v = new Float4(value, 0.0f);
fixed (byte* data = _data)
- _sampler.Write(data + index * _stride, ref v);
+ _sampler.Write(data + index * _stride, &v);
}
///
@@ -179,8 +217,12 @@ namespace FlaxEngine
/// Value to assign.
public void SetFloat4(int index, Float4 value)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
fixed (byte* data = _data)
- _sampler.Write(data + index * _stride, ref value);
+ _sampler.Write(data + index * _stride, &value);
}
///
@@ -190,6 +232,10 @@ namespace FlaxEngine
/// Pointer to the source data.
public void SetLinear(IntPtr data)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
new Span(data.ToPointer(), _data.Length).CopyTo(_data);
}
@@ -199,6 +245,10 @@ namespace FlaxEngine
/// The source .
public void Set(Span src)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
if (IsLinear(PixelFormat.R32G32_Float))
{
src.CopyTo(MemoryMarshal.Cast(_data));
@@ -211,7 +261,7 @@ namespace FlaxEngine
for (int i = 0; i < count; i++)
{
var v = new Float4(src[i], 0.0f, 0.0f);
- _sampler.Write(data + i * _stride, ref v);
+ _sampler.Write(data + i * _stride, &v);
}
}
}
@@ -223,6 +273,10 @@ namespace FlaxEngine
/// The source .
public void Set(Span src)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
if (IsLinear(PixelFormat.R32G32B32_Float))
{
src.CopyTo(MemoryMarshal.Cast(_data));
@@ -235,7 +289,7 @@ namespace FlaxEngine
for (int i = 0; i < count; i++)
{
var v = new Float4(src[i], 0.0f);
- _sampler.Write(data + i * _stride, ref v);
+ _sampler.Write(data + i * _stride, &v);
}
}
}
@@ -247,6 +301,10 @@ namespace FlaxEngine
/// The source .
public void Set(Span src)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
if (IsLinear(PixelFormat.R32G32B32A32_Float))
{
src.CopyTo(MemoryMarshal.Cast(_data));
@@ -259,7 +317,7 @@ namespace FlaxEngine
for (int i = 0; i < count; i++)
{
var v = (Float4)src[i];
- _sampler.Write(data + i * _stride, ref v);
+ _sampler.Write(data + i * _stride, &v);
}
}
}
@@ -271,6 +329,10 @@ namespace FlaxEngine
/// The source .
public void Set(Span src)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
if (IsLinear(PixelFormat.R32_UInt))
{
src.CopyTo(MemoryMarshal.Cast(_data));
@@ -292,7 +354,7 @@ namespace FlaxEngine
for (int i = 0; i < count; i++)
{
var v = new Float4(src[i]);
- _sampler.Write(data + i * _stride, ref v);
+ _sampler.Write(data + i * _stride, &v);
}
}
}
@@ -304,6 +366,10 @@ namespace FlaxEngine
/// The destination .
public void CopyTo(Span dst)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
if (IsLinear(PixelFormat.R32G32_Float))
{
_data.CopyTo(MemoryMarshal.Cast(dst));
@@ -325,6 +391,10 @@ namespace FlaxEngine
/// The destination .
public void CopyTo(Span dst)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
if (IsLinear(PixelFormat.R32G32B32_Float))
{
_data.CopyTo(MemoryMarshal.Cast(dst));
@@ -346,6 +416,10 @@ namespace FlaxEngine
/// The destination .
public void CopyTo(Span dst)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
if (IsLinear(PixelFormat.R32G32B32A32_Float))
{
_data.CopyTo(MemoryMarshal.Cast(dst));
@@ -367,6 +441,10 @@ namespace FlaxEngine
/// The destination .
public void CopyTo(Span dst)
{
+#if !BUILD_RELEASE
+ if (_data == null)
+ throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
+#endif
if (IsLinear(PixelFormat.R32_UInt))
{
_data.CopyTo(MemoryMarshal.Cast(dst));
@@ -390,6 +468,17 @@ namespace FlaxEngine
}
}
}
+
+ ///
+ /// Checks if stream is valid.
+ ///
+ /// The stream to check.
+ /// True if stream is valid, otherwise false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator bool(Stream stream)
+ {
+ return stream.IsValid;
+ }
}
private byte[][] _data = new byte[(int)MeshBufferType.MAX][];
@@ -510,19 +599,19 @@ namespace FlaxEngine
bool use16BitIndexBuffer = false;
IntPtr[] vbData = new IntPtr[3];
GPUVertexLayout[] vbLayout = new GPUVertexLayout[3];
- if (_data[VB0] != null)
+ if (_data[VB0] != null && _data[VB0].Length != 0)
{
vbData[0] = dataPtr[VB0];
vbLayout[0] = _layouts[VB0];
vertices = (uint)_data[VB0].Length / _layouts[VB0].Stride;
}
- if (_data[VB1] != null)
+ if (_data[VB1] != null && _data[VB1].Length != 0)
{
vbData[1] = dataPtr[VB1];
vbLayout[1] = _layouts[VB1];
vertices = (uint)_data[VB1].Length / _layouts[VB1].Stride;
}
- if (_data[VB2] != null)
+ if (_data[VB2] != null && _data[VB2].Length != 0)
{
vbData[2] = dataPtr[VB2];
vbLayout[2] = _layouts[VB2];
@@ -576,15 +665,16 @@ namespace FlaxEngine
{
Span data = new Span();
PixelFormat format = PixelFormat.Unknown;
- int stride = 0;
+ int stride = 0, count = 0;
var ib = _data[(int)MeshBufferType.Index];
if (ib != null)
{
data = ib;
format = _formats[(int)MeshBufferType.Index];
stride = PixelFormatExtensions.SizeInBytes(format);
+ count = data.Length / stride;
}
- return new Stream(data, format, stride);
+ return new Stream(data, format, stride, count);
}
///
@@ -596,7 +686,7 @@ namespace FlaxEngine
{
Span data = new Span();
PixelFormat format = PixelFormat.Unknown;
- int stride = 0;
+ int stride = 0, count = 0;
for (int vbIndex = 0; vbIndex < 3 && format == PixelFormat.Unknown; vbIndex++)
{
int idx = vbIndex + 1;
@@ -611,11 +701,12 @@ namespace FlaxEngine
data = new Span(vb).Slice(e.Offset);
format = e.Format;
stride = (int)layout.Stride;
+ count = vb.Length / stride;
break;
}
}
}
- return new Stream(data, format, stride);
+ return new Stream(data, format, stride, count);
}
///
@@ -722,6 +813,16 @@ namespace FlaxEngine
set => SetStreamFloat3(VertexElement.Types.Normal, value, PackNormal);
}
+ ///
+ /// Gets or sets the vertex tangent vectors (unpacked, normalized). Null if does not exist in vertex buffers of the mesh.
+ ///
+ /// Uses stream to read or write data to the vertex buffer.
+ public Float3[] Tangents
+ {
+ get => GetStreamFloat3(VertexElement.Types.Tangent, UnpackNormal);
+ set => SetStreamFloat3(VertexElement.Types.Tangent, value, PackNormal);
+ }
+
///
/// Gets or sets the vertex UVs (texcoord channel 0). Null if does not exist in vertex buffers of the mesh.
///
@@ -732,6 +833,70 @@ namespace FlaxEngine
set => SetStreamFloat2(VertexElement.Types.TexCoord, value);
}
+ ///
+ /// Recalculates normal vectors for all vertices.
+ ///
+ public void ComputeNormals()
+ {
+ var positions = Position();
+ var indices = Index();
+ if (!positions)
+ throw new Exception("Cannot compute tangents without positions.");
+ if (!indices)
+ throw new Exception("Cannot compute tangents without indices.");
+ if (!Normal())
+ throw new Exception("Cannot compute tangents without Normal vertex element.");
+ var vertexCount = positions.Count;
+ if (vertexCount == 0)
+ return;
+ var indexCount = indices.Count;
+
+ // Compute per-face normals but store them per-vertex
+ var normals = new Float3[vertexCount];
+ for (int i = 0; i < indexCount; i += 3)
+ {
+ var i1 = indices.GetInt(i + 0);
+ var i2 = indices.GetInt(i + 1);
+ var i3 = indices.GetInt(i + 2);
+ Float3 v1 = positions.GetFloat3(i1);
+ Float3 v2 = positions.GetFloat3(i2);
+ Float3 v3 = positions.GetFloat3(i3);
+ Float3 n = Float3.Cross((v2 - v1), (v3 - v1)).Normalized;
+
+ normals[i1] += n;
+ normals[i2] += n;
+ normals[i3] += n;
+ }
+
+ // Average normals
+ for (int i = 0; i < normals.Length; i++)
+ normals[i] = normals[i].Normalized;
+
+ // Write back to the buffer
+ Normals = normals;
+ }
+
+ ///
+ /// Recalculates tangent vectors for all vertices based on normals.
+ ///
+ public void ComputeTangents()
+ {
+ var normals = Normal();
+ var tangents = Tangent();
+ if (!normals)
+ throw new Exception("Cannot compute tangents without normals.");
+ if (!tangents)
+ throw new Exception("Cannot compute tangents without Tangent vertex element.");
+ var count = normals.Count;
+ for (int i = 0; i < count; i++)
+ {
+ Float3 normal = normals.GetFloat3(i);
+ UnpackNormal(ref normal);
+ RenderTools.CalculateTangentFrame(out var n, out var t, ref normal);
+ tangents.SetFloat4(i, t);
+ }
+ }
+
private uint[] GetStreamUInt(Stream stream)
{
uint[] result = null;
diff --git a/Source/Engine/Graphics/Models/MeshAccessor.h b/Source/Engine/Graphics/Models/MeshAccessor.h
index 595f76c6b..f0b40b623 100644
--- a/Source/Engine/Graphics/Models/MeshAccessor.h
+++ b/Source/Engine/Graphics/Models/MeshAccessor.h
@@ -25,10 +25,10 @@ public:
private:
Span _data;
PixelFormat _format;
- int32 _stride;
+ int32 _stride, _count;
PixelFormatSampler _sampler;
- Stream(Span data, PixelFormat format, int32 stride);
+ Stream(Span data, PixelFormat format, int32 stride, int32 count);
public:
Span GetData() const;
@@ -38,6 +38,11 @@ public:
bool IsValid() const;
bool IsLinear(PixelFormat expectedFormat) const;
+ FORCE_INLINE operator bool() const
+ {
+ return IsValid();
+ }
+
FORCE_INLINE int32 GetInt(int32 index) const
{
ASSERT_LOW_LAYER(index * _stride < _data.Length());
diff --git a/Source/Engine/Graphics/Models/MeshBase.cpp b/Source/Engine/Graphics/Models/MeshBase.cpp
index 81fffb3e7..435f6d0c8 100644
--- a/Source/Engine/Graphics/Models/MeshBase.cpp
+++ b/Source/Engine/Graphics/Models/MeshBase.cpp
@@ -38,10 +38,11 @@ namespace
#endif
}
-MeshAccessor::Stream::Stream(Span data, PixelFormat format, int32 stride)
+MeshAccessor::Stream::Stream(Span data, PixelFormat format, int32 stride, int32 count)
: _data(data)
, _format(PixelFormat::Unknown)
, _stride(stride)
+ , _count(count)
{
auto sampler = PixelFormatSampler::Get(format);
if (sampler)
@@ -72,7 +73,7 @@ int32 MeshAccessor::Stream::GetStride() const
int32 MeshAccessor::Stream::GetCount() const
{
- return _data.Length() / _stride;
+ return _count;
}
bool MeshAccessor::Stream::IsValid() const
@@ -368,22 +369,23 @@ MeshAccessor::Stream MeshAccessor::Index()
{
Span data;
PixelFormat format = PixelFormat::Unknown;
- int32 stride = 0;
+ int32 stride = 0, count = 0;
auto& ib = _data[(int32)MeshBufferType::Index];
if (ib.IsValid())
{
data = ib;
format = _formats[(int32)MeshBufferType::Index];
stride = PixelFormatExtensions::SizeInBytes(format);
+ count = data.Length() / stride;
}
- return Stream(data, format, stride);
+ return Stream(data, format, stride, count);
}
MeshAccessor::Stream MeshAccessor::Attribute(VertexElement::Types attribute)
{
Span data;
PixelFormat format = PixelFormat::Unknown;
- int32 stride = 0;
+ int32 stride = 0, count = 0;
for (int32 vbIndex = 0; vbIndex < 3 && format == PixelFormat::Unknown; vbIndex++)
{
static_assert((int32)MeshBufferType::Vertex0 == 1, "Update code.");
@@ -399,11 +401,12 @@ MeshAccessor::Stream MeshAccessor::Attribute(VertexElement::Types attribute)
data = vb.Slice(e.Offset);
format = e.Format;
stride = layout->GetStride();
+ count = vb.Length() / stride;
break;
}
}
}
- return Stream(data, format, stride);
+ return Stream(data, format, stride, count);
}
MeshBase::~MeshBase()
diff --git a/Source/Engine/Graphics/PixelFormatSampler.cs b/Source/Engine/Graphics/PixelFormatSampler.cs
index 8ff5a32fb..62046b2a5 100644
--- a/Source/Engine/Graphics/PixelFormatSampler.cs
+++ b/Source/Engine/Graphics/PixelFormatSampler.cs
@@ -22,7 +22,7 @@ namespace FlaxEngine
///
/// Write data function.
///
- public delegate* unmanaged Write;
+ public delegate* unmanaged Write;
///
/// Tries to get a sampler tool for the specified format to read pixels.
@@ -38,7 +38,7 @@ namespace FlaxEngine
Format = format,
PixelSize = pixelSize,
Read = (delegate* unmanaged)read.ToPointer(),
- Write = (delegate* unmanaged)write.ToPointer(),
+ Write = (delegate* unmanaged)write.ToPointer(),
};
return pixelSize != 0;
}
diff --git a/Source/Engine/Graphics/RenderTools.cpp b/Source/Engine/Graphics/RenderTools.cpp
index 94226d4b4..0a98f1401 100644
--- a/Source/Engine/Graphics/RenderTools.cpp
+++ b/Source/Engine/Graphics/RenderTools.cpp
@@ -575,6 +575,13 @@ void RenderTools::ComputeCascadeUpdateFrequency(int32 cascadeIndex, int32 cascad
}
}
+bool RenderTools::ShouldUpdateCascade(int32 frameIndex, int32 cascadeIndex, int32 cascadeCount, int32 updateMaxCountPerFrame, bool updateForce)
+{
+ int32 updateFrequency, updatePhrase;
+ ComputeCascadeUpdateFrequency(cascadeIndex, cascadeCount, updateFrequency, updatePhrase, updateMaxCountPerFrame);
+ return (frameIndex % updateFrequency == updatePhrase) || updateForce;
+}
+
float RenderTools::ComputeTemporalTime()
{
const float time = Time::Draw.UnscaledTime.GetTotalSeconds();
diff --git a/Source/Engine/Graphics/RenderTools.h b/Source/Engine/Graphics/RenderTools.h
index 92790c288..7be73c912 100644
--- a/Source/Engine/Graphics/RenderTools.h
+++ b/Source/Engine/Graphics/RenderTools.h
@@ -133,12 +133,7 @@ public:
static void ComputeCascadeUpdateFrequency(int32 cascadeIndex, int32 cascadeCount, int32& updateFrequency, int32& updatePhrase, int32 updateMaxCountPerFrame = 1);
// Checks if cached data should be updated during the given frame.
- FORCE_INLINE static bool ShouldUpdateCascade(int32 frameIndex, int32 cascadeIndex, int32 cascadeCount, int32 updateMaxCountPerFrame = 1, bool updateForce = false)
- {
- int32 updateFrequency, updatePhrase;
- ComputeCascadeUpdateFrequency(cascadeIndex, cascadeCount, updateFrequency, updatePhrase, updateMaxCountPerFrame);
- return (frameIndex % updateFrequency == updatePhrase) || updateForce;
- }
+ static bool ShouldUpdateCascade(int32 frameIndex, int32 cascadeIndex, int32 cascadeCount, int32 updateMaxCountPerFrame = 1, bool updateForce = false);
// Calculates temporal offset in the dithering factor that gets cleaned out by TAA.
// Returns 0-1 value based on unscaled draw time for temporal effects to reduce artifacts from screen-space dithering when using Temporal Anti-Aliasing.
@@ -150,8 +145,9 @@ public:
DEPRECATED("Use CalculateTangentFrame with unpacked Float3/Float4.") static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent);
// Result normal/tangent are already packed into [0;1] range.
- static void CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal);
- static void CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal, const Float3& tangent);
+ API_FUNCTION() static void CalculateTangentFrame(API_PARAM(Out) Float3& resultNormal, API_PARAM(Out) Float4& resultTangent, API_PARAM(Ref) const Float3& normal);
+ // Result normal/tangent are already packed into [0;1] range.
+ API_FUNCTION() static void CalculateTangentFrame(API_PARAM(Out) Float3& resultNormal, API_PARAM(Out) Float4& resultTangent, API_PARAM(Ref) const Float3& normal, API_PARAM(Ref) const Float3& tangent);
static void ComputeSphereModelDrawMatrix(const RenderView& view, const Float3& position, float radius, Matrix& resultWorld, bool& resultIsViewInside);
static void ComputeBoxModelDrawMatrix(const RenderView& view, const OrientedBoundingBox& box, Matrix& resultWorld, bool& resultIsViewInside);
diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp
index 8123142aa..3f1ab905b 100644
--- a/Source/Engine/Level/Actor.cpp
+++ b/Source/Engine/Level/Actor.cpp
@@ -20,12 +20,13 @@
#include "Engine/Core/Math/Double4x4.h"
#include "Engine/Debug/Exceptions/JsonParseException.h"
#include "Engine/Graphics/RenderTask.h"
-#include "Engine/Graphics/RenderView.h"
#include "Engine/Physics/Physics.h"
#include "Engine/Profiler/ProfilerCPU.h"
+#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Serialization/ISerializeModifier.h"
#include "Engine/Serialization/Serialization.h"
+#include "Engine/Serialization/JsonTools.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Serialization/MemoryWriteStream.h"
@@ -70,6 +71,44 @@ namespace
}
return result;
}
+
+ void LinkPrefab(SceneObject* object, Prefab* linkPrefab)
+ {
+ // Find prefab object that is used by this object in a given prefab
+ Guid currentPrefabId = object->GetPrefabID(), currentObjectId = object->GetPrefabObjectID();
+ RETRY:
+ auto prefab = Content::Load(currentPrefabId);
+ Guid nestedPrefabId, nestedObjectId;
+ if (prefab && prefab->GetNestedObject(currentObjectId, nestedPrefabId, nestedObjectId))
+ {
+ auto nestedPrefab = Content::Load(nestedPrefabId);
+ if (nestedPrefab)
+ {
+ auto nestedObject = (Actor*)nestedPrefab->GetDefaultInstance(nestedObjectId);
+ if (nestedObject && nestedPrefab == linkPrefab)
+ {
+ object->LinkPrefab(nestedPrefabId, nestedObjectId);
+ return;
+ }
+ }
+
+ // Try deeper
+ currentPrefabId = nestedPrefabId;
+ currentObjectId = nestedObjectId;
+ goto RETRY;
+ }
+
+ // Failed to resolve properly object from a given prefab
+ object->BreakPrefabLink();
+ }
+
+ void LinkPrefabRecursive(Actor* actor, Prefab* linkPrefab)
+ {
+ for (auto script : actor->Scripts)
+ LinkPrefab(script, linkPrefab);
+ for (auto child : actor->Children)
+ LinkPrefab(child, linkPrefab);
+ }
}
Actor::Actor(const SpawnParams& params)
@@ -1185,11 +1224,15 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
}
else if (!parent && parentId.IsValid())
{
+ // Skip warning if object was mapped to empty id (intentionally ignored)
Guid tmpId;
- if (_prefabObjectID.IsValid())
- LOG(Warning, "Missing parent actor {0} for \'{1}\', prefab object {2}", parentId, ToString(), _prefabObjectID);
- else if (!modifier->IdsMapping.TryGet(parentId, tmpId) || tmpId.IsValid()) // Skip warning if object was mapped to empty id (intentionally ignored)
- LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString());
+ if (!modifier->IdsMapping.TryGet(parentId, tmpId) || tmpId.IsValid())
+ {
+ if (_prefabObjectID.IsValid())
+ LOG(Warning, "Missing parent actor {0} for \'{1}\', prefab object {2}", parentId, ToString(), _prefabObjectID);
+ else
+ LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString());
+ }
}
}
}
@@ -1729,7 +1772,6 @@ void WriteObjectToBytes(SceneObject* obj, rapidjson_flax::StringBuffer& buffer,
{
// Create JSON
CompactJsonWriter writer(buffer);
-
writer.SceneObject(obj);
// Write json to output
@@ -1737,28 +1779,24 @@ void WriteObjectToBytes(SceneObject* obj, rapidjson_flax::StringBuffer& buffer,
output.WriteInt32((int32)buffer.GetSize());
output.WriteBytes((byte*)buffer.GetString(), (int32)buffer.GetSize());
- // Store order in parent. Makes life easier for editor to sync objects order on undo/redo actions.
- output.WriteInt32(obj->GetOrderInParent());
-
// Reuse string buffer
buffer.Clear();
}
bool Actor::ToBytes(const Array& actors, MemoryWriteStream& output)
{
- PROFILE_CPU();
if (actors.IsEmpty())
{
// Cannot serialize empty list
return true;
}
+ PROFILE_CPU();
+ PROFILE_MEM(Level);
// Collect object ids that exist in the serialized data to allow references mapping later
Array ids(actors.Count());
for (int32 i = 0; i < actors.Count(); i++)
{
- // By default we collect actors and scripts (they are ManagedObjects recognized by the id)
-
auto actor = actors[i];
if (!actor)
continue;
@@ -1766,7 +1804,8 @@ bool Actor::ToBytes(const Array& actors, MemoryWriteStream& output)
for (int32 j = 0; j < actor->Scripts.Count(); j++)
{
const auto script = actor->Scripts[j];
- ids.Add(script->GetID());
+ if (script)
+ ids.Add(script->GetID());
}
}
@@ -1776,23 +1815,28 @@ bool Actor::ToBytes(const Array& actors, MemoryWriteStream& output)
// Serialized objects ids (for references mapping)
output.Write(ids);
- // Objects data
+ // Objects data (JSON)
rapidjson_flax::StringBuffer buffer;
+ CompactJsonWriter writer(buffer);
+ writer.StartArray();
for (int32 i = 0; i < actors.Count(); i++)
{
Actor* actor = actors[i];
if (!actor)
continue;
-
- WriteObjectToBytes(actor, buffer, output);
-
+ writer.SceneObject(actor);
for (int32 j = 0; j < actor->Scripts.Count(); j++)
{
Script* script = actor->Scripts[j];
-
- WriteObjectToBytes(script, buffer, output);
+ if (!script)
+ continue;
+ writer.SceneObject(script);
}
}
+ writer.EndArray();
+ // TODO: compress json (LZ4) if it's big enough
+ output.WriteInt32((int32)buffer.GetSize());
+ output.WriteBytes((byte*)buffer.GetString(), (int32)buffer.GetSize());
return false;
}
@@ -1811,8 +1855,8 @@ Array Actor::ToBytes(const Array& actors)
bool Actor::FromBytes(const Span& data, Array& output, ISerializeModifier* modifier)
{
PROFILE_CPU();
+ PROFILE_MEM(Level);
output.Clear();
-
ASSERT(modifier);
if (data.Length() <= 0)
return true;
@@ -1828,50 +1872,71 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM
}
// Serialized objects ids (for references mapping)
+#if 0
Array ids;
stream.Read(ids);
int32 objectsCount = ids.Count();
+#else
+ int32 objectsCount;
+ stream.ReadInt32(&objectsCount);
+ stream.Move(objectsCount);
+#endif
if (objectsCount < 0)
return true;
+ // Load objects data (JSON)
+ int32 bufferSize;
+ stream.ReadInt32(&bufferSize);
+ const char* buffer = (const char*)stream.Move(bufferSize);
+ rapidjson_flax::Document document;
+ {
+ PROFILE_CPU_NAMED("Json.Parse");
+ document.Parse(buffer, bufferSize);
+ }
+ if (document.HasParseError())
+ {
+ Log::JsonParseException(document.GetParseError(), document.GetErrorOffset());
+ return true;
+ }
+
// Prepare
- Array order;
- order.Resize(objectsCount);
modifier->EngineBuild = engineBuild;
CollectionPoolCache::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
sceneObjects->Resize(objectsCount);
SceneObjectsFactory::Context context(modifier);
+ // Fix root linkage for prefab instances (eg. when user duplicates a sub-prefab actor but not a root one)
+ SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, document, modifier);
+ SceneObjectsFactory::SetupPrefabInstances(context, prefabSyncData);
+ for (auto& instance : context.Instances)
+ {
+ Guid prefabObjectId;
+ if (!JsonTools::GetGuidIfValid(prefabObjectId, document[instance.RootIndex], "PrefabObjectID"))
+ continue;
+
+ // Get the original object from prefab
+ SceneObject* prefabObject = instance.Prefab->GetDefaultInstance(prefabObjectId);
+ if (prefabObject && prefabObject->GetParent())
+ {
+ // Add empty mapping to parent object in prefab to prevent linking to it
+ auto prefabObjectParentId = prefabObject->GetParent()->GetPrefabObjectID();
+ instance.IdsMapping[prefabObjectParentId] = Guid::Empty;
+ modifier->IdsMapping[prefabObjectParentId] = Guid::Empty;
+ Guid nestedPrefabId, nestedPrefabObjectId;
+ if (instance.Prefab->GetNestedObject(prefabObjectParentId, nestedPrefabId, nestedPrefabObjectId))
+ {
+ instance.IdsMapping[nestedPrefabObjectId] = Guid::Empty;
+ modifier->IdsMapping[nestedPrefabObjectId] = Guid::Empty;
+ }
+ }
+ }
+
// Deserialize objects
Scripting::ObjectsLookupIdMapping.Set(&modifier->IdsMapping);
- auto startPos = stream.GetPosition();
for (int32 i = 0; i < objectsCount; i++)
{
- // Buffer
- int32 bufferSize;
- stream.ReadInt32(&bufferSize);
- const char* buffer = (const char*)stream.GetPositionHandle();
- stream.Move(bufferSize);
-
- // Order in parent
- int32 orderInParent;
- stream.ReadInt32(&orderInParent);
- order[i] = orderInParent;
-
- // Load JSON
- rapidjson_flax::Document document;
- {
- PROFILE_CPU_NAMED("Json.Parse");
- document.Parse(buffer, bufferSize);
- }
- if (document.HasParseError())
- {
- Log::JsonParseException(document.GetParseError(), document.GetErrorOffset());
- return true;
- }
-
- // Create object
- auto obj = SceneObjectsFactory::Spawn(context, document);
+ auto& objData = document[i];
+ auto obj = SceneObjectsFactory::Spawn(context, objData);
sceneObjects->At(i) = obj;
if (obj == nullptr)
{
@@ -1879,57 +1944,21 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM
continue;
}
obj->RegisterObject();
-
- // Add to results
Actor* actor = dynamic_cast(obj);
if (actor)
- {
output.Add(actor);
- }
}
- // TODO: optimize this to call json parsing only once per-object instead of twice (spawn + load)
- stream.SetPosition(startPos);
for (int32 i = 0; i < objectsCount; i++)
{
- // Buffer
- int32 bufferSize;
- stream.ReadInt32(&bufferSize);
- const char* buffer = (const char*)stream.GetPositionHandle();
- stream.Move(bufferSize);
-
- // Order in parent
- int32 orderInParent;
- stream.ReadInt32(&orderInParent);
-
- // Load JSON
- rapidjson_flax::Document document;
- {
- PROFILE_CPU_NAMED("Json.Parse");
- document.Parse(buffer, bufferSize);
- }
- if (document.HasParseError())
- {
- Log::JsonParseException(document.GetParseError(), document.GetErrorOffset());
- return true;
- }
-
- // Deserialize object
+ auto& objData = document[i];
auto obj = sceneObjects->At(i);
if (obj)
- SceneObjectsFactory::Deserialize(context, obj, document);
+ SceneObjectsFactory::Deserialize(context, obj, objData);
else
- SceneObjectsFactory::HandleObjectDeserializationError(document);
+ SceneObjectsFactory::HandleObjectDeserializationError(objData);
}
Scripting::ObjectsLookupIdMapping.Set(nullptr);
- // Update objects order
- //for (int32 i = 0; i < objectsCount; i++)
- {
- //SceneObject* obj = sceneObjects->At(i);
- // TODO: remove order from saved data?
- //obj->SetOrderInParent(order[i]);
- }
-
// Call events (only for parents because they will propagate events down the tree)
CollectionPoolCache::ScopeCache parents = ActorsCache::ActorsListCache.Get();
parents->EnsureCapacity(output.Count());
@@ -1940,6 +1969,32 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM
Actor* actor = parents->At(i);
if (actor->HasPrefabLink() && !actor->IsPrefabRoot())
{
+ // Find a prefab in which that object is a root to establish a new linkage
+ Guid currentPrefabId = actor->GetPrefabID(), currentObjectId = actor->GetPrefabObjectID();
+ RETRY:
+ auto prefab = Content::Load(currentPrefabId);
+ Guid nestedPrefabId, nestedObjectId;
+ if (prefab && prefab->GetNestedObject(currentObjectId, nestedPrefabId, nestedObjectId))
+ {
+ auto nestedPrefab = Content::Load(nestedPrefabId);
+ if (nestedPrefab)
+ {
+ auto nestedObject = (Actor*)nestedPrefab->GetDefaultInstance(nestedObjectId);
+ if (nestedObject && nestedObject->IsPrefabRoot())
+ {
+ // Change link to the nested prefab
+ actor->LinkPrefab(nestedPrefabId, nestedObjectId);
+ LinkPrefabRecursive(actor, nestedPrefab);
+ continue;
+ }
+ }
+
+ // Try deeper
+ currentPrefabId = nestedPrefabId;
+ currentObjectId = nestedObjectId;
+ goto RETRY;
+ }
+
actor->BreakPrefabLink();
}
}
@@ -1949,8 +2004,7 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM
}
for (int32 i = 0; i < parents->Count(); i++)
{
- Actor* actor = parents->At(i);
- actor->OnTransformChanged();
+ parents->At(i)->OnTransformChanged();
}
// Initialize actor that are spawned to scene or create managed instanced for others
diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp
index d6c2417c8..271609396 100644
--- a/Source/Engine/Level/Actors/Spline.cpp
+++ b/Source/Engine/Level/Actors/Spline.cpp
@@ -507,7 +507,7 @@ namespace
return;
Spline::Keyframe* prev = spline->Curve.GetKeyframes().Get();
Vector3 prevPos = transform.LocalToWorld(prev->Value.Translation);
- float distance = Vector3::Distance(prevPos, DebugDraw::GetViewPos());
+ Real distance = Vector3::Distance(prevPos, DebugDraw::GetViewPos());
if (distance < METERS_TO_UNITS(800)) // 800m
{
// Bezier curve
diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp
index 378b706ed..5136a6cce 100644
--- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp
+++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp
@@ -650,6 +650,11 @@ bool Prefab::ApplyAll(Actor* targetActor)
LOG(Warning, "Failed to create default prefab instance for the prefab asset.");
return true;
}
+ if (targetActor == _defaultInstance || targetActor->HasActorInHierarchy(_defaultInstance) || _defaultInstance->HasActorInHierarchy(targetActor))
+ {
+ LOG(Error, "Cannot apply changes to the prefab using default instance. Use manually spawned prefab instance instead.");
+ return true;
+ }
if (targetActor->GetPrefabObjectID() != GetRootObjectId())
{
LOG(Warning, "Applying prefab changes with modified root object. Root object id: {0}, new root: {1} (prefab object id: {2})", GetRootObjectId().ToString(), targetActor->ToString(), targetActor->GetPrefabObjectID());
diff --git a/Source/Engine/Level/Prefabs/Prefab.h b/Source/Engine/Level/Prefabs/Prefab.h
index 9e6023a82..cde99a2cb 100644
--- a/Source/Engine/Level/Prefabs/Prefab.h
+++ b/Source/Engine/Level/Prefabs/Prefab.h
@@ -60,12 +60,14 @@ public:
///
/// Requests the default prefab object instance. Deserializes the prefab objects from the asset. Skips if already done.
///
+ /// Default instances of the prefab are read-only and are used internally for objects serialization (prefab diff).
/// The root of the prefab object loaded from the prefab. Contains the default values. It's not added to gameplay but deserialized with postLoad and init event fired.
API_FUNCTION() Actor* GetDefaultInstance();
///
/// Requests the default prefab object instance. Deserializes the prefab objects from the asset. Skips if already done.
///
+ /// Default instances of the prefab are read-only and are used internally for objects serialization (prefab diff).
/// The ID of the object to get from prefab default object. It can be one of the child-actors or any script that exists in the prefab. Methods returns root if id is empty.
/// The object of the prefab loaded from the prefab. Contains the default values. It's not added to gameplay but deserialized with postLoad and init event fired.
API_FUNCTION() SceneObject* GetDefaultInstance(API_PARAM(Ref) const Guid& objectId);
diff --git a/Source/Engine/Level/SceneObject.cpp b/Source/Engine/Level/SceneObject.cpp
index 1a447af15..f6a14ae5d 100644
--- a/Source/Engine/Level/SceneObject.cpp
+++ b/Source/Engine/Level/SceneObject.cpp
@@ -61,7 +61,7 @@ void SceneObject::BreakPrefabLink()
String SceneObject::GetNamePath(Char separatorChar) const
{
- Array names;
+ Array> names;
const Actor* a = dynamic_cast(this);
if (!a)
a = GetParent();
diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp
index ced964436..2ee14508a 100644
--- a/Source/Engine/Networking/NetworkReplicator.cpp
+++ b/Source/Engine/Networking/NetworkReplicator.cpp
@@ -1863,7 +1863,7 @@ void NetworkInternal::NetworkReplicatorClientConnected(NetworkClient* client)
{
auto& item = it->Item;
ScriptingObject* obj = item.Object.Get();
- if (!obj || !item.Spawned || item.Role != NetworkObjectRole::OwnedAuthoritative)
+ if (!obj || item.Role != NetworkObjectRole::OwnedAuthoritative)
continue;
// Mark this client as missing cached data
diff --git a/Source/Engine/Renderer/GBufferPass.cpp b/Source/Engine/Renderer/GBufferPass.cpp
index e62970f83..6b3a655c0 100644
--- a/Source/Engine/Renderer/GBufferPass.cpp
+++ b/Source/Engine/Renderer/GBufferPass.cpp
@@ -425,7 +425,7 @@ void GBufferPass::DrawSky(RenderContext& renderContext, GPUContext* context)
BoundingSphere frustumBounds;
renderContext.View.CullingFrustum.GetSphere(frustumBounds);
origin = frustumBounds.Center;
- size = frustumBounds.Radius;
+ size = (float)frustumBounds.Radius;
}
Matrix::Scaling(size / ((float)box.GetSize().Y * 0.5f) * 0.95f, m1); // Scale to fit whole view frustum
Matrix::CreateWorld(origin, Float3::Up, Float3::Backward, m2); // Rotate sphere model
diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp
index 9737ae509..95e4ce0dc 100644
--- a/Source/Engine/Renderer/RenderList.cpp
+++ b/Source/Engine/Renderer/RenderList.cpp
@@ -747,7 +747,7 @@ void RenderList::AddDrawCall(const RenderContextBatch& renderContextBatch, DrawP
if (drawModes != DrawPass::None &&
(staticFlags & renderContext.View.StaticFlagsMask) == renderContext.View.StaticFlagsCompare &&
renderContext.View.CullingFrustum.Intersects(bounds) &&
- RenderTools::ComputeBoundsScreenRadiusSquared(bounds.Center, bounds.Radius, renderContext.View) * (renderContext.View.ScreenSize.X * renderContext.View.ScreenSize.Y) >= minObjectPixelSizeSq)
+ RenderTools::ComputeBoundsScreenRadiusSquared(bounds.Center, (float)bounds.Radius, renderContext.View) * (renderContext.View.ScreenSize.X * renderContext.View.ScreenSize.Y) >= minObjectPixelSizeSq)
{
renderContext.List->ShadowDepthDrawCallsList.Indices.Add(index);
}
diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp
index bbcd7de57..02358fac0 100644
--- a/Source/Engine/Scripting/BinaryModule.cpp
+++ b/Source/Engine/Scripting/BinaryModule.cpp
@@ -17,7 +17,6 @@
#include "FlaxEngine.Gen.h"
#include "Scripting.h"
#include "Events.h"
-#include "Internal/StdTypesContainer.h"
Dictionary, void(*)(ScriptingObject*, void*, bool)> ScriptingEvents::EventsTable;
Delegate, ScriptingTypeHandle, StringView> ScriptingEvents::Event;
@@ -32,6 +31,18 @@ ManagedBinaryModule* GetBinaryModuleCorlib()
#endif
}
+MMethod* MClass::FindMethod(const char* name, int32 numParams, bool checkBaseClasses) const
+{
+ MMethod* method = GetMethod(name, numParams);
+ if (!method && checkBaseClasses)
+ {
+ MClass* base = GetBaseClass();
+ if (base)
+ method = base->FindMethod(name, numParams, true);
+ }
+ return method;
+}
+
ScriptingTypeHandle::ScriptingTypeHandle(const ScriptingTypeInitializer& initializer)
: Module(initializer.Module)
, TypeIndex(initializer.TypeIndex)
@@ -835,61 +846,17 @@ namespace
}
return nullptr;
}
-
- bool VariantTypeEquals(const VariantType& type, MType* mType, bool isOut = false)
- {
- MClass* mClass = MCore::Type::GetClass(mType);
- MClass* variantClass = MUtils::GetClass(type);
- if (variantClass != mClass)
- {
- // Hack for Vector2/3/4 which alias with Float2/3/4 or Double2/3/4 (depending on USE_LARGE_WORLDS)
- const auto& stdTypes = *StdTypesContainer::Instance();
- if (mClass == stdTypes.Vector2Class && (type.Type == VariantType::Float2 || type.Type == VariantType::Double2))
- return true;
- if (mClass == stdTypes.Vector3Class && (type.Type == VariantType::Float3 || type.Type == VariantType::Double3))
- return true;
- if (mClass == stdTypes.Vector4Class && (type.Type == VariantType::Float4 || type.Type == VariantType::Double4))
- return true;
-
- return false;
- }
- return true;
- }
}
#endif
-MMethod* ManagedBinaryModule::FindMethod(MClass* mclass, const ScriptingTypeMethodSignature& signature)
+MMethod* ManagedBinaryModule::FindMethod(const MClass* mclass, const ScriptingTypeMethodSignature& signature)
{
#if USE_CSHARP
- if (!mclass)
- return nullptr;
- const auto& methods = mclass->GetMethods();
- for (MMethod* method : methods)
- {
- if (method->IsStatic() != signature.IsStatic)
- continue;
- if (method->GetName() != signature.Name)
- continue;
- if (method->GetParametersCount() != signature.Params.Count())
- continue;
- bool isValid = true;
- for (int32 paramIdx = 0; paramIdx < signature.Params.Count(); paramIdx++)
- {
- auto& param = signature.Params[paramIdx];
- MType* type = method->GetParameterType(paramIdx);
- if (param.IsOut != method->GetParameterIsOut(paramIdx) ||
- !VariantTypeEquals(param.Type, type, param.IsOut))
- {
- isValid = false;
- break;
- }
- }
- if (isValid && VariantTypeEquals(signature.ReturnType, method->GetReturnType()))
- return method;
- }
-#endif
+ return mclass ? mclass->GetMethod(signature) : nullptr;
+#else
return nullptr;
+#endif
}
#if USE_CSHARP
diff --git a/Source/Engine/Scripting/BinaryModule.h b/Source/Engine/Scripting/BinaryModule.h
index 1da35401b..db346b13c 100644
--- a/Source/Engine/Scripting/BinaryModule.h
+++ b/Source/Engine/Scripting/BinaryModule.h
@@ -23,7 +23,7 @@ struct ScriptingTypeMethodSignature
StringAnsiView Name;
VariantType ReturnType;
- bool IsStatic;
+ bool IsStatic = false;
Array> Params;
};
@@ -322,7 +322,7 @@ public:
#endif
static ScriptingObject* ManagedObjectSpawn(const ScriptingObjectSpawnParams& params);
- static MMethod* FindMethod(MClass* mclass, const ScriptingTypeMethodSignature& signature);
+ static MMethod* FindMethod(const MClass* mclass, const ScriptingTypeMethodSignature& signature);
#if USE_CSHARP
static ManagedBinaryModule* FindModule(const MClass* klass);
static ScriptingTypeHandle FindType(const MClass* klass);
diff --git a/Source/Engine/Scripting/ManagedCLR/MClass.h b/Source/Engine/Scripting/ManagedCLR/MClass.h
index 61273dae7..ea26451c1 100644
--- a/Source/Engine/Scripting/ManagedCLR/MClass.h
+++ b/Source/Engine/Scripting/ManagedCLR/MClass.h
@@ -188,11 +188,11 @@ public:
MClass* GetBaseClass() const;
///
- /// Checks if this class is a sub class of the specified class (including any derived types).
+ /// Checks if this class is a subclass of the specified class (including any derived types).
///
/// The class.
/// True if check interfaces, otherwise just base class.
- /// True if this class is a sub class of the specified class.
+ /// True if this class is a subclass of the specified class.
bool IsSubClassOf(const MClass* klass, bool checkInterfaces = false) const;
///
@@ -206,7 +206,7 @@ public:
/// Checks is the provided object instance of this class' type.
///
/// The object to check.
- /// True if object is an instance the this class.
+ /// True if object is an instance this class.
bool IsInstanceOfType(MObject* object) const;
///
@@ -227,17 +227,7 @@ public:
/// The method parameters count.
/// True if check base classes when searching for the given method.
/// The method or null if failed to find it.
- MMethod* FindMethod(const char* name, int32 numParams, bool checkBaseClasses = true) const
- {
- MMethod* method = GetMethod(name, numParams);
- if (!method && checkBaseClasses)
- {
- MClass* base = GetBaseClass();
- if (base)
- method = base->FindMethod(name, numParams, true);
- }
- return method;
- }
+ MMethod* FindMethod(const char* name, int32 numParams, bool checkBaseClasses = true) const;
///
/// Returns an object referencing a method with the specified name and number of parameters.
@@ -248,6 +238,13 @@ public:
/// The method or null if failed to get it.
MMethod* GetMethod(const char* name, int32 numParams = 0) const;
+ ///
+ /// Returns an object referencing a method with the specified signature.
+ ///
+ /// The method signature.
+ /// The method or null if failed to get it.
+ MMethod* GetMethod(const struct ScriptingTypeMethodSignature& signature) const;
+
///
/// Returns all methods belonging to this class.
///
@@ -271,7 +268,7 @@ public:
const Array& GetFields() const;
///
- /// Returns an object referencing a event with the specified name.
+ /// Returns an object referencing an event with the specified name.
///
/// The event name.
/// The event object.
diff --git a/Source/Engine/Scripting/ManagedCLR/MMethod.h b/Source/Engine/Scripting/ManagedCLR/MMethod.h
index 51264d1b8..f6e6b37e2 100644
--- a/Source/Engine/Scripting/ManagedCLR/MMethod.h
+++ b/Source/Engine/Scripting/ManagedCLR/MMethod.h
@@ -29,6 +29,7 @@ protected:
int32 _paramsCount;
mutable void* _returnType;
mutable Array> _parameterTypes;
+ mutable uint64 _parameterOuts = 0;
void CacheSignature() const;
#else
StringAnsiView _name;
diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp
index 69fb5aa08..0d7617e0d 100644
--- a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp
+++ b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp
@@ -845,7 +845,6 @@ MClass* MUtils::GetClass(const VariantType& value)
auto mclass = Scripting::FindClass(StringAnsiView(value.TypeName));
if (mclass)
return mclass;
- const auto& stdTypes = *StdTypesContainer::Instance();
switch (value.Type)
{
case VariantType::Void:
@@ -891,25 +890,25 @@ MClass* MUtils::GetClass(const VariantType& value)
case VariantType::Double4:
return Double4::TypeInitializer.GetClass();
case VariantType::Color:
- return stdTypes.ColorClass;
+ return Color::TypeInitializer.GetClass();
case VariantType::Guid:
- return stdTypes.GuidClass;
+ return GetBinaryModuleCorlib()->Assembly->GetClass("System.Guid");
case VariantType::Typename:
- return stdTypes.TypeClass;
+ return GetBinaryModuleCorlib()->Assembly->GetClass("System.Type");
case VariantType::BoundingBox:
- return stdTypes.BoundingBoxClass;
+ return BoundingBox::TypeInitializer.GetClass();
case VariantType::BoundingSphere:
- return stdTypes.BoundingSphereClass;
+ return BoundingSphere::TypeInitializer.GetClass();
case VariantType::Quaternion:
- return stdTypes.QuaternionClass;
+ return Quaternion::TypeInitializer.GetClass();
case VariantType::Transform:
- return stdTypes.TransformClass;
+ return Transform::TypeInitializer.GetClass();
case VariantType::Rectangle:
- return stdTypes.RectangleClass;
+ return Rectangle::TypeInitializer.GetClass();
case VariantType::Ray:
- return stdTypes.RayClass;
+ return Ray::TypeInitializer.GetClass();
case VariantType::Matrix:
- return stdTypes.MatrixClass;
+ return Matrix::TypeInitializer.GetClass();
case VariantType::Array:
if (value.TypeName)
{
@@ -1202,8 +1201,7 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, MType* type, bool& failed)
if (value.Type.Type != VariantType::Array)
return nullptr;
MObject* object = BoxVariant(value);
- auto typeStr = MCore::Type::ToString(type);
- if (object && !MCore::Object::GetClass(object)->IsSubClassOf(MCore::Type::GetClass(type)))
+ if (object && MCore::Type::GetClass(type) != MCore::Array::GetArrayClass((MArray*)object))
object = nullptr;
return object;
}
@@ -1238,6 +1236,29 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, MType* type, bool& failed)
return nullptr;
}
+bool MUtils::VariantTypeEquals(const VariantType& type, MType* mType, bool isOut)
+{
+ MClass* mClass = MCore::Type::GetClass(mType);
+ MClass* variantClass = MUtils::GetClass(type);
+ if (variantClass != mClass)
+ {
+ // Hack for Vector2/3/4 which alias with Float2/3/4 or Double2/3/4 (depending on USE_LARGE_WORLDS)
+ if (mClass->GetFullName() == StringAnsiView("FlaxEngine.Vector2", 18) && (type.Type == VariantType::Float2 || type.Type == VariantType::Double2))
+ return true;
+ if (mClass->GetFullName() == StringAnsiView("FlaxEngine.Vector3", 18) && (type.Type == VariantType::Float3 || type.Type == VariantType::Double3))
+ return true;
+ if (mClass->GetFullName() == StringAnsiView("FlaxEngine.Vector4", 18) && (type.Type == VariantType::Float4 || type.Type == VariantType::Double4))
+ return true;
+
+ // Arrays
+ if (type == VariantType::Array && type.GetElementType() == VariantType::Object)
+ return MCore::Type::GetType(mType) == MTypes::Array;
+
+ return false;
+ }
+ return true;
+}
+
MObject* MUtils::ToManaged(const Version& value)
{
#if USE_NETCORE
diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.h b/Source/Engine/Scripting/ManagedCLR/MUtils.h
index f642681d2..5598dbee0 100644
--- a/Source/Engine/Scripting/ManagedCLR/MUtils.h
+++ b/Source/Engine/Scripting/ManagedCLR/MUtils.h
@@ -621,6 +621,7 @@ namespace MUtils
#endif
extern void* VariantToManagedArgPtr(Variant& value, MType* type, bool& failed);
+ extern bool VariantTypeEquals(const VariantType& type, MType* mType, bool isOut = false);
extern MObject* ToManaged(const Version& value);
extern Version ToNative(MObject* value);
diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp
index 4be0ce1a1..65dd09b44 100644
--- a/Source/Engine/Scripting/Runtime/DotNet.cpp
+++ b/Source/Engine/Scripting/Runtime/DotNet.cpp
@@ -1062,10 +1062,39 @@ MClass* MClass::GetElementClass() const
MMethod* MClass::GetMethod(const char* name, int32 numParams) const
{
GetMethods();
- for (int32 i = 0; i < _methods.Count(); i++)
+ for (MMethod* method : _methods)
{
- if (_methods[i]->GetParametersCount() == numParams && _methods[i]->GetName() == name)
- return _methods[i];
+ if (method->GetParametersCount() == numParams && method->GetName() == name)
+ return method;
+ }
+ return nullptr;
+}
+
+MMethod* MClass::GetMethod(const ScriptingTypeMethodSignature& signature) const
+{
+ GetMethods();
+ for (MMethod* method : _methods)
+ {
+ if (method->IsStatic() != signature.IsStatic)
+ continue;
+ if (method->GetName() != signature.Name)
+ continue;
+ if (method->GetParametersCount() != signature.Params.Count())
+ continue;
+ bool isValid = true;
+ for (int32 paramIdx = 0; paramIdx < signature.Params.Count(); paramIdx++)
+ {
+ auto& param = signature.Params[paramIdx];
+ MType* type = method->GetParameterType(paramIdx);
+ if (param.IsOut != method->GetParameterIsOut(paramIdx) ||
+ !MUtils::VariantTypeEquals(param.Type, type, param.IsOut))
+ {
+ isValid = false;
+ break;
+ }
+ }
+ if (isValid && (signature.ReturnType.Type == VariantType::Null || MUtils::VariantTypeEquals(signature.ReturnType, method->GetReturnType())))
+ return method;
}
return nullptr;
}
@@ -1485,13 +1514,16 @@ void MMethod::CacheSignature() const
static void* GetMethodReturnTypePtr = GetStaticMethodPointer(TEXT("GetMethodReturnType"));
static void* GetMethodParameterTypesPtr = GetStaticMethodPointer(TEXT("GetMethodParameterTypes"));
+ static void* GetMethodParameterIsOutPtr = GetStaticMethodPointer(TEXT("GetMethodParameterIsOut"));
_returnType = CallStaticMethod(GetMethodReturnTypePtr, _handle);
+ _parameterOuts = 0;
if (_paramsCount != 0)
{
void** parameterTypeHandles;
CallStaticMethod(GetMethodParameterTypesPtr, _handle, ¶meterTypeHandles);
_parameterTypes.Set(parameterTypeHandles, _paramsCount);
MCore::GC::FreeMemory(parameterTypeHandles);
+ _parameterOuts = CallStaticMethod(GetMethodParameterIsOutPtr, _handle);
}
_hasCachedSignature = true;
@@ -1558,9 +1590,7 @@ bool MMethod::GetParameterIsOut(int32 paramIdx) const
if (!_hasCachedSignature)
CacheSignature();
ASSERT_LOW_LAYER(paramIdx >= 0 && paramIdx < _paramsCount);
- // TODO: cache GetParameterIsOut maybe?
- static void* GetMethodParameterIsOutPtr = GetStaticMethodPointer(TEXT("GetMethodParameterIsOut"));
- return CallStaticMethod(GetMethodParameterIsOutPtr, _handle, paramIdx);
+ return _parameterOuts & (1ull << paramIdx);
}
bool MMethod::HasAttribute(const MClass* klass) const
diff --git a/Source/Engine/Scripting/Runtime/Mono.cpp b/Source/Engine/Scripting/Runtime/Mono.cpp
index 06392f932..1449d405f 100644
--- a/Source/Engine/Scripting/Runtime/Mono.cpp
+++ b/Source/Engine/Scripting/Runtime/Mono.cpp
@@ -1357,6 +1357,11 @@ MMethod* MClass::GetMethod(const char* name, int32 numParams) const
return method;
}
+MMethod* MClass::GetMethod(const ScriptingTypeMethodSignature& signature) const
+{
+ return GetMethod(signature.Name.Get(), signature.Params.Count());
+}
+
const Array& MClass::GetMethods() const
{
if (_hasCachedMethods)
diff --git a/Source/Engine/Scripting/Runtime/None.cpp b/Source/Engine/Scripting/Runtime/None.cpp
index 1ddaeae8e..178e98444 100644
--- a/Source/Engine/Scripting/Runtime/None.cpp
+++ b/Source/Engine/Scripting/Runtime/None.cpp
@@ -363,6 +363,11 @@ MMethod* MClass::GetMethod(const char* name, int32 numParams) const
return nullptr;
}
+MMethod* MClass::GetMethod(const ScriptingTypeMethodSignature& signature) const
+{
+ return nullptr;
+}
+
const Array& MClass::GetMethods() const
{
_hasCachedMethods = true;
diff --git a/Source/Engine/Serialization/JsonTools.cpp b/Source/Engine/Serialization/JsonTools.cpp
index 1e5b8fff6..9d89fe034 100644
--- a/Source/Engine/Serialization/JsonTools.cpp
+++ b/Source/Engine/Serialization/JsonTools.cpp
@@ -72,6 +72,13 @@ void ChangeIds(rapidjson_flax::Value& obj, rapidjson_flax::Document& document, c
}
}
+void JsonTools::MergeObjects(Value& target, Value& source, Value::AllocatorType& allocator)
+{
+ ASSERT(target.IsObject() && source.IsObject());
+ for (auto itr = source.MemberBegin(); itr != source.MemberEnd(); ++itr)
+ target.AddMember(itr->name, itr->value, allocator);
+}
+
void JsonTools::ChangeIds(Document& doc, const Dictionary& mapping)
{
if (mapping.IsEmpty())
@@ -235,6 +242,14 @@ Plane JsonTools::GetPlane(const Value& value)
return result;
}
+Rectangle JsonTools::GetRectangle(const Value& value)
+{
+ return Rectangle(
+ GetVector2(value, "Location", Vector2::Zero),
+ GetVector2(value, "Size", Vector2::Zero)
+ );
+}
+
BoundingSphere JsonTools::GetBoundingSphere(const Value& value)
{
BoundingSphere result;
@@ -283,3 +298,14 @@ DateTime JsonTools::GetDateTime(const Value& value)
{
return DateTime(value.GetInt64());
}
+
+bool JsonTools::GetGuidIfValid(Guid& result, const Value& node, const char* name)
+{
+ auto member = node.FindMember(name);
+ if (member != node.MemberEnd())
+ {
+ result = GetGuid(member->value);
+ return result.IsValid();
+ }
+ return false;
+}
diff --git a/Source/Engine/Serialization/JsonTools.h b/Source/Engine/Serialization/JsonTools.h
index 0d5c7115d..6e744fcc2 100644
--- a/Source/Engine/Serialization/JsonTools.h
+++ b/Source/Engine/Serialization/JsonTools.h
@@ -35,13 +35,7 @@ public:
MergeObjects(target, source, target.GetAllocator());
}
- static void MergeObjects(Value& target, Value& source, Value::AllocatorType& allocator)
- {
- ASSERT(target.IsObject() && source.IsObject());
- for (auto itr = source.MemberBegin(); itr != source.MemberEnd(); ++itr)
- target.AddMember(itr->name, itr->value, allocator);
- }
-
+ static void MergeObjects(Value& target, Value& source, Value::AllocatorType& allocator);
static void ChangeIds(Document& doc, const Dictionary& mapping);
public:
@@ -75,11 +69,9 @@ public:
static Float2 GetFloat2(const Value& value);
static Float3 GetFloat3(const Value& value);
static Float4 GetFloat4(const Value& value);
-
static Double2 GetDouble2(const Value& value);
static Double3 GetDouble3(const Value& value);
static Double4 GetDouble4(const Value& value);
-
static Color GetColor(const Value& value);
static Quaternion GetQuaternion(const Value& value);
static Ray GetRay(const Value& value);
@@ -87,15 +79,7 @@ public:
static Transform GetTransform(const Value& value);
static void GetTransform(Transform& result, const Value& value);
static Plane GetPlane(const Value& value);
-
- static Rectangle GetRectangle(const Value& value)
- {
- return Rectangle(
- GetVector2(value, "Location", Vector2::Zero),
- GetVector2(value, "Size", Vector2::Zero)
- );
- }
-
+ static Rectangle GetRectangle(const Value& value);
static BoundingSphere GetBoundingSphere(const Value& value);
static BoundingBox GetBoundingBox(const Value& value);
static Guid GetGuid(const Value& value);
@@ -176,16 +160,7 @@ public:
return member != node.MemberEnd() ? GetGuid(member->value) : Guid::Empty;
}
- FORCE_INLINE static bool GetGuidIfValid(Guid& result, const Value& node, const char* name)
- {
- auto member = node.FindMember(name);
- if (member != node.MemberEnd())
- {
- result = GetGuid(member->value);
- return result.IsValid();
- }
- return false;
- }
+ static bool GetGuidIfValid(Guid& result, const Value& node, const char* name);
public:
FORCE_INLINE static void GetBool(bool& result, const Value& node, const char* name)
diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp
index cadc9821c..164fce747 100644
--- a/Source/Engine/Terrain/TerrainPatch.cpp
+++ b/Source/Engine/Terrain/TerrainPatch.cpp
@@ -439,7 +439,7 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh
// Calculate normals for quad two vertices
Float3 n0 = Float3::Normalize((v00 - v01) ^ (v01 - v10));
Float3 n1 = Float3::Normalize((v11 - v10) ^ (v10 - v01));
- Float3 n2 = n0 + n1;
+ Float3 n2 = Float3::Normalize(n0 + n1);
// Apply normal to each vertex using it
normalsPerVertex[i00] += n1;
@@ -2572,9 +2572,9 @@ void TerrainPatch::ExtractCollisionGeometry(Array& vertexBuffer, Array& vertexBuffer, Array',
'<',
+ '=',
};
///
diff --git a/Source/Engine/UI/UICanvas.cs b/Source/Engine/UI/UICanvas.cs
index df542aacf..4a6bb7ce1 100644
--- a/Source/Engine/UI/UICanvas.cs
+++ b/Source/Engine/UI/UICanvas.cs
@@ -61,6 +61,9 @@ namespace FlaxEngine
///
public override bool CanRender()
{
+ if (!Canvas)
+ return false;
+
// Sync with canvas options
Location = Canvas.RenderLocation;
Order = Canvas.Order;
diff --git a/Source/Engine/Visject/ShaderGraph.cpp b/Source/Engine/Visject/ShaderGraph.cpp
index b297ad91e..1df4ea439 100644
--- a/Source/Engine/Visject/ShaderGraph.cpp
+++ b/Source/Engine/Visject/ShaderGraph.cpp
@@ -493,35 +493,26 @@ void ShaderGenerator::ProcessGroupPacking(Box* box, Node* node, Value& value)
// Unpack
case 30:
{
- Box* b = node->GetBox(0);
- Value v = tryGetValue(b, Float2::Zero).AsFloat2();
-
- int32 subIndex = box->ID - 1;
- ASSERT(subIndex >= 0 && subIndex < 2);
-
- value = Value(ValueType::Float, v.Value + _subs[subIndex]);
+ value = tryGetValue(node->GetBox(0), Float2::Zero).AsFloat2();
+ const int32 subIndex = box->ID - 1;
+ if (subIndex >= 0 && subIndex < 2)
+ value = Value(ValueType::Float, value.Value + _subs[subIndex]);
break;
}
case 31:
{
- Box* b = node->GetBox(0);
- Value v = tryGetValue(b, Float3::Zero).AsFloat3();
-
- int32 subIndex = box->ID - 1;
- ASSERT(subIndex >= 0 && subIndex < 3);
-
- value = Value(ValueType::Float, v.Value + _subs[subIndex]);
+ value = tryGetValue(node->GetBox(0), Float3::Zero).AsFloat3();
+ const int32 subIndex = box->ID - 1;
+ if (subIndex >= 0 && subIndex < 3)
+ value = Value(ValueType::Float, value.Value + _subs[subIndex]);
break;
}
case 32:
{
- Box* b = node->GetBox(0);
- Value v = tryGetValue(b, Float4::Zero).AsFloat4();
-
- int32 subIndex = box->ID - 1;
- ASSERT(subIndex >= 0 && subIndex < 4);
-
- value = Value(ValueType::Float, v.Value + _subs[subIndex]);
+ value = tryGetValue(node->GetBox(0), Float4::Zero).AsFloat4();
+ const int32 subIndex = box->ID - 1;
+ if (subIndex >= 0 && subIndex < 4)
+ value = Value(ValueType::Float, value.Value + _subs[subIndex]);
break;
}
case 33:
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
index 760e7a926..6dd2aba6f 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
@@ -201,13 +201,15 @@ namespace Flax.Build.Bindings
if (apiType.IsStruct && !apiType.IsPod && !CppUsedNonPodTypes.Contains(apiType))
CppUsedNonPodTypes.Add(apiType);
+ var fullname = apiType.FullNameManaged;
if (apiType.IsEnum)
- return $"Variant::Enum(VariantType(VariantType::Enum, StringAnsiView(\"{apiType.FullNameManaged}\", {apiType.FullNameManaged.Length})), {value})";
+ return $"Variant::Enum(VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length})), {value})";
if (apiType.IsStruct && !CppInBuildVariantStructures.Contains(apiType.Name))
- if (typeInfo.IsPtr)
- return $"Variant::Structure(VariantType(VariantType::Structure, StringAnsiView(\"{apiType.FullNameManaged}\", {apiType.FullNameManaged.Length})), *{value})";
- else
- return $"Variant::Structure(VariantType(VariantType::Structure, StringAnsiView(\"{apiType.FullNameManaged}\", {apiType.FullNameManaged.Length})), {value})";
+ {
+ if (apiType.IsInBuild)
+ return $"Variant::Structure(VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length})), {(typeInfo.IsPtr ? "*" + value : value)})";
+ return $"Variant::Structure(VariantType(VariantType::Structure, {apiType.FullNameNative}::TypeInitializer.GetType()), {(typeInfo.IsPtr ? "*" + value : value)})";
+ }
}
if (typeInfo.IsPtr && typeInfo.IsConst)
@@ -215,6 +217,84 @@ namespace Flax.Build.Bindings
return $"Variant({value})";
}
+ public static string GenerateCppWrapperNativeToVariantType(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller)
+ {
+ // In-built types
+ switch (typeInfo.Type)
+ {
+ case "void": return "VariantType(VariantType::Void)";
+ case "bool": return "VariantType(VariantType::Bool)";
+ case "int":
+ case "int32": return "VariantType(VariantType::Int)";
+ case "uint":
+ case "uint32": return "VariantType(VariantType::Uint)";
+ case "int64": return "VariantType(VariantType::Int64)";
+ case "uint64": return "VariantType(VariantType::Uint64)";
+ case "Real": return "VariantType(VariantType::Real)";
+ case "float": return "VariantType(VariantType::Float)";
+ case "double": return "VariantType(VariantType::Double)";
+ case "StringAnsiView":
+ case "StringAnsi":
+ case "StringView":
+ case "String": return "VariantType(VariantType::String)";
+ case "Guid": return "VariantType(VariantType::Guid)";
+ case "Asset": return "VariantType(VariantType::Asset)";
+ case "Float2": return "VariantType(VariantType::Float2)";
+ case "Float3": return "VariantType(VariantType::Float3)";
+ case "Float4": return "VariantType(VariantType::Float4)";
+ case "Double2": return "VariantType(VariantType::Double2)";
+ case "Double3": return "VariantType(VariantType::Double3)";
+ case "Double4": return "VariantType(VariantType::Double4)";
+ case "Vector2": return "VariantType(VariantType::Vector2)";
+ case "Vector3": return "VariantType(VariantType::Vector3)";
+ case "Vector4": return "VariantType(VariantType::Vector4)";
+ case "Int2": return "VariantType(VariantType::Int2)";
+ case "Int3": return "VariantType(VariantType::Int3)";
+ case "Int4": return "VariantType(VariantType::Int4)";
+ case "Color": return "VariantType(VariantType::Color)";
+ case "BoundingBox": return "VariantType(VariantType::BoundingBox)";
+ case "BoundingSphere": return "VariantType(VariantType::BoundingSphere)";
+ case "Quaternion": return "VariantType(VariantType::Quaternion)";
+ case "Transform": return "VariantType(VariantType::Transform)";
+ case "Rectangle": return "VariantType(VariantType::Rectangle)";
+ case "Ray": return "VariantType(VariantType::Ray)";
+ case "Matrix": return "VariantType(VariantType::Matrix)";
+ case "Type": return "VariantType(VariantType::Typename)";
+ }
+
+ // Array
+ if (typeInfo.IsArray)
+ return "VariantType(VariantType::Array)";
+ if ((typeInfo.Type == "Array" || typeInfo.Type == "Span") && typeInfo.GenericArgs != null)
+ {
+ var elementType = FindApiTypeInfo(buildData, typeInfo.GenericArgs[0], caller);
+ var elementName = $"{(elementType != null ? elementType.FullNameManaged : typeInfo.GenericArgs[0].Type)}[]";
+ return $"VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length}))";
+ }
+ if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null)
+ return "VariantType(VariantType::Dictionary)";
+
+ // Scripting type
+ var apiType = FindApiTypeInfo(buildData, typeInfo, caller);
+ if (apiType != null)
+ {
+ var fullname = apiType.FullNameManaged;
+ if (apiType.IsEnum)
+ return $"VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length}))";
+ if (apiType.IsStruct)
+ {
+ if (apiType.IsInBuild)
+ return $"VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length}))";
+ return $"VariantType(VariantType::Structure, {apiType.FullNameNative}::TypeInitializer.GetType())";
+ }
+ if (apiType.IsClass)
+ return $"VariantType(VariantType::Object, {apiType.FullNameNative}::TypeInitializer.GetType())";
+ }
+
+ // Unknown
+ return "VariantType()";
+ }
+
public static string GenerateCppWrapperVariantToNative(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, string value)
{
if (typeInfo.Type == "Variant")
@@ -393,7 +473,7 @@ namespace Flax.Build.Bindings
return "Scripting::FindClass(\"" + managedType + "\")";
}
- private static string GenerateCppGetNativeType(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, FunctionInfo functionInfo)
+ private static string GenerateCppGetNativeType(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, FunctionInfo functionInfo = null)
{
CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h");
@@ -922,6 +1002,22 @@ namespace Flax.Build.Bindings
if (typeInfo.Type == "BytesContainer" && typeInfo.GenericArgs == null)
return $"MUtils::ToArray({value})";
+ // Dictionary
+ if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null)
+ {
+ CppIncludeFiles.Add("Engine/Scripting/Internal/ManagedDictionary.h");
+ var keyClass = GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[0], caller);
+ var valueClass = GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[1], caller);
+ return $"ManagedDictionary::ToManaged({value}, {keyClass}, {valueClass})";
+ }
+
+ // HashSet
+ if (typeInfo.Type == "HashSet" && typeInfo.GenericArgs != null)
+ {
+ // TODO: automatic converting managed-native for HashSet
+ throw new NotImplementedException("TODO: converting managed HashSet to native");
+ }
+
// Construct native typename for MUtils template argument
var nativeType = new StringBuilder(64);
nativeType.Append(typeInfo.Type);
@@ -1751,7 +1847,23 @@ namespace Flax.Build.Bindings
{
if (!functionInfo.IsVirtual)
continue;
- contents.AppendLine($" scriptVTable[{scriptVTableIndex++}] = mclass->GetMethod(\"{functionInfo.Name}\", {functionInfo.Parameters.Count});");
+
+ // Don't use exact signature for parameter-less methods or the ones without duplicates
+ if (functionInfo.Parameters.Count == 0 || !classInfo.Functions.Any(x => x != functionInfo && x.Parameters.Count == functionInfo.Parameters.Count && x.Name == functionInfo.Name))
+ contents.AppendLine($" scriptVTable[{scriptVTableIndex++}] = mclass->GetMethod(\"{functionInfo.Name}\", {functionInfo.Parameters.Count});");
+ else
+ {
+ contents.AppendLine(" {");
+ contents.AppendLine(" ScriptingTypeMethodSignature signature;");
+ contents.AppendLine($" signature.Name = StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length});");
+ contents.AppendLine($" signature.Params.Resize({functionInfo.Parameters.Count});");
+ for (var i = 0; i < functionInfo.Parameters.Count; i++)
+ contents.AppendLine($" signature.Params[{i}] = {{ {GenerateCppWrapperNativeToVariantType(buildData, functionInfo.Parameters[i].Type, classInfo)}, {(functionInfo.Parameters[i].IsOut ? "true" : "false")} }};");
+ contents.AppendLine($" scriptVTable[{scriptVTableIndex++}] = mclass->GetMethod(signature);");
+ if (buildData.Configuration != TargetConfiguration.Release)
+ contents.AppendLine($" ASSERT(scriptVTable[{scriptVTableIndex - 1}]);");
+ contents.AppendLine(" }");
+ }
}
contents.AppendLine(" }");
contents.AppendLine("");
@@ -2701,10 +2813,22 @@ namespace Flax.Build.Bindings
{
contents.AppendLine(" Variant* parameters = nullptr;");
}
+ if (functionInfo.Parameters.Count != 0)
+ {
+ // Build method signature to find method using exact parameter types to match on name collisions
+ contents.AppendLine(" ScriptingTypeMethodSignature signature;");
+ contents.AppendLine($" signature.Name = StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length});");
+ contents.AppendLine($" signature.Params.Resize({functionInfo.Parameters.Count});");
+ for (var i = 0; i < functionInfo.Parameters.Count; i++)
+ contents.AppendLine($" signature.Params[{i}] = {{ parameters[{i}].Type, {(functionInfo.Parameters[i].IsOut ? "true" : "false")} }};");
+ }
contents.AppendLine(" auto typeHandle = Object->GetTypeHandle();");
contents.AppendLine(" while (typeHandle)");
contents.AppendLine(" {");
- contents.AppendLine($" auto method = typeHandle.Module->FindMethod(typeHandle, StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length}), {functionInfo.Parameters.Count});");
+ if (functionInfo.Parameters.Count == 0)
+ contents.AppendLine($" auto method = typeHandle.Module->FindMethod(typeHandle, StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length}), {functionInfo.Parameters.Count});");
+ else
+ contents.AppendLine(" auto method = typeHandle.Module->FindMethod(typeHandle, signature);");
contents.AppendLine(" if (method)");
contents.AppendLine(" {");
contents.AppendLine(" Variant __result;");
@@ -2977,7 +3101,9 @@ namespace Flax.Build.Bindings
header.Append($"{wrapperName}Array(const {valueType}* v, const int32 length)").AppendLine();
header.Append('{').AppendLine();
header.Append(" Variant result;").AppendLine();
- header.Append(" result.SetType(VariantType(VariantType::Array));").AppendLine();
+ var apiType = FindApiTypeInfo(buildData, valueType, moduleInfo);
+ var elementName = $"{(apiType != null ? apiType.FullNameManaged : valueType.Type)}[]";
+ header.Append($" result.SetType(VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length})));").AppendLine();
header.Append(" auto* array = reinterpret_cast*>(result.AsData);").AppendLine();
header.Append(" array->Resize(length);").AppendLine();
header.Append(" for (int32 i = 0; i < length; i++)").AppendLine();
@@ -3345,13 +3471,9 @@ namespace Flax.Build.Bindings
contents.AppendLine($"extern \"C\" BinaryModule* GetBinaryModule{binaryModuleName}()");
contents.AppendLine("{");
if (useCSharp)
- {
contents.AppendLine($" static NativeBinaryModule module(\"{binaryModuleName}\");");
- }
else
- {
contents.AppendLine($" static NativeOnlyBinaryModule module(\"{binaryModuleName}\");");
- }
contents.AppendLine(" return &module;");
contents.AppendLine("}");
if (project.VersionControlBranch.Length != 0)
diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs
index 15f42c3ae..ef5f959c8 100644
--- a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs
+++ b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
+using System.Threading;
using Flax.Build;
using Flax.Build.Platforms;
@@ -199,9 +200,14 @@ namespace Flax.Deploy
var dmgPath = Path.Combine(Deployer.PackageOutputPath, "FlaxEditor.dmg");
Log.Info(string.Empty);
Log.Info("Building disk image...");
+ Thread.Sleep(100);
if (File.Exists(dmgPath))
+ {
+ Log.Verbose("Removing old image");
File.Delete(dmgPath);
- Utilities.Run("hdiutil", $"create -srcFolder \"{appPath}\" -o \"{dmgPath}\" -force", null, null, Utilities.RunOptions.Default | Utilities.RunOptions.ThrowExceptionOnError);
+ Thread.Sleep(100);
+ }
+ Utilities.Run("hdiutil", $"create -srcFolder \"{appPath}\" -o \"{dmgPath}\"", null, null, Utilities.RunOptions.Default | Utilities.RunOptions.ThrowExceptionOnError);
CodeSign(dmgPath);
Log.Info("Output disk image size: " + Utilities.GetFileSize(dmgPath));
diff --git a/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs b/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs
index a1018368b..00838a73d 100644
--- a/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs
+++ b/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs
@@ -67,7 +67,8 @@ namespace Flax.Build.Platforms
if (subDirs.Length != 0)
{
Utilities.SortVersionDirectories(subDirs);
- FindNDK(subDirs.Last());
+ for (int i = subDirs.Length - 1; i >= 0 && !IsValid; i--)
+ FindNDK(subDirs[i]);
}
if (!IsValid)
@@ -109,6 +110,14 @@ namespace Flax.Build.Platforms
}
if (IsValid)
{
+ var minVersion = new Version(27, 0); // NDK 27 (and newer) contains the libc++_shared.so with 16kb alignment
+ if (Version < minVersion)
+ {
+ IsValid = false;
+ Log.Verbose(RootPath);
+ Log.Error(string.Format("Unsupported Android NDK version {0}. Minimum supported is {1}.", Version, minVersion));
+ return;
+ }
RootPath = sdkPath;
Log.Info(string.Format("Found Android NDK {1} at {0}", RootPath, Version));
}
diff --git a/Source/Tools/Flax.Build/Platforms/Android/AndroidToolchain.cs b/Source/Tools/Flax.Build/Platforms/Android/AndroidToolchain.cs
index ab332fdb1..34b77ae5a 100644
--- a/Source/Tools/Flax.Build/Platforms/Android/AndroidToolchain.cs
+++ b/Source/Tools/Flax.Build/Platforms/Android/AndroidToolchain.cs
@@ -104,6 +104,9 @@ namespace Flax.Build.Platforms
args.Add("-fno-function-sections");
}
+ // Support 16kb pages
+ args.Add("-D__BIONIC_NO_PAGE_SIZE_MACRO");
+
switch (Architecture)
{
case TargetArchitecture.x86:
@@ -195,10 +198,10 @@ namespace Flax.Build.Platforms
{
// https://android.googlesource.com/platform/ndk/+/master/docs/BuildSystemMaintainers.md#libc
var ndkPath = AndroidNdk.Instance.RootPath;
- var libCppSharedPath = Path.Combine(ndkPath, "sources/cxx-stl/llvm-libc++/libs/", GetAbiName(Architecture), "libc++_shared.so"); // NDK24 (and older) location
+ var libCppSharedPath = Path.Combine(ndkPath, "toolchains/llvm/prebuilt", AndroidSdk.GetHostName(), "sysroot/usr/lib/", GetToolchainName(TargetPlatform.Android, Architecture), "libc++_shared.so"); // NDK25+ location
if (!File.Exists(libCppSharedPath))
{
- libCppSharedPath = Path.Combine(ndkPath, "toolchains/llvm/prebuilt", AndroidSdk.GetHostName(), "sysroot/usr/lib/", GetToolchainName(TargetPlatform.Android, Architecture), "libc++_shared.so"); // NDK25+ location
+ libCppSharedPath = Path.Combine(ndkPath, "sources/cxx-stl/llvm-libc++/libs/", GetAbiName(Architecture), "libc++_shared.so"); // NDK24 (and older) location
if (!File.Exists(libCppSharedPath))
throw new Exception($"Missing Android NDK `libc++_shared.so` for architecture {Architecture}.");
}