Merge remote-tracking branch 'origin/1.12' into 1.12

This commit is contained in:
Wojtek Figat
2026-03-27 11:22:32 +01:00
129 changed files with 1493 additions and 402 deletions

View File

@@ -175,15 +175,13 @@ namespace FlaxEditor.Content
}
}
bool isExpanded = isAnyChildVisible;
if (isExpanded)
if (!noFilter)
{
Expand(true);
}
else
{
Collapse(true);
bool isExpanded = isAnyChildVisible;
if (isExpanded)
Expand(true);
else
Collapse(true);
}
Visible = isThisVisible | isAnyChildVisible;

View File

@@ -52,6 +52,7 @@ namespace FlaxEditor.CustomEditors
private readonly List<CustomEditor> _children = new List<CustomEditor>();
private ValueContainer _values;
private bool _isSetBlocked;
private bool _isRebuilding;
private bool _skipChildrenRefresh;
private bool _hasValueDirty;
private bool _rebuildOnRefresh;
@@ -178,7 +179,7 @@ namespace FlaxEditor.CustomEditors
public void RebuildLayout()
{
// Skip rebuilding during init
if (CurrentCustomEditor == this)
if (CurrentCustomEditor == this || _isRebuilding)
return;
// Special case for root objects to run normal layout build
@@ -197,6 +198,7 @@ namespace FlaxEditor.CustomEditors
_parent?.RebuildLayout();
return;
}
_isRebuilding = true;
var control = layout.ContainerControl;
var parent = _parent;
var parentScrollV = (_presenter?.Panel.Parent as Panel)?.VScrollBar?.Value ?? -1;
@@ -216,6 +218,7 @@ namespace FlaxEditor.CustomEditors
// Restore scroll value
if (parentScrollV > -1 && _presenter != null && _presenter.Panel.Parent is Panel panel && panel.VScrollBar != null)
panel.VScrollBar.Value = parentScrollV;
_isRebuilding = false;
}
/// <summary>

View File

@@ -61,6 +61,7 @@ public class Editor : EditorModule
options.PrivateDependencies.Add("Renderer");
options.PrivateDependencies.Add("TextureTool");
options.PrivateDependencies.Add("Particles");
options.PrivateDependencies.Add("Terrain");
var platformToolsRoot = Path.Combine(FolderPath, "Cooker", "Platform");
var platformToolsRootExternal = Path.Combine(Globals.EngineRoot, "Source", "Platforms");

View File

@@ -135,11 +135,7 @@ namespace FlaxEditor.GUI.Docking
settings.MaximumSize = Float2.Zero; // Unlimited size
settings.Fullscreen = false;
settings.HasBorder = true;
#if PLATFORM_SDL
settings.SupportsTransparency = true;
#else
settings.SupportsTransparency = false;
#endif
settings.ActivateWhenFirstShown = true;
settings.AllowInput = true;
settings.AllowMinimize = true;
@@ -211,6 +207,7 @@ namespace FlaxEditor.GUI.Docking
{
if (ChildPanelsCount > 0)
return;
// Close window
_window?.Close();
}

View File

@@ -269,8 +269,9 @@ namespace FlaxEditor.GUI.Docking
if (_toDock == null)
return;
if (_toDock.RootWindow.Window != _dragSourceWindow)
_toDock.RootWindow.Window.MouseUp -= OnMouseUp;
var window = _toDock.RootWindow?.Window;
if (window != null && window != _dragSourceWindow)
window.MouseUp -= OnMouseUp;
_dockHintDown?.Parent.RemoveChild(_dockHintDown);
_dockHintUp?.Parent.RemoveChild(_dockHintUp);
@@ -327,10 +328,10 @@ namespace FlaxEditor.GUI.Docking
_toDock?.RootWindow.Window.BringToFront();
//_toDock?.RootWindow.Window.Focus();
#if PLATFORM_SDL
// Make the dragged window transparent when dock hints are visible
_toMove.Window.Window.Opacity = _toDock == null ? 1.0f : DragWindowOpacity;
#else
#if !PLATFORM_SDL
// Bring the drop source always to the top
if (_dragSourceWindow != null)
_dragSourceWindow.BringToFront();

View File

@@ -0,0 +1,267 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using FlaxEditor.Viewport;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Gizmo;
[HideInEditor]
internal class DirectionGizmo : ContainerControl
{
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 AxisData _xAxisData;
private AxisData _yAxisData;
private AxisData _zAxisData;
private AxisData _negXAxisData;
private AxisData _negYAxisData;
private AxisData _negZAxisData;
private List<AxisData> _axisData = new List<AxisData>();
private int _hoveredAxisIndex = -1;
private SpriteHandle _posHandle;
private SpriteHandle _negHandle;
private FontReference _fontReference;
// Store sprite positions for hover detection
private List<(Float2 position, AxisDirection direction)> _spritePositions = new List<(Float2, AxisDirection)>();
private struct ViewportProjection
{
private Matrix _viewProjection;
private BoundingFrustum _frustum;
private FlaxEngine.Viewport _viewport;
private Vector3 _origin;
public void Init(EditorViewport editorViewport)
{
// Inline EditorViewport.ProjectPoint to save on calculation for large set of points
_viewport = new FlaxEngine.Viewport(0, 0, editorViewport.Width, editorViewport.Height);
_frustum = editorViewport.ViewFrustum;
_viewProjection = _frustum.Matrix;
_origin = editorViewport.Task.View.Origin;
}
public void ProjectPoint(Vector3 worldSpaceLocation, out Float2 viewportSpaceLocation)
{
worldSpaceLocation -= _origin;
_viewport.Project(ref worldSpaceLocation, ref _viewProjection, out var projected);
viewportSpaceLocation = new Float2((float)projected.X, (float)projected.Y);
}
}
private struct AxisData
{
public Float2 Delta;
public float Distance;
public string Label;
public Color AxisColor;
public bool Negative;
public AxisDirection Direction;
}
private enum AxisDirection
{
PosX,
PosY,
PosZ,
NegX,
NegY,
NegZ
}
/// <summary>
/// Constructor of the Direction Gizmo
/// </summary>
/// <param name="owner">The owner of this object.</param>
public DirectionGizmo(IGizmoOwner owner)
{
_owner = owner;
_viewport = owner.Viewport;
_viewportProjection.Init(owner.Viewport);
_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 };
_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 };
_axisData.EnsureCapacity(6);
_spritePositions.EnsureCapacity(6);
_posHandle = Editor.Instance.Icons.VisjectBoxClosed32;
_negHandle = Editor.Instance.Icons.VisjectBoxOpen32;
_fontReference = new FontReference(Style.Current.FontSmall);
_fontReference.Size = 8;
}
private bool IsPointInSprite(Float2 point, Float2 spriteCenter)
{
Float2 delta = point - spriteCenter;
float distanceSq = delta.LengthSquared;
float radiusSq = _spriteRadius * _spriteRadius;
return distanceSq <= radiusSq;
}
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
_hoveredAxisIndex = -1;
// Check which axis is being hovered - check from closest to farthest for proper layering
for (int i = _spritePositions.Count - 1; i >= 0; i--)
{
if (IsPointInSprite(location, _spritePositions[i].position))
{
_hoveredAxisIndex = i;
break;
}
}
base.OnMouseMove(location);
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (base.OnMouseDown(location, button))
return true;
// Check which axis is being clicked - check from closest to farthest for proper layering
for (int i = _spritePositions.Count - 1; i >= 0; i--)
{
if (IsPointInSprite(location, _spritePositions[i].position))
{
OrientViewToAxis(_spritePositions[i].direction);
return true;
}
}
return false;
}
private void OrientViewToAxis(AxisDirection direction)
{
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),
_ => Quaternion.Identity
};
_viewport.OrientViewport(ref orientation);
}
/// <summary>
/// Used to Draw the gizmo.
/// </summary>
public override void DrawSelf()
{
base.DrawSelf();
var features = Render2D.Features;
Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping;
_viewportProjection.Init(_owner.Viewport);
_gizmoCenter = _viewport.Task.View.WorldPosition + _viewport.Task.View.Direction * 1500;
_viewportProjection.ProjectPoint(_gizmoCenter, out var gizmoCenterScreen);
var relativeCenter = Size * 0.5f;
// Project unit vectors
_viewportProjection.ProjectPoint(_gizmoCenter + Vector3.Right, out var xProjected);
_viewportProjection.ProjectPoint(_gizmoCenter + Vector3.Up, out var yProjected);
_viewportProjection.ProjectPoint(_gizmoCenter + Vector3.Forward, out var zProjected);
_viewportProjection.ProjectPoint(_gizmoCenter - Vector3.Right, out var negXProjected);
_viewportProjection.ProjectPoint(_gizmoCenter - Vector3.Up, out var negYProjected);
_viewportProjection.ProjectPoint(_gizmoCenter - Vector3.Forward, out var negZProjected);
// Normalize by viewport height to keep size independent of FOV and viewport dimensions
float heightNormalization = _viewport.Height / 720.0f; // 720 = reference height
if (_owner.Viewport.UseOrthographicProjection)
heightNormalization /= _owner.Viewport.OrthographicScale * 0.5f; // Fix in ortho view to keep consistent size regardless of zoom level
Float2 xDelta = (xProjected - gizmoCenterScreen) / heightNormalization;
Float2 yDelta = (yProjected - gizmoCenterScreen) / heightNormalization;
Float2 zDelta = (zProjected - gizmoCenterScreen) / heightNormalization;
Float2 negXDelta = (negXProjected - gizmoCenterScreen) / heightNormalization;
Float2 negYDelta = (negYProjected - gizmoCenterScreen) / heightNormalization;
Float2 negZDelta = (negZProjected - gizmoCenterScreen) / heightNormalization;
// Calculate distances from camera to determine draw order
Vector3 cameraPosition = _viewport.Task.View.Position;
float xDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter + Vector3.Right);
float yDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter + Vector3.Up);
float zDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter + Vector3.Forward);
float negXDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter - Vector3.Right);
float negYDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter - Vector3.Up);
float negZDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter - Vector3.Forward);
_xAxisData.Delta = xDelta;
_xAxisData.Distance = xDistance;
_yAxisData.Delta = yDelta;
_yAxisData.Distance = yDistance;
_zAxisData.Delta = zDelta;
_zAxisData.Distance = zDistance;
_negXAxisData.Delta = negXDelta;
_negXAxisData.Distance = negXDistance;
_negYAxisData.Delta = negYDelta;
_negYAxisData.Distance = negYDistance;
_negZAxisData.Delta = negZDelta;
_negZAxisData.Distance = negZDistance;
// Sort for correct draw order.
_axisData.Clear();
_axisData.AddRange([_xAxisData, _yAxisData, _zAxisData, _negXAxisData, _negYAxisData, _negZAxisData]);
_axisData.Sort((a, b) => -a.Distance.CompareTo(b.Distance));
// Rebuild sprite positions list for hover detection
_spritePositions.Clear();
Render2D.DrawSprite(_posHandle, new Rectangle(0, 0, Size), Color.Black.AlphaMultiplied(0.1f));
// 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));
var axisColor = isHovered ? new Color(1.0f, 0.8980392f, 0.039215688f) : axis.AxisColor;
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);
}
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);
if (isHovered)
Render2D.DrawText(font, axis.Label, Color.Black, tipTextScreen - font.MeasureText(axis.Label) * 0.5f);
}
}
Render2D.Features = features;
}
}

View File

@@ -710,6 +710,10 @@ namespace FlaxEditor.Options
[EditorDisplay("Node Editors"), EditorOrder(4580)]
public InputBinding NodesDistributeVertical = new InputBinding(KeyboardKeys.A, KeyboardKeys.Alt);
[DefaultValue(typeof(InputBinding), "Shift+F")]
[EditorDisplay("Node Editors"), EditorOrder(4590)]
public InputBinding FocusSelectedNodes = new InputBinding(KeyboardKeys.F, KeyboardKeys.Shift);
#endregion
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine;
namespace FlaxEditor.SceneGraph.Actors
{
/// <summary>
/// Scene tree node for <see cref="RigidBody"/> actor type.
/// </summary>
[HideInEditor]
public sealed class RigidBodyNode : ActorNode
{
/// <inheritdoc />
public RigidBodyNode(Actor actor)
: base(actor)
{
}
/// <inheritdoc />
public override void PostSpawn()
{
base.PostSpawn();
if (HasPrefabLink)
return;
Actor.StaticFlags = StaticFlags.None;
}
}
}

View File

@@ -324,13 +324,12 @@ namespace FlaxEditor.SceneGraph.GUI
isExpanded = Editor.Instance.ProjectCache.IsExpandedActor(ref id);
}
if (isExpanded)
if (!noFilter)
{
Expand(true);
}
else
{
Collapse(true);
if (isExpanded)
Expand(true);
else
Collapse(true);
}
Visible = isThisVisible | isAnyChildVisible;

View File

@@ -74,6 +74,7 @@ namespace FlaxEditor.SceneGraph
CustomNodesTypes.Add(typeof(NavMesh), typeof(ActorNode));
CustomNodesTypes.Add(typeof(SpriteRender), typeof(SpriteRenderNode));
CustomNodesTypes.Add(typeof(Joint), typeof(JointNode));
CustomNodesTypes.Add(typeof(RigidBody), typeof(RigidBodyNode));
}
/// <summary>

View File

@@ -52,6 +52,7 @@ namespace FlaxEditor.Surface.Archetypes
},
new NodeArchetype
{
// [Deprecated]
TypeID = 3,
Title = "Pack Material Layer",
Description = "Pack material properties",
@@ -75,6 +76,7 @@ namespace FlaxEditor.Surface.Archetypes
},
new NodeArchetype
{
// [Deprecated]
TypeID = 4,
Title = "Unpack Material Layer",
Description = "Unpack material properties",
@@ -120,6 +122,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 6,
Title = "Pack Material Layer",
Description = "Pack material properties",
AlternativeTitles = new[] { "Make Material Layer", "Construct Material Layer", "Compose Material Layer" },
Flags = NodeFlags.MaterialGraph,
Size = new Float2(200, 280),
Elements = new[]
@@ -146,6 +149,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 7,
Title = "Unpack Material Layer",
Description = "Unpack material properties",
AlternativeTitles = new[] { "Break Material Layer", "Deconstruct Material Layer", "Decompose Material Layer", "Split Material Layer" },
Flags = NodeFlags.MaterialGraph,
Size = new Float2(210, 280),
Elements = new[]

View File

@@ -342,6 +342,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 20,
Title = "Pack Float2",
Description = "Pack components to Float2",
AlternativeTitles = new[] { "Make Float2", "Construct Float2", "Compose Float2" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 40),
DefaultValues = new object[]
@@ -361,6 +362,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 21,
Title = "Pack Float3",
Description = "Pack components to Float3",
AlternativeTitles = new[] { "Make Float3", "Construct Float3", "Compose Float3" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 60),
DefaultValues = new object[]
@@ -382,6 +384,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 22,
Title = "Pack Float4",
Description = "Pack components to Float4",
AlternativeTitles = new[] { "Make Float4", "Construct Float4", "Compose Float4" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 80),
DefaultValues = new object[]
@@ -405,6 +408,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 23,
Title = "Pack Rotation",
Description = "Pack components to Rotation",
AlternativeTitles = new[] { "Make Rotation", "Construct Rotation", "Compose Rotation" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 60),
DefaultValues = new object[]
@@ -426,6 +430,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 24,
Title = "Pack Transform",
Description = "Pack components to Transform",
AlternativeTitles = new[] { "Make Transform", "Construct Transform", "Compose Transform" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 80),
Elements = new[]
@@ -441,6 +446,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 25,
Title = "Pack Box",
Description = "Pack components to BoundingBox",
AlternativeTitles = new[] { "Make Box", "Construct Box", "Compose Box" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 40),
Elements = new[]
@@ -454,6 +460,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 26,
Title = "Pack Structure",
AlternativeTitles = new[] { "Make Structure", "Construct Structure", "Compose Structure" },
Create = (id, context, arch, groupArch) => new PackStructureNode(id, context, arch, groupArch),
IsInputCompatible = PackStructureNode.IsInputCompatible,
IsOutputCompatible = PackStructureNode.IsOutputCompatible,
@@ -479,6 +486,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 30,
Title = "Unpack Float2",
Description = "Unpack components from Float2",
AlternativeTitles = new[] { "Break Float2", "Deconstruct Float2", "Decompose Float2", "Split Float2" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 40),
Elements = new[]
@@ -493,6 +501,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 31,
Title = "Unpack Float3",
Description = "Unpack components from Float3",
AlternativeTitles = new[] { "Break Float3", "Deconstruct Float3", "Decompose Float3", "Split Float3" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 60),
Elements = new[]
@@ -508,6 +517,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 32,
Title = "Unpack Float4",
Description = "Unpack components from Float4",
AlternativeTitles = new[] { "Break Float4", "Deconstruct Float4", "Decompose Float4", "Split Float4" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 80),
Elements = new[]
@@ -524,6 +534,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 33,
Title = "Unpack Rotation",
Description = "Unpack components from Rotation",
AlternativeTitles = new[] { "Break Rotation", "Deconstruct Rotation", "Decompose Rotation", "Split Rotation" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(170, 60),
Elements = new[]
@@ -539,6 +550,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 34,
Title = "Unpack Transform",
Description = "Unpack components from Transform",
AlternativeTitles = new[] { "Break Transform", "Deconstruct Transform", "Decompose Transform", "Split Transform" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(170, 60),
Elements = new[]
@@ -554,6 +566,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 35,
Title = "Unpack Box",
Description = "Unpack components from BoundingBox",
AlternativeTitles = new[] { "Break BoundingBox", "Deconstruct BoundingBox", "Decompose BoundingBox", "Split BoundingBox" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(170, 40),
Elements = new[]
@@ -572,6 +585,7 @@ namespace FlaxEditor.Surface.Archetypes
IsOutputCompatible = UnpackStructureNode.IsOutputCompatible,
GetInputOutputDescription = UnpackStructureNode.GetInputOutputDescription,
Description = "Breaks the structure data to allow extracting components from it.",
AlternativeTitles = new[] { "Break Structure", "Deconstruct Structure", "Decompose Structure", "Split Structure" },
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI,
Size = new Float2(180, 20),
DefaultValues = new object[]

View File

@@ -585,7 +585,7 @@ namespace FlaxEditor.Surface.ContextMenu
private void UpdateFilters()
{
if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBoxes[0] == null)
if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBoxes.Count == 0)
{
ResetView();
Profiler.EndEvent();

View File

@@ -34,11 +34,6 @@ namespace FlaxEditor.Surface.Elements
/// </summary>
public const float DefaultConnectionOffset = 24f;
/// <summary>
/// Distance for the mouse to be considered above the connection
/// </summary>
public float MouseOverConnectionDistance => 100f / Surface.ViewScale;
/// <inheritdoc />
public OutputBox(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(parentNode, archetype, archetype.Position + new Float2(parentNode.Archetype.Size.X, 0))
@@ -109,12 +104,13 @@ namespace FlaxEditor.Surface.Elements
/// </summary>
/// <param name="targetBox">The other box.</param>
/// <param name="mousePosition">The mouse position</param>
public bool IntersectsConnection(Box targetBox, ref Float2 mousePosition)
/// <param name="distance">Distance at which its an intersection</param>
public bool IntersectsConnection(Box targetBox, ref Float2 mousePosition, float distance)
{
float connectionOffset = Mathf.Max(0f, DefaultConnectionOffset * (1 - Editor.Instance.Options.Options.Interface.ConnectionCurvature));
Float2 start = new Float2(ConnectionOrigin.X + connectionOffset, ConnectionOrigin.Y);
Float2 end = new Float2(targetBox.ConnectionOrigin.X - connectionOffset, targetBox.ConnectionOrigin.Y);
return IntersectsConnection(ref start, ref end, ref mousePosition, MouseOverConnectionDistance);
return IntersectsConnection(ref start, ref end, ref mousePosition, distance);
}
/// <summary>
@@ -182,7 +178,7 @@ namespace FlaxEditor.Surface.Elements
{
// Draw all the connections
var style = Surface.Style;
var mouseOverDistance = MouseOverConnectionDistance;
var mouseOverDistance = Surface.MouseOverConnectionDistance;
var startPos = ConnectionOrigin;
var startHighlight = ConnectionsHighlightIntensity;
for (int i = 0; i < Connections.Count; i++)

View File

@@ -574,13 +574,13 @@ namespace FlaxEditor.Surface
var showSearch = () => editor.ContentFinding.ShowSearch(window);
// Toolstrip
saveButton = toolStrip.AddButton(editor.Icons.Save64, window.Save).LinkTooltip("Save", ref inputOptions.Save);
saveButton = toolStrip.AddButton(editor.Icons.Save64, window.Save).LinkTooltip("Save.", ref inputOptions.Save);
toolStrip.AddSeparator();
undoButton = toolStrip.AddButton(editor.Icons.Undo64, undo.PerformUndo).LinkTooltip("Undo", ref inputOptions.Undo);
redoButton = toolStrip.AddButton(editor.Icons.Redo64, undo.PerformRedo).LinkTooltip("Redo", ref inputOptions.Redo);
undoButton = toolStrip.AddButton(editor.Icons.Undo64, undo.PerformUndo).LinkTooltip("Undo.", ref inputOptions.Undo);
redoButton = toolStrip.AddButton(editor.Icons.Redo64, undo.PerformRedo).LinkTooltip("Redo.", ref inputOptions.Redo);
toolStrip.AddSeparator();
toolStrip.AddButton(editor.Icons.Search64, showSearch).LinkTooltip("Open content search tool", ref inputOptions.Search);
toolStrip.AddButton(editor.Icons.CenterView64, surface.ShowWholeGraph).LinkTooltip("Show whole graph");
toolStrip.AddButton(editor.Icons.Search64, showSearch).LinkTooltip("Open content search tool.", ref inputOptions.Search);
toolStrip.AddButton(editor.Icons.CenterView64, surface.ShowWholeGraph).LinkTooltip("Show whole graph.");
var gridSnapButton = toolStrip.AddButton(editor.Icons.Grid32, surface.ToggleGridSnapping);
gridSnapButton.LinkTooltip("Toggle grid snapping for nodes.");
gridSnapButton.AutoCheck = true;

View File

@@ -410,8 +410,11 @@ namespace FlaxEditor.Surface
}
menu.AddSeparator();
_cmFormatNodesMenu = menu.AddChildMenu("Format node(s)");
_cmFormatNodesMenu.Enabled = CanEdit && HasNodesSelection;
bool allNodesNoMove = SelectedNodes.All(n => n.Archetype.Flags.HasFlag(NodeFlags.NoMove));
bool clickedNodeNoMove = ((SelectedNodes.Count == 1 && controlUnderMouse is SurfaceNode n && n.Archetype.Flags.HasFlag(NodeFlags.NoMove)));
_cmFormatNodesMenu = menu.AddChildMenu("Format nodes");
_cmFormatNodesMenu.Enabled = CanEdit && HasNodesSelection && !(allNodesNoMove || clickedNodeNoMove);
_cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Auto format", Editor.Instance.Options.Options.Input.NodesAutoFormat, () => { FormatGraph(SelectedNodes); });
_cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Straighten connections", Editor.Instance.Options.Options.Input.NodesStraightenConnections, () => { StraightenGraphConnections(SelectedNodes); });

View File

@@ -213,6 +213,44 @@ namespace FlaxEditor.Surface
}
}
/// <summary>
/// Draw connection hints for lazy connect feature.
/// </summary>
protected virtual void DrawLazyConnect()
{
var style = FlaxEngine.GUI.Style.Current;
if (_lazyConnectStartNode != null)
{
Float2 upperLeft = _rootControl.PointToParent(_lazyConnectStartNode.UpperLeft);
Rectangle startNodeOutline = new Rectangle(upperLeft + 1f, _lazyConnectStartNode.Size - 1f);
startNodeOutline.Size *= ViewScale;
Render2D.DrawRectangle(startNodeOutline.MakeExpanded(4f), style.BackgroundSelected, 4f);
}
if (_lazyConnectEndNode != null)
{
Float2 upperLeft = _rootControl.PointToParent(_lazyConnectEndNode.UpperLeft);
Rectangle startNodeOutline = new Rectangle(upperLeft + 1f, _lazyConnectEndNode.Size - 1f);
startNodeOutline.Size *= ViewScale;
Render2D.DrawRectangle(startNodeOutline.MakeExpanded(4f), style.BackgroundSelected, 4f);
}
Rectangle startRect = new Rectangle(_rightMouseDownPos - 6f, new Float2(12f));
Rectangle endRect = new Rectangle(_mousePos - 6f, new Float2(12f));
// Start and end shadows/ outlines
Render2D.FillRectangle(startRect.MakeExpanded(2.5f), Color.Black);
Render2D.FillRectangle(endRect.MakeExpanded(2.5f), Color.Black);
Render2D.DrawLine(_rightMouseDownPos, _mousePos, Color.Black, 7.5f);
Render2D.DrawLine(_rightMouseDownPos, _mousePos, style.ForegroundGrey, 5f);
// Draw start and end boxes over the lines to hide ugly artifacts at the ends
Render2D.FillRectangle(startRect, style.ForegroundGrey);
Render2D.FillRectangle(endRect, style.ForegroundGrey);
}
/// <summary>
/// Draws the contents of the surface (nodes, connections, comments, etc.).
/// </summary>
@@ -260,6 +298,9 @@ namespace FlaxEditor.Surface
DrawContents();
if (_isLazyConnecting)
DrawLazyConnect();
//Render2D.DrawText(style.FontTitle, string.Format("Scale: {0}", _rootControl.Scale), rect, Enabled ? Color.Red : Color.Black);
// Draw border

View File

@@ -39,6 +39,8 @@ namespace FlaxEditor.Surface
if (nodes.Count <= 1)
return;
List<MoveNodesAction> undoActions = new List<MoveNodesAction>();
var nodesToVisit = new HashSet<SurfaceNode>(nodes);
// While we haven't formatted every node
@@ -73,18 +75,23 @@ namespace FlaxEditor.Surface
}
}
FormatConnectedGraph(connectedNodes);
undoActions.AddRange(FormatConnectedGraph(connectedNodes));
}
Undo?.AddAction(new MultiUndoAction(undoActions, "Format nodes"));
MarkAsEdited(false);
}
/// <summary>
/// Formats a graph where all nodes are connected.
/// </summary>
/// <param name="nodes">List of connected nodes.</param>
protected void FormatConnectedGraph(List<SurfaceNode> nodes)
private List<MoveNodesAction> FormatConnectedGraph(List<SurfaceNode> nodes)
{
List<MoveNodesAction> undoActions = new List<MoveNodesAction>();
if (nodes.Count <= 1)
return;
return undoActions;
var boundingBox = GetNodesBounds(nodes);
@@ -140,7 +147,6 @@ namespace FlaxEditor.Surface
}
// Set the node positions
var undoActions = new List<MoveNodesAction>();
var topRightPosition = endNodes[0].Location;
for (int i = 0; i < nodes.Count; i++)
{
@@ -155,16 +161,18 @@ namespace FlaxEditor.Surface
}
}
MarkAsEdited(false);
Undo?.AddAction(new MultiUndoAction(undoActions, "Format nodes"));
return undoActions;
}
/// <summary>
/// Straightens every connection between nodes in <paramref name="nodes"/>.
/// </summary>
/// <param name="nodes">List of nodes.</param>
/// <returns>List of undo actions.</returns>
public void StraightenGraphConnections(List<SurfaceNode> nodes)
{
{
nodes = nodes.Where(n => !n.Archetype.Flags.HasFlag(NodeFlags.NoMove)).ToList();
if (nodes.Count <= 1)
return;
@@ -350,8 +358,10 @@ namespace FlaxEditor.Surface
/// <param name="nodes">List of nodes.</param>
/// <param name="alignmentType">Alignemnt type.</param>
public void AlignNodes(List<SurfaceNode> nodes, NodeAlignmentType alignmentType)
{
if(nodes.Count <= 1)
{
nodes = nodes.Where(n => !n.Archetype.Flags.HasFlag(NodeFlags.NoMove)).ToList();
if (nodes.Count <= 1)
return;
var undoActions = new List<MoveNodesAction>();
@@ -392,6 +402,8 @@ namespace FlaxEditor.Surface
/// <param name="vertically">If false will be done horizontally, if true will be done vertically.</param>
public void DistributeNodes(List<SurfaceNode> nodes, bool vertically)
{
nodes = nodes.Where(n => !n.Archetype.Flags.HasFlag(NodeFlags.NoMove)).ToList();
if(nodes.Count <= 1)
return;

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static FlaxEditor.Surface.Archetypes.Particles;
using FlaxEditor.Options;
using FlaxEditor.Surface.Elements;
using FlaxEditor.Surface.Undo;
@@ -23,12 +24,26 @@ namespace FlaxEditor.Surface
/// </summary>
public bool PanWithMiddleMouse = false;
/// <summary>
/// Distance for the mouse to be considered above the connection.
/// </summary>
public float MouseOverConnectionDistance => 100f / ViewScale;
/// <summary>
/// Distance of a node from which it is able to be slotted into an existing connection.
/// </summary>
public float SlotNodeIntoConnectionDistance => 250f / ViewScale;
private string _currentInputText = string.Empty;
private Float2 _movingNodesDelta;
private Float2 _gridRoundingDelta;
private HashSet<SurfaceNode> _movingNodes;
private HashSet<SurfaceNode> _temporarySelectedNodes;
private readonly Stack<InputBracket> _inputBrackets = new Stack<InputBracket>();
private bool _isLazyConnecting;
private SurfaceNode _lazyConnectStartNode;
private SurfaceNode _lazyConnectEndNode;
private InputBinding _focusSelectedNodeBinding;
private class InputBracket
{
@@ -250,8 +265,13 @@ namespace FlaxEditor.Surface
// Cache mouse location
_mousePos = location;
if (_isLazyConnecting && GetControlUnderMouse() is SurfaceNode nodeUnderMouse && !(nodeUnderMouse is SurfaceComment || nodeUnderMouse is ParticleEmitterNode))
_lazyConnectEndNode = nodeUnderMouse;
else if (_isLazyConnecting && Nodes.Count > 0)
_lazyConnectEndNode = GetClosestNodeAtLocation(location);
// Moving around surface with mouse
if (_rightMouseDown)
if (_rightMouseDown && !_isLazyConnecting)
{
// Calculate delta
var delta = location - _rightMouseDownPos;
@@ -321,6 +341,33 @@ namespace FlaxEditor.Surface
foreach (var node in _movingNodes)
{
// Allow ripping the node from its current connection
if (RootWindow.GetKey(KeyboardKeys.Alt))
{
InputBox nodeConnectedInput = null;
OutputBox nodeConnectedOuput = null;
var boxes = node.GetBoxes();
foreach (var box in boxes)
{
if (!box.IsOutput && box.Connections.Count > 0)
{
nodeConnectedInput = (InputBox)box;
continue;
}
if (box.IsOutput && box.Connections.Count > 0)
{
nodeConnectedOuput = (OutputBox)box;
continue;
}
}
if (nodeConnectedInput != null && nodeConnectedOuput != null)
TryConnect(nodeConnectedOuput.Connections[0], nodeConnectedInput.Connections[0]);
node.RemoveConnections();
}
if (gridSnap)
{
Float2 unroundedLocation = node.Location;
@@ -420,7 +467,7 @@ namespace FlaxEditor.Surface
if (!handled && CanEdit && CanUseNodeType(7, 29))
{
var mousePos = _rootControl.PointFromParent(ref _mousePos);
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox) && GetControlUnderMouse() == null)
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox, MouseOverConnectionDistance) && GetControlUnderMouse() == null)
{
if (Undo != null)
{
@@ -515,11 +562,17 @@ namespace FlaxEditor.Surface
_middleMouseDownPos = location;
}
if (root.GetKey(KeyboardKeys.Alt) && button == MouseButton.Right)
_isLazyConnecting = true;
// Check if any node is under the mouse
SurfaceControl controlUnderMouse = GetControlUnderMouse();
var cLocation = _rootControl.PointFromParent(ref location);
if (controlUnderMouse != null)
{
if (controlUnderMouse is SurfaceNode node && _isLazyConnecting && !(controlUnderMouse is SurfaceComment || controlUnderMouse is ParticleEmitterNode))
_lazyConnectStartNode = node;
// Check if mouse is over header and user is pressing mouse left button
if (_leftMouseDown && controlUnderMouse.CanSelect(ref cLocation))
{
@@ -554,6 +607,9 @@ namespace FlaxEditor.Surface
}
else
{
if (_isLazyConnecting && Nodes.Count > 0)
_lazyConnectStartNode = GetClosestNodeAtLocation(location);
// Cache flags and state
if (_leftMouseDown)
{
@@ -602,8 +658,71 @@ namespace FlaxEditor.Surface
{
if (_movingNodes != null && _movingNodes.Count > 0)
{
// Allow dropping a single node onto an existing connection and connect it
if (_movingNodes.Count == 1)
{
var mousePos = _rootControl.PointFromParent(ref _mousePos);
InputBox intersectedConnectionInputBox;
OutputBox intersectedConnectionOutputBox;
if (IntersectsConnection(mousePos, out intersectedConnectionInputBox, out intersectedConnectionOutputBox, SlotNodeIntoConnectionDistance))
{
SurfaceNode node = _movingNodes.First();
InputBox nodeInputBox = (InputBox)node.GetBoxes().First(b => !b.IsOutput);
OutputBox nodeOutputBox = (OutputBox)node.GetBoxes().First(b => b.IsOutput);
TryConnect(intersectedConnectionOutputBox, nodeInputBox);
TryConnect(nodeOutputBox, intersectedConnectionInputBox);
float intersectedConnectionNodesXDistance = intersectedConnectionInputBox.ParentNode.Left - intersectedConnectionOutputBox.ParentNode.Right;
float paddedNodeWidth = node.Width + 2f;
if (intersectedConnectionNodesXDistance < paddedNodeWidth)
{
List<SurfaceNode> visitedNodes = new List<SurfaceNode>{ node };
List<SurfaceNode> movedNodes = new List<SurfaceNode>();
Float2 locationDelta = new Float2(paddedNodeWidth, 0f);
MoveConnectedNodes(intersectedConnectionInputBox.ParentNode);
void MoveConnectedNodes(SurfaceNode node)
{
// Only move node if it is to the right of the node we have connected the moved node to
if (node.Right > intersectedConnectionInputBox.ParentNode.Left + 15f && !node.Archetype.Flags.HasFlag(NodeFlags.NoMove))
{
node.Location += locationDelta;
movedNodes.Add(node);
}
visitedNodes.Add(node);
foreach (var box in node.GetBoxes())
{
if (!box.HasAnyConnection || box == intersectedConnectionInputBox)
continue;
foreach (var connectedBox in box.Connections)
{
SurfaceNode nextNode = connectedBox.ParentNode;
if (visitedNodes.Contains(nextNode))
continue;
MoveConnectedNodes(nextNode);
}
}
}
Float2 nodeMoveOffset = new Float2(node.Width * 0.5f, 0f);
node.Location += nodeMoveOffset;
var moveNodesAction = new MoveNodesAction(Context, movedNodes.Select(n => n.ID).ToArray(), locationDelta);
var moveNodeAction = new MoveNodesAction(Context, [node.ID], nodeMoveOffset);
var multiAction = new MultiUndoAction(moveNodeAction, moveNodesAction);
AddBatchedUndoAction(multiAction);
}
}
}
if (Undo != null && !_movingNodesDelta.IsZero && CanEdit)
Undo.AddAction(new MoveNodesAction(Context, _movingNodes.Select(x => x.ID).ToArray(), _movingNodesDelta));
AddBatchedUndoAction(new MoveNodesAction(Context, _movingNodes.Select(x => x.ID).ToArray(), _movingNodesDelta));
_movingNodes.Clear();
}
_movingNodesDelta = Float2.Zero;
@@ -630,12 +749,36 @@ namespace FlaxEditor.Surface
{
// Check if any control is under the mouse
_cmStartPos = location;
if (controlUnderMouse == null)
if (controlUnderMouse == null && !_isLazyConnecting)
{
showPrimaryMenu = true;
}
}
_mouseMoveAmount = 0;
if (_isLazyConnecting)
{
if (_lazyConnectStartNode != null && _lazyConnectEndNode != null && _lazyConnectStartNode != _lazyConnectEndNode)
{
// First check if there is a type matching input and output where input
OutputBox startNodeOutput = (OutputBox)_lazyConnectStartNode.GetBoxes().FirstOrDefault(b => b.IsOutput, null);
InputBox endNodeInput = null;
if (startNodeOutput != null)
endNodeInput = (InputBox)_lazyConnectEndNode.GetBoxes().FirstOrDefault(b => !b.IsOutput && b.CurrentType == startNodeOutput.CurrentType && !b.HasAnyConnection && b.IsActive && b.CanConnectWith(startNodeOutput), null);
// Perform less strict checks (less ideal conditions for connection but still good) if the first checks failed
if (endNodeInput == null)
endNodeInput = (InputBox)_lazyConnectEndNode.GetBoxes().FirstOrDefault(b => !b.IsOutput && !b.HasAnyConnection && b.CanConnectWith(startNodeOutput), null);
if (startNodeOutput != null && endNodeInput != null)
TryConnect(startNodeOutput, endNodeInput);
}
_isLazyConnecting = false;
_lazyConnectStartNode = null;
_lazyConnectEndNode = null;
}
}
if (_middleMouseDown && button == MouseButton.Middle)
{
@@ -651,7 +794,7 @@ namespace FlaxEditor.Surface
{
// Surface was not moved with MMB so try to remove connection underneath
var mousePos = _rootControl.PointFromParent(ref location);
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox))
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox, MouseOverConnectionDistance))
{
var action = new EditNodeConnections(inputBox.ParentNode.Context, inputBox.ParentNode);
inputBox.BreakConnection(outputBox);
@@ -704,13 +847,21 @@ namespace FlaxEditor.Surface
private void MoveSelectedNodes(Float2 delta)
{
// TODO: undo
List<MoveNodesAction> undoActions = new List<MoveNodesAction>();
delta /= _targetScale;
OnGetNodesToMove();
foreach (var node in _movingNodes)
{
node.Location += delta;
if (Undo != null)
undoActions.Add(new MoveNodesAction(Context, new[] { node.ID }, delta));
}
_isMovingSelection = false;
MarkAsEdited(false);
if (undoActions.Count > 0)
Undo?.AddAction(new MultiUndoAction(undoActions, "Moved "));
}
/// <inheritdoc />
@@ -837,6 +988,29 @@ namespace FlaxEditor.Surface
return false;
}
private SurfaceNode GetClosestNodeAtLocation(Float2 location)
{
SurfaceNode currentClosestNode = null;
float currentClosestDistanceSquared = float.MaxValue;
foreach (var node in Nodes)
{
if (node is SurfaceComment || node is ParticleEmitterNode)
continue;
Float2 nodeSurfaceLocation = _rootControl.PointToParent(node.Center);
float distanceSquared = Float2.DistanceSquared(location, nodeSurfaceLocation);
if (distanceSquared < currentClosestDistanceSquared)
{
currentClosestNode = node;
currentClosestDistanceSquared = distanceSquared;
}
}
return currentClosestNode;
}
private void ResetInput()
{
InputText = "";
@@ -845,7 +1019,8 @@ namespace FlaxEditor.Surface
private void CurrentInputTextChanged(string currentInputText)
{
if (string.IsNullOrEmpty(currentInputText))
// Check if focus selected nodes binding is being pressed to prevent it triggering primary menu
if (string.IsNullOrEmpty(currentInputText) || _focusSelectedNodeBinding.Process(RootWindow))
return;
if (IsPrimaryMenuOpened || !CanEdit)
{
@@ -1025,7 +1200,7 @@ namespace FlaxEditor.Surface
return new Float2(xLocation, yLocation);
}
private bool IntersectsConnection(Float2 mousePosition, out InputBox inputBox, out OutputBox outputBox)
private bool IntersectsConnection(Float2 mousePosition, out InputBox inputBox, out OutputBox outputBox, float distance)
{
for (int i = 0; i < Nodes.Count; i++)
{
@@ -1035,7 +1210,7 @@ namespace FlaxEditor.Surface
{
for (int k = 0; k < ob.Connections.Count; k++)
{
if (ob.IntersectsConnection(ob.Connections[k], ref mousePosition))
if (ob.IntersectsConnection(ob.Connections[k], ref mousePosition, distance))
{
outputBox = ob;
inputBox = ob.Connections[k] as InputBox;

View File

@@ -423,8 +423,9 @@ namespace FlaxEditor.Surface
new InputActionsContainer.Binding(options => options.NodesAlignLeft, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Left); }),
new InputActionsContainer.Binding(options => options.NodesAlignCenter, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Center); }),
new InputActionsContainer.Binding(options => options.NodesAlignRight, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Right); }),
new InputActionsContainer.Binding(options => options.NodesDistributeHorizontal, () => { DistributeNodes(SelectedNodes, false); }),
new InputActionsContainer.Binding(options => options.NodesDistributeVertical, () => { DistributeNodes(SelectedNodes, true); }),
new InputActionsContainer.Binding(options => options.NodesDistributeHorizontal, () => { DistributeNodes(SelectedNodes, false); }),
new InputActionsContainer.Binding(options => options.NodesDistributeVertical, () => { DistributeNodes(SelectedNodes, true); }),
new InputActionsContainer.Binding(options => options.FocusSelectedNodes, () => { FocusSelectionOrWholeGraph(); }),
});
Context.ControlSpawned += OnSurfaceControlSpawned;
@@ -436,7 +437,10 @@ namespace FlaxEditor.Surface
DragHandlers.Add(_dragAssets = new DragAssets<DragDropEventArgs>(ValidateDragItem));
DragHandlers.Add(_dragParameters = new DragNames<DragDropEventArgs>(SurfaceParameter.DragPrefix, ValidateDragParameter));
OnEditorOptionsChanged(Editor.Instance.Options.Options);
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged;
}
private void OnScriptsReloadBegin()
@@ -446,6 +450,11 @@ namespace FlaxEditor.Surface
_cmPrimaryMenu = null;
}
private void OnEditorOptionsChanged(EditorOptions options)
{
_focusSelectedNodeBinding = options.Input.FocusSelectedNodes;
}
/// <summary>
/// Gets the display name of the connection type used in the surface.
/// </summary>
@@ -648,6 +657,37 @@ namespace FlaxEditor.Surface
ViewCenterPosition = areaRect.Center;
}
/// <summary>
/// Adjusts the view to focus on the currently selected nodes, or the entire graph if no nodes are selected.
/// </summary>
public void FocusSelectionOrWholeGraph()
{
if (SelectedNodes.Count > 0)
ShowSelection();
else
ShowWholeGraph();
}
/// <summary>
/// Shows the selected controls by changing the view scale and the position.
/// </summary>
public void ShowSelection()
{
var selection = SelectedControls;
if (selection.Count == 0)
return;
// Calculate the bounds of all selected controls
Rectangle bounds = selection[0].Bounds;
for (int i = 1; i < selection.Count; i++)
bounds = Rectangle.Union(bounds, selection[i].Bounds);
// Add margin
bounds = bounds.MakeExpanded(250.0f);
ShowArea(bounds);
}
/// <summary>
/// Shows the given surface node by changing the view scale and the position and focuses the node.
/// </summary>
@@ -1071,6 +1111,7 @@ namespace FlaxEditor.Surface
_cmPrimaryMenu?.Dispose();
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged;
base.OnDestroy();
}

View File

@@ -89,7 +89,7 @@ namespace FlaxEditor.Tools.Terrain
if (!terrain.HasPatch(ref patchCoord) && _planeModel)
{
var planeSize = 100.0f;
var patchSize = terrain.ChunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
var patchSize = terrain.PatchSize;
Matrix world = Matrix.RotationX(-Mathf.PiOverTwo) *
Matrix.Scaling(patchSize / planeSize) *
Matrix.Translation(patchSize * (0.5f + patchCoord.X), 0, patchSize * (0.5f + patchCoord.Y)) *

View File

@@ -69,9 +69,9 @@ namespace FlaxEditor.Tools.Terrain.Paint
var splatmapIndex = ActiveSplatmapIndex;
var splatmapIndexOther = (splatmapIndex + 1) % 2;
var chunkSize = terrain.ChunkSize;
var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
var heightmapSize = terrain.HeightmapSize;
var heightmapLength = heightmapSize * heightmapSize;
var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
var patchSize = terrain.PatchSize;
var tempBuffer = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, splatmapIndex).ToPointer();
var tempBufferOther = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, (splatmapIndex + 1) % 2).ToPointer();
var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex;

View File

@@ -70,9 +70,9 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
// Prepare
var chunkSize = terrain.ChunkSize;
var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
var heightmapSize = terrain.HeightmapSize;
var heightmapLength = heightmapSize * heightmapSize;
var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
var patchSize = terrain.PatchSize;
var tempBuffer = (float*)gizmo.GetHeightmapTempBuffer(heightmapLength * sizeof(float)).ToPointer();
var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex;

View File

@@ -382,7 +382,8 @@ bool TerrainTools::ExportTerrain(Terrain* terrain, String outputFolder)
const Int2 heightmapSize = size * Terrain::ChunksCountEdge * terrain->GetChunkSize() + 1;
Array<float> heightmap;
heightmap.Resize(heightmapSize.X * heightmapSize.Y);
heightmap.SetAll(firstPatch->GetHeightmapData()[0]);
if (const float* heightmapData = firstPatch->GetHeightmapData())
heightmap.SetAll(heightmapData[0]);
// Fill heightmap with data from all patches
const int32 rowSize = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1;
@@ -392,8 +393,16 @@ bool TerrainTools::ExportTerrain(Terrain* terrain, String outputFolder)
const Int2 pos(patch->GetX() - start.X, patch->GetZ() - start.Y);
const float* src = patch->GetHeightmapData();
float* dst = heightmap.Get() + pos.X * (rowSize - 1) + pos.Y * heightmapSize.X * (rowSize - 1);
for (int32 row = 0; row < rowSize; row++)
Platform::MemoryCopy(dst + row * heightmapSize.X, src + row * rowSize, rowSize * sizeof(float));
if (src)
{
for (int32 row = 0; row < rowSize; row++)
Platform::MemoryCopy(dst + row * heightmapSize.X, src + row * rowSize, rowSize * sizeof(float));
}
else
{
for (int32 row = 0; row < rowSize; row++)
Platform::MemoryClear(dst + row * heightmapSize.X, rowSize * sizeof(float));
}
}
// Interpolate to 16-bit int

View File

@@ -85,8 +85,7 @@ namespace FlaxEditor.Tools.Terrain.Undo
{
_terrain = terrain.ID;
_patches = new List<PatchData>(4);
var chunkSize = terrain.ChunkSize;
var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
var heightmapSize = terrain.HeightmapSize;
_heightmapLength = heightmapSize * heightmapSize;
_heightmapDataSize = _heightmapLength * stride;

View File

@@ -1229,7 +1229,7 @@ namespace FlaxEditor.Viewport
/// Orients the viewport.
/// </summary>
/// <param name="orientation">The orientation.</param>
protected void OrientViewport(Quaternion orientation)
public void OrientViewport(Quaternion orientation)
{
OrientViewport(ref orientation);
}
@@ -1238,7 +1238,7 @@ namespace FlaxEditor.Viewport
/// Orients the viewport.
/// </summary>
/// <param name="orientation">The orientation.</param>
protected virtual void OrientViewport(ref Quaternion orientation)
public virtual void OrientViewport(ref Quaternion orientation)
{
if (ViewportCamera is FPSCamera fpsCamera)
{

View File

@@ -108,13 +108,14 @@ namespace FlaxEditor.Viewport
private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32);
private EditorSpritesRenderer _editorSpritesRenderer;
private ViewportRubberBandSelector _rubberBandSelector;
private DirectionGizmo _directionGizmo;
private bool _gameViewActive;
private ViewFlags _preGameViewFlags;
private ViewMode _preGameViewViewMode;
private bool _gameViewWasGridShown;
private bool _gameViewWasFpsCounterShown;
private bool _gameViewWasNagivationShown;
private bool _gameViewWasNavigationShown;
/// <summary>
/// Drag and drop handlers
@@ -225,6 +226,12 @@ 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 grid
Grid = new GridGizmo(this);
@@ -244,6 +251,12 @@ namespace FlaxEditor.Viewport
_showNavigationButton = ViewWidgetShowMenu.AddButton("Navigation", inputOptions.ToggleNavMeshVisibility, () => ShowNavigation = !ShowNavigation);
_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;
// Game View
ViewWidgetButtonMenu.AddSeparator();
_toggleGameViewButton = ViewWidgetButtonMenu.AddButton("Game View", inputOptions.ToggleGameView, ToggleGameView);
@@ -514,14 +527,14 @@ namespace FlaxEditor.Viewport
_preGameViewViewMode = Task.ViewMode;
_gameViewWasGridShown = Grid.Enabled;
_gameViewWasFpsCounterShown = ShowFpsCounter;
_gameViewWasNagivationShown = ShowNavigation;
_gameViewWasNavigationShown = ShowNavigation;
}
// Set flags & values
Task.ViewFlags = _gameViewActive ? _preGameViewFlags : ViewFlags.DefaultGame;
Task.ViewMode = _gameViewActive ? _preGameViewViewMode : ViewMode.Default;
ShowFpsCounter = _gameViewActive ? _gameViewWasFpsCounterShown : false;
ShowNavigation = _gameViewActive ? _gameViewWasNagivationShown : false;
ShowNavigation = _gameViewActive ? _gameViewWasNavigationShown : false;
Grid.Enabled = _gameViewActive ? _gameViewWasGridShown : false;
_gameViewActive = !_gameViewActive;
@@ -647,7 +660,7 @@ namespace FlaxEditor.Viewport
}
/// <inheritdoc />
protected override void OrientViewport(ref Quaternion orientation)
public override void OrientViewport(ref Quaternion orientation)
{
if (TransformGizmo.SelectedParents.Count != 0)
FocusSelection(ref orientation);

View File

@@ -681,7 +681,7 @@ namespace FlaxEditor.Viewport
}
/// <inheritdoc />
protected override void OrientViewport(ref Quaternion orientation)
public override void OrientViewport(ref Quaternion orientation)
{
if (TransformGizmo.SelectedParents.Count != 0)
FocusSelection(ref orientation);

View File

@@ -303,8 +303,7 @@ namespace FlaxEditor.Viewport.Previews
{
_terrain = new Terrain();
_terrain.Setup(1, 63);
var chunkSize = _terrain.ChunkSize;
var heightMapSize = chunkSize * Terrain.PatchEdgeChunksCount + 1;
var heightMapSize = _terrain.HeightmapSize;
var heightMapLength = heightMapSize * heightMapSize;
var heightmap = new float[heightMapLength];
var patchCoord = new Int2(0, 0);

View File

@@ -431,6 +431,9 @@ namespace FlaxEditor.Windows.Assets
_isWaitingForTimelineLoad = true;
base.OnItemReimported(item);
// Drop virtual asset state and get a new one from the reimported file
LoadFromOriginal();
}
/// <inheritdoc />

View File

@@ -53,7 +53,7 @@ namespace FlaxEditor.Windows.Assets
{
Parent = this
};
_toolstrip.AddButton(editor.Icons.Search64, () => Editor.Windows.ContentWin.Select(_item)).LinkTooltip("Show and select in Content Window");
_toolstrip.AddButton(editor.Icons.Search64, () => Editor.Windows.ContentWin.Select(_item)).LinkTooltip("Show and select in Content Window.");
InputActions.Add(options => options.Save, Save);
@@ -527,6 +527,16 @@ namespace FlaxEditor.Windows.Assets
return false;
}
/// <summary>
/// Loads the asset from the original location to reflect the state (eg. after original asset reimport).
/// </summary>
protected virtual void LoadFromOriginal()
{
_asset = LoadAsset();
OnAssetLoaded();
ClearEditedFlag();
}
/// <inheritdoc />
protected override T LoadAsset()
{

View File

@@ -115,6 +115,7 @@ namespace FlaxEditor.Windows
var root = _root;
root.LockChildrenRecursive();
PerformLayout();
// Update tree
var query = _foldersSearchBox.Text;

View File

@@ -1126,6 +1126,8 @@ namespace FlaxEditor.Windows
if (Editor.ContentDatabase.Find(_lastViewedFolderBeforeReload) is ContentFolder folder)
_tree.Select(folder.Node);
}
OnFoldersSearchBoxTextChanged();
}
private void Refresh()

View File

@@ -67,6 +67,7 @@ namespace FlaxEditor.Windows
TooltipText = "Search the scene tree.\n\nYou can prefix your search with different search operators:\ns: -> Actor with script of type\na: -> Actor type\nc: -> Control type",
};
_searchBox.TextChanged += OnSearchBoxTextChanged;
ScriptsBuilder.ScriptsReloadEnd += OnSearchBoxTextChanged;
// Scene tree panel
_sceneTreePanel = new Panel
@@ -111,7 +112,7 @@ namespace FlaxEditor.Windows
InputActions.Add(options => options.LockFocusSelection, () => Editor.Windows.EditWin.Viewport.LockFocusSelection());
InputActions.Add(options => options.Rename, RenameSelection);
}
/// <inheritdoc />
public override void OnPlayBeginning()
{
@@ -124,6 +125,7 @@ namespace FlaxEditor.Windows
{
base.OnPlayBegin();
_blockSceneTreeScroll = false;
OnSearchBoxTextChanged();
}
/// <inheritdoc />
@@ -138,6 +140,7 @@ namespace FlaxEditor.Windows
{
base.OnPlayEnd();
_blockSceneTreeScroll = true;
OnSearchBoxTextChanged();
}
/// <summary>
@@ -173,6 +176,7 @@ namespace FlaxEditor.Windows
return;
_tree.LockChildrenRecursive();
PerformLayout();
// Update tree
var query = _searchBox.Text;
@@ -586,6 +590,7 @@ namespace FlaxEditor.Windows
_dragHandlers = null;
_tree = null;
_searchBox = null;
ScriptsBuilder.ScriptsReloadEnd -= OnSearchBoxTextChanged;
base.OnDestroy();
}