diff --git a/Source/Editor/Surface/NodeArchetype.cs b/Source/Editor/Surface/NodeArchetype.cs index 609b7b5f7..32d75c71c 100644 --- a/Source/Editor/Surface/NodeArchetype.cs +++ b/Source/Editor/Surface/NodeArchetype.cs @@ -109,6 +109,7 @@ namespace FlaxEditor.Surface /// public CreateCustomNodeFunc Create; + private Float2 _size; /// /// Function for asynchronously loaded nodes to check if input ports are compatible, for filtering. /// @@ -122,7 +123,17 @@ namespace FlaxEditor.Surface /// /// Default initial size of the node. /// - public Float2 Size; + public Float2 Size + { + get + { + return _size; + } + set + { + _size = VisjectSurface.RoundToGrid(value, true); + } + } /// /// Custom set of flags. diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs index fe79ec3af..a6fc82d55 100644 --- a/Source/Editor/Surface/SurfaceUtils.cs +++ b/Source/Editor/Surface/SurfaceUtils.cs @@ -13,6 +13,9 @@ using FlaxEditor.Scripting; using FlaxEditor.Utilities; using FlaxEngine.Utilities; using FlaxEngine; +using FlaxEditor.GUI; +using FlaxEngine.GUI; +using FlaxEditor.Options; namespace FlaxEditor.Surface { @@ -556,5 +559,42 @@ namespace FlaxEditor.Surface return true; return AreScriptTypesEqualInner(left, right) || AreScriptTypesEqualInner(right, left); } + + // This might not be the greatest place to put this but I couldn't find anything better yet. + public static void VisjectCommonToolstripSetup(Editor editor, ToolStrip toolStrip, FlaxEditor.Undo undo, + Action save, Action showWholeGraph, Action toggleGridSnap, InputActionsContainer actionsContainer, + out ToolStripButton saveButton, out ToolStripButton undoButton, out ToolStripButton redoButton, out ToolStripButton gridSnapButton) + { + var inputOptions = editor.Options.Options.Input; + + // 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"); + gridSnapButton = (ToolStripButton)toolStrip.AddButton(editor.Icons.Stop64, toggleGridSnap).LinkTooltip("Toggle grid snapping for nodes."); + gridSnapButton.BackgroundColor = Style.Current.Background; // Default color for grid snap button. + + // Setup input actions + actionsContainer.Add(options => options.Undo, undo.PerformUndo); + actionsContainer.Add(options => options.Redo, undo.PerformRedo); + actionsContainer.Add(options => options.Search, editor.ContentFinding.ShowSearch); + } + + public static void ToggleSurfaceGridSnap(VisjectSurface surface, ToolStripButton gridSnapButton) + { + surface.GridSnappingEnabled = !surface.GridSnappingEnabled; + if (surface.GridSnappingEnabled) + { + gridSnapButton.BackgroundColor = Style.Current.BackgroundSelected; + } + else + { + gridSnapButton.BackgroundColor = Style.Current.Background; + } + } } } diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index 9b6f86bcb..c540a2bd1 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,29 @@ namespace FlaxEditor.Surface } } + /// + /// Round a visject coordinate point to the grid. + /// + /// The point to be rounded. + /// Round to ceiling instead? + /// + public static Float2 RoundToGrid(Float2 point, bool ceil = false) + { + Func round = x => + { + double pointGridUnits = Math.Abs((double)x) / GridSize; + pointGridUnits = ceil ? Math.Ceiling(pointGridUnits) : Math.Floor(pointGridUnits); + + return (float)Math.CopySign(pointGridUnits * GridSize, x); + }; + + Float2 pointToRound = point; + pointToRound.X = round(pointToRound.X); + pointToRound.Y = round(pointToRound.Y); + + return pointToRound; + } + /// public override void OnMouseEnter(Float2 location) { @@ -256,18 +281,40 @@ namespace FlaxEditor.Surface // Moving else if (_isMovingSelection) { + if (!GridSnappingEnabled) + _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 ((!GridSnappingEnabled || Math.Abs(delta.X) >= GridSize || (Math.Abs(delta.Y) >= GridSize)) + && deltaLengthSquared > 0.01f) { - // Move selected nodes - delta /= _targetScale; + if (GridSnappingEnabled) + { + Float2 unroundedDelta = delta; + + delta = RoundToGrid(unroundedDelta); + _gridRoundingDelta = (unroundedDelta - delta) * _targetScale; // Standardize unit of the rounding delta, in case user zooms between node movements. + } + foreach (var node in _movingNodes) + { + if (GridSnappingEnabled) + { + Float2 unroundedLocation = node.Location; + node.Location = RoundToGrid(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..e4eb3269b 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -31,6 +31,16 @@ namespace FlaxEditor.Surface /// protected SurfaceRootControl _rootControl; + /// + /// Is grid snapping enabled for this surface? + /// + public bool GridSnappingEnabled = false; + + /// + /// The size of the snapping grid. + /// + public static readonly float GridSize = 20f; + private float _targetScale = 1.0f; private float _moveViewWithMouseDragSpeed = 1.0f; private bool _canEdit = true; diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs index 7da0d9707..ec3db7a9d 100644 --- a/Source/Editor/Surface/VisjectSurfaceWindow.cs +++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs @@ -894,6 +894,7 @@ namespace FlaxEditor.Surface private readonly ToolStripButton _saveButton; private readonly ToolStripButton _undoButton; private readonly ToolStripButton _redoButton; + private readonly ToolStripButton _gridSnapButton; private bool _showWholeGraphOnLoad = true; /// @@ -999,19 +1000,9 @@ namespace FlaxEditor.Surface } _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); + SurfaceUtils.VisjectCommonToolstripSetup(editor, _toolstrip, _undo, + Save, ShowWholeGraph, ToggleGridSnap, InputActions, + out _saveButton, out _undoButton, out _redoButton, out _gridSnapButton); } private void OnUndoRedo(IUndoAction action) @@ -1050,6 +1041,11 @@ namespace FlaxEditor.Surface _surface.ShowWholeGraph(); } + private void ToggleGridSnap() + { + SurfaceUtils.ToggleSurfaceGridSnap(_surface, _gridSnapButton); + } + /// /// Refreshes temporary asset to see changes live when editing the surface. /// diff --git a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs index 9618b30eb..1cdd07611 100644 --- a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs +++ b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs @@ -30,6 +30,7 @@ namespace FlaxEditor.Windows.Assets private readonly ToolStripButton _saveButton; private readonly ToolStripButton _undoButton; private readonly ToolStripButton _redoButton; + private readonly ToolStripButton _gridSnapButton; private bool _showWholeGraphOnLoad = true; /// @@ -70,13 +71,9 @@ 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.VisjectCommonToolstripSetup(editor, _toolstrip, _undo, + Save, ShowWholeGraph, ToggleGridSnap, InputActions, + out _saveButton, out _undoButton, out _redoButton, out _gridSnapButton); // Panel _panel = new Panel(ScrollBars.None) @@ -85,11 +82,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) @@ -106,6 +98,11 @@ namespace FlaxEditor.Windows.Assets _surface.ShowWholeGraph(); } + private void ToggleGridSnap() + { + SurfaceUtils.ToggleSurfaceGridSnap(_surface, _gridSnapButton); + } + /// /// Refreshes temporary asset to see changes live when editing the surface. /// diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index a7201e81c..3f742e09d 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -533,6 +533,7 @@ namespace FlaxEditor.Windows.Assets private readonly ToolStripButton _saveButton; private readonly ToolStripButton _undoButton; private readonly ToolStripButton _redoButton; + private readonly ToolStripButton _gridSnapButton; private Control[] _debugToolstripControls; private bool _showWholeGraphOnLoad = true; private bool _tmpAssetIsDirty; @@ -597,13 +598,11 @@ 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.VisjectCommonToolstripSetup(editor, _toolstrip, _undo, + Save, ShowWholeGraph, ToggleGridSnap, InputActions, + out _saveButton, out _undoButton, out _redoButton, out _gridSnapButton); + + // The rest of the toolstrip _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 +642,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); @@ -687,6 +683,11 @@ namespace FlaxEditor.Windows.Assets _surface.ShowWholeGraph(); } + private void ToggleGridSnap() + { + SurfaceUtils.ToggleSurfaceGridSnap(_surface, _gridSnapButton); + } + /// /// Refreshes temporary asset to see changes live when editing the surface. ///