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);

View File

@@ -18,6 +18,7 @@
#include "Engine/Tools/AudioTool/OggVorbisDecoder.h"
#include "Engine/Tools/AudioTool/OggVorbisEncoder.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Platform/MessageBox.h"
bool ImportAudio::TryGetImportOptions(const StringView& path, Options& options)
{
@@ -118,6 +119,7 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder&
}
#else
#define HANDLE_VORBIS(chunkIndex, dataPtr, dataSize) \
MessageBox::Show(TEXT("Vorbis format is not supported."), TEXT("Import warning"), MessageBoxButtons::OK, MessageBoxIcon::Warning);
LOG(Warning, "Vorbis format is not supported."); \
return CreateAssetResult::Error;
#endif
@@ -140,6 +142,7 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder&
break; \
default: \
{ \
MessageBox::Show(TEXT("Unknown audio format."), TEXT("Import warning"), MessageBoxButtons::OK, MessageBoxIcon::Warning); \
LOG(Warning, "Unknown audio format."); \
return CreateAssetResult::Error; \
} \

View File

@@ -16,6 +16,7 @@ API_CLASS(InBuild) class BitArray
public:
using ItemType = uint64;
using AllocationData = typename AllocationType::template Data<ItemType>;
static constexpr int32 ItemBitCount = 64;
private:
int32 _count;
@@ -209,8 +210,8 @@ public:
bool Get(const int32 index) const
{
ASSERT(index >= 0 && index < _count);
const ItemType offset = index / sizeof(ItemType);
const ItemType bitMask = (ItemType)(int32)(1 << (index & ((int32)sizeof(ItemType) - 1)));
const ItemType offset = index / ItemBitCount;
const ItemType bitMask = (ItemType)(1 << (index & (ItemBitCount - 1)));
const ItemType item = ((ItemType*)_allocation.Get())[offset];
return (item & bitMask) != 0;
}
@@ -223,13 +224,13 @@ public:
void Set(const int32 index, const bool value)
{
ASSERT(index >= 0 && index < _count);
const ItemType offset = index / sizeof(ItemType);
const ItemType bitMask = (ItemType)(int32)(1 << (index & ((int32)sizeof(ItemType) - 1)));
const ItemType offset = index / ItemBitCount;
const ItemType bitMask = (ItemType)(1 << (index & (ItemBitCount - 1)));
ItemType& item = ((ItemType*)_allocation.Get())[offset];
if (value)
item |= bitMask;
item |= bitMask; // Set the bit
else
item &= ~bitMask; // Clear the bit
item &= ~bitMask; // Unset the bit
}
public:

View File

@@ -26,6 +26,8 @@
_Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")
#define PRAGMA_ENABLE_DEPRECATION_WARNINGS \
_Pragma("clang diagnostic pop")
#define PRAGMA_DISABLE_OPTIMIZATION
#define PRAGMA_ENABLE_OPTIMIZATION
#pragma clang diagnostic ignored "-Wswitch"
#pragma clang diagnostic ignored "-Wmacro-redefined"
@@ -54,6 +56,8 @@
#define OFFSET_OF(X, Y) __builtin_offsetof(X, Y)
#define PRAGMA_DISABLE_DEPRECATION_WARNINGS
#define PRAGMA_ENABLE_DEPRECATION_WARNINGS
#define PRAGMA_DISABLE_OPTIMIZATION
#define PRAGMA_ENABLE_OPTIMIZATION
#elif defined(_MSC_VER)
@@ -86,6 +90,8 @@
__pragma(warning(disable: 4996))
#define PRAGMA_ENABLE_DEPRECATION_WARNINGS \
__pragma (warning(pop))
#define PRAGMA_DISABLE_OPTIMIZATION __pragma(optimize("", off))
#define PRAGMA_ENABLE_OPTIMIZATION __pragma(optimize("", on))
#pragma warning(disable: 4251)

View File

@@ -12,6 +12,7 @@
#include "Engine/Tools/ModelTool/VertexTriangleAdjacency.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Platform/Platform.h"
#include "Engine/Platform/MessageBox.h"
#define USE_MIKKTSPACE 1
#include "ThirdParty/MikkTSpace/mikktspace.h"
#if USE_ASSIMP
@@ -181,6 +182,7 @@ bool MeshData::GenerateLightmapUVs()
for (int32 i = 0; i < (int32)vb.size(); i++)
lightmapChannel.Get()[i] = *(Float2*)&vb[i].uv;
#else
MessageBox::Show(TEXT("Model lightmap UVs generation is not supported on this platform."), TEXT("Import error"), MessageBoxButtons::OK, MessageBoxIcon::Error);
LOG(Error, "Model lightmap UVs generation is not supported on this platform.");
#endif

View File

@@ -656,6 +656,23 @@ GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipInde
return task;
}
bool GPUTexture::UploadData(TextureData& data, bool copyData)
{
if (!IsAllocated())
return true;
if (data.Width != Width() || data.Height != Height() || data.Depth != Depth() || data.GetArraySize() != ArraySize() || data.Format != Format())
return true;
for (int32 arrayIndex = 0; arrayIndex < ArraySize(); arrayIndex++)
{
for (int32 mipLevel = 0; mipLevel < MipLevels(); mipLevel++)
{
TextureMipData* mip = data.GetData(arrayIndex, mipLevel);
UploadMipMapAsync(mip->Data, mipLevel, mip->RowPitch, mip->DepthPitch, copyData);
}
}
return false;
}
class TextureDownloadDataTask : public ThreadPoolTask
{
private:

View File

@@ -499,7 +499,7 @@ public:
/// <summary>
/// Uploads mip map data to the GPU. Creates async GPU task.
/// </summary>
/// <param name="data">Data to upload (it must be valid for the next a few frames due to GPU latency and async works executing)</param>
/// <param name="data">Data to upload, it must match texture dimensions. It must be valid for the next couple of frames due to GPU async task latency or use data copy.</param>
/// <param name="mipIndex">Mip level index.</param>
/// <param name="copyData">If true, the data will be copied to the async execution task instead of using the input pointer provided.</param>
/// <returns>Created async task or null if cannot.</returns>
@@ -508,7 +508,7 @@ public:
/// <summary>
/// Uploads mip map data to the GPU. Creates async GPU task.
/// </summary>
/// <param name="data">Data to upload (it must be valid for the next a few frames due to GPU latency and async works executing)</param>
/// <param name="data">Data to upload, it must match texture dimensions. It must be valid for the next couple of frames due to GPU async task latency or use data copy.</param>
/// <param name="mipIndex">Mip level index.</param>
/// <param name="rowPitch">The data row pitch.</param>
/// <param name="slicePitch">The data slice pitch.</param>
@@ -516,6 +516,14 @@ public:
/// <returns>Created async task or null if cannot.</returns>
GPUTask* UploadMipMapAsync(const BytesContainer& data, int32 mipIndex, int32 rowPitch, int32 slicePitch, bool copyData = false);
/// <summary>
/// Uploads texture data to the GPU. Actual data copy to the GPU memory is performed via async task.
/// </summary>
/// <param name="data">Data to upload, it must match texture dimensions. It must be valid for the next couple of frames due to GPU async task latency or use data copy.</param>
/// <param name="copyData">If true, the data will be copied to the async execution task instead of using the input pointer provided.</param>
/// <returns>True if cannot upload data, otherwise false.</returns>
API_FUNCTION() bool UploadData(TextureData& data, bool copyData = false);
/// <summary>
/// Downloads the texture data to be accessible from CPU. For frequent access, use staging textures, otherwise current thread will be stalled to wait for the GPU frame to copy data into staging buffer.
/// </summary>

View File

@@ -110,7 +110,7 @@ public:
/// <summary>
/// The animation update delta timescale. Can be used to speed up animation playback or create slow motion effect.
/// </summary>
API_FIELD(Attributes="EditorOrder(45), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(45), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Skinned Model\")")
float UpdateSpeed = 1.0f;
/// <summary>
@@ -122,7 +122,7 @@ public:
/// <summary>
/// The master scale parameter for the actor bounding box. Helps to reduce mesh flickering effect on screen edges.
/// </summary>
API_FIELD(Attributes="EditorOrder(60), DefaultValue(1.5f), Limit(0), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(60), DefaultValue(1.5f), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Skinned Model\")")
float BoundsScale = 1.5f;
/// <summary>

View File

@@ -443,4 +443,18 @@ bool ParticleEmitter::Save(const StringView& path)
return SaveSurface(data);
}
bool ParticleEmitter::HasShaderCode() const
{
if (SimulationMode != ParticlesSimulationMode::GPU)
return false;
#if COMPILE_WITH_PARTICLE_GPU_GRAPH && COMPILE_WITH_SHADER_COMPILER
if (_shaderHeader.ParticleEmitter.GraphVersion == PARTICLE_GPU_GRAPH_VERSION
&& HasChunk(SHADER_FILE_CHUNK_SOURCE)
&& !HasDependenciesModified())
return true;
#endif
return false;
}
#endif

View File

@@ -173,6 +173,11 @@ public:
#if USE_EDITOR
void GetReferences(Array<Guid>& assets, Array<String>& files) const override;
bool Save(const StringView& path = StringView::Empty) override;
/// <summary>
/// Checks if the particle emitter has valid shader code present.
/// </summary>
API_PROPERTY() bool HasShaderCode() const;
#endif
protected:

View File

@@ -262,16 +262,16 @@ void CharacterController::OnDebugDrawSelected()
Quaternion rotation = Quaternion::Euler(90, 0, 0);
const Vector3 position = GetControllerPosition();
DEBUG_DRAW_WIRE_CAPSULE(position, rotation, _radius, _height, Color::GreenYellow, 0, false);
if (_contactOffset > 0)
DEBUG_DRAW_WIRE_CAPSULE(position, rotation, _radius - _contactOffset, _height, Color::Blue.AlphaMultiplied(0.4f), 0, false);
#if 1
// More technical visuals debugging
if (_controller)
{
// Physics backend capsule shape
float height, radius;
GetControllerSize(height, radius);
Vector3 base = PhysicsBackend::GetControllerBasePosition(_controller);
Vector3 pos = PhysicsBackend::GetControllerPosition(_controller);
DEBUG_DRAW_WIRE_CAPSULE(pos, rotation, radius, height, Color::Blue.AlphaMultiplied(0.2f), 0, false);
#if 0
// More technical visuals debugging
Vector3 base = PhysicsBackend::GetControllerBasePosition(_controller);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(base, 5.0f), Color::Red, 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos, 4.0f), Color::Red, 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos + Vector3(0, height * 0.5f, 0), 2.0f), Color::Red, 0, false);
@@ -279,17 +279,8 @@ void CharacterController::OnDebugDrawSelected()
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos + Vector3(0, height * 0.5f, 0), radius), Color::Red.AlphaMultiplied(0.5f), 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos - Vector3(0, height * 0.5f, 0), radius), Color::Red.AlphaMultiplied(0.5f), 0, false);
DEBUG_DRAW_WIRE_CYLINDER(pos, Quaternion::Identity, radius, height, Color::Red.AlphaMultiplied(0.2f), 0, false);
}
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(position, 3.0f), Color::GreenYellow, 0, false);
#else
if (_controller)
{
// Physics backend capsule shape
float height, radius;
GetControllerSize(height, radius);
DEBUG_DRAW_WIRE_CAPSULE(PhysicsBackend::GetControllerPosition(_controller), rotation, radius, height, Color::Blue.AlphaMultiplied(0.2f), 0, false);
}
#endif
}
// Base
Collider::OnDebugDrawSelected();

View File

@@ -78,6 +78,32 @@ TEST_CASE("Array")
TEST_CASE("BitArray")
{
SECTION("Test Access")
{
BitArray<> a1;
CHECK(a1.Count() == 0);
for (int32 i = 0; i < 310; i++)
{
a1.Add(false);
}
CHECK(a1.Count() == 310);
a1.Resize(300);
CHECK(a1.Count() == 300);
CHECK(a1.Capacity() >= 300);
a1.SetAll(true);
a1.SetAll(false);
for (int32 i = 0; i < 300; i++)
{
a1.Set(i, true);
for (int32 j = 0; j < 300; j++)
{
bool expected = j == i;
CHECK(a1.Get(j) == expected);
}
a1.Set(i, false);
}
}
SECTION("Test Allocators")
{
BitArray<> a1;
@@ -142,7 +168,7 @@ TEST_CASE("BitArray")
// Generate some random data for testing
BitArray<> testData;
testData.Resize(32);
testData.Resize(128);
RandomStream rand(101);
for (int32 i = 0; i < testData.Count(); i++)
testData.Set(i, rand.GetBool());
@@ -151,8 +177,8 @@ TEST_CASE("BitArray")
{
const BitArray<> a1(testData);
const BitArray<InlinedAllocation<8>> a2(testData);
const BitArray<InlinedAllocation<64>> a3(testData);
const BitArray<FixedAllocation<64>> a4(testData);
const BitArray<InlinedAllocation<256>> a3(testData);
const BitArray<FixedAllocation<256>> a4(testData);
CHECK(a1 == testData);
CHECK(a2 == testData);
CHECK(a3 == testData);

View File

@@ -13,6 +13,7 @@
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#if USE_EDITOR
#include "Engine/Platform/MessageBox.h"
#include "Engine/Graphics/GPUDevice.h"
#endif
#include "Engine/Utilities/AnsiPathTempFile.h"
@@ -358,6 +359,9 @@ HRESULT LoadFromEXRFile(const StringView& path, DirectX::ScratchImage& image)
free(pixels);
return result;
#else
#if USE_EDITOR
MessageBox::Show(TEXT("EXR format is not supported."), TEXT("Import warning"), MessageBoxButtons::OK, MessageBoxIcon::Warning);
#endif
LOG(Warning, "EXR format is not supported.");
return E_FAIL;
#endif

View File

@@ -13,6 +13,7 @@
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Utilities/AnsiPathTempFile.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/MessageBox.h"
#define STBI_ASSERT(x) ASSERT(x)
#define STBI_MALLOC(sz) Allocator::Allocate(sz)
@@ -286,21 +287,27 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
break;
}
case ImageType::GIF:
MessageBox::Show(TEXT("GIF format is not supported."), TEXT("Export warning"), MessageBoxButtons::OK, MessageBoxIcon::Warning);
LOG(Warning, "GIF format is not supported.");
break;
case ImageType::TIFF:
MessageBox::Show(TEXT("TIFF format is not supported."), TEXT("Export warning"), MessageBoxButtons::OK, MessageBoxIcon::Warning);
LOG(Warning, "GIF format is not supported.");
break;
case ImageType::DDS:
MessageBox::Show(TEXT("DDS format is not supported."), TEXT("Export warning"), MessageBoxButtons::OK, MessageBoxIcon::Warning);
LOG(Warning, "DDS format is not supported.");
break;
case ImageType::RAW:
MessageBox::Show(TEXT("RAW format is not supported."), TEXT("Export warning"), MessageBoxButtons::OK, MessageBoxIcon::Warning);
LOG(Warning, "RAW format is not supported.");
break;
case ImageType::EXR:
MessageBox::Show(TEXT("EXR format is not supported."), TEXT("Export warning"), MessageBoxButtons::OK, MessageBoxIcon::Warning);
LOG(Warning, "EXR format is not supported.");
break;
default:
MessageBox::Show(TEXT("Unknown format."), TEXT("Export warning"), MessageBoxButtons::OK, MessageBoxIcon::Warning);
LOG(Warning, "Unknown format.");
break;
}
@@ -434,17 +441,21 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
free(pixels);
#else
MessageBox::Show(TEXT("EXR format is not supported."), TEXT("Import warning"), MessageBoxButtons::OK, MessageBoxIcon::Warning);
LOG(Warning, "EXR format is not supported.");
#endif
break;
}
case ImageType::DDS:
MessageBox::Show(TEXT("DDS format is not supported."), TEXT("Import warning"), MessageBoxButtons::OK, MessageBoxIcon::Warning);
LOG(Warning, "DDS format is not supported.");
break;
case ImageType::TIFF:
MessageBox::Show(TEXT("TIFF format is not supported."), TEXT("Import warning"), MessageBoxButtons::OK, MessageBoxIcon::Warning);
LOG(Warning, "TIFF format is not supported.");
break;
default:
MessageBox::Show(TEXT("Unknown format."), TEXT("Import warning"), MessageBoxButtons::OK, MessageBoxIcon::Warning);
LOG(Warning, "Unknown format.");
return true;
}

View File

@@ -3,7 +3,7 @@
// Ben Golus
// https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8#3e73
#define USE_FORWARD true;
#define MIN_DERIV 0.00001f
#include "./Flax/Common.hlsl"
@@ -61,11 +61,6 @@ float remap(float origFrom, float origTo, float targetFrom, float targetTo, floa
return lerp(targetFrom, targetTo, rel);
}
float ddLength(float a)
{
return length(float2(ddx(a), ddy(a)));
}
float GetLine(float pos, float scale, float thickness)
{
float lineWidth = thickness;
@@ -73,7 +68,7 @@ float GetLine(float pos, float scale, float thickness)
float2 uvDDXY = float2(ddx(coord), ddy(coord));
float deriv = float(length(uvDDXY.xy));
float deriv = max(float(length(uvDDXY.xy)), MIN_DERIV);
float drawWidth = clamp(lineWidth, deriv, 0.5);
float lineAA = deriv * 1.5;
float gridUV = abs(coord);
@@ -92,7 +87,7 @@ float GetGrid(float3 pos, float scale, float thickness)
float4 uvDDXY = float4(ddx(coord), ddy(coord));
float2 deriv = float2(length(uvDDXY.xz), length(uvDDXY.yw));
float2 deriv = max(float2(length(uvDDXY.xz), length(uvDDXY.yw)), float2(MIN_DERIV, MIN_DERIV));
float2 drawWidth = clamp(lineWidth, deriv, 0.5);
float2 lineAA = deriv * 1.5;
float2 gridUV = 1.0 - abs(frac(coord) * 2.0 - 1.0);

View File

@@ -17,6 +17,7 @@ namespace Flax.Build
private static Platform _buildPlatform;
private static Platform[] _platforms;
private Dictionary<TargetArchitecture, Toolchain> _toolchains;
private uint _failedArchitectures = 0;
/// <summary>
/// Gets the current target platform that build tool runs on.
@@ -251,7 +252,8 @@ namespace Flax.Build
public Toolchain TryGetToolchain(TargetArchitecture targetArchitecture)
{
Toolchain result = null;
if (HasRequiredSDKsInstalled)
uint failedMask = 1u << (int)targetArchitecture; // Skip retrying if it already failed once on this arch
if (HasRequiredSDKsInstalled && (_failedArchitectures & failedMask) != failedMask)
{
try
{
@@ -259,6 +261,7 @@ namespace Flax.Build
}
catch (Exception ex)
{
_failedArchitectures |= failedMask;
Log.Exception(ex);
}
}