diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index 25b3dea0c..0d2be75cc 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -296,6 +296,16 @@ namespace FlaxEditor.CustomEditors _values.Set(_parent.Values, value); } + private bool SyncParent() + { + // TODO: add attribute for types that want to sync their contents with a parent + var type = Values.Type.Type; + if (type == typeof(LocalizedString) || + type == typeof(FontReference)) + return true; + return _parent != null && !(_parent is SyncPointEditor); + } + internal virtual void RefreshInternal() { if (_values == null) @@ -317,7 +327,7 @@ namespace FlaxEditor.CustomEditors // Propagate values up (eg. when member of structure gets modified, also structure should be updated as a part of the other object) var obj = _parent; - while (obj._parent != null && !(obj._parent is SyncPointEditor)) + while (obj.SyncParent()) { obj.Values.Set(obj._parent.Values, obj.Values); obj = obj._parent; diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cs b/Source/Editor/CustomEditors/CustomEditorsUtil.cs index d6d2455d0..27dbdbb7b 100644 --- a/Source/Editor/CustomEditors/CustomEditorsUtil.cs +++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cs @@ -72,6 +72,8 @@ namespace FlaxEditor.CustomEditors return new GenericEditor(); if (targetType.IsArray) { + if (targetType.Type == null) + return new ArrayEditor(); if (targetType.Type.GetArrayRank() != 1) return new GenericEditor(); // Not-supported multidimensional array diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index eaaa5685b..eb20560ba 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -7,6 +7,7 @@ using FlaxEditor.CustomEditors.Editors; using FlaxEditor.CustomEditors.Elements; using FlaxEditor.GUI; using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.GUI.Input; using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; @@ -634,26 +635,29 @@ namespace FlaxEditor.CustomEditors.Dedicated LayoutElementsContainer vEl; Color axisColorX = ActorTransformEditor.AxisColorX; Color axisColorY = ActorTransformEditor.AxisColorY; + FloatValueBox xV, yV, wV, hV; if (xEq) { - xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values), axisColorX); - vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values), axisColorX); + xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values), axisColorX, out xV); + vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values), axisColorX, out wV); } else { - xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values), axisColorX); - vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values), axisColorX); + xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values), axisColorX, out xV); + vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values), axisColorX, out wV); } if (yEq) { - yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values), axisColorY); - hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values), axisColorY); + yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values), axisColorY, out yV); + hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values), axisColorY, out hV); } else { - yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values), axisColorY); - hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values), axisColorY); + yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values), axisColorY, out yV); + hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values), axisColorY, out hV); } + + // Anchors xEl.Control.AnchorMin = new Float2(0, xEl.Control.AnchorMin.Y); xEl.Control.AnchorMax = new Float2(0.5f, xEl.Control.AnchorMax.Y); @@ -665,6 +669,15 @@ namespace FlaxEditor.CustomEditors.Dedicated hEl.Control.AnchorMin = new Float2(0.5f, xEl.Control.AnchorMin.Y); hEl.Control.AnchorMax = new Float2(1, xEl.Control.AnchorMax.Y); + + // Navigation path + xV.NavTargetRight = yV; + yV.NavTargetRight = wV; + wV.NavTargetRight = hV; + + yV.NavTargetLeft = xV; + wV.NavTargetLeft = yV; + hV.NavTargetLeft = wV; } private VerticalPanelElement VerticalPanelWithoutMargin(LayoutElementsContainer cont) @@ -684,17 +697,19 @@ namespace FlaxEditor.CustomEditors.Dedicated return grid; } - private CustomElementsContainer UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values, Color borderColor) + private CustomElementsContainer UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values, Color borderColor, out FloatValueBox valueBox) { + valueBox = null; var grid = UniformGridTwoByOne(el); grid.CustomControl.SlotPadding = new Margin(5, 5, 1, 1); var label = grid.Label(text, TextAlignment.Far); var editor = grid.Object(values); if (editor is FloatEditor floatEditor && floatEditor.Element is FloatValueElement floatEditorElement) { + valueBox = floatEditorElement.ValueBox; var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; - floatEditorElement.ValueBox.BorderColor = Color.Lerp(borderColor, back, ActorTransformEditor.AxisGreyOutFactor); - floatEditorElement.ValueBox.BorderSelectedColor = borderColor; + valueBox.BorderColor = Color.Lerp(borderColor, back, ActorTransformEditor.AxisGreyOutFactor); + valueBox.BorderSelectedColor = borderColor; } return grid; } diff --git a/Source/Editor/CustomEditors/Editors/Vector2Editor.cs b/Source/Editor/CustomEditors/Editors/Vector2Editor.cs index f7efea223..032a237a8 100644 --- a/Source/Editor/CustomEditors/Editors/Vector2Editor.cs +++ b/Source/Editor/CustomEditors/Editors/Vector2Editor.cs @@ -69,7 +69,7 @@ namespace FlaxEditor.CustomEditors.Editors private void OnValueChanged() { - if (IsSetBlocked) + if (IsSetBlocked || Values == null) return; var isSliding = XElement.IsSliding || YElement.IsSliding; @@ -158,7 +158,7 @@ namespace FlaxEditor.CustomEditors.Editors private void OnValueChanged() { - if (IsSetBlocked) + if (IsSetBlocked || Values == null) return; var isSliding = XElement.IsSliding || YElement.IsSliding; @@ -247,7 +247,7 @@ namespace FlaxEditor.CustomEditors.Editors private void OnValueChanged() { - if (IsSetBlocked) + if (IsSetBlocked || Values == null) return; var isSliding = XElement.IsSliding || YElement.IsSliding; diff --git a/Source/Editor/CustomEditors/Editors/Vector3Editor.cs b/Source/Editor/CustomEditors/Editors/Vector3Editor.cs index 719fbf05d..80fe7e205 100644 --- a/Source/Editor/CustomEditors/Editors/Vector3Editor.cs +++ b/Source/Editor/CustomEditors/Editors/Vector3Editor.cs @@ -143,7 +143,7 @@ namespace FlaxEditor.CustomEditors.Editors private void OnValueChanged() { - if (IsSetBlocked) + if (IsSetBlocked || Values == null) return; var xValue = XElement.ValueBox.Value; @@ -318,7 +318,7 @@ namespace FlaxEditor.CustomEditors.Editors private void OnValueChanged() { - if (IsSetBlocked) + if (IsSetBlocked || Values == null) return; var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding; @@ -418,7 +418,7 @@ namespace FlaxEditor.CustomEditors.Editors private void OnValueChanged() { - if (IsSetBlocked) + if (IsSetBlocked || Values == null) return; var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding; diff --git a/Source/Editor/CustomEditors/Editors/Vector4Editor.cs b/Source/Editor/CustomEditors/Editors/Vector4Editor.cs index 9f8514bc4..6b953ddd8 100644 --- a/Source/Editor/CustomEditors/Editors/Vector4Editor.cs +++ b/Source/Editor/CustomEditors/Editors/Vector4Editor.cs @@ -89,7 +89,7 @@ namespace FlaxEditor.CustomEditors.Editors private void OnValueChanged() { - if (IsSetBlocked) + if (IsSetBlocked || Values == null) return; var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding || WElement.IsSliding; @@ -200,7 +200,7 @@ namespace FlaxEditor.CustomEditors.Editors private void OnValueChanged() { - if (IsSetBlocked) + if (IsSetBlocked || Values == null) return; var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding || WElement.IsSliding; @@ -311,7 +311,7 @@ namespace FlaxEditor.CustomEditors.Editors private void OnValueChanged() { - if (IsSetBlocked) + if (IsSetBlocked || Values == null) return; var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding || WElement.IsSliding; diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs index 69f644827..c1ef7c362 100644 --- a/Source/Editor/GUI/AssetPicker.cs +++ b/Source/Editor/GUI/AssetPicker.cs @@ -218,6 +218,13 @@ namespace FlaxEditor.GUI Render2D.FillRectangle(bounds, style.Selection); Render2D.DrawRectangle(bounds, style.SelectionBorder); } + + // Navigation focus highlight + if (IsNavFocused) + { + var bounds = new Rectangle(Float2.Zero, Size); + Render2D.DrawRectangle(bounds, style.BackgroundSelected); + } } /// @@ -286,35 +293,7 @@ namespace FlaxEditor.GUI else if (Button1Rect.Contains(location)) { Focus(); - if (Validator.AssetType != ScriptType.Null) - { - // Show asset picker popup - var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item => - { - Validator.SelectedItem = item; - RootWindow.Focus(); - Focus(); - }); - if (Validator.SelectedAsset != null) - { - var selectedAssetName = Path.GetFileNameWithoutExtension(Validator.SelectedAsset.Path); - popup.ScrollToAndHighlightItemByName(selectedAssetName); - } - } - else - { - // Show content item picker popup - var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item => - { - Validator.SelectedItem = item; - RootWindow.Focus(); - Focus(); - }); - if (Validator.SelectedItem != null) - { - popup.ScrollToAndHighlightItemByName(Validator.SelectedItem.ShortName); - } - } + OnSubmit(); } else if (Validator.SelectedAsset != null || Validator.SelectedItem != null) { @@ -412,5 +391,41 @@ namespace FlaxEditor.GUI return DragDropEffect.Move; } + + /// + public override void OnSubmit() + { + base.OnSubmit(); + + if (Validator.AssetType != ScriptType.Null) + { + // Show asset picker popup + var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item => + { + Validator.SelectedItem = item; + RootWindow.Focus(); + Focus(); + }); + if (Validator.SelectedAsset != null) + { + var selectedAssetName = Path.GetFileNameWithoutExtension(Validator.SelectedAsset.Path); + popup.ScrollToAndHighlightItemByName(selectedAssetName); + } + } + else + { + // Show content item picker popup + var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item => + { + Validator.SelectedItem = item; + RootWindow.Focus(); + Focus(); + }); + if (Validator.SelectedItem != null) + { + popup.ScrollToAndHighlightItemByName(Validator.SelectedItem.ShortName); + } + } + } } } diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs index e298288a4..74af68a42 100644 --- a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs +++ b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs @@ -155,6 +155,11 @@ namespace FlaxEditor.Modules.SourceCodeEditing /// public readonly CachedTypesCollection All = new CachedAllTypesCollection(8096, ScriptType.Null, type => true, HasAssemblyValidAnyTypes); + /// + /// The all types collection from all assemblies (including C# system libraries). + /// + public readonly CachedTypesCollection AllWithStd = new CachedTypesCollection(8096, ScriptType.Null, type => true, assembly => true); + /// /// The all valid types collection for the Visual Script property types (includes basic types like int/float, structures, object references). /// @@ -574,21 +579,17 @@ namespace FlaxEditor.Modules.SourceCodeEditing private static bool HasAssemblyValidAnyTypes(Assembly assembly) { var codeBase = Utils.GetAssemblyLocation(assembly); + if (string.IsNullOrEmpty(codeBase)) + return true; #if USE_NETCORE if (assembly.ManifestModule.FullyQualifiedName == "") return false; - if (string.IsNullOrEmpty(codeBase)) - return true; - // Skip runtime related assemblies string repositoryUrl = assembly.GetCustomAttributes().FirstOrDefault(x => x.Key == "RepositoryUrl")?.Value ?? ""; if (repositoryUrl != "https://github.com/dotnet/runtime") return true; #else - if (string.IsNullOrEmpty(codeBase)) - return true; - // Skip assemblies from in-build Mono directory if (!codeBase.Contains("/Mono/lib/mono/")) return true; diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index 3417f72a0..d8493a70a 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -211,10 +211,10 @@ namespace FlaxEditor.Options public bool SeparateValueAndUnit { get; set; } /// - /// Gets or sets the option to put a space between numbers and units for unit formatting. + /// Gets or sets tree line visibility. /// [DefaultValue(true)] - [EditorDisplay("Interface"), EditorOrder(320)] + [EditorDisplay("Interface"), EditorOrder(320), Tooltip("Toggles tree line visibility in places like the Scene or Content Panel.")] public bool ShowTreeLines { get; set; } = true; /// @@ -369,7 +369,7 @@ namespace FlaxEditor.Options public int NumberOfGameClientsToLaunch = 1; /// - /// Gets or sets the visject connection curvature. + /// Gets or sets the curvature of the line connecting to connected visject nodes. /// [DefaultValue(1.0f), Range(0.0f, 2.0f)] [EditorDisplay("Visject"), EditorOrder(550)] diff --git a/Source/Editor/Surface/SurfaceComment.cs b/Source/Editor/Surface/SurfaceComment.cs index 54c438fe3..d072ee28e 100644 --- a/Source/Editor/Surface/SurfaceComment.cs +++ b/Source/Editor/Surface/SurfaceComment.cs @@ -74,6 +74,7 @@ namespace FlaxEditor.Surface Visible = false, Parent = this, EndEditOnClick = false, // We have to handle this ourselves, otherwise the textbox instantly loses focus when double-clicking the header + HorizontalAlignment = TextAlignment.Center, }; } diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs index 123168950..f19adbff5 100644 --- a/Source/Editor/Surface/SurfaceUtils.cs +++ b/Source/Editor/Surface/SurfaceUtils.cs @@ -448,6 +448,8 @@ namespace FlaxEditor.Surface sb.Append("virtual "); sb.Append(valueType.Name); sb.Append(' '); + if (member.IsMethod) + sb.Append(member.DeclaringType.Namespace).Append('.'); sb.Append(declaringType.Name); sb.Append('.'); sb.Append(name); diff --git a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs index d086aa851..18041f314 100644 --- a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs +++ b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs @@ -140,12 +140,12 @@ namespace FlaxEditor.Surface var searchStartTime = DateTime.Now; #endif - foreach (var scriptType in Editor.Instance.CodeEditing.All.Get()) + foreach (var scriptType in Editor.Instance.CodeEditing.AllWithStd.Get()) { - if (!SurfaceUtils.IsValidVisualScriptType(scriptType)) - continue; - - _iterator(scriptType, _cache, _version); + if (SurfaceUtils.IsValidVisualScriptType(scriptType)) + { + _iterator(scriptType, _cache, _version); + } } // Add group to context menu (on a main thread) diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index e17453b0a..ff0153e8a 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -17,7 +17,6 @@ using FlaxEditor.Surface.Elements; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; -using Object = FlaxEngine.Object; namespace FlaxEditor.Surface { @@ -36,6 +35,14 @@ namespace FlaxEditor.Surface Archetypes = new List(), }; + private static readonly string[] _blacklistedTypeNames = + { + "Newtonsoft.Json.", + "System.Array", + "System.Linq.Expressions.", + "System.Reflection.", + }; + private static NodesCache _nodesCache = new NodesCache(IterateNodesCache); private DragActors _dragActors; @@ -269,8 +276,11 @@ namespace FlaxEditor.Surface { // Skip Newtonsoft.Json stuff var scriptTypeTypeName = scriptType.TypeName; - if (scriptTypeTypeName.StartsWith("Newtonsoft.Json.")) - return; + for (var i = 0; i < _blacklistedTypeNames.Length; i++) + { + if (scriptTypeTypeName.StartsWith(_blacklistedTypeNames[i])) + return; + } var scriptTypeName = scriptType.Name; // Enum diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs index 0a0591015..d230f53fb 100644 --- a/Source/Editor/Viewport/EditorGizmoViewport.cs +++ b/Source/Editor/Viewport/EditorGizmoViewport.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using FlaxEditor.Gizmo; using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.GUI.Input; using FlaxEditor.SceneGraph; using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Widgets; @@ -298,9 +299,33 @@ namespace FlaxEditor.Viewport } var buttonBB = translateSnappingCM.AddButton("Bounding Box").LinkTooltip("Snaps the selection based on it's bounding volume"); buttonBB.Tag = -1.0f; - translateSnappingCM.ButtonClicked += button => + var buttonCustom = translateSnappingCM.AddButton("Custom"); + buttonCustom.LinkTooltip("Custom grid size"); + const float defaultCustomTranslateSnappingValue = 250.0f; + float customTranslateSnappingValue = transformGizmo.TranslationSnapValue; + if (customTranslateSnappingValue < 0.0f) + customTranslateSnappingValue = defaultCustomTranslateSnappingValue; + foreach (var v in TranslateSnapValues) { - var v = (float)button.Tag; + if (Mathf.Abs(transformGizmo.TranslationSnapValue - v) < 0.001f) + { + customTranslateSnappingValue = defaultCustomTranslateSnappingValue; + break; + } + } + buttonCustom.Tag = customTranslateSnappingValue; + var customValue = new FloatValueBox(customTranslateSnappingValue, Style.Current.FontMedium.MeasureText(buttonCustom.Text).X + 5, 2, 70.0f, 0.001f, float.MaxValue, 0.1f) + { + Parent = buttonCustom + }; + customValue.ValueChanged += () => + { + buttonCustom.Tag = customValue.Value; + buttonCustom.Click(); + }; + translateSnappingCM.ButtonClicked += b => + { + var v = (float)b.Tag; transformGizmo.TranslationSnapValue = v; if (v < 0.0f) translateSnapping.Text = "Bounding Box"; diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index cf39357e3..cbb35e155 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -221,7 +221,7 @@ namespace FlaxEditor.Viewport editor.SceneEditing.SelectionChanged += OnSelectionChanged; // Gizmo widgets - AddGizmoViewportWidgets(this, TransformGizmo); + AddGizmoViewportWidgets(this, TransformGizmo, true); // Show grid widget _showGridButton = ViewWidgetShowMenu.AddButton("Grid", () => Grid.Enabled = !Grid.Enabled); diff --git a/Source/Editor/Windows/Assets/AnimationGraphFunctionWindow.cs b/Source/Editor/Windows/Assets/AnimationGraphFunctionWindow.cs index 36acb0a57..5693e7b3b 100644 --- a/Source/Editor/Windows/Assets/AnimationGraphFunctionWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationGraphFunctionWindow.cs @@ -32,6 +32,7 @@ namespace FlaxEditor.Windows.Assets _surface.ContextChanged += OnSurfaceContextChanged; // Toolstrip + SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton); _toolstrip.AddSeparator(); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/anim-graph/index.html")).LinkTooltip("See documentation to learn more"); diff --git a/Source/Editor/Windows/Assets/MaterialFunctionWindow.cs b/Source/Editor/Windows/Assets/MaterialFunctionWindow.cs index 076d2a863..fcafeadbc 100644 --- a/Source/Editor/Windows/Assets/MaterialFunctionWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialFunctionWindow.cs @@ -28,6 +28,7 @@ namespace FlaxEditor.Windows.Assets }; // Toolstrip + SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton); _toolstrip.AddSeparator(); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/materials/index.html")).LinkTooltip("See documentation to learn more"); } diff --git a/Source/Editor/Windows/Assets/ParticleEmitterFunctionWindow.cs b/Source/Editor/Windows/Assets/ParticleEmitterFunctionWindow.cs index e3993d445..c7869ff2c 100644 --- a/Source/Editor/Windows/Assets/ParticleEmitterFunctionWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleEmitterFunctionWindow.cs @@ -28,6 +28,7 @@ namespace FlaxEditor.Windows.Assets }; // Toolstrip + SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton); _toolstrip.AddSeparator(); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more"); } diff --git a/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs b/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs index 1b60f4257..9d32e5077 100644 --- a/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs +++ b/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs @@ -153,6 +153,14 @@ namespace FlaxEditor.Windows.Assets { var menu = new ContextMenu(); + var copySprite = menu.AddButton("Copy sprite"); + copySprite.Tag = groupPanel.Tag; + copySprite.ButtonClicked += OnCopySpriteClicked; + + var pasteSprite = menu.AddButton("Paste sprite"); + pasteSprite.Tag = groupPanel.Tag; + pasteSprite.ButtonClicked += OnPasteSpriteClicked; + var deleteSprite = menu.AddButton("Delete sprite"); deleteSprite.Tag = groupPanel.Tag; deleteSprite.ButtonClicked += OnDeleteSpriteClicked; @@ -160,6 +168,24 @@ namespace FlaxEditor.Windows.Assets menu.Show(groupPanel, location); } + private void OnCopySpriteClicked(ContextMenuButton button) + { + var window = ((PropertiesProxy)ParentEditor.Values[0])._window; + var index = (int)button.Tag; + var sprite = window.Asset.GetSprite(index); + Clipboard.Text = FlaxEngine.Json.JsonSerializer.Serialize(sprite, typeof(Sprite)); + } + + private void OnPasteSpriteClicked(ContextMenuButton button) + { + var window = ((PropertiesProxy)ParentEditor.Values[0])._window; + var index = (int)button.Tag; + var sprite = window.Asset.GetSprite(index); + var pasted = FlaxEngine.Json.JsonSerializer.Deserialize(Clipboard.Text); + sprite.Area = pasted.Area; + window.Asset.SetSprite(index, ref sprite); + } + private void OnDeleteSpriteClicked(ContextMenuButton button) { var window = ((PropertiesProxy)ParentEditor.Values[0])._window; diff --git a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs index 24b3e3f9b..edfe16f86 100644 --- a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs +++ b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs @@ -27,9 +27,21 @@ namespace FlaxEditor.Windows.Assets /// protected readonly Panel _panel; - private readonly ToolStripButton _saveButton; - private readonly ToolStripButton _undoButton; - private readonly ToolStripButton _redoButton; + /// + /// Save button. + /// + protected ToolStripButton _saveButton; + + /// + /// Undo button. + /// + protected ToolStripButton _undoButton; + + /// + /// Redo button. + /// + protected ToolStripButton _redoButton; + private bool _showWholeGraphOnLoad = true; /// @@ -61,17 +73,12 @@ namespace FlaxEditor.Windows.Assets protected VisjectFunctionSurfaceWindow(Editor editor, AssetItem item) : base(editor, item) { - var inputOptions = Editor.Options.Options.Input; - // Undo _undo = new Undo(); _undo.UndoDone += OnUndoRedo; _undo.RedoDone += OnUndoRedo; _undo.ActionDone += OnUndoRedo; - // Toolstrip - SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton); - // Panel _panel = new Panel(ScrollBars.None) { diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index dfa1b4bf7..49257d281 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -135,6 +135,7 @@ const Char* SplashScreenQuotes[] = TEXT("Drum roll please"), TEXT("Good Luck Have Fun"), TEXT("GG Well Played"), + TEXT("Now with documentation."), }; SplashScreen::~SplashScreen() diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index e5b9d8d60..2833cf624 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -396,8 +396,8 @@ void BehaviorTreeMoveToNode::GetAgentSize(Actor* agent, float& outRadius, float& // Estimate actor bounds to extract capsule information const BoundingBox box = agent->GetBox(); const BoundingSphere sphere = agent->GetSphere(); - outRadius = sphere.Radius; - outHeight = box.GetSize().Y; + outRadius = (float)sphere.Radius; + outHeight = (float)box.GetSize().Y; } int32 BehaviorTreeMoveToNode::GetStateSize() const @@ -522,7 +522,7 @@ String BehaviorTreeMoveToNode::GetDebugInfo(const BehaviorUpdateContext& context goal = state->GoalLocation.ToString(); const Vector3 agentLocation = state->Agent->GetPosition(); const Vector3 agentLocationOnPath = agentLocation + state->AgentOffset; - float distanceLeft = state->Path.Count() > state->TargetPathIndex ? Vector3::Distance(state->Path[state->TargetPathIndex], agentLocationOnPath) : 0; + Real distanceLeft = state->Path.Count() > state->TargetPathIndex ? Vector3::Distance(state->Path[state->TargetPathIndex], agentLocationOnPath) : 0; for (int32 i = state->TargetPathIndex; i < state->Path.Count(); i++) distanceLeft += Vector3::Distance(state->Path[i - 1], state->Path[i]); return String::Format(TEXT("Agent: '{}'\nGoal: '{}'\nDistance: {}"), agent, goal, (int32)distanceLeft); @@ -559,7 +559,7 @@ void BehaviorTreeMoveToNode::State::OnUpdate() const float acceptableHeightPercentage = 1.05f; const float testHeight = agentHeight * acceptableHeightPercentage; const Vector3 toGoal = agentLocationOnPath - pathSegmentEnd; - const float toGoalHeightDiff = (toGoal * UpVector).SumValues(); + const Real toGoalHeightDiff = (toGoal * UpVector).SumValues(); if (toGoal.Length() <= testRadius && toGoalHeightDiff <= testHeight) { TargetPathIndex++; diff --git a/Source/Engine/Animations/Curve.cs b/Source/Engine/Animations/Curve.cs index 1ff129b57..a0d911141 100644 --- a/Source/Engine/Animations/Curve.cs +++ b/Source/Engine/Animations/Curve.cs @@ -465,7 +465,7 @@ namespace FlaxEngine /// /// A single keyframe that can be injected into linear curve. /// - [StructLayout(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential, Pack = 2)] public struct Keyframe : IComparable, IComparable { /// @@ -720,7 +720,7 @@ namespace FlaxEngine /// /// A single keyframe that can be injected into Bezier curve. /// - [StructLayout(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential, Pack = 2)] public struct Keyframe : IComparable, IComparable { /// diff --git a/Source/Engine/Animations/InverseKinematics.cpp b/Source/Engine/Animations/InverseKinematics.cpp index 2b3940147..42e298ebf 100644 --- a/Source/Engine/Animations/InverseKinematics.cpp +++ b/Source/Engine/Animations/InverseKinematics.cpp @@ -113,7 +113,7 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ newMidJointPos = rootTransform.Translation + projJointDist * toTargetDir + jointLineDist * bendDirection; } // TODO: fix the new IK impl (https://github.com/FlaxEngine/FlaxEngine/pull/2421) to properly work for character from https://github.com/PrecisionRender/CharacterControllerPro -#define OLD 0 +#define OLD 1 // Update root joint orientation { #if OLD diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index b63abcc24..c772cb2d9 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -7,6 +7,7 @@ #include "Loading/ContentLoadingManager.h" #include "Loading/Tasks/LoadAssetTask.h" #include "Engine/Core/Log.h" +#include "Engine/Core/LogContext.h" #include "Engine/Engine/Engine.h" #include "Engine/Threading/Threading.h" #include "Engine/Profiler/ProfilerCPU.h" @@ -596,9 +597,10 @@ bool Asset::IsInternalType() const bool Asset::onLoad(LoadAssetTask* task) { - if (task->Asset.Get() != this || Platform::AtomicRead(&_loadingTask) == 0) // It may fail when task is cancelled and new one was created later (don't crash but just end with an error) + if (task->Asset.Get() != this || Platform::AtomicRead(&_loadingTask) == 0) return true; + LogContextScope logContext(GetID()); Locker.Lock(); diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index fa4517911..92e1baf7b 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -9,6 +9,7 @@ #include "Storage/JsonStorageProxy.h" #include "Factories/IAssetFactory.h" #include "Engine/Core/Log.h" +#include "Engine/Core/LogContext.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/ObjectsRemovalService.h" #include "Engine/Engine/EngineService.h" @@ -970,6 +971,7 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type) if (IsAssetTypeIdInvalid(type, result->GetTypeHandle()) && !result->Is(type)) { LOG(Warning, "Different loaded asset type! Asset: \'{0}\'. Expected type: {1}", result->ToString(), type.ToString()); + LogContext::Print(LogType::Warning); return nullptr; } return result; @@ -1004,6 +1006,7 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type) if (!GetAssetInfo(id, assetInfo)) { LOG(Warning, "Invalid or missing asset ({0}, {1}).", id, type.ToString()); + LogContext::Print(LogType::Warning); LOAD_FAILED(); } #if ASSETS_LOADING_EXTRA_VERIFICATION diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp index cc7805e6b..77cac2f03 100644 --- a/Source/Engine/ContentImporters/ImportModel.cpp +++ b/Source/Engine/ContentImporters/ImportModel.cpp @@ -5,6 +5,7 @@ #if COMPILE_WITH_ASSETS_IMPORTER #include "Engine/Core/Log.h" +#include "Engine/Core/Cache.h" #include "Engine/Core/Collections/Sorting.h" #include "Engine/Core/Collections/ArrayExtensions.h" #include "Engine/Serialization/MemoryWriteStream.h" @@ -766,6 +767,7 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, ModelDa // Link with object from prefab (if reimporting) if (prefab) { + rapidjson_flax::StringBuffer buffer; for (Actor* a : nodeActors) { for (const auto& i : prefab->ObjectsCache) @@ -776,6 +778,32 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, ModelDa if (o->GetName() != a->GetName()) // Name match continue; + // Preserve local changes made in the prefab + { + // Serialize + buffer.Clear(); + CompactJsonWriter writer(buffer); + writer.StartObject(); + const void* defaultInstance = o->GetType().GetDefaultInstance(); + o->Serialize(writer, defaultInstance); + writer.EndObject(); + + // Parse json + rapidjson_flax::Document document; + document.Parse(buffer.GetString(), buffer.GetSize()); + + // Strip unwanted data + document.RemoveMember("ID"); + document.RemoveMember("ParentID"); + document.RemoveMember("PrefabID"); + document.RemoveMember("PrefabObjectID"); + document.RemoveMember("Name"); + + // Deserialize object + auto modifier = Cache::ISerializeModifier.Get(); + a->Deserialize(document, &*modifier); + } + // Mark as this object already exists in prefab so will be preserved when updating it a->LinkPrefab(o->GetPrefabID(), o->GetPrefabObjectID()); break; diff --git a/Source/Engine/Core/LogContext.cpp b/Source/Engine/Core/LogContext.cpp index d4516e2d1..9df4bce99 100644 --- a/Source/Engine/Core/LogContext.cpp +++ b/Source/Engine/Core/LogContext.cpp @@ -1,9 +1,16 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #include "LogContext.h" +#include "Engine/Core/Log.h" #include "Engine/Core/Types/Guid.h" #include "Engine/Core/Types/String.h" +#include "Engine/Core/Types/StringBuilder.h" #include "Engine/Core/Collections/Array.h" +#include "Engine/Scripting/Scripting.h" +#include "Engine/Scripting/Script.h" +#include "Engine/Content/Asset.h" +#include "Engine/Content/Content.h" +#include "Engine/Level/Actor.h" #include "Engine/Threading/ThreadLocal.h" struct LogContextThreadData @@ -30,7 +37,7 @@ struct LogContextThreadData Count--; } - LogContextData Peek() + LogContextData Peek() const { return Count > 0 ? Ptr[Count - 1] : LogContextData(); } @@ -38,12 +45,58 @@ struct LogContextThreadData ThreadLocal GlobalLogContexts; -String LogContext::GetInfo() +void LogContext::Print(LogType verbosity) { - LogContextData context = LogContext::Get(); - if (context.ObjectID != Guid::Empty) - return String::Format(TEXT("(Loading source was {0})"), context.ObjectID); - return String::Empty; + auto& stack = GlobalLogContexts.Get(); + if (stack.Count == 0) + return; + const StringView indentation(TEXT(" ")); + StringBuilder msg; + for (int32 index = (int32)stack.Count - 1; index >= 0; index--) + { + // Build call hierarchy via indentation + msg.Clear(); + for (uint32 i = index; i < stack.Count; i++) + msg.Append(indentation); + + LogContextData& context = stack.Ptr[index]; + if (context.ObjectID != Guid::Empty) + { + // Object reference context + msg.Append(TEXT(" Referenced by ")); + if (ScriptingObject* object = Scripting::TryFindObject(context.ObjectID)) + { + const StringAnsiView typeName(object->GetType().Fullname); + if (Asset* asset = ScriptingObject::Cast(object)) + { + msg.AppendFormat(TEXT("asset '{}' ({}, {})"), asset->GetPath(), asset->GetTypeName(), context.ObjectID); + } + else if (Actor* actor = ScriptingObject::Cast(object)) + { + msg.AppendFormat(TEXT("actor '{}' ({}, {})"), actor->GetNamePath(), String(typeName), context.ObjectID); + } + else if (Script* script = ScriptingObject::Cast