diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings
index 0808c4488..655924b88 100644
--- a/Flax.sln.DotSettings
+++ b/Flax.sln.DotSettings
@@ -323,6 +323,7 @@
True
True
True
+ True
True
True
True
diff --git a/Source/Editor/Content/Import/ModelImportEntry.cs b/Source/Editor/Content/Import/ModelImportEntry.cs
index c7bbf767b..2174eebdc 100644
--- a/Source/Editor/Content/Import/ModelImportEntry.cs
+++ b/Source/Editor/Content/Import/ModelImportEntry.cs
@@ -262,6 +262,13 @@ namespace FlaxEditor.Content.Import
[EditorOrder(1050), DefaultValue(true)]
public bool OptimizeKeyframes { get; set; } = true;
+ ///
+ /// If checked, the importer will import scale animation tracks (otherwise scale animation will be ignored).
+ ///
+ [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))]
+ [EditorOrder(1055), DefaultValue(false)]
+ public bool ImportScaleTracks { get; set; } = false;
+
///
/// Enables root motion extraction support from this animation.
///
@@ -390,6 +397,7 @@ namespace FlaxEditor.Content.Import
public float SamplingRate;
public byte SkipEmptyCurves;
public byte OptimizeKeyframes;
+ public byte ImportScaleTracks;
public byte EnableRootMotion;
public string RootNodeName;
@@ -599,6 +607,7 @@ namespace FlaxEditor.Content.Import
SamplingRate = SamplingRate,
SkipEmptyCurves = (byte)(SkipEmptyCurves ? 1 : 0),
OptimizeKeyframes = (byte)(OptimizeKeyframes ? 1 : 0),
+ ImportScaleTracks = (byte)(ImportScaleTracks ? 1 : 0),
EnableRootMotion = (byte)(EnableRootMotion ? 1 : 0),
RootNodeName = RootNodeName,
GenerateLODs = (byte)(GenerateLODs ? 1 : 0),
@@ -640,6 +649,7 @@ namespace FlaxEditor.Content.Import
SamplingRate = options.SamplingRate;
SkipEmptyCurves = options.SkipEmptyCurves != 0;
OptimizeKeyframes = options.OptimizeKeyframes != 0;
+ ImportScaleTracks = options.ImportScaleTracks != 0;
EnableRootMotion = options.EnableRootMotion != 0;
RootNodeName = options.RootNodeName;
GenerateLODs = options.GenerateLODs != 0;
diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs
index d93ab544b..684593352 100644
--- a/Source/Editor/CustomEditors/CustomEditor.cs
+++ b/Source/Editor/CustomEditors/CustomEditor.cs
@@ -40,6 +40,11 @@ namespace FlaxEditor.CustomEditors
[HideInEditor]
public abstract class CustomEditor
{
+ ///
+ /// True if Editor is during value setting (eg. by user or from copy/paste).
+ ///
+ public static bool IsSettingValue = false;
+
private LayoutElementsContainer _layout;
private CustomEditorPresenter _presenter;
private CustomEditor _parent;
@@ -266,23 +271,30 @@ namespace FlaxEditor.CustomEditors
// Check if need to update value
if (_hasValueDirty)
{
- // Cleanup (won't retry update in case of exception)
- object val = _valueToSet;
- _hasValueDirty = false;
- _valueToSet = null;
-
- // Assign value
- SynchronizeValue(val);
-
- // 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))
+ IsSettingValue = true;
+ try
{
- obj.Values.Set(obj._parent.Values, obj.Values);
- obj = obj._parent;
- }
+ // Cleanup (won't retry update in case of exception)
+ object val = _valueToSet;
+ _hasValueDirty = false;
+ _valueToSet = null;
- OnUnDirty();
+ // Assign value
+ SynchronizeValue(val);
+
+ // 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))
+ {
+ obj.Values.Set(obj._parent.Values, obj.Values);
+ obj = obj._parent;
+ }
+ }
+ finally
+ {
+ OnUnDirty();
+ IsSettingValue = false;
+ }
}
else
{
diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
index 1977e0e59..93967c444 100644
--- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
@@ -645,7 +645,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
cm.ItemClicked += controlType => SetType((ScriptType)controlType.Tag);
cm.SortItems();
- cm.Show(button.Parent, button.BottomLeft);
+ cm.Show(button.Parent, button.BottomLeft - new Float2((cm.Width - button.Width) / 2, 0));
}
private void SetType(ref ScriptType controlType, UIControl uiControl)
diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
index f65bb4b06..727575a49 100644
--- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEngine;
+using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Editors
{
@@ -77,11 +78,36 @@ namespace FlaxEditor.CustomEditors.Editors
///
public class ScaleEditor : Float3Editor
{
+ private Image _linkImage;
+
///
public override void Initialize(LayoutElementsContainer layout)
{
base.Initialize(layout);
+ LinkValues = Editor.Instance.Windows.PropertiesWin.ScaleLinked;
+
+ _linkImage = new Image
+ {
+ Parent = LinkedLabel,
+ Width = 18,
+ Height = 18,
+ Brush = LinkValues ? new SpriteBrush(Editor.Instance.Icons.Link32) : new SpriteBrush(),
+ AnchorPreset = AnchorPresets.TopLeft,
+ TooltipText = "Scale values are linked together.",
+ };
+ _linkImage.LocalX += 40;
+ _linkImage.LocalY += 1;
+
+ LinkedLabel.SetupContextMenu += (label, menu, editor) =>
+ {
+ menu.AddSeparator();
+ if (LinkValues)
+ menu.AddButton("Unlink", ToggleLink).LinkTooltip("Unlinks scale components from uniform scaling");
+ else
+ menu.AddButton("Link", ToggleLink).LinkTooltip("Links scale components for uniform scaling");
+ };
+
// Override colors
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
var grayOutFactor = 0.6f;
@@ -92,6 +118,16 @@ namespace FlaxEditor.CustomEditors.Editors
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor);
ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
}
+
+ ///
+ /// Toggles the linking functionality.
+ ///
+ public void ToggleLink()
+ {
+ LinkValues = !LinkValues;
+ Editor.Instance.Windows.PropertiesWin.ScaleLinked = LinkValues;
+ _linkImage.Brush = LinkValues ? new SpriteBrush(Editor.Instance.Icons.Link32) : new SpriteBrush();
+ }
}
}
}
diff --git a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
index 434d0c558..ddfe54897 100644
--- a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
@@ -72,7 +72,7 @@ namespace FlaxEditor.CustomEditors.Editors
var customType = TypeUtils.GetType(assetReference.TypeName);
if (customType != ScriptType.Null)
assetType = customType;
- else
+ else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName));
}
}
diff --git a/Source/Editor/CustomEditors/Editors/Vector3Editor.cs b/Source/Editor/CustomEditors/Editors/Vector3Editor.cs
index 8ea21e988..bd154056f 100644
--- a/Source/Editor/CustomEditors/Editors/Vector3Editor.cs
+++ b/Source/Editor/CustomEditors/Editors/Vector3Editor.cs
@@ -44,6 +44,20 @@ namespace FlaxEditor.CustomEditors.Editors
///
public override DisplayStyle Style => DisplayStyle.Inline;
+ ///
+ /// If true, when one value is changed, the other 2 will change as well.
+ ///
+ public bool LinkValues = false;
+
+ private enum ValueChanged
+ {
+ X = 0,
+ Y = 1,
+ Z = 2
+ }
+
+ private ValueChanged _valueChanged;
+
///
public override void Initialize(LayoutElementsContainer layout)
{
@@ -63,28 +77,83 @@ namespace FlaxEditor.CustomEditors.Editors
XElement = grid.FloatValue();
XElement.SetLimits(limit);
- XElement.ValueBox.ValueChanged += OnValueChanged;
+ XElement.ValueBox.ValueChanged += OnXValueChanged;
XElement.ValueBox.SlidingEnd += ClearToken;
YElement = grid.FloatValue();
YElement.SetLimits(limit);
- YElement.ValueBox.ValueChanged += OnValueChanged;
+ YElement.ValueBox.ValueChanged += OnYValueChanged;
YElement.ValueBox.SlidingEnd += ClearToken;
ZElement = grid.FloatValue();
ZElement.SetLimits(limit);
- ZElement.ValueBox.ValueChanged += OnValueChanged;
+ ZElement.ValueBox.ValueChanged += OnZValueChanged;
ZElement.ValueBox.SlidingEnd += ClearToken;
}
+ private void OnXValueChanged()
+ {
+ if (IsSetBlocked)
+ return;
+ if (LinkValues)
+ _valueChanged = ValueChanged.X;
+ OnValueChanged();
+ }
+
+ private void OnYValueChanged()
+ {
+ if (IsSetBlocked)
+ return;
+ if (LinkValues)
+ _valueChanged = ValueChanged.Y;
+ OnValueChanged();
+ }
+
+ private void OnZValueChanged()
+ {
+ if (IsSetBlocked)
+ return;
+ if (LinkValues)
+ _valueChanged = ValueChanged.Z;
+ OnValueChanged();
+ }
+
private void OnValueChanged()
{
if (IsSetBlocked)
return;
+ var xValue = XElement.ValueBox.Value;
+ var yValue = YElement.ValueBox.Value;
+ var zValue = ZElement.ValueBox.Value;
+
+ if (LinkValues)
+ {
+ var valueChange = 0.0f;
+ switch (_valueChanged)
+ {
+ case ValueChanged.X:
+ valueChange = xValue - ((Float3)Values[0]).X;
+ yValue += valueChange;
+ zValue += valueChange;
+ break;
+ case ValueChanged.Y:
+ valueChange = yValue - ((Float3)Values[0]).Y;
+ xValue += valueChange;
+ zValue += valueChange;
+ break;
+ case ValueChanged.Z:
+ valueChange = zValue - ((Float3)Values[0]).Z;
+ xValue += valueChange;
+ yValue += valueChange;
+ break;
+ default: break;
+ }
+ }
+
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding;
var token = isSliding ? this : null;
- var value = new Float3(XElement.ValueBox.Value, YElement.ValueBox.Value, ZElement.ValueBox.Value);
+ var value = new Float3(xValue, yValue, zValue);
object v = Values[0];
if (v is Vector3)
v = (Vector3)value;
diff --git a/Source/Editor/CustomEditors/Values/ValueContainer.cs b/Source/Editor/CustomEditors/Values/ValueContainer.cs
index dfec33f49..61c223835 100644
--- a/Source/Editor/CustomEditors/Values/ValueContainer.cs
+++ b/Source/Editor/CustomEditors/Values/ValueContainer.cs
@@ -198,7 +198,7 @@ namespace FlaxEditor.CustomEditors
{
for (int i = 0; i < Count; i++)
{
- if (!Equals(this[i], _referenceValue))
+ if (!ValueEquals(this[i], _referenceValue))
return true;
}
}
@@ -228,20 +228,23 @@ namespace FlaxEditor.CustomEditors
{
for (int i = 0; i < Count; i++)
{
- if (!Equals(this[i], _defaultValue))
- {
- // Special case for String (null string is kind of equal to empty string from the user perspective)
- if (this[i] == null && _defaultValue is string defaultValueStr && defaultValueStr.Length == 0)
- continue;
-
+ if (!ValueEquals(this[i], _defaultValue))
return true;
- }
}
}
return false;
}
}
+ private static bool ValueEquals(object objA, object objB)
+ {
+ // Special case for String (null string is kind of equal to empty string from the user perspective)
+ if (objA == null && objB is string objBStr && objBStr.Length == 0)
+ return true;
+
+ return Newtonsoft.Json.Utilities.MiscellaneousUtils.DefaultValueEquals(objA, objB);
+ }
+
///
/// Initializes a new instance of the class.
///
diff --git a/Source/Editor/GUI/Input/ValueBox.cs b/Source/Editor/GUI/Input/ValueBox.cs
index d9c1b9325..492887611 100644
--- a/Source/Editor/GUI/Input/ValueBox.cs
+++ b/Source/Editor/GUI/Input/ValueBox.cs
@@ -181,6 +181,7 @@ namespace FlaxEditor.GUI.Input
_cursorChanged = false;
}
SlidingEnd?.Invoke();
+ Defocus();
}
///
diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp
index dc0dc8299..020cb3c61 100644
--- a/Source/Editor/Managed/ManagedEditor.Internal.cpp
+++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp
@@ -183,6 +183,7 @@ struct InternalModelOptions
float SamplingRate;
byte SkipEmptyCurves;
byte OptimizeKeyframes;
+ byte ImportScaleTracks;
byte EnableRootMotion;
MonoString* RootNodeName;
@@ -231,6 +232,7 @@ struct InternalModelOptions
to->SamplingRate = from->SamplingRate;
to->SkipEmptyCurves = from->SkipEmptyCurves;
to->OptimizeKeyframes = from->OptimizeKeyframes;
+ to->ImportScaleTracks = from->ImportScaleTracks;
to->EnableRootMotion = from->EnableRootMotion;
to->RootNodeName = MUtils::ToString(from->RootNodeName);
to->GenerateLODs = from->GenerateLODs;
@@ -272,6 +274,7 @@ struct InternalModelOptions
to->SamplingRate = from->SamplingRate;
to->SkipEmptyCurves = from->SkipEmptyCurves;
to->OptimizeKeyframes = from->OptimizeKeyframes;
+ to->ImportScaleTracks = from->ImportScaleTracks;
to->EnableRootMotion = from->EnableRootMotion;
to->RootNodeName = MUtils::ToString(from->RootNodeName);
to->GenerateLODs = from->GenerateLODs;
diff --git a/Source/Editor/Scripting/TypeUtils.cs b/Source/Editor/Scripting/TypeUtils.cs
index 949d2a0a2..3af3106f0 100644
--- a/Source/Editor/Scripting/TypeUtils.cs
+++ b/Source/Editor/Scripting/TypeUtils.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Reflection;
using System.Text;
using FlaxEngine;
@@ -365,7 +366,8 @@ namespace FlaxEditor.Scripting
}
}
- Editor.LogWarning($"Failed to find type '{typeName}'.");
+ if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(typeName))
+ Editor.LogWarning($"Failed to find type '{typeName}'.");
return ScriptType.Null;
}
diff --git a/Source/Editor/States/PlayingState.cs b/Source/Editor/States/PlayingState.cs
index 18a94348d..7a550948a 100644
--- a/Source/Editor/States/PlayingState.cs
+++ b/Source/Editor/States/PlayingState.cs
@@ -82,6 +82,16 @@ namespace FlaxEditor.States
}
}
+ ///
+ /// True if play mode is starting.
+ ///
+ public bool IsPlayModeStarting;
+
+ ///
+ /// True if play mode is ending.
+ ///
+ public bool IsPlayModeEnding;
+
internal PlayingState(Editor editor)
: base(editor)
{
@@ -127,6 +137,7 @@ namespace FlaxEditor.States
public override void OnEnter()
{
Profiler.BeginEvent("PlayingState.OnEnter");
+ IsPlayModeStarting = true;
Editor.OnPlayBeginning();
CacheSelection();
@@ -150,6 +161,7 @@ namespace FlaxEditor.States
RestoreSelection();
Editor.OnPlayBegin();
+ IsPlayModeStarting = false;
Profiler.EndEvent();
}
@@ -171,6 +183,7 @@ namespace FlaxEditor.States
public override void OnExit(State nextState)
{
Profiler.BeginEvent("PlayingState.OnExit");
+ IsPlayModeEnding = true;
Editor.OnPlayEnding();
IsPaused = true;
@@ -194,6 +207,7 @@ namespace FlaxEditor.States
RestoreSelection();
Editor.OnPlayEnd();
+ IsPlayModeEnding = false;
Profiler.EndEvent();
}
}
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
index 313a38c47..625fd92b4 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
@@ -114,10 +114,8 @@ namespace FlaxEditor.Windows.Assets
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
var result = base.OnDragMove(ref location, data);
- if (result == DragDropEffect.None)
- {
+ if (result == DragDropEffect.None && _dragHandlers != null)
result = _dragHandlers.Effect;
- }
return result;
}
diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs
index aaed484a1..7b815bc98 100644
--- a/Source/Editor/Windows/GameWindow.cs
+++ b/Source/Editor/Windows/GameWindow.cs
@@ -817,6 +817,7 @@ namespace FlaxEditor.Windows
// Selected UI controls outline
bool drawAnySelectedControl = false;
+ // TODO: optimize this (eg. cache list of selected UIControl's when selection gets changed)
for (var i = 0; i < Editor.Instance.SceneEditing.Selection.Count; i++)
{
if (Editor.Instance.SceneEditing.Selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null)
@@ -827,7 +828,8 @@ namespace FlaxEditor.Windows
Render2D.PushTransform(ref _viewport._cachedTransform);
}
var control = controlActor.Control;
- var bounds = Rectangle.FromPoints(control.PointToParent(_viewport, Float2.Zero), control.PointToParent(_viewport, control.Size));
+ var bounds = control.EditorBounds;
+ bounds = Rectangle.FromPoints(control.PointToParent(_viewport, bounds.Location), control.PointToParent(_viewport, bounds.Size));
Render2D.DrawRectangle(bounds, Editor.Instance.Options.Options.Visual.SelectionOutlineColor0, Editor.Instance.Options.Options.Visual.UISelectionOutlineSize);
}
}
diff --git a/Source/Editor/Windows/PropertiesWindow.cs b/Source/Editor/Windows/PropertiesWindow.cs
index 3c2d06852..601717f38 100644
--- a/Source/Editor/Windows/PropertiesWindow.cs
+++ b/Source/Editor/Windows/PropertiesWindow.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
+using System.Xml;
using FlaxEditor.CustomEditors;
using FlaxEngine.GUI;
@@ -16,11 +17,19 @@ namespace FlaxEditor.Windows
{
private IEnumerable