Merge remote-tracking branch 'origin/master' into 1.11

This commit is contained in:
Wojtek Figat
2025-06-16 22:46:17 +02:00
44 changed files with 720 additions and 173 deletions

View File

@@ -1,6 +1,5 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using FlaxEditor.Content.Settings;
using FlaxEngine;
@@ -16,6 +15,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
private int _layersCount;
private List<CheckBox> _checkBoxes;
private VerticalPanel _upperRightCell;
private VerticalPanel _bottomLeftCell;
private UniformGridPanel _grid;
private Border _horizontalHighlight;
private Border _verticalHighlight;
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.InlineIntoParent;
@@ -37,12 +41,29 @@ namespace FlaxEditor.CustomEditors.Dedicated
Parent = panel,
};
var style = FlaxEngine.GUI.Style.Current;
_horizontalHighlight = new Border()
{
Parent = panel,
BorderColor = style.Foreground,
BorderWidth = 1.0f,
Visible = false,
};
_verticalHighlight = new Border()
{
Parent = panel,
BorderColor = style.Foreground,
BorderWidth = 1.0f,
Visible = false,
};
var upperLeftCell = new Label
{
Parent = gridPanel,
};
var upperRightCell = new VerticalPanel
_upperRightCell = new VerticalPanel
{
ClipChildren = false,
Pivot = new Float2(0.00001f, 0.0f),
@@ -54,7 +75,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
Parent = gridPanel,
};
var bottomLeftCell = new VerticalPanel
_bottomLeftCell = new VerticalPanel
{
Pivot = Float2.Zero,
Spacing = 0,
@@ -63,7 +84,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
Parent = gridPanel,
};
var grid = new UniformGridPanel(0)
_grid = new UniformGridPanel(0)
{
SlotsHorizontally = layersCount,
SlotsVertically = layersCount,
@@ -74,13 +95,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
int layerIndex = 0;
for (; layerIndex < layerNames.Length; layerIndex++)
{
upperRightCell.AddChild(new Label
_upperRightCell.AddChild(new Label
{
Height = labelsHeight,
Text = layerNames[layerNames.Length - layerIndex - 1],
HorizontalAlignment = TextAlignment.Near,
});
bottomLeftCell.AddChild(new Label
_bottomLeftCell.AddChild(new Label
{
Height = labelsHeight,
Text = layerNames[layerIndex],
@@ -90,13 +111,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
for (; layerIndex < layersCount; layerIndex++)
{
string name = "Layer " + layerIndex;
upperRightCell.AddChild(new Label
_upperRightCell.AddChild(new Label
{
Height = labelsHeight,
Text = name,
HorizontalAlignment = TextAlignment.Near,
});
bottomLeftCell.AddChild(new Label
_bottomLeftCell.AddChild(new Label
{
Height = labelsHeight,
Text = name,
@@ -118,7 +139,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
var box = new CheckBox(0, 0, true)
{
Tag = new Float2(_layersCount - column - 1, row),
Parent = grid,
Parent = _grid,
Checked = GetBit(column, row),
};
box.StateChanged += OnCheckBoxChanged;
@@ -126,7 +147,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
for (; column < layersCount; column++)
{
grid.AddChild(new Label());
_grid.AddChild(new Label());
}
}
}
@@ -141,6 +162,18 @@ namespace FlaxEditor.CustomEditors.Dedicated
/// <inheritdoc />
public override void Refresh()
{
int selectedColumn = -1;
int selectedRow = -1;
var style = FlaxEngine.GUI.Style.Current;
bool mouseOverGrid = _grid.IsMouseOver;
// Only hide highlights if mouse is not over the grid to reduce flickering
if (!mouseOverGrid)
{
_horizontalHighlight.Visible = false;
_verticalHighlight.Visible = false;
}
// Sync check boxes
for (int i = 0; i < _checkBoxes.Count; i++)
{
@@ -148,6 +181,39 @@ namespace FlaxEditor.CustomEditors.Dedicated
int column = (int)((Float2)box.Tag).X;
int row = (int)((Float2)box.Tag).Y;
box.Checked = GetBit(column, row);
if (box.IsMouseOver)
{
selectedColumn = column;
selectedRow = row;
_horizontalHighlight.X = _grid.X - _bottomLeftCell.Width;
_horizontalHighlight.Y = _grid.Y + box.Y;
_horizontalHighlight.Width = _bottomLeftCell.Width + box.Width + box.X;
_horizontalHighlight.Height = box.Height;
_horizontalHighlight.Visible = true;
_verticalHighlight.X = _grid.X + box.X;
_verticalHighlight.Y = _grid.Y - _upperRightCell.Height;
_verticalHighlight.Width = box.Width;
_verticalHighlight.Height = _upperRightCell.Height + box.Height + box.Y;
_verticalHighlight.Visible = true;
}
}
for (int i = 0; i < _checkBoxes.Count; i++)
{
var box = _checkBoxes[i];
int column = (int)((Float2)box.Tag).X;
int row = (int)((Float2)box.Tag).Y;
if (!mouseOverGrid)
box.ImageColor = style.BorderSelected;
else if (selectedColumn > -1 && selectedRow > -1)
{
bool isRowOrColumn = column == selectedColumn || row == selectedRow;
box.ImageColor = style.BorderSelected * (isRowOrColumn ? 1.2f : 0.75f);
}
}
}

View File

@@ -670,6 +670,8 @@ namespace FlaxEditor
{
FlaxEngine.Networking.NetworkManager.Stop(); // Shutdown any multiplayer from playmode
PlayModeEnding?.Invoke();
for (int i = 0; i < _modules.Count; i++)
_modules[i].OnPlayEnding();
}
internal void OnPlayEnd()

View File

@@ -299,6 +299,7 @@ namespace FlaxEditor.GUI
{
// Select asset
Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
Editor.Instance.Windows.ContentWin.ClearItemsSearch();
}
}
else if (Button1Rect.Contains(location))
@@ -312,6 +313,7 @@ namespace FlaxEditor.GUI
{
// Select asset
Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
Editor.Instance.Windows.ContentWin.ClearItemsSearch();
}
else if (Button3Rect.Contains(location))
{

View File

@@ -563,8 +563,17 @@ namespace FlaxEditor.GUI
case KeyboardKeys.Escape:
Hide();
return true;
case KeyboardKeys.Backspace:
// Alow the user to quickly focus the searchbar
if (_searchBox != null && !_searchBox.IsFocused)
{
_searchBox.Focus();
_searchBox.SelectAll();
return true;
}
break;
case KeyboardKeys.ArrowDown:
{
case KeyboardKeys.ArrowUp:
if (RootWindow.FocusedControl == null)
{
// Focus search box if nothing is focused
@@ -572,39 +581,17 @@ namespace FlaxEditor.GUI
return true;
}
// Focus the first visible item or then next one
// Get the next item
var items = GetVisibleItems();
var focusedIndex = items.IndexOf(focusedItem);
if (focusedIndex == -1)
focusedIndex = -1;
if (focusedIndex + 1 < items.Count)
{
var item = items[focusedIndex + 1];
item.Focus();
_scrollPanel.ScrollViewTo(item);
return true;
}
break;
}
case KeyboardKeys.ArrowUp:
if (focusedItem != null)
{
// Focus the previous visible item or the search box
var items = GetVisibleItems();
var focusedIndex = items.IndexOf(focusedItem);
if (focusedIndex == 0)
{
_searchBox?.Focus();
}
else if (focusedIndex > 0)
{
var item = items[focusedIndex - 1];
item.Focus();
_scrollPanel.ScrollViewTo(item);
return true;
}
}
break;
int delta = key == KeyboardKeys.ArrowDown ? -1 : 1;
int nextIndex = Mathf.Wrap(focusedIndex - delta, 0, items.Count - 1);
var nextItem = items[nextIndex];
// Focus the next item
nextItem.Focus();
_scrollPanel.ScrollViewTo(nextItem);
return true;
case KeyboardKeys.Return:
if (focusedItem != null)
{

View File

@@ -266,6 +266,19 @@ namespace FlaxEditor.GUI
return AddChild(new MainMenuButton(text));
}
/// <summary>
/// Gets or adds a button.
/// </summary>
/// <param name="text">The button text</param>
/// <returns>The existing or created button control.</returns>
public MainMenuButton GetOrAddButton(string text)
{
MainMenuButton result = GetButton(text);
if (result == null)
result = AddButton(text);
return result;
}
/// <summary>
/// Gets the button.
/// </summary>

View File

@@ -122,6 +122,14 @@ namespace FlaxEditor.GUI
return this;
}
private void OnClicked()
{
if (AutoCheck)
Checked = !Checked;
Clicked?.Invoke();
(Parent as ToolStrip)?.OnButtonClicked(this);
}
/// <inheritdoc />
public override void Draw()
{
@@ -196,11 +204,7 @@ namespace FlaxEditor.GUI
if (button == MouseButton.Left && _primaryMouseDown)
{
_primaryMouseDown = false;
if (AutoCheck)
Checked = !Checked;
Clicked?.Invoke();
(Parent as ToolStrip)?.OnButtonClicked(this);
OnClicked();
return true;
}
if (button == MouseButton.Right && _secondaryMouseDown)
@@ -215,6 +219,18 @@ namespace FlaxEditor.GUI
return base.OnMouseUp(location, button);
}
/// <inheritdoc />
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
if (button == MouseButton.Left)
{
OnClicked();
return true;
}
return false;
}
/// <inheritdoc />
public override void OnMouseLeave()
{

View File

@@ -73,6 +73,11 @@ namespace FlaxEditor.GUI.Tree
/// </summary>
public bool DrawRootTreeLine = true;
/// <summary>
/// Occurs when the deferred layout operation was performed.
/// </summary>
public event Action AfterDeferredLayout;
/// <summary>
/// Gets or sets the margin for the child tree nodes.
/// </summary>
@@ -375,6 +380,7 @@ namespace FlaxEditor.GUI.Tree
if (_deferLayoutUpdate)
{
base.PerformLayout();
AfterDeferredLayout?.Invoke();
_deferLayoutUpdate = false;
}

View File

@@ -76,6 +76,13 @@ namespace FlaxEditor.Modules
{
}
/// <summary>
/// Called when Editor will leave the play mode.
/// </summary>
public virtual void OnPlayEnding()
{
}
/// <summary>
/// Called when Editor leaves the play mode.
/// </summary>

View File

@@ -1223,6 +1223,13 @@ namespace FlaxEditor.Modules
Windows[i].OnPlayBegin();
}
/// <inheritdoc />
public override void OnPlayEnding()
{
for (int i = 0; i < Windows.Count; i++)
Windows[i].OnPlayEnding();
}
/// <inheritdoc />
public override void OnPlayEnd()
{

View File

@@ -139,6 +139,10 @@ namespace FlaxEditor.Options
[EditorDisplay("Common"), EditorOrder(240)]
public InputBinding ToggleFullscreen = new InputBinding(KeyboardKeys.F11);
[DefaultValue(typeof(InputBinding), "Ctrl+BackQuote")]
[EditorDisplay("Common"), EditorOrder(250)]
public InputBinding FocusConsoleCommand = new InputBinding(KeyboardKeys.BackQuote, KeyboardKeys.Control);
#endregion
#region File
@@ -647,5 +651,45 @@ namespace FlaxEditor.Options
public InputBinding VisualScriptDebuggerWindow = new InputBinding(KeyboardKeys.None);
#endregion
#region Node editors
[DefaultValue(typeof(InputBinding), "Shift+W")]
[EditorDisplay("Node editors"), EditorOrder(4500)]
public InputBinding NodesAlignTop = new InputBinding(KeyboardKeys.W, KeyboardKeys.Shift);
[DefaultValue(typeof(InputBinding), "Shift+A")]
[EditorDisplay("Node editors"), EditorOrder(4510)]
public InputBinding NodesAlignLeft = new InputBinding(KeyboardKeys.A, KeyboardKeys.Shift);
[DefaultValue(typeof(InputBinding), "Shift+S")]
[EditorDisplay("Node editors"), EditorOrder(4520)]
public InputBinding NodesAlignBottom = new InputBinding(KeyboardKeys.S, KeyboardKeys.Shift);
[DefaultValue(typeof(InputBinding), "Shift+D")]
[EditorDisplay("Node editors"), EditorOrder(4530)]
public InputBinding NodesAlignRight = new InputBinding(KeyboardKeys.D, KeyboardKeys.Shift);
[DefaultValue(typeof(InputBinding), "Alt+Shift+W")]
[EditorDisplay("Node editors"), EditorOrder(4540)]
public InputBinding NodesAlignMiddle = new InputBinding(KeyboardKeys.W, KeyboardKeys.Shift, KeyboardKeys.Alt);
[DefaultValue(typeof(InputBinding), "Alt+Shift+S")]
[EditorDisplay("Node editors"), EditorOrder(4550)]
public InputBinding NodesAlignCenter = new InputBinding(KeyboardKeys.S, KeyboardKeys.Shift, KeyboardKeys.Alt);
[DefaultValue(typeof(InputBinding), "Q")]
[EditorDisplay("Node editors"), EditorOrder(4560)]
public InputBinding NodesAutoFormat = new InputBinding(KeyboardKeys.Q);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Node editors"), EditorOrder(4570)]
public InputBinding NodesDistributeHorizontal = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Node editors"), EditorOrder(4580)]
public InputBinding NodesDistributeVertical = new InputBinding(KeyboardKeys.None);
#endregion
}
}

View File

@@ -0,0 +1,43 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine;
namespace FlaxEditor.Surface
{
/// <summary>
/// Node Alignment type
/// </summary>
[HideInEditor]
public enum NodeAlignmentType
{
/// <summary>
/// Align nodes vertically to top, matching top-most node
/// </summary>
Top,
/// <summary>
/// Align nodes vertically to middle, using average of all nodes
/// </summary>
Middle,
/// <summary>
/// Align nodes vertically to bottom, matching bottom-most node
/// </summary>
Bottom,
/// <summary>
/// Align nodes horizontally to left, matching left-most node
/// </summary>
Left,
/// <summary>
/// Align nodes horizontally to center, using average of all nodes
/// </summary>
Center,
/// <summary>
/// Align nodes horizontally to right, matching right-most node
/// </summary>
Right,
}
}

View File

@@ -912,7 +912,7 @@ namespace FlaxEditor.Surface
/// <inheritdoc />
public override bool OnTestTooltipOverControl(ref Float2 location)
{
return _headerRect.Contains(ref location) && ShowTooltip;
return _headerRect.Contains(ref location) && ShowTooltip && !Surface.IsConnecting && !Surface.IsBoxSelecting;
}
/// <inheritdoc />
@@ -1070,7 +1070,7 @@ namespace FlaxEditor.Surface
// Header
var headerColor = style.BackgroundHighlighted;
if (_headerRect.Contains(ref _mousePosition))
if (_headerRect.Contains(ref _mousePosition) && !Surface.IsConnecting && !Surface.IsBoxSelecting)
headerColor *= 1.07f;
Render2D.FillRectangle(_headerRect, headerColor);
Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center);
@@ -1078,7 +1078,8 @@ namespace FlaxEditor.Surface
// Close button
if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0 && Surface.CanEdit)
{
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
bool highlightClose = _closeButtonRect.Contains(_mousePosition) && !Surface.IsConnecting && !Surface.IsBoxSelecting;
Render2D.DrawSprite(style.Cross, _closeButtonRect, highlightClose ? style.Foreground : style.ForegroundGrey);
}
// Footer
@@ -1123,8 +1124,9 @@ namespace FlaxEditor.Surface
if (base.OnMouseUp(location, button))
return true;
// Close
if (button == MouseButton.Left && (Archetype.Flags & NodeFlags.NoCloseButton) == 0 && _closeButtonRect.Contains(ref location))
// Close/ delete
bool canDelete = !Surface.IsConnecting && !Surface.WasBoxSelecting && !Surface.WasMovingSelection;
if (button == MouseButton.Left && canDelete && (Archetype.Flags & NodeFlags.NoCloseButton) == 0 && _closeButtonRect.Contains(ref location))
{
Surface.Delete(this);
return true;

View File

@@ -191,7 +191,16 @@ namespace FlaxEditor.Surface
private ContextMenuButton _cmCopyButton;
private ContextMenuButton _cmDuplicateButton;
private ContextMenuChildMenu _cmFormatNodesMenu;
private ContextMenuButton _cmFormatNodesConnectionButton;
private ContextMenuButton _cmAlignNodesTopButton;
private ContextMenuButton _cmAlignNodesMiddleButton;
private ContextMenuButton _cmAlignNodesBottomButton;
private ContextMenuButton _cmAlignNodesLeftButton;
private ContextMenuButton _cmAlignNodesCenterButton;
private ContextMenuButton _cmAlignNodesRightButton;
private ContextMenuButton _cmDistributeNodesHorizontallyButton;
private ContextMenuButton _cmDistributeNodesVerticallyButton;
private ContextMenuButton _cmRemoveNodeConnectionsButton;
private ContextMenuButton _cmRemoveBoxConnectionsButton;
private readonly Float2 ContextMenuOffset = new Float2(5);
@@ -399,8 +408,24 @@ namespace FlaxEditor.Surface
}
menu.AddSeparator();
_cmFormatNodesConnectionButton = menu.AddButton("Format node(s)", () => { FormatGraph(SelectedNodes); });
_cmFormatNodesConnectionButton.Enabled = CanEdit && HasNodesSelection;
_cmFormatNodesMenu = menu.AddChildMenu("Format node(s)");
_cmFormatNodesMenu.Enabled = CanEdit && HasNodesSelection;
_cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Auto format", Editor.Instance.Options.Options.Input.NodesAutoFormat, () => { FormatGraph(SelectedNodes); });
_cmFormatNodesMenu.ContextMenu.AddSeparator();
_cmAlignNodesTopButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align top", Editor.Instance.Options.Options.Input.NodesAlignTop, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Top); });
_cmAlignNodesMiddleButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align middle", Editor.Instance.Options.Options.Input.NodesAlignMiddle, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Middle); });
_cmAlignNodesBottomButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align bottom", Editor.Instance.Options.Options.Input.NodesAlignBottom, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Bottom); });
_cmFormatNodesMenu.ContextMenu.AddSeparator();
_cmAlignNodesLeftButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align left", Editor.Instance.Options.Options.Input.NodesAlignLeft, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Left); });
_cmAlignNodesCenterButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align center", Editor.Instance.Options.Options.Input.NodesAlignCenter, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Center); });
_cmAlignNodesRightButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align right", Editor.Instance.Options.Options.Input.NodesAlignRight, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Right); });
_cmFormatNodesMenu.ContextMenu.AddSeparator();
_cmDistributeNodesHorizontallyButton = _cmFormatNodesMenu.ContextMenu.AddButton("Distribute horizontally", Editor.Instance.Options.Options.Input.NodesDistributeHorizontal, () => { DistributeNodes(SelectedNodes, false); });
_cmDistributeNodesVerticallyButton = _cmFormatNodesMenu.ContextMenu.AddButton("Distribute vertically", Editor.Instance.Options.Options.Input.NodesDistributeVertical, () => { DistributeNodes(SelectedNodes, true); });
_cmRemoveNodeConnectionsButton = menu.AddButton("Remove all connections to that node(s)", () =>
{

View File

@@ -225,7 +225,7 @@ namespace FlaxEditor.Surface
_rootControl.DrawComments();
if (IsSelecting)
if (IsBoxSelecting)
{
DrawSelection();
}

View File

@@ -282,5 +282,122 @@ namespace FlaxEditor.Surface
return maxOffset;
}
/// <summary>
/// Align given nodes on a graph using the given alignment type.
/// Ignores any potential overlap.
/// </summary>
/// <param name="nodes">List of nodes</param>
/// <param name="alignmentType">Alignemnt type</param>
public void AlignNodes(List<SurfaceNode> nodes, NodeAlignmentType alignmentType)
{
if(nodes.Count <= 1)
return;
var undoActions = new List<MoveNodesAction>();
var boundingBox = GetNodesBounds(nodes);
for(int i = 0; i < nodes.Count; i++)
{
var centerY = boundingBox.Center.Y - (nodes[i].Height / 2);
var centerX = boundingBox.Center.X - (nodes[i].Width / 2);
var newLocation = alignmentType switch
{
NodeAlignmentType.Top => new Float2(nodes[i].Location.X, boundingBox.Top),
NodeAlignmentType.Middle => new Float2(nodes[i].Location.X, centerY),
NodeAlignmentType.Bottom => new Float2(nodes[i].Location.X, boundingBox.Bottom - nodes[i].Height),
NodeAlignmentType.Left => new Float2(boundingBox.Left, nodes[i].Location.Y),
NodeAlignmentType.Center => new Float2(centerX, nodes[i].Location.Y),
NodeAlignmentType.Right => new Float2(boundingBox.Right - nodes[i].Width, nodes[i].Location.Y),
_ => throw new NotImplementedException($"Unsupported node alignment type: {alignmentType}"),
};
var locationDelta = newLocation - nodes[i].Location;
nodes[i].Location = newLocation;
if(Undo != null)
undoActions.Add(new MoveNodesAction(Context, new[] { nodes[i].ID }, locationDelta));
}
MarkAsEdited(false);
Undo?.AddAction(new MultiUndoAction(undoActions, $"Align nodes ({alignmentType})"));
}
/// <summary>
/// Distribute the given nodes as equally as possible inside the bounding box, if no fit can be done it will use a default pad of 10 pixels between nodes.
/// </summary>
/// <param name="nodes">List of nodes</param>
/// <param name="vertically">If false will be done horizontally, if true will be done vertically</param>
public void DistributeNodes(List<SurfaceNode> nodes, bool vertically)
{
if(nodes.Count <= 1)
return;
var undoActions = new List<MoveNodesAction>();
var boundingBox = GetNodesBounds(nodes);
float padding = 10;
float totalSize = 0;
for (int i = 0; i < nodes.Count; i++)
{
if (vertically)
{
totalSize += nodes[i].Height;
}
else
{
totalSize += nodes[i].Width;
}
}
if(vertically)
{
nodes.Sort((leftValue, rightValue) => { return leftValue.Y.CompareTo(rightValue.Y); });
float position = boundingBox.Top;
if(totalSize < boundingBox.Height)
{
padding = (boundingBox.Height - totalSize) / nodes.Count;
}
for(int i = 0; i < nodes.Count; i++)
{
var newLocation = new Float2(nodes[i].X, position);
var locationDelta = newLocation - nodes[i].Location;
nodes[i].Location = newLocation;
position += nodes[i].Height + padding;
if (Undo != null)
undoActions.Add(new MoveNodesAction(Context, new[] { nodes[i].ID }, locationDelta));
}
}
else
{
nodes.Sort((leftValue, rightValue) => { return leftValue.X.CompareTo(rightValue.X); });
float position = boundingBox.Left;
if(totalSize < boundingBox.Width)
{
padding = (boundingBox.Width - totalSize) / nodes.Count;
}
for(int i = 0; i < nodes.Count; i++)
{
var newLocation = new Float2(position, nodes[i].Y);
var locationDelta = newLocation - nodes[i].Location;
nodes[i].Location = newLocation;
position += nodes[i].Width + padding;
if (Undo != null)
undoActions.Add(new MoveNodesAction(Context, new[] { nodes[i].ID }, locationDelta));
}
}
MarkAsEdited(false);
Undo?.AddAction(new MultiUndoAction(undoActions, vertically ? "Distribute nodes vertically" : "Distribute nodes horizontally"));
}
}
}

View File

@@ -27,6 +27,7 @@ namespace FlaxEditor.Surface
private Float2 _movingNodesDelta;
private Float2 _gridRoundingDelta;
private HashSet<SurfaceNode> _movingNodes;
private HashSet<SurfaceNode> _temporarySelectedNodes;
private readonly Stack<InputBracket> _inputBrackets = new Stack<InputBracket>();
private class InputBracket
@@ -130,13 +131,34 @@ namespace FlaxEditor.Surface
if (_rootControl.Children[i] is SurfaceControl control)
{
var select = control.IsSelectionIntersecting(ref selectionRect);
if (select != control.IsSelected)
if (Root.GetKey(KeyboardKeys.Shift))
{
control.IsSelected = select;
selectionChanged = true;
if (select == control.IsSelected && _temporarySelectedNodes.Contains(control))
{
control.IsSelected = !select;
selectionChanged = true;
}
}
else if (Root.GetKey(KeyboardKeys.Control))
{
if (select != control.IsSelected && !_temporarySelectedNodes.Contains(control))
{
control.IsSelected = select;
selectionChanged = true;
}
}
else
{
if (select != control.IsSelected)
{
control.IsSelected = select;
selectionChanged = true;
}
}
}
}
if (selectionChanged)
SelectionChanged?.Invoke();
}
@@ -461,6 +483,19 @@ namespace FlaxEditor.Surface
// Cache data
_isMovingSelection = false;
_mousePos = location;
if(_temporarySelectedNodes == null)
_temporarySelectedNodes = new HashSet<SurfaceNode>();
else
_temporarySelectedNodes.Clear();
for (int i = 0; i < _rootControl.Children.Count; i++)
{
if (_rootControl.Children[i] is SurfaceNode node && node.IsSelected)
{
_temporarySelectedNodes.Add(node);
}
}
if (button == MouseButton.Left)
{
_leftMouseDown = true;
@@ -488,9 +523,11 @@ namespace FlaxEditor.Surface
// Check if user is pressing control
if (Root.GetKey(KeyboardKeys.Control))
{
// Add/remove from selection
controlUnderMouse.IsSelected = !controlUnderMouse.IsSelected;
SelectionChanged?.Invoke();
AddToSelection(controlUnderMouse);
}
else if (Root.GetKey(KeyboardKeys.Shift))
{
RemoveFromSelection(controlUnderMouse);
}
// Check if node isn't selected
else if (!controlUnderMouse.IsSelected)
@@ -500,10 +537,14 @@ namespace FlaxEditor.Surface
}
// Start moving selected nodes
StartMouseCapture();
_movingSelectionViewPos = _rootControl.Location;
_movingNodesDelta = Float2.Zero;
OnGetNodesToMove();
if (!Root.GetKey(KeyboardKeys.Shift))
{
StartMouseCapture();
_movingSelectionViewPos = _rootControl.Location;
_movingNodesDelta = Float2.Zero;
OnGetNodesToMove();
}
Focus();
return true;
}
@@ -515,7 +556,12 @@ namespace FlaxEditor.Surface
{
// Start selecting or commenting
StartMouseCapture();
ClearSelection();
if (!Root.GetKey(KeyboardKeys.Control) && !Root.GetKey(KeyboardKeys.Shift))
{
ClearSelection();
}
Focus();
return true;
}
@@ -544,6 +590,9 @@ namespace FlaxEditor.Surface
// Cache flags and state
if (_leftMouseDown && button == MouseButton.Left)
{
WasBoxSelecting = IsBoxSelecting;
WasMovingSelection = _isMovingSelection;
_leftMouseDown = false;
EndMouseCapture();
Cursor = CursorType.Default;

View File

@@ -232,15 +232,25 @@ namespace FlaxEditor.Surface
}
/// <summary>
/// Gets a value indicating whether user is selecting nodes.
/// Gets a value indicating whether user is box selecting nodes.
/// </summary>
public bool IsSelecting => _leftMouseDown && !_isMovingSelection && _connectionInstigator == null;
public bool IsBoxSelecting => _leftMouseDown && !_isMovingSelection && _connectionInstigator == null;
/// <summary>
/// Gets a value indicating whether user was previously box selecting nodes.
/// </summary>
public bool WasBoxSelecting { get; private set; }
/// <summary>
/// Gets a value indicating whether user is moving selected nodes.
/// </summary>
public bool IsMovingSelection => _leftMouseDown && _isMovingSelection && _connectionInstigator == null;
/// <summary>
/// Gets a value indicating whether user was previously moving selected nodes.
/// </summary>
public bool WasMovingSelection { get; private set; }
/// <summary>
/// Gets a value indicating whether user is connecting nodes.
/// </summary>
@@ -405,6 +415,15 @@ namespace FlaxEditor.Surface
new InputActionsContainer.Binding(options => options.Paste, Paste),
new InputActionsContainer.Binding(options => options.Cut, Cut),
new InputActionsContainer.Binding(options => options.Duplicate, Duplicate),
new InputActionsContainer.Binding(options => options.NodesAutoFormat, () => { FormatGraph(SelectedNodes); }),
new InputActionsContainer.Binding(options => options.NodesAlignTop, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Top); }),
new InputActionsContainer.Binding(options => options.NodesAlignMiddle, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Middle); }),
new InputActionsContainer.Binding(options => options.NodesAlignBottom, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Bottom); }),
new InputActionsContainer.Binding(options => options.NodesAlignLeft, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Left); }),
new InputActionsContainer.Binding(options => options.NodesAlignCenter, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Center); }),
new InputActionsContainer.Binding(options => options.NodesAlignRight, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Right); }),
new InputActionsContainer.Binding(options => options.NodesDistributeHorizontal, () => { DistributeNodes(SelectedNodes, false); }),
new InputActionsContainer.Binding(options => options.NodesDistributeVertical, () => { DistributeNodes(SelectedNodes, true); }),
});
Context.ControlSpawned += OnSurfaceControlSpawned;
@@ -710,6 +729,18 @@ namespace FlaxEditor.Surface
SelectionChanged?.Invoke();
}
/// <summary>
/// Removes the specified control from the selection.
/// </summary>
/// <param name="control">The control.</param>
public void RemoveFromSelection(SurfaceControl control)
{
if (!control.IsSelected)
return;
control.IsSelected = false;
SelectionChanged?.Invoke();
}
/// <summary>
/// Selects the specified control.
/// </summary>

View File

@@ -1518,6 +1518,7 @@ namespace FlaxEditor.Utilities
inputActions.Add(options => options.OpenScriptsProject, () => Editor.Instance.CodeEditing.OpenSolution());
inputActions.Add(options => options.GenerateScriptsProject, () => Editor.Instance.ProgressReporting.GenerateScriptsProjectFiles.RunAsync());
inputActions.Add(options => options.RecompileScripts, ScriptsBuilder.Compile);
inputActions.Add(options => options.FocusConsoleCommand, () => Editor.Instance.Windows.OutputLogWin.FocusCommand());
}
internal static string ToPathProject(string path)

View File

@@ -236,6 +236,7 @@ namespace FlaxEditor.Windows.Assets
var group = layout.Group("General");
var minScreenSize = group.FloatValue("Min Screen Size", "The minimum screen size to draw model (the bottom limit). Used to cull small models. Set to 0 to disable this feature.");
minScreenSize.ValueBox.SlideSpeed = 0.005f;
minScreenSize.ValueBox.MinValue = 0.0f;
minScreenSize.ValueBox.MaxValue = 1.0f;
minScreenSize.ValueBox.Value = proxy.Asset.MinScreenSize;
@@ -476,12 +477,12 @@ namespace FlaxEditor.Windows.Assets
}
}
[EditorOrder(1), EditorDisplay(null, "LOD"), Limit(0, Model.MaxLODs), VisibleIf("ShowUVs")]
[Tooltip("Level Of Detail index to preview UVs layout.")]
[EditorOrder(1), EditorDisplay(null, "LOD"), Limit(0, Model.MaxLODs, 0.01f), VisibleIf("ShowUVs")]
[Tooltip("Level Of Detail index to preview UVs layout at.")]
public int LOD = 0;
[EditorOrder(2), EditorDisplay(null, "Mesh"), Limit(-1, 1000000), VisibleIf("ShowUVs")]
[Tooltip("Mesh index to preview UVs layout. Use -1 for all meshes")]
[EditorOrder(2), EditorDisplay(null, "Mesh"), Limit(-1, 1000000, 0.01f), VisibleIf("ShowUVs")]
[Tooltip("Mesh index to show UVs layout for. Use -1 to display all UVs of all meshes")]
public int Mesh = -1;
private bool ShowUVs => _uvChannel != UVChannel.None;

View File

@@ -81,6 +81,7 @@ namespace FlaxEditor.Windows.Assets
}
var resolution = group.FloatValue("Resolution Scale", Window.Editor.CodeDocs.GetTooltip(typeof(ModelTool.Options), nameof(ModelImportSettings.Settings.SDFResolution)));
resolution.ValueBox.SlideSpeed = 0.001f;
resolution.ValueBox.MinValue = 0.0001f;
resolution.ValueBox.MaxValue = 100.0f;
resolution.ValueBox.Value = sdf.Texture != null ? sdf.ResolutionScale : 1.0f;

View File

@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Content;
using FlaxEditor.CustomEditors;
using FlaxEditor.GUI;
using FlaxEditor.Scripting;
using FlaxEditor.Surface;
using FlaxEditor.Viewport.Previews;
@@ -114,6 +115,7 @@ namespace FlaxEditor.Windows.Assets
private readonly PropertiesProxy _properties;
private Tab _previewTab;
private ToolStripButton _showSourceCodeButton;
/// <inheritdoc />
public ParticleEmitterWindow(Editor editor, AssetItem item)
@@ -146,7 +148,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
_toolstrip.AddButton(editor.Icons.Code64, ShowSourceCode).LinkTooltip("Show generated shader source code");
_showSourceCodeButton = _toolstrip.AddButton(editor.Icons.Code64, ShowSourceCode);
_showSourceCodeButton.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");
}
@@ -285,5 +288,15 @@ namespace FlaxEditor.Windows.Assets
/// <inheritdoc />
public SearchAssetTypes AssetType => SearchAssetTypes.ParticleEmitter;
/// <inheritdoc />
public override void Update(float deltaTime)
{
base.Update(deltaTime);
if (_asset == null)
return;
_showSourceCodeButton.Enabled = _asset.HasShaderCode;
}
}
}

View File

@@ -97,10 +97,7 @@ namespace FlaxEditor.Windows.Assets
// For single node selected scroll view so user can see it
if (nodes.Count == 1)
{
nodes[0].ExpandAllParents(true);
ScrollViewTo(nodes[0]);
}
ScrollToSelectedNode();
}
// Update properties editor

View File

@@ -318,7 +318,7 @@ namespace FlaxEditor.Windows
private Color _colorWarning;
private Color _colorError;
private bool _colorDebugLogText;
/// <summary>
/// Initializes a new instance of the <see cref="DebugLogWindow"/> class.
/// </summary>
@@ -352,24 +352,12 @@ namespace FlaxEditor.Windows
editor.Options.Apply(editor.Options.Options);
}).SetAutoCheck(true).LinkTooltip("Performs auto pause on error");
toolstrip.AddSeparator();
_groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error32, () =>
{
UpdateLogTypeVisibility(LogGroup.Error, _groupButtons[0].Checked);
editor.Options.Options.Interface.DebugLogShowErrorMessages = _groupButtons[0].Checked;
editor.Options.Apply(editor.Options.Options);
}).SetAutoCheck(true).LinkTooltip("Shows/hides error messages");
_groupButtons[1] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Warning32, () =>
{
UpdateLogTypeVisibility(LogGroup.Warning, _groupButtons[1].Checked);
editor.Options.Options.Interface.DebugLogShowWarningMessages = _groupButtons[1].Checked;
editor.Options.Apply(editor.Options.Options);
}).SetAutoCheck(true).LinkTooltip("Shows/hides warning messages");
_groupButtons[2] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Info32, () =>
{
UpdateLogTypeVisibility(LogGroup.Info, _groupButtons[2].Checked);
editor.Options.Options.Interface.DebugLogShowInfoMessages = _groupButtons[2].Checked;
editor.Options.Apply(editor.Options.Options);
}).SetAutoCheck(true).LinkTooltip("Shows/hides info messages");
_groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error32, () => { OnGroupButtonPressed(0); })
.SetAutoCheck(true).LinkTooltip("Shows/hides error messages");
_groupButtons[1] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Warning32, () => { OnGroupButtonPressed(1); })
.SetAutoCheck(true).LinkTooltip("Shows/hides warning messages");
_groupButtons[2] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Info32, () => { OnGroupButtonPressed(2); })
.SetAutoCheck(true).LinkTooltip("Shows/hides info messages");
UpdateCount();
// Split panel
@@ -418,6 +406,27 @@ namespace FlaxEditor.Windows
OnEditorOptionsChanged(Editor.Options.Options);
}
private void OnGroupButtonPressed(int index)
{
UpdateLogTypeVisibility((LogGroup)index, _groupButtons[index].Checked);
if (Input.GetKey(KeyboardKeys.Shift))
{
for (int i = 0; i < (int)LogGroup.Max; i++)
{
if (i == index)
continue;
_groupButtons[i].Checked = !_groupButtons[index].Checked;
UpdateLogTypeVisibility((LogGroup)i, _groupButtons[i].Checked);
}
}
var options = Editor.Options.Options.Interface;
options.DebugLogShowErrorMessages = _groupButtons[0].Checked;
options.DebugLogShowWarningMessages = _groupButtons[1].Checked;
options.DebugLogShowInfoMessages = _groupButtons[2].Checked;
Editor.Options.Apply(Editor.Options.Options);
}
private void OnEditorOptionsChanged(EditorOptions options)
{
_timestampsFormats = options.Interface.DebugLogTimestampsFormat;
@@ -455,15 +464,9 @@ namespace FlaxEditor.Windows
// Create new entry
switch (_timestampsFormats)
{
case InterfaceOptions.TimestampsFormats.Utc:
desc.Title = $"[{DateTime.UtcNow}] {desc.Title}";
break;
case InterfaceOptions.TimestampsFormats.LocalTime:
desc.Title = $"[{DateTime.Now}] {desc.Title}";
break;
case InterfaceOptions.TimestampsFormats.TimeSinceStartup:
desc.Title = string.Format("[{0:g}] ", TimeSpan.FromSeconds(Time.TimeSinceStartup)) + desc.Title;
break;
case InterfaceOptions.TimestampsFormats.Utc: desc.Title = $"[{DateTime.UtcNow}] {desc.Title}"; break;
case InterfaceOptions.TimestampsFormats.LocalTime: desc.Title = $"[{DateTime.Now}] {desc.Title}"; break;
case InterfaceOptions.TimestampsFormats.TimeSinceStartup: desc.Title = string.Format("[{0:g}] ", TimeSpan.FromSeconds(Time.TimeSinceStartup)) + desc.Title; break;
}
var newEntry = new LogEntry(this, ref desc);

View File

@@ -219,6 +219,13 @@ namespace FlaxEditor.Windows
{
}
/// <summary>
/// Called when Editor will leave the play mode.
/// </summary>
public virtual void OnPlayEnding()
{
}
/// <summary>
/// Called when Editor leaves the play mode.
/// </summary>

View File

@@ -405,6 +405,7 @@ namespace FlaxEditor.Windows
return;
Editor.Instance.SceneEditing.Delete();
});
InputActions.Add(options => options.FocusConsoleCommand, () => Editor.Instance.Windows.OutputLogWin.FocusCommand());
}
private void ChangeViewportRatio(ViewportScaleOptions v)

View File

@@ -830,6 +830,15 @@ namespace FlaxEditor.Windows
OnOutputTextChanged();
}
/// <summary>
/// Focus the debug command line and ensure that the output log window is visible.
/// </summary>
public void FocusCommand()
{
FocusOrShow();
_commandLineBox.Focus();
}
/// <inheritdoc />
public override void Update(float deltaTime)
{

View File

@@ -34,6 +34,7 @@ namespace FlaxEditor.Windows
private DragScriptItems _dragScriptItems;
private DragHandlers _dragHandlers;
private bool _isDropping = false;
private bool _forceScrollNodeToView = false;
/// <summary>
/// Scene tree panel.
@@ -91,6 +92,15 @@ namespace FlaxEditor.Windows
_tree.SelectedChanged += Tree_OnSelectedChanged;
_tree.RightClick += OnTreeRightClick;
_tree.Parent = _sceneTreePanel;
_tree.AfterDeferredLayout += () =>
{
if (_forceScrollNodeToView)
{
_forceScrollNodeToView = false;
ScrollToSelectedNode();
}
};
headerPanel.Parent = this;
// Setup input actions
@@ -142,6 +152,16 @@ namespace FlaxEditor.Windows
root.TreeNode.UpdateFilter(query);
_tree.UnlockChildrenRecursive();
// When keep the selected nodes in a view
var nodeSelection = _tree.Selection;
if (nodeSelection.Count != 0)
{
var node = nodeSelection[nodeSelection.Count - 1];
node.Expand(true);
_forceScrollNodeToView = true;
}
PerformLayout();
PerformLayout();
}

View File

@@ -42,6 +42,7 @@ namespace FlaxEditor.Windows.Search
if (value == _selectedItem || (value != null && !_matchedItems.Contains(value)))
return;
// Restore the previous selected item to the non-selected color
if (_selectedItem != null)
{
_selectedItem.BackgroundColor = Color.Transparent;
@@ -54,6 +55,7 @@ namespace FlaxEditor.Windows.Search
_selectedItem.BackgroundColor = Style.Current.BackgroundSelected;
if (_matchedItems.Count > VisibleItemCount)
{
_selectedItem.Focus();
_resultPanel.ScrollViewTo(_selectedItem, true);
}
}
@@ -180,39 +182,17 @@ namespace FlaxEditor.Windows.Search
switch (key)
{
case KeyboardKeys.ArrowDown:
{
if (_matchedItems.Count == 0)
return true;
int currentPos;
if (_selectedItem != null)
{
currentPos = _matchedItems.IndexOf(_selectedItem) + 1;
if (currentPos >= _matchedItems.Count)
currentPos--;
}
else
{
currentPos = 0;
}
SelectedItem = _matchedItems[currentPos];
return true;
}
case KeyboardKeys.ArrowUp:
{
if (_matchedItems.Count == 0)
return true;
int currentPos;
if (_selectedItem != null)
{
currentPos = _matchedItems.IndexOf(_selectedItem) - 1;
if (currentPos < 0)
currentPos = 0;
}
else
{
currentPos = 0;
}
SelectedItem = _matchedItems[currentPos];
var focusedIndex = _matchedItems.IndexOf(_selectedItem);
int delta = key == KeyboardKeys.ArrowDown ? -1 : 1;
int nextIndex = Mathf.Wrap(focusedIndex - delta, 0, _matchedItems.Count - 1);
var nextItem = _matchedItems[nextIndex];
SelectedItem = nextItem;
return true;
}
case KeyboardKeys.Return:
@@ -234,6 +214,17 @@ namespace FlaxEditor.Windows.Search
Hide();
return true;
}
case KeyboardKeys.Backspace:
{
// Alow the user to quickly focus the searchbar
if (_searchBox != null && !_searchBox.IsFocused)
{
_searchBox.Focus();
_searchBox.SelectAll();
return true;
}
break;
}
}
return base.OnKeyDown(key);