diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ada5e5fa5..7f91b6ddc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,7 @@ Go check out our [Trello](https://trello.com/b/NQjLXRCP/flax-roadmap). * For feature PR's the first thing you should evaluate is the value of your contribution, as in, what would it bring to this engine? Is it really required? If its a small change you could preferably suggest it to us on our discord, else feel free to open up a PR for it. -* Ensure when creating a PR that your contribution is well explained with a adequate description and title. +* Ensure when creating a PR that your contribution is well explained with an adequate description and title. * Generally, good code quality is expected, make sure your contribution works as intended and is appropriately commented where necessary. diff --git a/Flax.flaxproj b/Flax.flaxproj index ed79458d2..e6d7badcc 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -4,7 +4,7 @@ "Major": 1, "Minor": 12, "Revision": 0, - "Build": 6914 + "Build": 6915 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.", diff --git a/Source/Editor/CustomEditors/CustomEditorPresenter.cs b/Source/Editor/CustomEditors/CustomEditorPresenter.cs index c7832cfe1..94496c334 100644 --- a/Source/Editor/CustomEditors/CustomEditorPresenter.cs +++ b/Source/Editor/CustomEditors/CustomEditorPresenter.cs @@ -8,6 +8,7 @@ using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; +using FlaxEditor.CustomEditors.GUI; namespace FlaxEditor.CustomEditors { @@ -244,10 +245,15 @@ namespace FlaxEditor.CustomEditors protected readonly RootEditor Editor; /// - /// The selected objects list (read-only). + /// The current selection. /// public readonly ValueContainer Selection = new ValueContainer(ScriptMemberInfo.Null); + /// + /// The current properties search query. + /// + public string SearchText = string.Empty; + /// /// The undo object used by this editor. /// @@ -529,6 +535,256 @@ namespace FlaxEditor.CustomEditors Editor?.RefreshInternal(); } + /// + /// Applies search filter query to the presenter layout controls. + /// + public void ApplySearchFilter(string query) + { + SearchText = query; + if (Root == null) + return; + + var isQueryEmpty = string.IsNullOrEmpty(query); + var groupMatchCache = new Dictionary(); + UpdateFilter(Root, query, isQueryEmpty, groupMatchCache); + UpdatePropertiesListsVisibility(Panel, query); + UpdateGroupsVisibility(Panel, query); + Panel.PerformLayout(); + } + + /// + /// Updates the visibility of properties lists and drop panels based on the current search query. + /// + public void UpdateGroupsAndListsVisibility() + { + if (string.IsNullOrEmpty(SearchText)) + { + RestoreVisibilities(Panel); + } + else + { + UpdatePropertiesListsVisibility(Panel, SearchText); + UpdateGroupsVisibility(Panel, SearchText); + } + Panel.PerformLayout(); + } + + private void RestoreVisibilities(Control control) + { + if (control is DropPanel dropPanel) + { + dropPanel.SearchText = string.Empty; + dropPanel.Visible = true; + } + else if (control is PropertiesList list) + { + list.Visible = true; + } + + if (control is ContainerControl container) + { + for (int i = 0; i < container.ChildrenCount; i++) + { + RestoreVisibilities(container.GetChild(i)); + } + } + } + + private void UpdatePropertiesListsVisibility(Control control, string query) + { + if (control is PropertiesList list) + { + if (string.IsNullOrEmpty(query)) + { + list.Visible = true; + } + else + { + bool anyVisible = false; + foreach (var label in list.Element.Labels) + { + if (label.Visible) + { + anyVisible = true; + break; + } + } + list.Visible = anyVisible; + } + } + + if (control is ContainerControl container) + { + for (int i = 0; i < container.ChildrenCount; i++) + { + UpdatePropertiesListsVisibility(container.GetChild(i), query); + } + } + } + + private void UpdateGroupsVisibility(Control control, string query) + { + if (control is DropPanel dropPanel) + { + dropPanel.SearchText = query; + for (int i = 0; i < dropPanel.ChildrenCount; i++) + { + UpdateGroupsVisibility(dropPanel.GetChild(i), query); + } + + if (string.IsNullOrEmpty(query)) + { + dropPanel.Visible = true; + } + else + { + dropPanel.Visible = HasVisibleDescendants(dropPanel); + } + } + else if (control is ContainerControl container) + { + for (int i = 0; i < container.ChildrenCount; i++) + { + UpdateGroupsVisibility(container.GetChild(i), query); + } + } + } + + private bool HasVisibleDescendants(ContainerControl container) + { + for (int i = 0; i < container.ChildrenCount; i++) + { + var child = container.GetChild(i); + if (child is PropertyNameLabel label && label.Visible) + { + return true; + } + if (child is DropPanel dropPanel && dropPanel.Visible) + { + return true; + } + if (child is ContainerControl subContainer && subContainer.Visible && HasVisibleDescendants(subContainer)) + { + return true; + } + } + return false; + } + + private bool IsInMatchingGroup(Control control, string query, Dictionary groupMatchCache) + { + if (string.IsNullOrEmpty(query)) + return false; + + var p = control.Parent; + while (p != null) + { + if (p is DropPanel dropPanel) + { + if (!groupMatchCache.TryGetValue(dropPanel, out bool matches)) + { + var headerText = dropPanel.HeaderText; + matches = headerText != null && headerText.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0; + groupMatchCache[dropPanel] = matches; + } + if (matches) + return true; + } + p = p.Parent; + } + return false; + } + + private bool UpdateFilter(CustomEditor editor, string query, bool forceVisible, Dictionary groupMatchCache) + { + bool isVisible = false; + + bool labelMatches = false; + if (editor.LinkedLabel != null) + { + editor.LinkedLabel.SearchText = query; + if (forceVisible) + { + labelMatches = true; + } + else if (!string.IsNullOrEmpty(query)) + { + var labelText = editor.LinkedLabel.Text.ToString(); + if (labelText.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0) + { + labelMatches = true; + } + else if (IsInMatchingGroup(editor.LinkedLabel, query, groupMatchCache)) + { + labelMatches = true; + } + } + } + + bool isThisEditorVisible = forceVisible || labelMatches; + + bool anyChildVisible = false; + bool forceChildrenVisible = isThisEditorVisible; + + foreach (var child in editor.ChildrenEditors) + { + if (UpdateFilter(child, query, forceChildrenVisible, groupMatchCache)) + { + anyChildVisible = true; + } + } + + isVisible = isThisEditorVisible || anyChildVisible; + + if (editor.LinkedLabel != null) + { + SetLabelAndControlsVisible(editor.LinkedLabel, isVisible); + } + + if (editor.Style == DisplayStyle.Group && editor.Layout != null && editor.Layout.Control != null) + { + editor.Layout.Control.Visible = isVisible; + } + + return isVisible; + } + + private void SetLabelAndControlsVisible(PropertyNameLabel label, bool visible) + { + label.Visible = visible; + var container = label.FirstChildControlContainer ?? label.Parent as PropertiesList; + if (container != null) + { + int startIndex = label.FirstChildControlIndex; + if (startIndex >= 0) + { + int endIndex = container.Children.Count; + var labels = container.Element.Labels; + int labelIndex = labels.IndexOf(label); + if (labelIndex >= 0 && labelIndex < labels.Count - 1) + { + for (int i = labelIndex + 1; i < labels.Count; i++) + { + var nextContainer = labels[i].FirstChildControlContainer ?? labels[i].Parent as PropertiesList; + if (nextContainer == container) + { + endIndex = labels[i].FirstChildControlIndex - 1; + break; + } + } + } + + for (int i = startIndex; i < endIndex; i++) + { + if (i < container.Children.Count) + { + container.Children[i].Visible = visible; + } + } + } + } + } + /// public override ContainerControl ContainerControl => Panel; } diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index a20b41f8c..900fc6c8d 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -868,6 +868,28 @@ namespace FlaxEditor.CustomEditors.Editors if (c.LabelIndex != -1 && c.PropertiesList != null && c.PropertiesList.Labels.Count > c.LabelIndex) { var label = c.PropertiesList.Labels[c.LabelIndex]; + if (visible && Presenter != null && !string.IsNullOrEmpty(Presenter.SearchText)) + { + bool match = label.Text.ToString().IndexOf(Presenter.SearchText, StringComparison.OrdinalIgnoreCase) >= 0; + if (!match) + { + var p = label.Parent; + while (p != null) + { + if (p is DropPanel dropPanel) + { + var headerText = dropPanel.HeaderText; + if (headerText != null && headerText.IndexOf(Presenter.SearchText, StringComparison.OrdinalIgnoreCase) >= 0) + { + match = true; + break; + } + } + p = p.Parent; + } + } + visible = match; + } label.Visible = visible; for (int j = label.FirstChildControlIndex; j < c.PropertiesList.Properties.Children.Count; j++) { @@ -908,6 +930,10 @@ namespace FlaxEditor.CustomEditors.Editors _visibleIfCaches = null; } } + if (Presenter != null && !string.IsNullOrEmpty(Presenter.SearchText)) + { + Presenter.UpdateGroupsAndListsVisibility(); + } base.Refresh(); } diff --git a/Source/Editor/CustomEditors/GUI/PropertyNameLabel.cs b/Source/Editor/CustomEditors/GUI/PropertyNameLabel.cs index c509dd1d2..3108d0a7e 100644 --- a/Source/Editor/CustomEditors/GUI/PropertyNameLabel.cs +++ b/Source/Editor/CustomEditors/GUI/PropertyNameLabel.cs @@ -1,6 +1,7 @@ // Copyright (c) Wojciech Figat. All rights reserved. using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.Utilities; using FlaxEngine; using FlaxEngine.GUI; @@ -45,6 +46,34 @@ namespace FlaxEditor.CustomEditors.GUI /// public Color HighlightStripColor; + /// + /// The active search text query used to highlight matching parts of the label. + /// + public string SearchText = string.Empty; + + private string _lastSearchText; + private string _lastText; + private QueryFilterHelper.Range[] _highlightRanges; + + private void UpdateHighlights() + { + var text = Text?.ToString() ?? string.Empty; + if (_lastSearchText == SearchText && _lastText == text) + return; + + _lastSearchText = SearchText; + _lastText = text; + + if (!string.IsNullOrEmpty(SearchText)) + { + QueryFilterHelper.Match(SearchText, text, out _highlightRanges); + } + else + { + _highlightRanges = null; + } + } + /// /// Occurs when label creates the context menu popup for th property. Can be used to add some custom logic per property editor. /// @@ -83,6 +112,31 @@ namespace FlaxEditor.CustomEditors.GUI { Render2D.FillRectangle(new Rectangle(0, 0, 2, Height), HighlightStripColor); } + + UpdateHighlights(); + + if (_highlightRanges != null && _highlightRanges.Length > 0) + { + var text = Text.ToString(); + var font = Font?.GetFont(); + if (font != null) + { + var style = Style.Current; + var color = style.ProgressNormal * 0.6f; + var margin = Margin; + var textSize = font.MeasureText(text); + var textX = margin.Left; + var textY = margin.Top + (Height - margin.Height - textSize.Y) * 0.5f; + + for (int i = 0; i < _highlightRanges.Length; i++) + { + var start = font.GetCharPosition(text, _highlightRanges[i].StartIndex); + var end = font.GetCharPosition(text, _highlightRanges[i].EndIndex); + var highlightRect = new Rectangle(start.X + textX, textY, end.X - start.X, textSize.Y); + Render2D.FillRectangle(highlightRect, color); + } + } + } } /// diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index a1b1873ba..7e624efd3 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -1337,10 +1337,7 @@ namespace FlaxEditor { var scenes = Level.Scenes; for (int i = 0; i < scenes.Length; i++) - { scenes[i].ClearLightmaps(); - } - Scene.MarkSceneEdited(scenes); } @@ -1384,6 +1381,7 @@ namespace FlaxEditor var scenes = Level.Scenes; Navigation.BuildNavMesh(); Scene.MarkSceneEdited(scenes); + ProgressReporting.NavMeshBuilding.DirtyScenesOnEnd = true; } /// diff --git a/Source/Editor/GUI/Table.cs b/Source/Editor/GUI/Table.cs index 71b01aa0f..bcb4dd163 100644 --- a/Source/Editor/GUI/Table.cs +++ b/Source/Editor/GUI/Table.cs @@ -82,6 +82,53 @@ namespace FlaxEditor.GUI } } + private bool _alternateRows = true; + + /// + /// Gets or sets a value indicating whether alternate row background colors should be applied. + /// + public bool AlternateRows + { + get => _alternateRows; + set + { + if (_alternateRows != value) + { + _alternateRows = value; + PerformLayout(); + } + } + } + + private Color? _rowColorEven; + private Color? _rowColorOdd; + + /// + /// Gets or sets the background color of even rows. + /// + public Color RowColorEven + { + get => _rowColorEven ?? Color.Transparent; + set + { + _rowColorEven = value; + PerformLayout(); + } + } + + /// + /// Gets or sets the background color of odd rows. + /// + public Color RowColorOdd + { + get => _rowColorOdd ?? (Style.Current != null ? Style.Current.Background * 1.4f : Color.Transparent); + set + { + _rowColorOdd = value; + PerformLayout(); + } + } + /// /// Initializes a new instance of the class. /// @@ -250,6 +297,7 @@ namespace FlaxEditor.GUI // Arrange rows float y = _headerHeight; + int visibleRowIndex = 0; for (int i = 0; i < Children.Count; i++) { var c = Children[i]; @@ -260,6 +308,12 @@ namespace FlaxEditor.GUI bounds.Y = y; c.Bounds = bounds; y += bounds.Height + 1; + + if (_alternateRows && c is Row row) + { + row.BackgroundColor = visibleRowIndex % 2 == 1 ? RowColorOdd : RowColorEven; + visibleRowIndex++; + } } } diff --git a/Source/Editor/Progress/Handlers/NavMeshBuildingProgress.cs b/Source/Editor/Progress/Handlers/NavMeshBuildingProgress.cs index c722929fc..21a7b684f 100644 --- a/Source/Editor/Progress/Handlers/NavMeshBuildingProgress.cs +++ b/Source/Editor/Progress/Handlers/NavMeshBuildingProgress.cs @@ -12,6 +12,8 @@ namespace FlaxEditor.Progress.Handlers { private bool _isActive; + internal bool DirtyScenesOnEnd; + /// /// Initializes a new instance of the class. /// @@ -34,6 +36,11 @@ namespace FlaxEditor.Progress.Handlers } else { + if (DirtyScenesOnEnd) + { + DirtyScenesOnEnd = false; + Editor.Instance.Scene.MarkAllScenesEdited(); + } OnEnd(); } } diff --git a/Source/Editor/States/BuildingScenesState.cs b/Source/Editor/States/BuildingScenesState.cs index 9ebf58fe2..52cc5ac81 100644 --- a/Source/Editor/States/BuildingScenesState.cs +++ b/Source/Editor/States/BuildingScenesState.cs @@ -73,6 +73,14 @@ namespace FlaxEditor.States { StateMachine.GoToState(); } + + protected void ConditionalDirtyScenes() + { + if (((SubStateMachine)StateMachine).States.Any(x => ((SubState)x).DirtyScenes)) + { + Editor.Instance.Scene.MarkAllScenesEdited(); + } + } } private sealed class BeginState : SubState @@ -83,18 +91,12 @@ namespace FlaxEditor.States { public override void OnEnter() { - var stateMachine = (SubStateMachine)StateMachine; - var scenesDirty = false; - foreach (var state in stateMachine.States) + var states = ((SubStateMachine)StateMachine).States; + foreach (var state in states) { ((SubState)state).Before(); - scenesDirty |= ((SubState)state).DirtyScenes; - } - if (scenesDirty) - { - foreach (var scene in Level.Scenes) - Editor.Instance.Scene.MarkSceneEdited(scene); } + ConditionalDirtyScenes(); Done(); } } @@ -262,6 +264,7 @@ namespace FlaxEditor.States { public override void OnEnter() { + ConditionalDirtyScenes(); Editor.Instance.StateMachine.GoToState(); } } diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs index 3151f9b17..3f3a76302 100644 --- a/Source/Editor/Surface/VisjectSurfaceWindow.cs +++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs @@ -42,6 +42,12 @@ namespace FlaxEditor.Surface /// IEnumerable NewParameterTypes { get; } + /// + /// Index of the parameter to start renaming once the properties panel is next rebuilt, or -1 if none. + /// Used to auto-start renaming of a freshly added parameter. + /// + int ParamToRename { get; set; } + /// /// Event called when surface gets loaded (eg. after opening the window). /// @@ -589,6 +595,7 @@ namespace FlaxEditor.Surface if (Utilities.Utils.OnAssetProperties(layout, asset)) return; var parameters = window.VisjectSurface.Parameters; + ParameterPropertyNameLabel labelToRename = null; CustomEditors.Editors.GenericEditor.OnGroupsBegin(); for (int i = 0; i < parameters.Count; i++) { @@ -643,6 +650,8 @@ namespace FlaxEditor.Surface tooltipText += '\n' + tooltip.Text; propertyLabel.MouseLeftDoubleClick += (label, location) => StartParameterRenaming(pIndex, label); propertyLabel.SetupContextMenu += OnPropertyLabelSetupContextMenu; + if (pIndex == window.ParamToRename) + labelToRename = propertyLabel; var property = itemLayout.AddPropertyItem(propertyLabel, tooltipText); property.Property("Value", propertyValue); } @@ -657,6 +666,21 @@ namespace FlaxEditor.Surface newParam.Button.ButtonClicked += OnAddParameterButtonClicked; layout.Space(10); } + // Defer renaming a newly added param once its label is built and laid out + // Adding a param can rebuild the panel more than once (disposing earlier labels) + // Because of this every rebuild recaptures the current label, only the surviving one actually calls StartParameterRenaming + if (labelToRename != null) + { + var index = window.ParamToRename; + var label = labelToRename; + FlaxEngine.Scripting.InvokeOnUpdate(() => + { + if (label.IsDisposing) + return; // A latter rebuild replaced this label, its own callback will handle it + window.ParamToRename = -1; + StartParameterRenaming(index, label); + }); + } } private void OnAddParameterButtonClicked(Button button) @@ -695,6 +719,7 @@ namespace FlaxEditor.Surface }; window.VisjectSurface.Undo.AddAction(action); action.Do(); + window.ParamToRename = action.Index; } private DragData OnDragParameter(DraggablePropertyNameLabel label) @@ -1276,6 +1301,9 @@ namespace FlaxEditor.Surface /// public abstract IEnumerable NewParameterTypes { get; } + /// + public int ParamToRename { get; set; } = -1; + /// public event Action SurfaceLoaded; diff --git a/Source/Editor/Undo/Actions/ParentActorsAction.cs b/Source/Editor/Undo/Actions/ParentActorsAction.cs index aed85cb3c..c4a6c7f29 100644 --- a/Source/Editor/Undo/Actions/ParentActorsAction.cs +++ b/Source/Editor/Undo/Actions/ParentActorsAction.cs @@ -148,8 +148,7 @@ namespace FlaxEditor.Actions // Prefab links are broken by the C++ backend on actor reparenting // Mark scenes as edited - foreach (var scene in scenes) - Editor.Instance.Scene.MarkSceneEdited(scene); + Editor.Instance.Scene.MarkSceneEdited(scenes); } public void Undo() diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs index 1317300c3..7772d59b2 100644 --- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs +++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs @@ -602,6 +602,9 @@ namespace FlaxEditor.Windows.Assets /// public IEnumerable NewParameterTypes => Editor.CodeEditing.VisualScriptPropertyTypes.Get(); + /// + public int ParamToRename { get; set; } = -1; + /// public event Action SurfaceLoaded; diff --git a/Source/Editor/Windows/Assets/JsonAssetWindow.cs b/Source/Editor/Windows/Assets/JsonAssetWindow.cs index c2722ed9d..4d9c04942 100644 --- a/Source/Editor/Windows/Assets/JsonAssetWindow.cs +++ b/Source/Editor/Windows/Assets/JsonAssetWindow.cs @@ -5,6 +5,7 @@ using FlaxEditor.Content; using FlaxEditor.CustomEditors; using FlaxEditor.GUI; using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.GUI.Input; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Json; @@ -67,6 +68,8 @@ namespace FlaxEditor.Windows.Assets } private readonly CustomEditorPresenter _presenter; + private SearchBox _searchBox; + private Panel _scrollingPanel; private readonly ToolStripButton _saveButton; private readonly ToolStripButton _undoButton; private readonly ToolStripButton _redoButton; @@ -100,18 +103,35 @@ namespace FlaxEditor.Windows.Assets _undoButton = _toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo", ref inputOptions.Undo); _redoButton = _toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo", ref inputOptions.Redo); - // Panel - var panel = new Panel(ScrollBars.Vertical) + // Header panel for search + var headerPanel = new ContainerControl + { + AnchorPreset = AnchorPresets.HorizontalStretchTop, + BackgroundColor = Style.Current.Background, + IsScrollable = false, + Offsets = new Margin(0, 0, _toolstrip.Bottom, 18 + 6), + Parent = this, + }; + _searchBox = new SearchBox + { + AnchorPreset = AnchorPresets.HorizontalStretchMiddle, + Parent = headerPanel, + Bounds = new Rectangle(4, 4, headerPanel.Width - 8, 18), + }; + _searchBox.TextChanged += ApplySearchFilter; + + _scrollingPanel = new Panel(ScrollBars.Vertical) { AnchorPreset = AnchorPresets.StretchAll, - Offsets = new Margin(0, 0, _toolstrip.Bottom, 0), - Parent = this + Offsets = new Margin(0, 0, headerPanel.Bottom, 0), + Parent = this, }; // Properties _presenter = new CustomEditorPresenter(_undo, "Loading..."); - _presenter.Panel.Parent = panel; + _presenter.Panel.Parent = _scrollingPanel; _presenter.Modified += MarkAsEdited; + _presenter.AfterLayout += OnPresenterAfterLayout; // Setup input actions InputActions.Add(options => options.Undo, _undo.PerformUndo); @@ -349,5 +369,15 @@ namespace FlaxEditor.Windows.Assets _optionsCM?.Dispose(); _typeText = null; } + + private void OnPresenterAfterLayout(LayoutElementsContainer layout) + { + ApplySearchFilter(); + } + + private void ApplySearchFilter() + { + _presenter.ApplySearchFilter(_searchBox.Text); + } } } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 1d6d827ab..06e007a17 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Xml; using FlaxEditor.Content; using FlaxEditor.CustomEditors; +using FlaxEditor.CustomEditors.GUI; using FlaxEditor.Gizmo; using FlaxEditor.GUI; using FlaxEditor.GUI.Input; @@ -29,6 +30,8 @@ namespace FlaxEditor.Windows.Assets private readonly PrefabTree _tree; private readonly PrefabWindowViewport _viewport; private readonly CustomEditorPresenter _propertiesEditor; + private SearchBox _propertiesSearchBox; + private Panel _propertiesScrollingPanel; private readonly ToolStripButton _saveButton; private readonly ToolStripButton _toolStripUndo; @@ -142,7 +145,7 @@ namespace FlaxEditor.Windows.Assets }; // Split Panel 2 - _split2 = new SplitPanel(Orientation.Horizontal, ScrollBars.None, ScrollBars.Vertical) + _split2 = new SplitPanel(Orientation.Horizontal, ScrollBars.None, ScrollBars.None) { AnchorPreset = AnchorPresets.StretchAll, Offsets = Margin.Zero, @@ -197,9 +200,33 @@ namespace FlaxEditor.Windows.Assets _viewport.TransformGizmo.ModeChanged += UpdateToolstrip; // Prefab properties editor + var propHeaderPanel = new ContainerControl + { + AnchorPreset = AnchorPresets.HorizontalStretchTop, + BackgroundColor = Style.Current.Background, + IsScrollable = false, + Offsets = new Margin(0, 0, 0, 18 + 6), + Parent = _split2.Panel2, + }; + _propertiesSearchBox = new SearchBox + { + AnchorPreset = AnchorPresets.HorizontalStretchMiddle, + Parent = propHeaderPanel, + Bounds = new Rectangle(4, 4, propHeaderPanel.Width - 8, 18), + }; + _propertiesSearchBox.TextChanged += ApplyPropertiesSearchFilter; + + _propertiesScrollingPanel = new Panel(ScrollBars.Vertical) + { + AnchorPreset = AnchorPresets.StretchAll, + Offsets = new Margin(0, 0, propHeaderPanel.Bottom, 0), + Parent = _split2.Panel2, + }; + _propertiesEditor = new CustomEditorPresenter(_undo, null, this); - _propertiesEditor.Panel.Parent = _split2.Panel2; + _propertiesEditor.Panel.Parent = _propertiesScrollingPanel; _propertiesEditor.Modified += MarkAsEdited; + _propertiesEditor.AfterLayout += OnPresenterAfterLayout; // Toolstrip _saveButton = _toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save", ref inputOptions.Save); @@ -580,6 +607,16 @@ namespace FlaxEditor.Windows.Assets /// public EditorViewport PresenterViewport => _viewport; + private void OnPresenterAfterLayout(LayoutElementsContainer layout) + { + ApplyPropertiesSearchFilter(); + } + + private void ApplyPropertiesSearchFilter() + { + _propertiesEditor.ApplySearchFilter(_propertiesSearchBox.Text); + } + /// EditorViewport ISceneEditingContext.Viewport => Viewport; } diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index 35b2d927d..708fd9319 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -1398,6 +1398,9 @@ namespace FlaxEditor.Windows.Assets /// public IEnumerable NewParameterTypes => Editor.CodeEditing.VisualScriptPropertyTypes.Get(); + /// + public int ParamToRename { get; set; } = -1; + /// public event Action SurfaceLoaded; diff --git a/Source/Editor/Windows/EditorOptionsWindow.cs b/Source/Editor/Windows/EditorOptionsWindow.cs index c6bf2fd16..dc12885a5 100644 --- a/Source/Editor/Windows/EditorOptionsWindow.cs +++ b/Source/Editor/Windows/EditorOptionsWindow.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using FlaxEditor.CustomEditors; using FlaxEditor.GUI; +using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Tabs; using FlaxEditor.Options; using FlaxEngine; @@ -25,6 +26,7 @@ namespace FlaxEditor.Windows private ToolStripButton _saveButton; private readonly Undo _undo; private readonly List _customTabs = new List(); + private SearchBox _searchBox; /// /// Initializes a new instance of the class. @@ -48,15 +50,33 @@ namespace FlaxEditor.Windows _saveButton = (ToolStripButton)toolstrip.AddButton(editor.Icons.Save64, SaveData).LinkTooltip("Save."); _saveButton.Enabled = false; + // Header panel for search + var headerPanel = new ContainerControl + { + AnchorPreset = AnchorPresets.HorizontalStretchTop, + BackgroundColor = Style.Current.Background, + IsScrollable = false, + Offsets = new Margin(0, 0, toolstrip.Bottom, 18 + 6), + Parent = this, + }; + _searchBox = new SearchBox + { + AnchorPreset = AnchorPresets.HorizontalStretchMiddle, + Parent = headerPanel, + Bounds = new Rectangle(4, 4, headerPanel.Width - 8, 18), + }; + _searchBox.TextChanged += ApplySearchFilter; + _tabs = new Tabs { Orientation = Orientation.Vertical, AnchorPreset = AnchorPresets.StretchAll, - Offsets = new Margin(0, 0, toolstrip.Bottom, 0), + Offsets = new Margin(0, 0, headerPanel.Bottom, 0), TabsSize = new Float2(120, 32), UseScroll = true, Parent = this }; + _tabs.SelectedTabChanged += OnSelectedTabChanged; CreateTab("General", () => _options.General); CreateTab("Interface", () => _options.Interface); @@ -94,6 +114,13 @@ namespace FlaxEditor.Windows settings.Panel.Parent = panel; settings.Panel.Tag = getValue; settings.Modified += MarkAsEdited; + settings.AfterLayout += layout => + { + if (_tabs.SelectedTab == tab) + { + ApplySearchFilter(); + } + }; return tab; } @@ -280,5 +307,25 @@ namespace FlaxEditor.Windows return base.OnClosing(reason); } + + private void OnSelectedTabChanged(Tabs tabs) + { + ApplySearchFilter(); + } + + private CustomEditorPresenter GetTabPresenter(Tab tab) + { + if (tab == null) + return null; + var panel = tab.GetChild(); + var settingsPanel = panel?.GetChild(); + return settingsPanel?.Presenter; + } + + private void ApplySearchFilter() + { + var presenter = GetTabPresenter(_tabs.SelectedTab); + presenter?.ApplySearchFilter(_searchBox.Text); + } } } diff --git a/Source/Editor/Windows/Profiler/Assets.cs b/Source/Editor/Windows/Profiler/Assets.cs index e4e41c668..8ee6f5f6d 100644 --- a/Source/Editor/Windows/Profiler/Assets.cs +++ b/Source/Editor/Windows/Profiler/Assets.cs @@ -80,6 +80,8 @@ namespace FlaxEditor.Windows.Profiler var textColor = style.Foreground; _table = new Table { + RowColorEven = Color.Transparent, + RowColorOdd = style.Background * 1.4f, Columns = new[] { new ColumnDefinition @@ -242,8 +244,6 @@ namespace FlaxEditor.Windows.Profiler var resourcesOrdered = resources.OrderByDescending(x => x.MemoryUsage); // Add rows - var rowColor2 = Style.Current.Background * 1.4f; - int rowIndex = 0; foreach (var e in resourcesOrdered) { ClickableRow row; @@ -271,9 +271,7 @@ namespace FlaxEditor.Windows.Profiler // Add row to the table row.Width = _table.Width; - row.BackgroundColor = rowIndex % 2 == 1 ? rowColor2 : Color.Transparent; row.Parent = _table; - rowIndex++; } } diff --git a/Source/Editor/Windows/Profiler/CPU.cs b/Source/Editor/Windows/Profiler/CPU.cs index d034a058b..fad7c4de8 100644 --- a/Source/Editor/Windows/Profiler/CPU.cs +++ b/Source/Editor/Windows/Profiler/CPU.cs @@ -106,6 +106,8 @@ namespace FlaxEditor.Windows.Profiler var textColor = style.Foreground; _table = new Table { + RowColorEven = Color.Transparent, + RowColorOdd = style.Background * 1.4f, Columns = new[] { new ColumnDefinition @@ -464,7 +466,6 @@ namespace FlaxEditor.Windows.Profiler float totalTimeMs = _mainChart.SelectedSample; // Add rows - var rowColor2 = Style.Current.Background * 1.4f; for (int j = 0; j < data.Length; j++) { var events = data[j].Events; @@ -540,7 +541,6 @@ namespace FlaxEditor.Windows.Profiler row.Depth = e.Depth; row.Width = _table.Width; row.Visible = e.Depth < 2; - row.BackgroundColor = i % 2 == 1 ? rowColor2 : Color.Transparent; row.Parent = _table; } } diff --git a/Source/Editor/Windows/Profiler/GPU.cs b/Source/Editor/Windows/Profiler/GPU.cs index 5cc9cd681..6abfef5e1 100644 --- a/Source/Editor/Windows/Profiler/GPU.cs +++ b/Source/Editor/Windows/Profiler/GPU.cs @@ -83,6 +83,8 @@ namespace FlaxEditor.Windows.Profiler var textColor = style.Foreground; _table = new Table { + RowColorEven = Color.Transparent, + RowColorOdd = style.Background * 1.4f, Columns = new[] { new ColumnDefinition @@ -339,7 +341,6 @@ namespace FlaxEditor.Windows.Profiler float totalTimeMs = _drawTimeGPU.SelectedSample; // Add rows - var rowColor2 = Style.Current.Background * 1.4f; for (int i = 0; i < data.Length; i++) { var e = data[i]; @@ -386,7 +387,6 @@ namespace FlaxEditor.Windows.Profiler row.Depth = e.Depth; row.Width = _table.Width; row.Visible = e.Depth < 3; - row.BackgroundColor = i % 2 == 1 ? rowColor2 : Color.Transparent; row.Parent = _table; } } diff --git a/Source/Editor/Windows/Profiler/Memory.cs b/Source/Editor/Windows/Profiler/Memory.cs index a74472a8c..56178acf2 100644 --- a/Source/Editor/Windows/Profiler/Memory.cs +++ b/Source/Editor/Windows/Profiler/Memory.cs @@ -84,6 +84,8 @@ namespace FlaxEditor.Windows.Profiler var textColor = style.Foreground; _table = new Table { + RowColorEven = Color.Transparent, + RowColorOdd = style.Background * 1.4f, Columns = new[] { new ColumnDefinition @@ -236,7 +238,6 @@ namespace FlaxEditor.Windows.Profiler }); // Add rows - var rowColor2 = Style.Current.Background * 1.4f; for (int i = 0; i < (int)ProfilerMemory.Groups.MAX; i++) { var group = _groupOrder[i]; @@ -278,7 +279,6 @@ namespace FlaxEditor.Windows.Profiler row.BackgroundColors[3] = Color.Red.AlphaMultiplied(Mathf.Min(1, (float)groupCount / totalCount) * 0.5f); } row.Width = _table.Width; - row.BackgroundColor = i % 2 == 1 ? rowColor2 : Color.Transparent; row.Parent = _table; var useBackground = group != (int)ProfilerMemory.Groups.Total && diff --git a/Source/Editor/Windows/Profiler/MemoryGPU.cs b/Source/Editor/Windows/Profiler/MemoryGPU.cs index 11b8a1980..76c02888b 100644 --- a/Source/Editor/Windows/Profiler/MemoryGPU.cs +++ b/Source/Editor/Windows/Profiler/MemoryGPU.cs @@ -81,6 +81,8 @@ namespace FlaxEditor.Windows.Profiler var textColor = style.Foreground; _table = new Table { + RowColorEven = Color.Transparent, + RowColorOdd = style.Background * 1.4f, Columns = new[] { new ColumnDefinition @@ -299,8 +301,6 @@ namespace FlaxEditor.Windows.Profiler var resourcesOrdered = resources.OrderByDescending(x => x?.MemoryUsage ?? 0); // Add rows - var rowColor2 = Style.Current.Background * 1.4f; - int rowIndex = 0; foreach (var e in resourcesOrdered) { if (e == null) @@ -335,9 +335,7 @@ namespace FlaxEditor.Windows.Profiler // Add row to the table row.Width = _table.Width; - row.BackgroundColor = rowIndex % 2 == 1 ? rowColor2 : Color.Transparent; row.Parent = _table; - rowIndex++; } } diff --git a/Source/Editor/Windows/Profiler/Network.cs b/Source/Editor/Windows/Profiler/Network.cs index 988398b0c..2a2d542f5 100644 --- a/Source/Editor/Windows/Profiler/Network.cs +++ b/Source/Editor/Windows/Profiler/Network.cs @@ -190,7 +190,6 @@ namespace FlaxEditor.Windows.Profiler var rowCount = Int2.Zero; if (events != null && events.Length != 0) { - var rowColor2 = Style.Current.Background * 1.4f; for (int i = 0; i < events.Length; i++) { var e = events[i]; @@ -230,7 +229,6 @@ namespace FlaxEditor.Windows.Profiler var table = isRpc ? _tableRpc : _tableRep; row.Width = table.Width; - row.BackgroundColor = rowCount[isRpc ? 0 : 1] % 2 == 1 ? rowColor2 : Color.Transparent; row.Parent = table; if (isRpc) rowCount.X++; @@ -266,6 +264,8 @@ namespace FlaxEditor.Windows.Profiler var textColor = style.Foreground; var table = new Table { + RowColorEven = Color.Transparent, + RowColorOdd = style.Background * 1.4f, Columns = new[] { new ColumnDefinition diff --git a/Source/Editor/Windows/PropertiesWindow.cs b/Source/Editor/Windows/PropertiesWindow.cs index 03316a221..a62f7508b 100644 --- a/Source/Editor/Windows/PropertiesWindow.cs +++ b/Source/Editor/Windows/PropertiesWindow.cs @@ -5,6 +5,9 @@ using System.Collections.Generic; using System.Linq; using System.Xml; using FlaxEditor.CustomEditors; +using FlaxEditor.CustomEditors.Elements; +using FlaxEditor.CustomEditors.GUI; +using FlaxEditor.GUI.Input; using FlaxEditor.SceneGraph; using FlaxEditor.Viewport; using FlaxEngine; @@ -23,6 +26,8 @@ namespace FlaxEditor.Windows private readonly Dictionary _actorScrollValues = new Dictionary(); private bool _lockObjects = false; + private SearchBox _searchBox; + private Panel _scrollingPanel; /// public override bool UseLayoutData => true; @@ -66,18 +71,42 @@ namespace FlaxEditor.Windows /// /// The editor. public PropertiesWindow(Editor editor) - : base(editor, true, ScrollBars.Vertical) + : base(editor, true, ScrollBars.None) { Title = "Properties"; Icon = editor.Icons.Build64; AutoFocus = true; + var headerPanel = new ContainerControl + { + AnchorPreset = AnchorPresets.HorizontalStretchTop, + BackgroundColor = Style.Current.Background, + IsScrollable = false, + Offsets = new Margin(0, 0, 0, 18 + 6), + Parent = this, + }; + _searchBox = new SearchBox + { + AnchorPreset = AnchorPresets.HorizontalStretchMiddle, + Parent = headerPanel, + Bounds = new Rectangle(4, 4, headerPanel.Width - 8, 18), + }; + _searchBox.TextChanged += ApplySearchFilter; + + _scrollingPanel = new Panel(ScrollBars.Vertical) + { + AnchorPreset = AnchorPresets.StretchAll, + Offsets = new Margin(0, 0, headerPanel.Bottom, 0), + Parent = this, + }; + Presenter = new CustomEditorPresenter(editor.Undo, null, this); - Presenter.Panel.Parent = this; + Presenter.Panel.Parent = _scrollingPanel; Presenter.GetUndoObjects += GetUndoObjects; Presenter.Features |= FeatureFlags.CacheExpandedGroups; + Presenter.AfterLayout += OnPresenterAfterLayout; - VScrollBar.ValueChanged += OnScrollValueChanged; + _scrollingPanel.VScrollBar.ValueChanged += OnScrollValueChanged; Editor.SceneEditing.SelectionChanged += OnSelectionChanged; } @@ -115,7 +144,8 @@ namespace FlaxEditor.Windows } } - _actorScrollValues[Editor.SceneEditing.Selection[0].ID] = VScrollBar.TargetValue; + if (_scrollingPanel.VScrollBar != null) + _actorScrollValues[Editor.SceneEditing.Selection[0].ID] = _scrollingPanel.VScrollBar.TargetValue; } private IEnumerable GetUndoObjects(CustomEditorPresenter customEditorPresenter) @@ -135,8 +165,18 @@ namespace FlaxEditor.Windows Presenter.Select(objects); // Set scroll value of window if it exists - if (Editor.SceneEditing.SelectionCount == 1) - VScrollBar.TargetValue = _actorScrollValues.GetValueOrDefault(Editor.SceneEditing.Selection[0].ID, 0); + if (Editor.SceneEditing.SelectionCount == 1 && _scrollingPanel.VScrollBar != null) + _scrollingPanel.VScrollBar.TargetValue = _actorScrollValues.GetValueOrDefault(Editor.SceneEditing.Selection[0].ID, 0); + } + + private void OnPresenterAfterLayout(LayoutElementsContainer layout) + { + ApplySearchFilter(); + } + + private void ApplySearchFilter() + { + Presenter.ApplySearchFilter(_searchBox.Text); } /// diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index 7428a5a8b..fc6458272 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -909,7 +909,7 @@ void Foliage::RebuildClusters() _box = BoundingBox(_transform.Translation, _transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, ISceneRenderingListener::Bounds); return; } diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 5e11cb2d9..e0fc7ceb9 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -372,7 +372,7 @@ void ParticleEffect::UpdateBounds() _box = bounds; BoundingSphere::FromBox(bounds, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, ISceneRenderingListener::Bounds); } void ParticleEffect::Sync() diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index da9f3909a..12848ed47 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -404,8 +404,17 @@ public: // Dirty static objects to redraw when changed (eg. material modification) if (a->HasStaticFlag(StaticFlags::Shadow)) { - DirtyStaticBounds(prevBounds); - DirtyStaticBounds(a->GetSphere()); + // TODO: skip actors that don't cast shadows (eg. particles) + BoundingSphere bounds = a->GetSphere(); + if (bounds != prevBounds) + { + // Avoid dirtying twice when bounds are close to each other + if (BoundingSphere::NearEqual(bounds, prevBounds, METERS_TO_UNITS_SCALE)) + BoundingSphere::Merge(bounds, prevBounds, bounds); + else + DirtyStaticBounds(prevBounds); + } + DirtyStaticBounds(bounds); } else if (flags & StaticFlags) { diff --git a/Source/Engine/UI/GUI/Panels/DropPanel.cs b/Source/Engine/UI/GUI/Panels/DropPanel.cs index 308272218..65d8a3c74 100644 --- a/Source/Engine/UI/GUI/Panels/DropPanel.cs +++ b/Source/Engine/UI/GUI/Panels/DropPanel.cs @@ -72,6 +72,33 @@ namespace FlaxEngine.GUI [EditorOrder(10), Tooltip("The text to show on a panel header.")] public string HeaderText { get; set; } + /// + /// The active search text query used to highlight matching parts of the header text. + /// + public string SearchText = string.Empty; + + private string _lastSearchText; + private string _lastHeaderText; + private int _highlightIndex = -1; + + private void UpdateHighlights() + { + if (_lastSearchText == SearchText && _lastHeaderText == HeaderText) + return; + + _lastSearchText = SearchText; + _lastHeaderText = HeaderText; + + if (!string.IsNullOrEmpty(SearchText) && !string.IsNullOrEmpty(HeaderText)) + { + _highlightIndex = HeaderText.IndexOf(SearchText, StringComparison.OrdinalIgnoreCase); + } + else + { + _highlightIndex = -1; + } + } + /// /// Gets or sets the height of the header. /// @@ -411,6 +438,21 @@ namespace FlaxEngine.GUI Render2D.PushClip(textRect); Render2D.DrawText(HeaderTextFont.GetFont(), HeaderTextMaterial, HeaderText, textRect, textColor, TextAlignment.Near, TextAlignment.Center); + UpdateHighlights(); + if (_highlightIndex >= 0) + { + var font = HeaderTextFont.GetFont(); + if (font != null) + { + var highlightColor = Style.Current.ProgressNormal * 0.6f; + var textSize = font.MeasureText(HeaderText); + var textY = (HeaderHeight - textSize.Y) * 0.5f; + var start = font.GetCharPosition(HeaderText, _highlightIndex); + var end = font.GetCharPosition(HeaderText, _highlightIndex + SearchText.Length); + var highlightRect = new Rectangle(start.X + textRect.X, textY, end.X - start.X, textSize.Y); + Render2D.FillRectangle(highlightRect, highlightColor); + } + } Render2D.PopClip(); if (!_isClosed && EnableContainmentLines) diff --git a/Source/Engine/Utilities/StringConverter.h b/Source/Engine/Utilities/StringConverter.h index 9e6fa9569..331b5863b 100644 --- a/Source/Engine/Utilities/StringConverter.h +++ b/Source/Engine/Utilities/StringConverter.h @@ -86,7 +86,8 @@ public: StringAsUTF8(const Char* text, int32 length) { int32 lengthUtf8; - if (length + 1 < InlinedSize) + // UTF-8 can use up to 3 bytes per UTF-16 code unit + if (length * 3 + 1 < InlinedSize) { StringUtils::ConvertUTF162UTF8(text, this->_inlined, length, lengthUtf8); this->_inlined[lengthUtf8] = 0;