diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs
index 8f786362d..f7cc7107a 100644
--- a/Source/Editor/Options/InterfaceOptions.cs
+++ b/Source/Editor/Options/InterfaceOptions.cs
@@ -382,6 +382,20 @@ namespace FlaxEditor.Options
[EditorDisplay("Visject"), EditorOrder(550), Tooltip("Shows/hides the description panel in the visual scripting context menu.")]
public bool VisualScriptingDescriptionPanel { get; set; } = true;
+ ///
+ /// Gets or sets the surface grid snapping option.
+ ///
+ [DefaultValue(false)]
+ [EditorDisplay("Visject", "Grid Snapping"), EditorOrder(551), Tooltip("Toggles grid snapping when moving nodes.")]
+ public bool SurfaceGridSnapping { get; set; } = false;
+
+ ///
+ /// Gets or sets the surface grid snapping option.
+ ///
+ [DefaultValue(20.0f)]
+ [EditorDisplay("Visject", "Grid Snapping Size"), EditorOrder(551), Tooltip("Defines the size of the grid for nodes snapping."), VisibleIf(nameof(SurfaceGridSnapping))]
+ public float SurfaceGridSnappingSize { get; set; } = 20.0f;
+
private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont);
private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont);
diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs
index fe79ec3af..c1f966812 100644
--- a/Source/Editor/Surface/SurfaceUtils.cs
+++ b/Source/Editor/Surface/SurfaceUtils.cs
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
@@ -10,9 +9,9 @@ using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.Options;
using FlaxEditor.Scripting;
-using FlaxEditor.Utilities;
using FlaxEngine.Utilities;
using FlaxEngine;
+using FlaxEditor.GUI;
namespace FlaxEditor.Surface
{
@@ -556,5 +555,33 @@ namespace FlaxEditor.Surface
return true;
return AreScriptTypesEqualInner(left, right) || AreScriptTypesEqualInner(right, left);
}
+
+ internal static void PerformCommonSetup(Windows.Assets.AssetEditorWindow window, ToolStrip toolStrip, VisjectSurface surface,
+ out ToolStripButton saveButton, out ToolStripButton undoButton, out ToolStripButton redoButton)
+ {
+ var editor = window.Editor;
+ var interfaceOptions = editor.Options.Options.Interface;
+ var inputOptions = editor.Options.Options.Input;
+ var undo = surface.Undo;
+
+ // Toolstrip
+ saveButton = (ToolStripButton)toolStrip.AddButton(editor.Icons.Save64, window.Save).LinkTooltip("Save");
+ toolStrip.AddSeparator();
+ undoButton = (ToolStripButton)toolStrip.AddButton(editor.Icons.Undo64, undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ redoButton = (ToolStripButton)toolStrip.AddButton(editor.Icons.Redo64, undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
+ toolStrip.AddSeparator();
+ toolStrip.AddButton(editor.Icons.Search64, editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({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;
+ gridSnapButton.Checked = surface.GridSnappingEnabled = interfaceOptions.SurfaceGridSnapping;
+ surface.GridSnappingSize = interfaceOptions.SurfaceGridSnappingSize;
+
+ // Setup input actions
+ window.InputActions.Add(options => options.Undo, undo.PerformUndo);
+ window.InputActions.Add(options => options.Redo, undo.PerformRedo);
+ window.InputActions.Add(options => options.Search, editor.ContentFinding.ShowSearch);
+ }
}
}
diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs
index 9b6f86bcb..416f0152e 100644
--- a/Source/Editor/Surface/VisjectSurface.Input.cs
+++ b/Source/Editor/Surface/VisjectSurface.Input.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Options;
@@ -24,6 +25,7 @@ namespace FlaxEditor.Surface
private string _currentInputText = string.Empty;
private Float2 _movingNodesDelta;
+ private Float2 _gridRoundingDelta;
private HashSet _movingNodes;
private readonly Stack _inputBrackets = new Stack();
@@ -189,6 +191,22 @@ namespace FlaxEditor.Surface
}
}
+ ///
+ /// Snaps a coordinate point to the grid.
+ ///
+ /// The point to be rounded.
+ /// Round to ceiling instead?
+ /// Rounded coordinate.
+ public Float2 SnapToGrid(Float2 point, bool ceil = false)
+ {
+ float gridSize = GridSnappingSize;
+ Float2 snapped = point.Absolute / gridSize;
+ snapped = ceil ? Float2.Ceil(snapped) : Float2.Floor(snapped);
+ snapped.X = (float)Math.CopySign(snapped.X * gridSize, point.X);
+ snapped.Y = (float)Math.CopySign(snapped.Y * gridSize, point.Y);
+ return snapped;
+ }
+
///
public override void OnMouseEnter(Float2 location)
{
@@ -256,18 +274,39 @@ namespace FlaxEditor.Surface
// Moving
else if (_isMovingSelection)
{
+ var gridSnap = GridSnappingEnabled;
+ if (!gridSnap)
+ _gridRoundingDelta = Float2.Zero; // Reset in case user toggled option between frames.
+
// Calculate delta (apply view offset)
var viewDelta = _rootControl.Location - _movingSelectionViewPos;
_movingSelectionViewPos = _rootControl.Location;
- var delta = location - _leftMouseDownPos - viewDelta;
- if (delta.LengthSquared > 0.01f)
+ var delta = location - _leftMouseDownPos - viewDelta + _gridRoundingDelta;
+ var deltaLengthSquared = delta.LengthSquared;
+
+ delta /= _targetScale;
+ if ((!gridSnap || Mathf.Abs(delta.X) >= GridSnappingSize || (Mathf.Abs(delta.Y) >= GridSnappingSize))
+ && deltaLengthSquared > 0.01f)
{
- // Move selected nodes
- delta /= _targetScale;
+ if (gridSnap)
+ {
+ Float2 unroundedDelta = delta;
+ delta = SnapToGrid(unroundedDelta);
+ _gridRoundingDelta = (unroundedDelta - delta) * _targetScale; // Standardize unit of the rounding delta, in case user zooms between node movements.
+ }
+
foreach (var node in _movingNodes)
+ {
+ if (gridSnap)
+ {
+ Float2 unroundedLocation = node.Location;
+ node.Location = SnapToGrid(unroundedLocation);
+ }
node.Location += delta;
+ }
+
_leftMouseDownPos = location;
- _movingNodesDelta += delta;
+ _movingNodesDelta += delta; // TODO: Figure out how to handle undo for differing values of _gridRoundingDelta between selected nodes. For now it will be a small error in undo.
if (_movingNodes.Count > 0)
{
Cursor = CursorType.SizeAll;
diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs
index b3869f304..28ec17d53 100644
--- a/Source/Editor/Surface/VisjectSurface.cs
+++ b/Source/Editor/Surface/VisjectSurface.cs
@@ -31,11 +31,29 @@ namespace FlaxEditor.Surface
///
protected SurfaceRootControl _rootControl;
+ ///
+ /// Is grid snapping enabled for this surface?
+ ///
+ public bool GridSnappingEnabled
+ {
+ get => _gridSnappingEnabled;
+ set
+ {
+ _gridSnappingEnabled = value;
+ _gridRoundingDelta = Float2.Zero;
+ }
+ }
+
+ ///
+ /// The size of the snapping grid.
+ ///
+ public float GridSnappingSize = 20f;
+
private float _targetScale = 1.0f;
private float _moveViewWithMouseDragSpeed = 1.0f;
private bool _canEdit = true;
private readonly bool _supportsDebugging;
- private bool _isReleasing;
+ private bool _isReleasing, _gridSnappingEnabled;
private VisjectCM _activeVisjectCM;
private GroupArchetype _customNodesGroup;
private List _customNodes;
@@ -632,6 +650,11 @@ namespace FlaxEditor.Surface
SelectionChanged?.Invoke();
}
+ internal void ToggleGridSnapping()
+ {
+ GridSnappingEnabled = !GridSnappingEnabled;
+ }
+
///
/// Selects all the nodes.
///
diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs
index 7da0d9707..16e4f303b 100644
--- a/Source/Editor/Surface/VisjectSurfaceWindow.cs
+++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs
@@ -891,9 +891,21 @@ namespace FlaxEditor.Surface
///
protected Tabs _tabs;
- private readonly ToolStripButton _saveButton;
- private readonly ToolStripButton _undoButton;
- private readonly ToolStripButton _redoButton;
+ ///
+ /// Save button on a toolstrip.
+ ///
+ protected ToolStripButton _saveButton;
+
+ ///
+ /// Undo button on a toolstrip.
+ ///
+ protected ToolStripButton _undoButton;
+
+ ///
+ /// Redo button on a toolstrip.
+ ///
+ protected ToolStripButton _redoButton;
+
private bool _showWholeGraphOnLoad = true;
///
@@ -950,8 +962,6 @@ namespace FlaxEditor.Surface
protected VisjectSurfaceWindow(Editor editor, AssetItem item, bool useTabs = false)
: base(editor, item)
{
- var inputOptions = Editor.Options.Options.Input;
-
// Undo
_undo = new FlaxEditor.Undo();
_undo.UndoDone += OnUndoRedo;
@@ -998,20 +1008,6 @@ namespace FlaxEditor.Surface
_propertiesEditor.Panel.Parent = _split2.Panel2;
}
_propertiesEditor.Modified += OnPropertyEdited;
-
- // Toolstrip
- _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
- _toolstrip.AddSeparator();
- _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
- _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
- _toolstrip.AddSeparator();
- _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
- _toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph");
-
- // Setup input actions
- InputActions.Add(options => options.Undo, _undo.PerformUndo);
- InputActions.Add(options => options.Redo, _undo.PerformRedo);
- InputActions.Add(options => options.Search, Editor.ContentFinding.ShowSearch);
}
private void OnUndoRedo(IUndoAction action)
diff --git a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs
index e75becf72..1cf03f062 100644
--- a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs
+++ b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs
@@ -206,6 +206,7 @@ namespace FlaxEditor.Windows.Assets
_surface.ContextChanged += OnSurfaceContextChanged;
// Toolstrip
+ SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
_showNodesButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Bone64, () => _preview.ShowNodes = !_preview.ShowNodes).LinkTooltip("Show animated model nodes debug view");
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/anim-graph/index.html")).LinkTooltip("See documentation to learn more");
diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
index 47d6f2840..f4e7a4f6a 100644
--- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
+++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
@@ -172,13 +172,8 @@ namespace FlaxEditor.Windows.Assets
_knowledgePropertiesEditor.Panel.Parent = _split2.Panel2;
// Toolstrip
- _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
+ SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
_toolstrip.AddSeparator();
- _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
- _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
- _toolstrip.AddSeparator();
- _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
- _toolstrip.AddButton(editor.Icons.CenterView64, _surface.ShowWholeGraph).LinkTooltip("Show whole graph");
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/scripting/ai/behavior-trees/index.html")).LinkTooltip("See documentation to learn more");
// Debug behavior picker
@@ -206,11 +201,6 @@ namespace FlaxEditor.Windows.Assets
_behaviorPicker.CheckValid = OnBehaviorPickerCheckValid;
_behaviorPicker.ValueChanged += OnBehaviorPickerValueChanged;
- // Setup input actions
- InputActions.Add(options => options.Undo, _undo.PerformUndo);
- InputActions.Add(options => options.Redo, _undo.PerformRedo);
- InputActions.Add(options => options.Search, Editor.ContentFinding.ShowSearch);
-
SetCanEdit(!Editor.IsPlayMode);
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
}
diff --git a/Source/Editor/Windows/Assets/MaterialWindow.cs b/Source/Editor/Windows/Assets/MaterialWindow.cs
index ef37fbe67..ad72c92d9 100644
--- a/Source/Editor/Windows/Assets/MaterialWindow.cs
+++ b/Source/Editor/Windows/Assets/MaterialWindow.cs
@@ -257,8 +257,9 @@ namespace FlaxEditor.Windows.Assets
};
// Toolstrip
- _toolstrip.AddSeparator();
+ SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
_toolstrip.AddButton(editor.Icons.Code64, ShowSourceCode).LinkTooltip("Show generated shader source code");
+ _toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/materials/index.html")).LinkTooltip("See documentation to learn more");
}
diff --git a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs
index 93dce7034..c373b4cdc 100644
--- a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs
+++ b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs
@@ -141,8 +141,9 @@ namespace FlaxEditor.Windows.Assets
};
// Toolstrip
- _toolstrip.AddSeparator();
+ SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
_toolstrip.AddButton(editor.Icons.Code64, ShowSourceCode).LinkTooltip("Show generated shader source code");
+ _toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more");
}
diff --git a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs
index 9618b30eb..24b3e3f9b 100644
--- a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs
+++ b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs
@@ -70,13 +70,7 @@ namespace FlaxEditor.Windows.Assets
_undo.ActionDone += OnUndoRedo;
// Toolstrip
- _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
- _toolstrip.AddSeparator();
- _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
- _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
- _toolstrip.AddSeparator();
- _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
- _toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph");
+ SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
// Panel
_panel = new Panel(ScrollBars.None)
@@ -85,11 +79,6 @@ namespace FlaxEditor.Windows.Assets
Offsets = new Margin(0, 0, _toolstrip.Bottom, 0),
Parent = this
};
-
- // Setup input actions
- InputActions.Add(options => options.Undo, _undo.PerformUndo);
- InputActions.Add(options => options.Redo, _undo.PerformRedo);
- InputActions.Add(options => options.Search, Editor.ContentFinding.ShowSearch);
}
private void OnUndoRedo(IUndoAction action)
diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs
index a7201e81c..b424ecffc 100644
--- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs
+++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs
@@ -597,13 +597,7 @@ namespace FlaxEditor.Windows.Assets
_propertiesEditor.Select(_properties);
// Toolstrip
- _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
- _toolstrip.AddSeparator();
- _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
- _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
- _toolstrip.AddSeparator();
- _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
- _toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph");
+ SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/scripting/visual/index.html")).LinkTooltip("See documentation to learn more");
_debugToolstripControls = new[]
@@ -643,9 +637,6 @@ namespace FlaxEditor.Windows.Assets
debugObjectPickerContainer.Parent = _toolstrip;
// Setup input actions
- InputActions.Add(options => options.Undo, _undo.PerformUndo);
- InputActions.Add(options => options.Redo, _undo.PerformRedo);
- InputActions.Add(options => options.Search, Editor.ContentFinding.ShowSearch);
InputActions.Add(options => options.DebuggerContinue, OnDebuggerContinue);
InputActions.Add(options => options.DebuggerStepOver, OnDebuggerStepOver);
InputActions.Add(options => options.DebuggerStepOut, OnDebuggerStepOut);
diff --git a/Source/Engine/Core/Math/Float2.cs b/Source/Engine/Core/Math/Float2.cs
index adbe61508..92247d865 100644
--- a/Source/Engine/Core/Math/Float2.cs
+++ b/Source/Engine/Core/Math/Float2.cs
@@ -865,6 +865,16 @@ namespace FlaxEngine
return new Float2(Mathf.Ceil(v.X), Mathf.Ceil(v.Y));
}
+ ///
+ /// Returns the vector with components containing the smallest integer smaller to or equal to the original value.
+ ///
+ /// The value.
+ /// The result.
+ public static Float2 Floor(Float2 v)
+ {
+ return new Float2(Mathf.Floor(v.X), Mathf.Floor(v.Y));
+ }
+
///
/// Breaks the components of the vector into an integral and a fractional part. Returns vector made of fractional parts.
///