diff --git a/Content/Shaders/Editor/Grid.flax b/Content/Shaders/Editor/Grid.flax index 311867604..09ecd00e6 100644 --- a/Content/Shaders/Editor/Grid.flax +++ b/Content/Shaders/Editor/Grid.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c13729c4ec2ef534271c60fee6fff2e0489bf4445fe91aa8a2bbc3d581715602 -size 4666 +oid sha256:e5671b8b77b460a17d0a3c14174994a05cf1b3d8869d10b350de4a8053419836 +size 4647 diff --git a/Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs b/Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs index 4b41db297..7e7f8483e 100644 --- a/Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs @@ -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 _checkBoxes; + private VerticalPanel _upperRightCell; + private VerticalPanel _bottomLeftCell; + private UniformGridPanel _grid; + private Border _horizontalHighlight; + private Border _verticalHighlight; /// 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 /// 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); + } } } diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index c6ead7a76..8c3256eb9 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -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() diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs index b3690e754..bf04aa068 100644 --- a/Source/Editor/GUI/AssetPicker.cs +++ b/Source/Editor/GUI/AssetPicker.cs @@ -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)) { diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 82699e55c..58f8c2eb4 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -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) { diff --git a/Source/Editor/GUI/MainMenu.cs b/Source/Editor/GUI/MainMenu.cs index 0b959d2c9..bce668451 100644 --- a/Source/Editor/GUI/MainMenu.cs +++ b/Source/Editor/GUI/MainMenu.cs @@ -266,6 +266,19 @@ namespace FlaxEditor.GUI return AddChild(new MainMenuButton(text)); } + /// + /// Gets or adds a button. + /// + /// The button text + /// The existing or created button control. + public MainMenuButton GetOrAddButton(string text) + { + MainMenuButton result = GetButton(text); + if (result == null) + result = AddButton(text); + return result; + } + /// /// Gets the button. /// diff --git a/Source/Editor/GUI/ToolStripButton.cs b/Source/Editor/GUI/ToolStripButton.cs index 9ef454ccc..c49cddcbb 100644 --- a/Source/Editor/GUI/ToolStripButton.cs +++ b/Source/Editor/GUI/ToolStripButton.cs @@ -122,6 +122,14 @@ namespace FlaxEditor.GUI return this; } + private void OnClicked() + { + if (AutoCheck) + Checked = !Checked; + Clicked?.Invoke(); + (Parent as ToolStrip)?.OnButtonClicked(this); + } + /// 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); } + /// + public override bool OnMouseDoubleClick(Float2 location, MouseButton button) + { + if (button == MouseButton.Left) + { + OnClicked(); + return true; + } + + return false; + } + /// public override void OnMouseLeave() { diff --git a/Source/Editor/GUI/Tree/Tree.cs b/Source/Editor/GUI/Tree/Tree.cs index e36f0ccd5..8df26c211 100644 --- a/Source/Editor/GUI/Tree/Tree.cs +++ b/Source/Editor/GUI/Tree/Tree.cs @@ -73,6 +73,11 @@ namespace FlaxEditor.GUI.Tree /// public bool DrawRootTreeLine = true; + /// + /// Occurs when the deferred layout operation was performed. + /// + public event Action AfterDeferredLayout; + /// /// Gets or sets the margin for the child tree nodes. /// @@ -375,6 +380,7 @@ namespace FlaxEditor.GUI.Tree if (_deferLayoutUpdate) { base.PerformLayout(); + AfterDeferredLayout?.Invoke(); _deferLayoutUpdate = false; } diff --git a/Source/Editor/Modules/EditorModule.cs b/Source/Editor/Modules/EditorModule.cs index 8285088fa..410453302 100644 --- a/Source/Editor/Modules/EditorModule.cs +++ b/Source/Editor/Modules/EditorModule.cs @@ -76,6 +76,13 @@ namespace FlaxEditor.Modules { } + /// + /// Called when Editor will leave the play mode. + /// + public virtual void OnPlayEnding() + { + } + /// /// Called when Editor leaves the play mode. /// diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index 289c5d7e8..5c9c613d2 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -1223,6 +1223,13 @@ namespace FlaxEditor.Modules Windows[i].OnPlayBegin(); } + /// + public override void OnPlayEnding() + { + for (int i = 0; i < Windows.Count; i++) + Windows[i].OnPlayEnding(); + } + /// public override void OnPlayEnd() { diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index ff7971667..871156662 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -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 } } diff --git a/Source/Editor/Surface/NodeAlignmentType.cs b/Source/Editor/Surface/NodeAlignmentType.cs new file mode 100644 index 000000000..141235783 --- /dev/null +++ b/Source/Editor/Surface/NodeAlignmentType.cs @@ -0,0 +1,43 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +using FlaxEngine; + +namespace FlaxEditor.Surface +{ + /// + /// Node Alignment type + /// + [HideInEditor] + public enum NodeAlignmentType + { + /// + /// Align nodes vertically to top, matching top-most node + /// + Top, + + /// + /// Align nodes vertically to middle, using average of all nodes + /// + Middle, + + /// + /// Align nodes vertically to bottom, matching bottom-most node + /// + Bottom, + + /// + /// Align nodes horizontally to left, matching left-most node + /// + Left, + + /// + /// Align nodes horizontally to center, using average of all nodes + /// + Center, + + /// + /// Align nodes horizontally to right, matching right-most node + /// + Right, + } +} \ No newline at end of file diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index 8a42a7a92..5dedd604f 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -912,7 +912,7 @@ namespace FlaxEditor.Surface /// public override bool OnTestTooltipOverControl(ref Float2 location) { - return _headerRect.Contains(ref location) && ShowTooltip; + return _headerRect.Contains(ref location) && ShowTooltip && !Surface.IsConnecting && !Surface.IsBoxSelecting; } /// @@ -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; diff --git a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs index ee7dd33e5..84055aaf0 100644 --- a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs +++ b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs @@ -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)", () => { diff --git a/Source/Editor/Surface/VisjectSurface.Draw.cs b/Source/Editor/Surface/VisjectSurface.Draw.cs index 5a63fe4de..01277d0d2 100644 --- a/Source/Editor/Surface/VisjectSurface.Draw.cs +++ b/Source/Editor/Surface/VisjectSurface.Draw.cs @@ -225,7 +225,7 @@ namespace FlaxEditor.Surface _rootControl.DrawComments(); - if (IsSelecting) + if (IsBoxSelecting) { DrawSelection(); } diff --git a/Source/Editor/Surface/VisjectSurface.Formatting.cs b/Source/Editor/Surface/VisjectSurface.Formatting.cs index c8819a559..2ff48b290 100644 --- a/Source/Editor/Surface/VisjectSurface.Formatting.cs +++ b/Source/Editor/Surface/VisjectSurface.Formatting.cs @@ -282,5 +282,122 @@ namespace FlaxEditor.Surface return maxOffset; } + + /// + /// Align given nodes on a graph using the given alignment type. + /// Ignores any potential overlap. + /// + /// List of nodes + /// Alignemnt type + public void AlignNodes(List nodes, NodeAlignmentType alignmentType) + { + if(nodes.Count <= 1) + return; + + var undoActions = new List(); + 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})")); + } + + /// + /// 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. + /// + /// List of nodes + /// If false will be done horizontally, if true will be done vertically + public void DistributeNodes(List nodes, bool vertically) + { + if(nodes.Count <= 1) + return; + + var undoActions = new List(); + 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")); + } } } diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index a874db681..63dfa063d 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -27,6 +27,7 @@ namespace FlaxEditor.Surface private Float2 _movingNodesDelta; private Float2 _gridRoundingDelta; private HashSet _movingNodes; + private HashSet _temporarySelectedNodes; private readonly Stack _inputBrackets = new Stack(); 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(); + 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; diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index faecebbd3..1201318d8 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -232,15 +232,25 @@ namespace FlaxEditor.Surface } /// - /// Gets a value indicating whether user is selecting nodes. + /// Gets a value indicating whether user is box selecting nodes. /// - public bool IsSelecting => _leftMouseDown && !_isMovingSelection && _connectionInstigator == null; + public bool IsBoxSelecting => _leftMouseDown && !_isMovingSelection && _connectionInstigator == null; + + /// + /// Gets a value indicating whether user was previously box selecting nodes. + /// + public bool WasBoxSelecting { get; private set; } /// /// Gets a value indicating whether user is moving selected nodes. /// public bool IsMovingSelection => _leftMouseDown && _isMovingSelection && _connectionInstigator == null; + /// + /// Gets a value indicating whether user was previously moving selected nodes. + /// + public bool WasMovingSelection { get; private set; } + /// /// Gets a value indicating whether user is connecting nodes. /// @@ -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(); } + /// + /// Removes the specified control from the selection. + /// + /// The control. + public void RemoveFromSelection(SurfaceControl control) + { + if (!control.IsSelected) + return; + control.IsSelected = false; + SelectionChanged?.Invoke(); + } + /// /// Selects the specified control. /// diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 179e50ebb..949479783 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -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) diff --git a/Source/Editor/Windows/Assets/ModelBaseWindow.cs b/Source/Editor/Windows/Assets/ModelBaseWindow.cs index 14344ef71..09b4606c4 100644 --- a/Source/Editor/Windows/Assets/ModelBaseWindow.cs +++ b/Source/Editor/Windows/Assets/ModelBaseWindow.cs @@ -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; diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index 9a0b1ad82..1dc30cae3 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -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; diff --git a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs index 80eafd9e0..6513ac9e0 100644 --- a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs @@ -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; /// 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 /// public SearchAssetTypes AssetType => SearchAssetTypes.ParticleEmitter; + + /// + public override void Update(float deltaTime) + { + base.Update(deltaTime); + + if (_asset == null) + return; + _showSourceCodeButton.Enabled = _asset.HasShaderCode; + } } } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs b/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs index 03e2a9652..6208aa7a1 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs @@ -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 diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs index fd6b333ef..b5d71f6a0 100644 --- a/Source/Editor/Windows/DebugLogWindow.cs +++ b/Source/Editor/Windows/DebugLogWindow.cs @@ -318,7 +318,7 @@ namespace FlaxEditor.Windows private Color _colorWarning; private Color _colorError; private bool _colorDebugLogText; - + /// /// Initializes a new instance of the class. /// @@ -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); diff --git a/Source/Editor/Windows/EditorWindow.cs b/Source/Editor/Windows/EditorWindow.cs index f61f5cce6..6d01432ba 100644 --- a/Source/Editor/Windows/EditorWindow.cs +++ b/Source/Editor/Windows/EditorWindow.cs @@ -219,6 +219,13 @@ namespace FlaxEditor.Windows { } + /// + /// Called when Editor will leave the play mode. + /// + public virtual void OnPlayEnding() + { + } + /// /// Called when Editor leaves the play mode. /// diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 4db3bcf0f..e05f0d2db 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -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) diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index 6fc0659d7..be6e6ff4d 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -830,6 +830,15 @@ namespace FlaxEditor.Windows OnOutputTextChanged(); } + /// + /// Focus the debug command line and ensure that the output log window is visible. + /// + public void FocusCommand() + { + FocusOrShow(); + _commandLineBox.Focus(); + } + /// public override void Update(float deltaTime) { diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 705976e6e..3c7583b0e 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -34,6 +34,7 @@ namespace FlaxEditor.Windows private DragScriptItems _dragScriptItems; private DragHandlers _dragHandlers; private bool _isDropping = false; + private bool _forceScrollNodeToView = false; /// /// 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(); } diff --git a/Source/Editor/Windows/Search/ContentFinder.cs b/Source/Editor/Windows/Search/ContentFinder.cs index e4227e5c4..9118739b8 100644 --- a/Source/Editor/Windows/Search/ContentFinder.cs +++ b/Source/Editor/Windows/Search/ContentFinder.cs @@ -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); diff --git a/Source/Engine/ContentImporters/ImportAudio.cpp b/Source/Engine/ContentImporters/ImportAudio.cpp index 6fd0cdc74..2c5b84d49 100644 --- a/Source/Engine/ContentImporters/ImportAudio.cpp +++ b/Source/Engine/ContentImporters/ImportAudio.cpp @@ -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; \ } \ diff --git a/Source/Engine/Core/Collections/BitArray.h b/Source/Engine/Core/Collections/BitArray.h index 8206f2164..6589fa9a1 100644 --- a/Source/Engine/Core/Collections/BitArray.h +++ b/Source/Engine/Core/Collections/BitArray.h @@ -16,6 +16,7 @@ API_CLASS(InBuild) class BitArray public: using ItemType = uint64; using AllocationData = typename AllocationType::template Data; + 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: diff --git a/Source/Engine/Core/Compiler.h b/Source/Engine/Core/Compiler.h index fb731930e..a45a4628a 100644 --- a/Source/Engine/Core/Compiler.h +++ b/Source/Engine/Core/Compiler.h @@ -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) diff --git a/Source/Engine/Graphics/Models/ModelData.Tool.cpp b/Source/Engine/Graphics/Models/ModelData.Tool.cpp index 52663c271..f1c84cfb7 100644 --- a/Source/Engine/Graphics/Models/ModelData.Tool.cpp +++ b/Source/Engine/Graphics/Models/ModelData.Tool.cpp @@ -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 diff --git a/Source/Engine/Graphics/Textures/GPUTexture.cpp b/Source/Engine/Graphics/Textures/GPUTexture.cpp index 07b85bc42..3b6fca968 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.cpp +++ b/Source/Engine/Graphics/Textures/GPUTexture.cpp @@ -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: diff --git a/Source/Engine/Graphics/Textures/GPUTexture.h b/Source/Engine/Graphics/Textures/GPUTexture.h index 977723f26..85ac4ae0b 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.h +++ b/Source/Engine/Graphics/Textures/GPUTexture.h @@ -499,7 +499,7 @@ public: /// /// Uploads mip map data to the GPU. Creates async GPU task. /// - /// Data to upload (it must be valid for the next a few frames due to GPU latency and async works executing) + /// 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. /// Mip level index. /// If true, the data will be copied to the async execution task instead of using the input pointer provided. /// Created async task or null if cannot. @@ -508,7 +508,7 @@ public: /// /// Uploads mip map data to the GPU. Creates async GPU task. /// - /// Data to upload (it must be valid for the next a few frames due to GPU latency and async works executing) + /// 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. /// Mip level index. /// The data row pitch. /// The data slice pitch. @@ -516,6 +516,14 @@ public: /// Created async task or null if cannot. GPUTask* UploadMipMapAsync(const BytesContainer& data, int32 mipIndex, int32 rowPitch, int32 slicePitch, bool copyData = false); + /// + /// Uploads texture data to the GPU. Actual data copy to the GPU memory is performed via async task. + /// + /// 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. + /// If true, the data will be copied to the async execution task instead of using the input pointer provided. + /// True if cannot upload data, otherwise false. + API_FUNCTION() bool UploadData(TextureData& data, bool copyData = false); + /// /// 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. /// diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index 6f87ab9cd..deadf8c7c 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -110,7 +110,7 @@ public: /// /// The animation update delta timescale. Can be used to speed up animation playback or create slow motion effect. /// - 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; /// @@ -122,7 +122,7 @@ public: /// /// The master scale parameter for the actor bounding box. Helps to reduce mesh flickering effect on screen edges. /// - 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; /// diff --git a/Source/Engine/Particles/ParticleEmitter.cpp b/Source/Engine/Particles/ParticleEmitter.cpp index c7bc647bf..0be85f136 100644 --- a/Source/Engine/Particles/ParticleEmitter.cpp +++ b/Source/Engine/Particles/ParticleEmitter.cpp @@ -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 diff --git a/Source/Engine/Particles/ParticleEmitter.h b/Source/Engine/Particles/ParticleEmitter.h index b3343da1a..772f5569e 100644 --- a/Source/Engine/Particles/ParticleEmitter.h +++ b/Source/Engine/Particles/ParticleEmitter.h @@ -173,6 +173,11 @@ public: #if USE_EDITOR void GetReferences(Array& assets, Array& files) const override; bool Save(const StringView& path = StringView::Empty) override; + + /// + /// Checks if the particle emitter has valid shader code present. + /// + API_PROPERTY() bool HasShaderCode() const; #endif protected: diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 74c324bad..a874a9947 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -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(); diff --git a/Source/Engine/Tests/TestCollections.cpp b/Source/Engine/Tests/TestCollections.cpp index fd0f3328d..b477140cf 100644 --- a/Source/Engine/Tests/TestCollections.cpp +++ b/Source/Engine/Tests/TestCollections.cpp @@ -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> a2(testData); - const BitArray> a3(testData); - const BitArray> a4(testData); + const BitArray> a3(testData); + const BitArray> a4(testData); CHECK(a1 == testData); CHECK(a2 == testData); CHECK(a3 == testData); diff --git a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp index 6d0421e3c..bf74213d4 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp @@ -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 diff --git a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp index 6fae6432e..f8f012c15 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp @@ -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; } diff --git a/Source/Shaders/Editor/Grid.shader b/Source/Shaders/Editor/Grid.shader index f109ddd1a..45bb1ed90 100644 --- a/Source/Shaders/Editor/Grid.shader +++ b/Source/Shaders/Editor/Grid.shader @@ -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); diff --git a/Source/Tools/Flax.Build/Build/Platform.cs b/Source/Tools/Flax.Build/Build/Platform.cs index 6304cdc4a..59572d669 100644 --- a/Source/Tools/Flax.Build/Build/Platform.cs +++ b/Source/Tools/Flax.Build/Build/Platform.cs @@ -17,6 +17,7 @@ namespace Flax.Build private static Platform _buildPlatform; private static Platform[] _platforms; private Dictionary _toolchains; + private uint _failedArchitectures = 0; /// /// 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); } }