diff --git a/Source/Editor/Surface/ResizableSurfaceNode.cs b/Source/Editor/Surface/ResizableSurfaceNode.cs
index ee6560a84..e435e82bf 100644
--- a/Source/Editor/Surface/ResizableSurfaceNode.cs
+++ b/Source/Editor/Surface/ResizableSurfaceNode.cs
@@ -1,24 +1,260 @@
// Copyright (c) Wojciech Figat. All rights reserved.
+using System;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Surface
{
///
- /// Visject Surface node control that cna be resized.
+ /// Visject Surface node control that can be resized.
///
///
[HideInEditor]
public class ResizableSurfaceNode : SurfaceNode
{
- private Float2 _startResizingSize;
- private Float2 _startResizingCornerOffset;
+ ///
+ /// Helper class for that handles mouse interactions resizing the node itself.
+ ///
+ public class ResizeBorder : ContainerControl
+ {
+ ///
+ /// Distance to each of the 4 node edges that the cursor has to be so the user can resize along the direction of the edge.
+ ///
+ private const float BorderWidth = 15f;
+
+ private readonly VisjectSurface _surface;
+ private Float2 _lastSurfaceMouseLoc;
+ private Float2 startResizingSize;
+ private Float2 noClampedSize;
+
+ ///
+ /// Whether to ignore the surface index in parent when updating the cursor type. Set to false for nodes that have order like .
+ ///
+ internal bool IgnoreSurfaceIndex = true;
+
+ ///
+ /// The resizable node that this controls.
+ ///
+ public readonly ResizableSurfaceNode ResizableNode;
+
+ ///
+ /// True if the mouse is at the border of the resizable node and not further away from the border than .
+ ///
+ public bool IsMouseOverResizeBorder { get; private set; }
+
+ ///
+ /// True if is being resized.
+ ///
+ public bool IsResizing { get; private set; }
+
+ ///
+ /// The direction in which to resize the node. Should be either -1, 0 or 1 on both axes.
+ ///
+ public Float2 ResizeDirection { get; private set; }
+
+ ///
+ /// The type of cursor to show to hint to the user that they can resize the node in a given direction.
+ ///
+ public CursorType CursorType
+ {
+ get
+ {
+ if ((ResizeDirection.X == 1 && ResizeDirection.Y == 0) || (ResizeDirection.X == -1 && ResizeDirection.Y == 0))
+ return CursorType.SizeWE;
+ if ((ResizeDirection.X == 0 && ResizeDirection.Y == 1) || (ResizeDirection.X == 0 && ResizeDirection.Y == -1))
+ return CursorType.SizeNS;
+ if ((ResizeDirection.X == -1 && ResizeDirection.Y == -1) || (ResizeDirection.X == 1 && ResizeDirection.Y == 1))
+ return CursorType.SizeNWSE;
+ if ((ResizeDirection.X == 1 && ResizeDirection.Y == -1) || (ResizeDirection.X == -1 && ResizeDirection.Y == 1))
+ return CursorType.SizeNESW;
+
+ return CursorType.Default;
+ }
+ }
+
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The surface.
+ /// The this controls.
+ public ResizeBorder(VisjectSurface surface, ResizableSurfaceNode resizableNode)
+ {
+ _surface = surface;
+ ResizableNode = resizableNode;
+ }
+
+ ///
+ /// Updates location and size to match the resizable node with the additional padding.
+ ///
+ /// The node size.
+ /// The node location.
+ public void MatchResizableNode(Float2 nodeSize, Float2 nodeLocation)
+ {
+ Size = nodeSize + new Float2(BorderWidth * 2);
+ Location = nodeLocation - new Float2(BorderWidth);
+ }
+
+ private void UpdateResizeFlags(Float2 mouseLocation)
+ {
+ var borderRect = Bounds with { Location = Float2.Zero };
+ bool onBorder = borderRect.Contains(mouseLocation);
+ // Check this the way we do because some resizable nodes (like comments) have an implementation of `Control.ContainsPoint`
+ // that does not check for the size you would think they have based on their visual appearance
+ bool inNode = borderRect.MakeExpanded(-BorderWidth * 2f).Contains(mouseLocation);
+
+ Float2 rawResizeDirection = (mouseLocation - borderRect.Center);
+ var nodeHalfSizeNoBorder = ResizableNode.Size * 0.5f - BorderWidth;
+ ResizeDirection = new Float2(Mathf.Abs(rawResizeDirection.X) >= nodeHalfSizeNoBorder.X ? Mathf.Sign(rawResizeDirection.X) : 0,
+ Mathf.Abs(rawResizeDirection.Y) >= nodeHalfSizeNoBorder.Y ? Mathf.Sign(rawResizeDirection.Y) : 0);
+
+ IsMouseOverResizeBorder = onBorder && !inNode;
+ }
+
+ private Float2 GetControlDelta(Control control, Float2 start, Float2 end)
+ {
+ var pointOrigin = control.Parent ?? control;
+ var startPos = pointOrigin.PointFromParent(ResizableNode, start);
+ var endPos = pointOrigin.PointFromParent(ResizableNode, end);
+ return endPos - startPos;
+ }
+
+ private void EndResizing()
+ {
+ EndMouseCapture();
+ IsResizing = false;
+ if (startResizingSize != ResizableNode.Size)
+ {
+ var emptySize = ResizableNode.CalculateNodeSize(0, 0);
+ ResizableNode.SizeValue = ResizableNode.Size - emptySize;
+ _surface.MarkAsEdited(false);
+ }
+ }
+
+ ///
+ public override void OnMouseMove(Float2 location)
+ {
+ if (!IsResizing)
+ {
+ UpdateResizeFlags(location);
+ }
+ else if (_surface.CanEdit)
+ {
+ var resizeAxisAbs = ResizeDirection.Absolute;
+ var resizeAxisPos = Float2.Clamp(ResizeDirection, Float2.Zero, Float2.One);
+ var resizeAxisNeg = Float2.Clamp(-ResizeDirection, Float2.Zero, Float2.One);
+
+ var currentSurfaceMouseLoc = _surface.PointFromScreen(Input.MouseScreenPosition);
+ var delta = currentSurfaceMouseLoc - _lastSurfaceMouseLoc;
+
+ // TODO: Snapping
+ delta *= resizeAxisAbs;
+ var moveLocation = currentSurfaceMouseLoc;
+ var uiControlDelta = GetControlDelta(this, _lastSurfaceMouseLoc, moveLocation);
+
+ // This ensures that the node is not resized below min size, but also keeps the resize behavior like expected (which just Max() -ing the value does not)
+ var emptySize = ResizableNode.CalculateNodeSize(0, 0);
+ var minSize = ResizableNode._sizeMin;
+ noClampedSize = noClampedSize + uiControlDelta * resizeAxisPos - uiControlDelta * resizeAxisNeg;
+ if (noClampedSize.X < minSize.X && noClampedSize.X < ResizableNode.Size.X)
+ resizeAxisAbs.X = resizeAxisPos.X = resizeAxisNeg.X = 0f;
+ if (noClampedSize.Y < minSize.Y && noClampedSize.Y < ResizableNode.Size.Y)
+ resizeAxisAbs.Y = resizeAxisPos.Y = resizeAxisNeg.Y = 0f;
+
+ ResizableNode.Size += uiControlDelta * resizeAxisPos - uiControlDelta * resizeAxisNeg;
+ ResizableNode.Location += uiControlDelta * resizeAxisNeg;
+ ResizableNode.SizeValue = ResizableNode.Size - emptySize;
+
+ ResizableNode.CalculateNodeSize(ResizableNode.Size.X, ResizableNode.Size.Y);
+ MatchResizableNode(ResizableNode.Size, ResizableNode.Location);
+
+ _lastSurfaceMouseLoc = currentSurfaceMouseLoc;
+ }
+
+ // Update the cursor shape
+ if ((_surface.resizeableNodeIndexInParent <= IndexInParent || IgnoreSurfaceIndex) && !_surface.IsConnecting && _surface.CanEdit)
+ {
+ if (!IgnoreSurfaceIndex)
+ _surface.resizeableNodeIndexInParent = IndexInParent;
+
+ if (IsMouseOverResizeBorder)
+ Cursor = CursorType;
+ else
+ Cursor = CursorType.Default;
+ }
+
+ base.OnMouseMove(location);
+ }
+
+ ///
+ public override void OnMouseEnter(Float2 location)
+ {
+ Cursor = CursorType.Default;
+ base.OnMouseEnter(location);
+ }
+
+ ///
+ public override void OnMouseLeave()
+ {
+ Cursor = CursorType.Default;
+ IsMouseOverResizeBorder = false;
+ _surface.resizeableNodeIndexInParent = -1; // Will get updated by MouseMove again to match current index
+ base.OnMouseLeave();
+ }
+
+ ///
+ public override bool OnMouseDown(Float2 location, MouseButton button)
+ {
+ if (button == MouseButton.Left && IsMouseOverResizeBorder && !IsResizing)
+ {
+ // Start resizing
+ _lastSurfaceMouseLoc = _surface.PointFromScreen(Input.MouseScreenPosition);
+ noClampedSize = ResizableNode.Size;
+ IsResizing = true;
+ startResizingSize = ResizableNode.Size;
+ StartMouseCapture();
+ return true;
+ }
+
+ return base.OnMouseDown(location, button);
+ }
+
+ ///
+ public override bool OnMouseUp(Float2 location, MouseButton button)
+ {
+ if (button == MouseButton.Left && IsResizing)
+ {
+ Cursor = CursorType.Default;
+ EndResizing();
+ return true;
+ }
+
+ return base.OnMouseUp(location, button);
+ }
+
+ ///
+ public override void OnLostFocus()
+ {
+ if (IsResizing)
+ EndResizing();
+
+ base.OnLostFocus();
+ }
+
+ ///
+ public override void OnEndMouseCapture()
+ {
+ if (IsResizing)
+ EndResizing();
+
+ base.OnEndMouseCapture();
+ }
+ }
///
- /// Indicates whether the node is currently being resized.
+ /// Represents the border control used for resizing the associated element.
///
- protected bool _isResizing;
+ public ResizeBorder ResizeBorderControl;
///
/// Index of the Float2 value in the node values list to store node size.
@@ -30,11 +266,6 @@ namespace FlaxEditor.Surface
///
protected Float2 _sizeMin = new Float2(240, 160);
- ///
- /// Node resizing rectangle bounds.
- ///
- protected Rectangle _resizeButtonRect;
-
private Float2 SizeValue
{
get => (Float2)Values[_sizeValueIndex];
@@ -45,22 +276,31 @@ namespace FlaxEditor.Surface
public ResizableSurfaceNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
+ ResizeBorderControl = new ResizeBorder(Surface, this)
+ {
+ Parent = Surface.SurfaceRoot,
+ };
+
+ Parent = ResizeBorderControl;
+ ResizeBorderControl.MatchResizableNode(Size, Location);
}
///
- public override bool CanSelect(ref Float2 location)
+ protected override void OnLocationChanged()
{
- return base.CanSelect(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location);
+ ResizeBorderControl.MatchResizableNode(Size, Location);
+ base.OnLocationChanged();
}
///
public override void OnSurfaceLoaded(SurfaceNodeActions action)
{
- // Reapply the curve node size
+ // Reapply the node size
var size = SizeValue;
if (Surface != null && Surface.GridSnappingEnabled)
size = Surface.SnapToGrid(size, true);
Resize(size.X, size.Y);
+ ResizeBorderControl.MatchResizableNode(Size, Location);
base.OnSurfaceLoaded(action);
}
@@ -85,104 +325,15 @@ namespace FlaxEditor.Surface
{
base.Draw();
- if (Surface.CanEdit)
- {
- var style = Style.Current;
- if (_isResizing)
- {
- Render2D.FillRectangle(_resizeButtonRect, style.Selection);
- Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
- }
- Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
- }
+ if (Surface.CanEdit && !Surface.IsConnecting && (ResizeBorderControl.IsResizing || ResizeBorderControl.IsMouseOverResizeBorder))
+ Render2D.DrawRectangle(new Rectangle(Float2.Zero, Size), Style.Current.Foreground, 0.5f);
}
///
- public override void OnLostFocus()
+ public override void OnDestroy()
{
- if (_isResizing)
- EndResizing();
-
- base.OnLostFocus();
- }
-
- ///
- public override void OnEndMouseCapture()
- {
- if (_isResizing)
- EndResizing();
-
- base.OnEndMouseCapture();
- }
-
- ///
- public override bool OnMouseDown(Float2 location, MouseButton button)
- {
- if (base.OnMouseDown(location, button))
- return true;
-
- if (button == MouseButton.Left && _resizeButtonRect.Contains(ref location) && Surface.CanEdit)
- {
- // Start resizing
- _isResizing = true;
- _startResizingSize = Size;
- _startResizingCornerOffset = Size - location;
- StartMouseCapture();
- Cursor = CursorType.SizeNWSE;
- return true;
- }
-
- return false;
- }
-
- ///
- public override void OnMouseMove(Float2 location)
- {
- if (_isResizing)
- {
- var emptySize = CalculateNodeSize(0, 0);
- var size = Float2.Max(location - emptySize + _startResizingCornerOffset, _sizeMin);
- Resize(size.X, size.Y);
- }
- else
- {
- base.OnMouseMove(location);
- }
- }
-
- ///
- public override bool OnMouseUp(Float2 location, MouseButton button)
- {
- if (button == MouseButton.Left && _isResizing)
- {
- EndResizing();
- return true;
- }
-
- return base.OnMouseUp(location, button);
- }
-
- ///
- protected override void UpdateRectangles()
- {
- base.UpdateRectangles();
-
- const float buttonMargin = Constants.NodeCloseButtonMargin;
- const float buttonSize = Constants.NodeCloseButtonSize;
- _resizeButtonRect = new Rectangle(_closeButtonRect.Left, Height - buttonSize - buttonMargin - 4, buttonSize, buttonSize);
- }
-
- private void EndResizing()
- {
- Cursor = CursorType.Default;
- EndMouseCapture();
- _isResizing = false;
- if (_startResizingSize != Size)
- {
- var emptySize = CalculateNodeSize(0, 0);
- SizeValue = Size - emptySize;
- Surface.MarkAsEdited(false);
- }
+ ResizeBorderControl.Parent = null;
+ base.OnDestroy();
}
}
}
diff --git a/Source/Editor/Surface/SurfaceComment.cs b/Source/Editor/Surface/SurfaceComment.cs
index 79a285bd8..60fd3e272 100644
--- a/Source/Editor/Surface/SurfaceComment.cs
+++ b/Source/Editor/Surface/SurfaceComment.cs
@@ -65,6 +65,8 @@ namespace FlaxEditor.Surface
EndEditOnClick = false, // We have to handle this ourselves, otherwise the textbox instantly loses focus when double-clicking the header
HorizontalAlignment = TextAlignment.Center,
};
+
+ ResizeBorderControl.IgnoreSurfaceIndex = false;
}
///
@@ -86,7 +88,7 @@ namespace FlaxEditor.Surface
}
else if (OrderValue != -1)
{
- IndexInParent = OrderValue;
+ ResizeBorderControl.IndexInParent = OrderValue;
}
}
@@ -99,8 +101,8 @@ namespace FlaxEditor.Surface
Color = ColorValue = Color.FromHSV(new Random().NextFloat(0, 360), 0.7f, 0.25f, 0.8f);
if (OrderValue == -1)
- OrderValue = Context.CommentCount - 1;
- IndexInParent = OrderValue;
+ OrderValue = Context.CommentCount;
+ ResizeBorderControl.IndexInParent = OrderValue;
}
///
@@ -130,7 +132,6 @@ namespace FlaxEditor.Surface
_headerRect = new Rectangle(0, 0, Width, headerSize);
_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;
_renameTextBox.Height = headerSize;
}
@@ -188,13 +189,9 @@ namespace FlaxEditor.Surface
// Color button
Render2D.DrawSprite(style.Settings, _colorButtonRect, _colorButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
- // Resize button
- if (_isResizing)
- {
- Render2D.FillRectangle(_resizeButtonRect, style.Selection);
- Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
- }
- Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
+ // Resize
+ if ((ResizeBorderControl.IsResizing || ResizeBorderControl.IsMouseOverResizeBorder) && !Surface.IsConnecting)
+ Render2D.DrawRectangle(new Rectangle(Float2.Zero, Size), Style.Current.Foreground, 0.5f);
}
// Selection outline
@@ -229,7 +226,7 @@ namespace FlaxEditor.Surface
///
public override bool ContainsPoint(ref Float2 location, bool precise)
{
- return _headerRect.Contains(ref location) || _resizeButtonRect.Contains(ref location);
+ return _headerRect.Contains(ref location);
}
///
@@ -334,25 +331,25 @@ namespace FlaxEditor.Surface
{
cmOrder.ContextMenu.AddButton("Bring Forward", () =>
{
- if (IndexInParent < Context.CommentCount - 1)
- IndexInParent++;
- OrderValue = IndexInParent;
+ if (ResizeBorderControl.IndexInParent < Context.CommentCount - 1)
+ ResizeBorderControl.IndexInParent++;
+ OrderValue = ResizeBorderControl.IndexInParent;
});
cmOrder.ContextMenu.AddButton("Bring to Front", () =>
{
- IndexInParent = Context.CommentCount - 1;
- OrderValue = IndexInParent;
+ ResizeBorderControl.IndexInParent = Context.CommentCount - 1;
+ OrderValue = ResizeBorderControl.IndexInParent;
});
cmOrder.ContextMenu.AddButton("Send Backward", () =>
{
- if (IndexInParent > 0)
- IndexInParent--;
- OrderValue = IndexInParent;
+ if (ResizeBorderControl.IndexInParent > 0)
+ ResizeBorderControl.IndexInParent--;
+ OrderValue = ResizeBorderControl.IndexInParent;
});
cmOrder.ContextMenu.AddButton("Send to Back", () =>
{
- IndexInParent = 0;
- OrderValue = IndexInParent;
+ ResizeBorderControl.IndexInParent = 0;
+ OrderValue = ResizeBorderControl.IndexInParent;
});
}
}
diff --git a/Source/Editor/Surface/SurfaceRootControl.cs b/Source/Editor/Surface/SurfaceRootControl.cs
index 15d4c43cc..c7639a27d 100644
--- a/Source/Editor/Surface/SurfaceRootControl.cs
+++ b/Source/Editor/Surface/SurfaceRootControl.cs
@@ -46,10 +46,10 @@ namespace FlaxEditor.Surface
{
var child = _children[i];
- if (child is SurfaceComment && child.Visible)
+ if (child is ResizableSurfaceNode.ResizeBorder border && border.ResizableNode is SurfaceComment comment2 && comment2.Visible)
{
- Render2D.PushTransform(ref child._cachedTransform);
- child.Draw();
+ Render2D.PushTransform(ref comment2._cachedTransform);
+ comment2.Draw();
Render2D.PopTransform();
}
}
diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs
index 55c643f12..f1ea1d788 100644
--- a/Source/Editor/Surface/VisjectSurface.cs
+++ b/Source/Editor/Surface/VisjectSurface.cs
@@ -62,6 +62,7 @@ namespace FlaxEditor.Surface
private int _selectedConnectionIndex;
internal int _isUpdatingBoxTypes;
+ internal int resizeableNodeIndexInParent = -1;
///
/// True if surface supports implicit casting of the FlaxEngine.Object types into Boolean value (as simple validate check).
@@ -855,10 +856,11 @@ namespace FlaxEditor.Surface
int lowestCommentOrder = int.MaxValue;
for (int i = 0; i < selection.Count; i++)
{
- if (selection[i] is not SurfaceComment || selection[i].IndexInParent >= lowestCommentOrder)
- continue;
- hasCommentsSelected = true;
- lowestCommentOrder = selection[i].IndexInParent;
+ if (selection[i] is ResizableSurfaceNode node && node is SurfaceComment && node.ResizeBorderControl.IndexInParent < lowestCommentOrder)
+ {
+ hasCommentsSelected = true;
+ lowestCommentOrder = node.ResizeBorderControl.IndexInParent;
+ }
}
return _context.CreateComment(ref surfaceArea, string.IsNullOrEmpty(text) ? "Comment" : text, new Color(1.0f, 1.0f, 1.0f, 0.2f), hasCommentsSelected ? lowestCommentOrder : -1);
diff --git a/Source/Editor/Surface/VisjectSurfaceContext.cs b/Source/Editor/Surface/VisjectSurfaceContext.cs
index 560a7c1d6..b6192a198 100644
--- a/Source/Editor/Surface/VisjectSurfaceContext.cs
+++ b/Source/Editor/Surface/VisjectSurfaceContext.cs
@@ -80,8 +80,9 @@ namespace FlaxEditor.Surface
var result = new List();
for (int i = 0; i < RootControl.Children.Count; i++)
{
- if (RootControl.Children[i] is SurfaceComment comment)
- result.Add(comment);
+ var child = RootControl.Children[i];
+ if (child is ResizableSurfaceNode.ResizeBorder border && border.ResizableNode is SurfaceComment comment2)
+ result.Add(comment2);
}
return result;
}
@@ -101,7 +102,8 @@ namespace FlaxEditor.Surface
int count = 0;
for (int i = 0; i < RootControl.Children.Count; i++)
{
- if (RootControl.Children[i] is SurfaceComment)
+ var child = RootControl.Children[i];
+ if (child is ResizableSurfaceNode.ResizeBorder border && border.ResizableNode is SurfaceComment)
count++;
}
return count;