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. ///