Merge branch 'moveVisjectConnection' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-moveVisjectConnection

# Conflicts:
#	Source/Editor/Surface/VisjectSurface.cs
This commit is contained in:
Wojtek Figat
2025-09-22 22:30:26 +02:00
12 changed files with 149 additions and 126 deletions

View File

@@ -229,20 +229,20 @@ namespace FlaxEditor.Surface
}
/// <inheritdoc />
protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox)
protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, List<Box> startBoxes)
{
// Check if show additional nodes in the current surface context
if (activeCM != _cmStateMachineMenu)
{
_nodesCache.Get(activeCM);
base.OnShowPrimaryMenu(activeCM, location, startBox);
base.OnShowPrimaryMenu(activeCM, location, startBoxes);
activeCM.VisibleChanged += OnActiveContextMenuVisibleChanged;
}
else
{
base.OnShowPrimaryMenu(activeCM, location, startBox);
base.OnShowPrimaryMenu(activeCM, location, startBoxes);
}
}

View File

@@ -101,12 +101,12 @@ namespace FlaxEditor.Surface
}
/// <inheritdoc />
protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox)
protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, List<Box> startBoxes)
{
activeCM.ShowExpanded = true;
_nodesCache.Get(activeCM);
base.OnShowPrimaryMenu(activeCM, location, startBox);
base.OnShowPrimaryMenu(activeCM, location, startBoxes);
activeCM.VisibleChanged += OnActiveContextMenuVisibleChanged;
}

View File

@@ -24,8 +24,8 @@ namespace FlaxEditor.Surface.ContextMenu
/// Visject context menu item clicked delegate.
/// </summary>
/// <param name="clickedItem">The item that was clicked</param>
/// <param name="selectedBox">The currently user-selected box. Can be null.</param>
public delegate void ItemClickedDelegate(VisjectCMItem clickedItem, Elements.Box selectedBox);
/// <param name="selectedBoxes">The currently user-selected boxes. Can be empty/ null.</param>
public delegate void ItemClickedDelegate(VisjectCMItem clickedItem, List<Elements.Box> selectedBoxes);
/// <summary>
/// Visject Surface node archetype spawn ability checking delegate.
@@ -53,7 +53,7 @@ namespace FlaxEditor.Surface.ContextMenu
private Panel _panel1;
private VerticalPanel _groupsPanel;
private readonly ParameterGetterDelegate _parametersGetter;
private Elements.Box _selectedBox;
private List<Elements.Box> _selectedBoxes = new List<Elements.Box>();
private NodeArchetype _parameterGetNodeArchetype;
private NodeArchetype _parameterSetNodeArchetype;
@@ -411,7 +411,8 @@ namespace FlaxEditor.Surface.ContextMenu
if (!IsLayoutLocked)
{
group.UnlockChildrenRecursive();
if (_contextSensitiveSearchEnabled && _selectedBox != null)
// TODO: Improve filtering to be based on boxes with the most common things instead of first box
if (_contextSensitiveSearchEnabled && _selectedBoxes[0] != null)
UpdateFilters();
else
SortGroups();
@@ -425,7 +426,8 @@ namespace FlaxEditor.Surface.ContextMenu
}
else if (_contextSensitiveSearchEnabled)
{
group.EvaluateVisibilityWithBox(_selectedBox);
// TODO: Filtering could be improved here as well
group.EvaluateVisibilityWithBox(_selectedBoxes[0]);
}
Profiler.EndEvent();
@@ -461,7 +463,7 @@ namespace FlaxEditor.Surface.ContextMenu
};
}
if (_contextSensitiveSearchEnabled)
group.EvaluateVisibilityWithBox(_selectedBox);
group.EvaluateVisibilityWithBox(_selectedBoxes[0]);
group.SortChildren();
if (ShowExpanded)
group.Open(false);
@@ -474,7 +476,7 @@ namespace FlaxEditor.Surface.ContextMenu
if (!isLayoutLocked)
{
if (_contextSensitiveSearchEnabled && _selectedBox != null)
if (_contextSensitiveSearchEnabled && _selectedBoxes[0] != null)
UpdateFilters();
else
SortGroups();
@@ -583,7 +585,7 @@ namespace FlaxEditor.Surface.ContextMenu
private void UpdateFilters()
{
if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBox == null)
if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBoxes[0] == null)
{
ResetView();
Profiler.EndEvent();
@@ -592,7 +594,7 @@ namespace FlaxEditor.Surface.ContextMenu
// Update groups
LockChildrenRecursive();
var contextSensitiveSelectedBox = _contextSensitiveSearchEnabled ? _selectedBox : null;
var contextSensitiveSelectedBox = _contextSensitiveSearchEnabled && _selectedBoxes.Count > 0 ? _selectedBoxes[0] : null;
for (int i = 0; i < _groups.Count; i++)
{
_groups[i].UpdateFilter(_searchBox.Text, contextSensitiveSelectedBox);
@@ -640,7 +642,7 @@ namespace FlaxEditor.Surface.ContextMenu
public void OnClickItem(VisjectCMItem item)
{
Hide();
ItemClicked?.Invoke(item, _selectedBox);
ItemClicked?.Invoke(item, _selectedBoxes);
}
/// <summary>
@@ -666,12 +668,12 @@ namespace FlaxEditor.Surface.ContextMenu
for (int i = 0; i < _groups.Count; i++)
{
_groups[i].ResetView();
if (_contextSensitiveSearchEnabled)
_groups[i].EvaluateVisibilityWithBox(_selectedBox);
if (_contextSensitiveSearchEnabled && _selectedBoxes.Count > 0)
_groups[i].EvaluateVisibilityWithBox(_selectedBoxes[0]);
}
UnlockChildrenRecursive();
if (_contextSensitiveSearchEnabled && _selectedBox != null)
if (_contextSensitiveSearchEnabled && _selectedBoxes.Count > 0 && _selectedBoxes[0] != null)
UpdateFilters();
else
SortGroups();
@@ -772,10 +774,10 @@ namespace FlaxEditor.Surface.ContextMenu
/// </summary>
/// <param name="parent">Parent control to attach to it.</param>
/// <param name="location">Popup menu origin location in parent control coordinates.</param>
/// <param name="startBox">The currently selected box that the new node will get connected to. Can be null</param>
public void Show(Control parent, Float2 location, Elements.Box startBox)
/// <param name="startBoxes">The currently selected boxes that the new node will get connected to. Can be empty/ null</param>
public void Show(Control parent, Float2 location, List<Elements.Box> startBoxes)
{
_selectedBox = startBox;
_selectedBoxes = startBoxes;
base.Show(parent, location);
}

View File

@@ -544,35 +544,39 @@ namespace FlaxEditor.Surface.Elements
public override void OnMouseLeave()
{
if (_originalTooltipText != null)
{
TooltipText = _originalTooltipText;
}
if (_isMouseDown)
{
_isMouseDown = false;
if (Surface.CanEdit)
{
if (!IsOutput && HasSingleConnection)
if (IsOutput && Input.GetKey(KeyboardKeys.Control))
{
var connectedBox = Connections[0];
List<Box> connectedBoxes = new List<Box>(Connections);
for (int i = 0; i < connectedBoxes.Count; i++)
{
BreakConnection(connectedBoxes[i]);
Surface.ConnectingStart(connectedBoxes[i], true);
}
}
else if (!IsOutput && HasSingleConnection)
{
var otherBox = Connections[0];
if (Surface.Undo != null && Surface.Undo.Enabled)
{
var action = new ConnectBoxesAction((InputBox)this, (OutputBox)connectedBox, false);
BreakConnection(connectedBox);
var action = new ConnectBoxesAction((InputBox)this, (OutputBox)otherBox, false);
BreakConnection(otherBox);
action.End();
Surface.AddBatchedUndoAction(action);
Surface.MarkAsEdited();
}
else
{
BreakConnection(connectedBox);
}
Surface.ConnectingStart(connectedBox);
BreakConnection(otherBox);
Surface.ConnectingStart(otherBox);
}
else
{
Surface.ConnectingStart(this);
}
}
}
base.OnMouseLeave();

View File

@@ -917,7 +917,7 @@ namespace FlaxEditor.Surface
/// <inheritdoc />
public override bool OnTestTooltipOverControl(ref Float2 location)
{
return _headerRect.Contains(ref location) && ShowTooltip && !Surface.IsConnecting && !Surface.IsBoxSelecting;
return _headerRect.Contains(ref location) && ShowTooltip && !Surface.IsConnecting && !Surface.IsSelecting;
}
/// <inheritdoc />
@@ -1075,7 +1075,7 @@ namespace FlaxEditor.Surface
// Header
var headerColor = style.BackgroundHighlighted;
if (_headerRect.Contains(ref _mousePosition) && !Surface.IsConnecting && !Surface.IsBoxSelecting)
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);
@@ -1083,7 +1083,7 @@ namespace FlaxEditor.Surface
// Close button
if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0 && Surface.CanEdit)
{
bool highlightClose = _closeButtonRect.Contains(_mousePosition) && !Surface.IsConnecting && !Surface.IsBoxSelecting;
bool highlightClose = _closeButtonRect.Contains(_mousePosition) && !Surface.IsConnecting && !Surface.IsSelecting;
Render2D.DrawSprite(style.Cross, _closeButtonRect, highlightClose ? style.Foreground : style.ForegroundGrey);
}
@@ -1133,7 +1133,7 @@ namespace FlaxEditor.Surface
return true;
// Close/ delete
bool canDelete = !Surface.IsConnecting && !Surface.WasBoxSelecting && !Surface.WasMovingSelection;
bool canDelete = !Surface.IsConnecting && !Surface.WasSelecting && !Surface.WasMovingSelection;
if (button == MouseButton.Left && canDelete && (Archetype.Flags & NodeFlags.NoCloseButton) == 0 && _closeButtonRect.Contains(ref location))
{
Surface.Delete(this);

View File

@@ -1,6 +1,8 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEngine;
namespace FlaxEditor.Surface
@@ -233,11 +235,15 @@ namespace FlaxEditor.Surface
/// Begins connecting surface objects action.
/// </summary>
/// <param name="instigator">The connection instigator (eg. start box).</param>
public void ConnectingStart(IConnectionInstigator instigator)
/// <param name="additive">If the instigator should be added to the list of instigators.</param>
public void ConnectingStart(IConnectionInstigator instigator, bool additive = false)
{
if (instigator != null && instigator != _connectionInstigator)
if (instigator != null && instigator != _connectionInstigators)
{
_connectionInstigator = instigator;
if (!additive)
_connectionInstigators.Clear();
_connectionInstigators.Add(instigator);
StartMouseCapture();
}
}
@@ -257,22 +263,30 @@ namespace FlaxEditor.Surface
/// <param name="end">The end object (eg. end box).</param>
public void ConnectingEnd(IConnectionInstigator end)
{
// Ensure that there was a proper start box
if (_connectionInstigator == null)
// Ensure that there is at least one connection instigator
if (_connectionInstigators.Count == 0)
return;
var start = _connectionInstigator;
_connectionInstigator = null;
// Check if boxes are different and end box is specified
if (start == end || end == null)
return;
// Connect them
if (start.CanConnectWith(end))
List<IConnectionInstigator> instigators = new List<IConnectionInstigator>(_connectionInstigators);
for (int i = 0; i < instigators.Count; i++)
{
start.Connect(end);
var start = instigators[i];
// Check if boxes are different and end box is specified
if (start == end || end == null)
return;
// Properly handle connecting to a socket that already has a connection
if (end is Box e && !e.IsOutput && start is Box s && e.AreConnected(s))
e.BreakConnection(s);
// Connect them
if (start.CanConnectWith(end))
start.Connect(end);
}
// Reset instigator list
_connectionInstigators.Clear();
}
}
}

View File

@@ -261,10 +261,10 @@ namespace FlaxEditor.Surface
/// </summary>
/// <param name="activeCM">The active context menu to show.</param>
/// <param name="location">The display location on the surface control.</param>
/// <param name="startBox">The start box.</param>
protected virtual void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox)
/// <param name="startBoxes">The start boxes.</param>
protected virtual void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, List<Box> startBoxes)
{
activeCM.Show(this, location, startBox);
activeCM.Show(this, location, startBoxes);
}
/// <summary>
@@ -298,8 +298,10 @@ namespace FlaxEditor.Surface
_cmStartPos = location;
// Offset added in case the user doesn't like the box and wants to quickly get rid of it by clicking
OnShowPrimaryMenu(_activeVisjectCM, _cmStartPos + ContextMenuOffset, _connectionInstigator as Box);
List<Box> startBoxes = new List<Box>(_connectionInstigators.Where(c => c is Box).Cast<Box>());
// Position offset added so the user can quickly close the menu by clicking
OnShowPrimaryMenu(_activeVisjectCM, _cmStartPos + ContextMenuOffset, startBoxes);
if (!string.IsNullOrEmpty(input))
{
@@ -513,17 +515,15 @@ namespace FlaxEditor.Surface
private void OnPrimaryMenuVisibleChanged(Control primaryMenu)
{
if (!primaryMenu.Visible)
{
_connectionInstigator = null;
}
_connectionInstigators.Clear();
}
/// <summary>
/// Handles Visject CM item click event by spawning the selected item.
/// </summary>
/// <param name="visjectCmItem">The item.</param>
/// <param name="selectedBox">The selected box.</param>
protected virtual void OnPrimaryMenuButtonClick(VisjectCMItem visjectCmItem, Box selectedBox)
/// <param name="selectedBoxes">The selected boxes.</param>
protected virtual void OnPrimaryMenuButtonClick(VisjectCMItem visjectCmItem, List<Box> selectedBoxes)
{
if (!CanEdit)
return;
@@ -550,34 +550,36 @@ namespace FlaxEditor.Surface
// Auto select new node
Select(node);
if (selectedBox != null)
for (int i = 0; i < selectedBoxes.Count; i++)
{
Box endBox = null;
foreach (var box in node.GetBoxes().Where(box => box.IsOutput != selectedBox.IsOutput))
Box currentBox = selectedBoxes[i];
if (currentBox != null)
{
if (selectedBox.IsOutput)
Box endBox = null;
foreach (var box in node.GetBoxes().Where(box => box.IsOutput != currentBox.IsOutput))
{
if (box.CanUseType(selectedBox.CurrentType))
if (currentBox.IsOutput)
{
endBox = box;
break;
if (box.CanUseType(currentBox.CurrentType))
{
endBox = box;
break;
}
}
}
else
{
if (selectedBox.CanUseType(box.CurrentType))
else
{
endBox = box;
break;
if (currentBox.CanUseType(box.CurrentType))
{
endBox = box;
break;
}
}
}
if (endBox == null && selectedBox.CanUseType(box.CurrentType))
{
endBox = box;
if (endBox == null && currentBox.CanUseType(box.CurrentType))
endBox = box;
}
TryConnect(currentBox, endBox);
}
TryConnect(selectedBox, endBox);
}
}
@@ -593,13 +595,8 @@ namespace FlaxEditor.Surface
}
// If the user is patiently waiting for his box to get connected to the newly created one fulfill his wish!
_connectionInstigator = startBox;
if (!IsConnecting)
{
ConnectingStart(startBox);
}
ConnectingEnd(endBox);
// Smart-Select next box

View File

@@ -1,5 +1,6 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using FlaxEditor.Surface.Elements;
using FlaxEngine;
@@ -126,40 +127,45 @@ namespace FlaxEditor.Surface
/// <remarks>Called only when user is connecting nodes.</remarks>
protected virtual void DrawConnectingLine()
{
// Get start position
var startPos = _connectionInstigator.ConnectionOrigin;
// Check if mouse is over any of box
var cmVisible = _activeVisjectCM != null && _activeVisjectCM.Visible;
var endPos = cmVisible ? _rootControl.PointFromParent(ref _cmStartPos) : _rootControl.PointFromParent(ref _mousePos);
Color lineColor = Style.Colors.Connecting;
if (_lastInstigatorUnderMouse != null && !cmVisible)
{
// Check if can connect objects
bool canConnect = _connectionInstigator.CanConnectWith(_lastInstigatorUnderMouse);
lineColor = canConnect ? Style.Colors.ConnectingValid : Style.Colors.ConnectingInvalid;
endPos = _lastInstigatorUnderMouse.ConnectionOrigin;
}
Float2 actualStartPos = startPos;
Float2 actualEndPos = endPos;
if (_connectionInstigator is Archetypes.Tools.RerouteNode)
List<IConnectionInstigator> instigators = new List<IConnectionInstigator>(_connectionInstigators);
for (int i = 0; i < instigators.Count; i++)
{
if (endPos.X < startPos.X && _lastInstigatorUnderMouse is null or Box { IsOutput: true })
IConnectionInstigator currentInstigator = instigators[i];
Float2 currentStartPosition = currentInstigator.ConnectionOrigin;
// Check if mouse is over any box
if (_lastInstigatorUnderMouse != null && !cmVisible)
{
// Check if can connect objects
bool canConnect = currentInstigator.CanConnectWith(_lastInstigatorUnderMouse);
lineColor = canConnect ? Style.Colors.ConnectingValid : Style.Colors.ConnectingInvalid;
endPos = _lastInstigatorUnderMouse.ConnectionOrigin;
}
Float2 actualStartPos = currentStartPosition;
Float2 actualEndPos = endPos;
if (currentInstigator is Archetypes.Tools.RerouteNode)
{
if (endPos.X < currentStartPosition.X && _lastInstigatorUnderMouse is null or Box { IsOutput: true })
{
actualStartPos = endPos;
actualEndPos = currentStartPosition;
}
}
else if (currentInstigator is Box { IsOutput: false })
{
actualStartPos = endPos;
actualEndPos = startPos;
actualEndPos = currentStartPosition;
}
}
else if (_connectionInstigator is Box { IsOutput: false })
{
actualStartPos = endPos;
actualEndPos = startPos;
}
// Draw connection
_connectionInstigator.DrawConnectingLine(ref actualStartPos, ref actualEndPos, ref lineColor);
// Draw connection
currentInstigator.DrawConnectingLine(ref actualStartPos, ref actualEndPos, ref lineColor);
}
}
/// <summary>
@@ -226,10 +232,10 @@ namespace FlaxEditor.Surface
_rootControl.DrawComments();
// Reset input flags here because this is the closest to Update we have
WasBoxSelecting = IsBoxSelecting;
WasSelecting = IsSelecting;
WasMovingSelection = IsMovingSelection;
if (IsBoxSelecting)
if (IsSelecting)
{
DrawSelection();
}

View File

@@ -290,7 +290,7 @@ namespace FlaxEditor.Surface
if (_leftMouseDown)
{
// Connecting
if (_connectionInstigator != null)
if (_connectionInstigators.Count > 0)
{
}
// Moving
@@ -460,7 +460,7 @@ namespace FlaxEditor.Surface
public override bool OnMouseDown(Float2 location, MouseButton button)
{
// Check if user is connecting boxes
if (_connectionInstigator != null)
if (_connectionInstigators.Count > 0)
return true;
// Base
@@ -606,7 +606,7 @@ namespace FlaxEditor.Surface
_movingNodesDelta = Float2.Zero;
}
// Connecting
else if (_connectionInstigator != null)
else if (_connectionInstigators.Count > 0)
{
}
// Selecting
@@ -678,7 +678,7 @@ namespace FlaxEditor.Surface
ShowPrimaryMenu(_cmStartPos);
}
// Letting go of a connection or right clicking while creating a connection
else if (!_isMovingSelection && _connectionInstigator != null && !IsPrimaryMenuOpened)
else if (!_isMovingSelection && _connectionInstigators.Count > 0 && !IsPrimaryMenuOpened)
{
_cmStartPos = location;
Cursor = CursorType.Default;

View File

@@ -33,7 +33,7 @@ namespace FlaxEditor.Surface
Enabled = false;
// Clean data
_connectionInstigator = null;
_connectionInstigators.Clear();
_lastInstigatorUnderMouse = null;
var failed = RootContext.Load();

View File

@@ -121,7 +121,7 @@ namespace FlaxEditor.Surface
/// <summary>
/// The connection start.
/// </summary>
protected IConnectionInstigator _connectionInstigator;
protected List<IConnectionInstigator> _connectionInstigators = new List<IConnectionInstigator>();
/// <summary>
/// The last connection instigator under mouse.
@@ -232,19 +232,19 @@ namespace FlaxEditor.Surface
}
/// <summary>
/// Gets a value indicating whether user is box selecting nodes.
/// Gets a value indicating whether user is selecting nodes.
/// </summary>
public bool IsBoxSelecting => _leftMouseDown && !_isMovingSelection && _connectionInstigator == null;
public bool IsSelecting => _leftMouseDown && !_isMovingSelection && _connectionInstigators.Count == 0;
/// <summary>
/// Gets a value indicating whether user was previously box selecting nodes.
/// Gets a value indicating whether user was previously selecting nodes.
/// </summary>
public bool WasBoxSelecting { get; private set; }
public bool WasSelecting { get; private set; }
/// <summary>
/// Gets a value indicating whether user is moving selected nodes.
/// </summary>
public bool IsMovingSelection => _leftMouseDown && _isMovingSelection && _connectionInstigator == null;
public bool IsMovingSelection => _leftMouseDown && _isMovingSelection && _connectionInstigators.Count == 0;
/// <summary>
/// Gets a value indicating whether user was previously moving selected nodes.
@@ -254,7 +254,7 @@ namespace FlaxEditor.Surface
/// <summary>
/// Gets a value indicating whether user is connecting nodes.
/// </summary>
public bool IsConnecting => _connectionInstigator != null;
public bool IsConnecting => _connectionInstigators.Count > 0;
/// <summary>
/// Gets a value indicating whether the left mouse button is down.

View File

@@ -212,7 +212,7 @@ namespace FlaxEditor.Surface
}
/// <inheritdoc />
protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox)
protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, List<Box> startBoxes)
{
// Update nodes for method overrides
Profiler.BeginEvent("Overrides");
@@ -268,7 +268,7 @@ namespace FlaxEditor.Surface
// Update nodes for invoke methods (async)
_nodesCache.Get(activeCM);
base.OnShowPrimaryMenu(activeCM, location, startBox);
base.OnShowPrimaryMenu(activeCM, location, startBoxes);
activeCM.VisibleChanged += OnActiveContextMenuVisibleChanged;
}