Merge remote-tracking branch 'origin/master' into 1.10

This commit is contained in:
Wojtek Figat
2024-12-10 11:07:31 +01:00
105 changed files with 2570 additions and 653 deletions

View File

@@ -112,6 +112,12 @@ namespace FlaxEditor.Content
throw new TargetException("Missing Visual Script asset.");
_type.Asset.SetScriptInstanceParameterValue(_parameter.Name, (Object)obj, value);
}
/// <inheritdoc />
public object Invoke(object obj, object[] parameters)
{
throw new NotSupportedException();
}
}
sealed class VisualScriptMethodInfo : IScriptMemberInfo
@@ -240,6 +246,14 @@ namespace FlaxEditor.Content
{
throw new NotSupportedException();
}
/// <inheritdoc />
public object Invoke(object obj, object[] parameters)
{
if (!_type.Asset)
throw new TargetException("Missing Visual Script asset.");
return _type.Asset.InvokeMethod(_index, obj, parameters);
}
}
/// <summary>

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using FlaxEditor.CustomEditors.GUI;
@@ -127,12 +128,41 @@ namespace FlaxEditor.CustomEditors
_isSetBlocked = true;
Initialize(layout);
ShowButtons();
Refresh();
_isSetBlocked = false;
CurrentCustomEditor = prev;
}
private void ShowButtons()
{
var values = Values;
if (values == null || values.HasDifferentTypes)
return;
var type = TypeUtils.GetObjectType(values[0]);
var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var method in methods)
{
if (!method.HasAttribute(typeof(ButtonAttribute)) ||
method.ParametersCount != 0)
continue;
var attribute = method.GetAttribute<ButtonAttribute>();
var text = string.IsNullOrEmpty(attribute.Text) ? Utilities.Utils.GetPropertyNameUI(method.Name) : attribute.Text;
var tooltip = string.IsNullOrEmpty(attribute.Tooltip) ? Editor.Instance.CodeDocs.GetTooltip(method) : attribute.Tooltip;
var button = _layout.Button(text, tooltip);
button.Button.Tag = method;
button.Button.ButtonClicked += OnButtonClicked;
}
}
private void OnButtonClicked(Button button)
{
var method = (ScriptMemberInfo)button.Tag;
var obj = method.IsStatic ? null : Values[0];
method.Invoke(obj);
}
internal static CustomEditor CurrentCustomEditor;
internal void OnChildCreated(CustomEditor child)
@@ -271,8 +301,16 @@ namespace FlaxEditor.CustomEditors
_valueToSet = null;
// Assign value
for (int i = 0; i < _values.Count; i++)
_values[i] = val;
if (val is IList l && l.Count == _values.Count)
{
for (int i = 0; i < _values.Count; i++)
_values[i] = l[i];
}
else
{
for (int i = 0; i < _values.Count; i++)
_values[i] = val;
}
}
finally
{

View File

@@ -2,6 +2,7 @@
using FlaxEditor.GUI;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Dedicated
{
@@ -11,7 +12,9 @@ namespace FlaxEditor.CustomEditors.Dedicated
class BezierCurveObjectEditor<T> : CustomEditor where T : struct
{
private bool _isSetting;
private int _firstTimeShow;
private BezierCurveEditor<T> _curve;
private Splitter _splitter;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
@@ -20,6 +23,14 @@ namespace FlaxEditor.CustomEditors.Dedicated
_curve = item.CustomControl;
_curve.Height = 120.0f;
_curve.Edited += OnCurveEdited;
_firstTimeShow = 4; // For some weird reason it needs several frames of warmup (probably due to sliders smoothing)
_splitter = new Splitter
{
Moved = OnSplitterMoved,
Parent = _curve,
AnchorPreset = AnchorPresets.HorizontalStretchBottom,
Bounds = new Rectangle(0, _curve.Height - Splitter.DefaultHeight, _curve.Width, Splitter.DefaultHeight),
};
}
private void OnCurveEdited()
@@ -32,6 +43,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
_isSetting = false;
}
private void OnSplitterMoved(Float2 location)
{
_curve.Height = Mathf.Clamp(_splitter.PointToParent(location).Y, 50.0f, 1000.0f);
}
/// <inheritdoc />
public override void Refresh()
{
@@ -44,12 +60,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
_curve.SetKeyframes(value.Keyframes);
_isSetting = false;
}
if (_firstTimeShow-- > 0)
_curve.ShowWholeCurve();
}
/// <inheritdoc />
protected override void Deinitialize()
{
_curve = null;
_splitter = null;
base.Deinitialize();
}
@@ -111,7 +130,9 @@ namespace FlaxEditor.CustomEditors.Dedicated
class LinearCurveObjectEditor<T> : CustomEditor where T : struct
{
private bool _isSetting;
private int _firstTimeShow;
private LinearCurveEditor<T> _curve;
private Splitter _splitter;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
@@ -120,6 +141,14 @@ namespace FlaxEditor.CustomEditors.Dedicated
_curve = item.CustomControl;
_curve.Height = 120.0f;
_curve.Edited += OnCurveEdited;
_firstTimeShow = 4; // For some weird reason it needs several frames of warmup (probably due to sliders smoothing)
_splitter = new Splitter
{
Moved = OnSplitterMoved,
Parent = _curve,
AnchorPreset = AnchorPresets.HorizontalStretchBottom,
Bounds = new Rectangle(0, _curve.Height - Splitter.DefaultHeight, _curve.Width, Splitter.DefaultHeight),
};
}
private void OnCurveEdited()
@@ -132,6 +161,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
_isSetting = false;
}
private void OnSplitterMoved(Float2 location)
{
_curve.Height = Mathf.Clamp(_splitter.PointToParent(location).Y, 50.0f, 1000.0f);
}
/// <inheritdoc />
public override void Refresh()
{
@@ -144,12 +178,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
_curve.SetKeyframes(value.Keyframes);
_isSetting = false;
}
if (_firstTimeShow-- > 0)
_curve.ShowWholeCurve();
}
/// <inheritdoc />
protected override void Deinitialize()
{
_curve = null;
_splitter = null;
base.Deinitialize();
}

View File

@@ -407,20 +407,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
/// <seealso cref="FlaxEditor.CustomEditors.Editors.GenericEditor" />
public class UIControlControlEditor : GenericEditor
{
private Type _cachedType;
private ScriptType[] _valueTypes;
private bool _anchorDropDownClosed = true;
private Button _pivotRelativeButton;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
_cachedType = null;
if (HasDifferentTypes)
{
// TODO: support stable editing multiple different control types (via generic way or for transform-only)
return;
}
// Set control type button
var space = layout.Space(20);
var buttonText = "Set Type";
@@ -445,12 +438,12 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
// Add control type helper label
if (!Values.HasDifferentTypes)
{
var type = Values[0].GetType();
_cachedType = type;
var label = layout.AddPropertyItem("Type", "The type of the created control.");
label.Label(type.FullName);
label.Label(Values[0].GetType().FullName);
}
_valueTypes = Values.ValuesTypes;
// Show control properties
base.Initialize(layout);
@@ -720,22 +713,20 @@ namespace FlaxEditor.CustomEditors.Dedicated
/// <inheritdoc />
public override void Refresh()
{
if (_cachedType != null)
// Automatic layout rebuild if control type gets changed
if (_valueTypes != null &&
!Values.HasNull &&
!Utils.ArraysEqual(_valueTypes, Values.ValuesTypes))
{
// Automatic layout rebuild if control type gets changed
var type = Values.HasNull ? null : Values[0].GetType();
if (type != _cachedType)
{
RebuildLayout();
return;
}
RebuildLayout();
return;
}
// Refresh anchors
GetAnchorEquality(out bool xEq, out bool yEq, ValuesTypes);
if (xEq != _cachedXEq || yEq != _cachedYEq)
{
RebuildLayout();
}
// Refresh anchors
GetAnchorEquality(out bool xEq, out bool yEq, ValuesTypes);
if (xEq != _cachedXEq || yEq != _cachedYEq)
{
RebuildLayout();
}
base.Refresh();

View File

@@ -33,6 +33,15 @@ namespace FlaxEditor.CustomEditors.Editors
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline;
/// <summary>
/// Whether to use the average for sliding different values.
/// </summary>
public virtual bool AllowSlidingForDifferentValues => true;
private ValueChanged _valueChanged;
private float _defaultSlidingSpeed;
private bool _slidingEnded = false;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
@@ -46,18 +55,31 @@ namespace FlaxEditor.CustomEditors.Editors
XElement = grid.FloatValue();
XElement.ValueBox.Category = Utils.ValueCategory.Angle;
XElement.ValueBox.ValueChanged += OnValueChanged;
XElement.ValueBox.SlidingEnd += ClearToken;
XElement.ValueBox.ValueChanged += OnXValueChanged;
XElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
_defaultSlidingSpeed = XElement.ValueBox.SlideSpeed;
YElement = grid.FloatValue();
YElement.ValueBox.Category = Utils.ValueCategory.Angle;
YElement.ValueBox.ValueChanged += OnValueChanged;
YElement.ValueBox.SlidingEnd += ClearToken;
YElement.ValueBox.ValueChanged += OnYValueChanged;
YElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
ZElement = grid.FloatValue();
ZElement.ValueBox.Category = Utils.ValueCategory.Angle;
ZElement.ValueBox.ValueChanged += OnValueChanged;
ZElement.ValueBox.SlidingEnd += ClearToken;
ZElement.ValueBox.ValueChanged += OnZValueChanged;
ZElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
if (LinkedLabel != null)
{
@@ -69,33 +91,118 @@ namespace FlaxEditor.CustomEditors.Editors
};
}
}
private void OnValueChanged()
private void OnXValueChanged()
{
if (IsSetBlocked)
return;
_valueChanged = ValueChanged.X;
OnValueChanged();
}
private void OnYValueChanged()
{
if (IsSetBlocked)
return;
_valueChanged = ValueChanged.Y;
OnValueChanged();
}
private void OnZValueChanged()
{
if (IsSetBlocked)
return;
_valueChanged = ValueChanged.Z;
OnValueChanged();
}
private void OnValueChanged()
{
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding;
var token = isSliding ? this : null;
var useCachedAngles = isSliding && token == _cachedToken;
float x = (useCachedAngles && !XElement.IsSliding) ? _cachedAngles.X : XElement.ValueBox.Value;
float y = (useCachedAngles && !YElement.IsSliding) ? _cachedAngles.Y : YElement.ValueBox.Value;
float z = (useCachedAngles && !ZElement.IsSliding) ? _cachedAngles.Z : ZElement.ValueBox.Value;
x = Mathf.UnwindDegrees(x);
y = Mathf.UnwindDegrees(y);
z = Mathf.UnwindDegrees(z);
if (!useCachedAngles)
if (HasDifferentValues && Values.Count > 1)
{
_cachedAngles = new Float3(x, y, z);
var xValue = XElement.ValueBox.Value;
var yValue = YElement.ValueBox.Value;
var zValue = ZElement.ValueBox.Value;
xValue = Mathf.UnwindDegrees(xValue);
yValue = Mathf.UnwindDegrees(yValue);
zValue = Mathf.UnwindDegrees(zValue);
var value = new Float3(xValue, yValue, zValue);
_cachedToken = token;
var newObjects = new object[Values.Count];
// Handle Sliding
if (AllowSlidingForDifferentValues && (isSliding || _slidingEnded))
{
Float3 average = Float3.Zero;
for (int i = 0; i < Values.Count; i++)
{
var v = (Quaternion)Values[0];
var euler = v.EulerAngles;
average += euler;
}
average /= Values.Count;
var newValue = value - average;
for (int i = 0; i < Values.Count; i++)
{
var v = Values[i];
var val = (Quaternion)v;
Quaternion.Euler(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0, out Quaternion qVal);
v = val * qVal;
newObjects[i] = v;
}
// Capture last sliding value
if (_slidingEnded)
_slidingEnded = false;
}
else
{
for (int i = 0; i < Values.Count; i++)
{
object v = Values[i];
var val = (Quaternion)v;
var euler = val.EulerAngles;
Quaternion.Euler(_valueChanged == ValueChanged.X ? xValue : euler.X, _valueChanged == ValueChanged.Y ? yValue : euler.Y, _valueChanged == ValueChanged.Z ? zValue : euler.Z, out Quaternion qVal);
v = val * qVal;
newObjects[i] = v;
}
}
SetValue(newObjects, token);
}
else
{
float x = (useCachedAngles && !XElement.IsSliding) ? _cachedAngles.X : XElement.ValueBox.Value;
float y = (useCachedAngles && !YElement.IsSliding) ? _cachedAngles.Y : YElement.ValueBox.Value;
float z = (useCachedAngles && !ZElement.IsSliding) ? _cachedAngles.Z : ZElement.ValueBox.Value;
_cachedToken = token;
x = Mathf.UnwindDegrees(x);
y = Mathf.UnwindDegrees(y);
z = Mathf.UnwindDegrees(z);
Quaternion.Euler(x, y, z, out Quaternion value);
SetValue(value, token);
if (!useCachedAngles)
{
_cachedAngles = new Float3(x, y, z);
}
_cachedToken = token;
Quaternion.Euler(x, y, z, out Quaternion value);
SetValue(value, token);
}
}
/// <inheritdoc />
@@ -112,7 +219,73 @@ namespace FlaxEditor.CustomEditors.Editors
if (HasDifferentValues)
{
// TODO: support different values for ValueBox<T>
// Get which values are different
bool xDifferent = false;
bool yDifferent = false;
bool zDifferent = false;
Float3 cachedFirstValue = Float3.Zero;
Float3 average = Float3.Zero;
for (int i = 0; i < Values.Count; i++)
{
var value = (Quaternion)Values[i];
var euler = value.EulerAngles;
average += euler;
if (i == 0)
{
cachedFirstValue = euler;
continue;
}
if (!Mathf.NearEqual(cachedFirstValue.X, value.X))
xDifferent = true;
if (!Mathf.NearEqual(cachedFirstValue.Y, value.Y))
yDifferent = true;
if (!Mathf.NearEqual(cachedFirstValue.Z, value.Z))
zDifferent = true;
}
average /= Values.Count;
if (!xDifferent)
{
XElement.ValueBox.Value = cachedFirstValue.X;
}
else
{
if (AllowSlidingForDifferentValues)
XElement.ValueBox.Value = average.X;
else
XElement.ValueBox.SlideSpeed = 0;
XElement.ValueBox.Text = "---";
}
if (!yDifferent)
{
YElement.ValueBox.Value = cachedFirstValue.Y;
}
else
{
if (AllowSlidingForDifferentValues)
YElement.ValueBox.Value = average.Y;
else
YElement.ValueBox.SlideSpeed = 0;
YElement.ValueBox.Text = "---";
}
if (!zDifferent)
{
ZElement.ValueBox.Value = cachedFirstValue.Z;
}
else
{
if (AllowSlidingForDifferentValues)
ZElement.ValueBox.Value = average.Z;
else
ZElement.ValueBox.SlideSpeed = 0;
ZElement.ValueBox.Text = "---";
}
}
else
{
@@ -121,6 +294,13 @@ namespace FlaxEditor.CustomEditors.Editors
XElement.ValueBox.Value = euler.X;
YElement.ValueBox.Value = euler.Y;
ZElement.ValueBox.Value = euler.Z;
if (!Mathf.NearEqual(XElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
XElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
if (!Mathf.NearEqual(YElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
YElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
if (!Mathf.NearEqual(ZElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
ZElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
}
}
}

View File

@@ -0,0 +1,45 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using FlaxEditor.Content.Settings;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.GUI;
namespace FlaxEditor.CustomEditors.Editors
{
/// <summary>
/// Custom editor for picking terrain layers. Instead of choosing bit mask or layer index it shows a combo box with simple layer picking by name.
/// </summary>
public sealed class TerrainLayerEditor : CustomEditor
{
private ComboBoxElement element;
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
element = layout.ComboBox();
element.ComboBox.SetItems(LayersAndTagsSettings.GetCurrentTerrainLayers());
element.ComboBox.SelectedIndex = (int)Values[0];
element.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged;
}
private void OnSelectedIndexChanged(ComboBox comboBox)
{
int value = comboBox.SelectedIndex;
if (value == -1)
value = 0;
SetValue(value);
}
/// <inheritdoc />
public override void Refresh()
{
base.Refresh();
element.ComboBox.SelectedIndex = (int)Values[0];
}
}
}

View File

@@ -7,6 +7,27 @@ using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Editors
{
/// <summary>
/// The value changed by custom Vector3 editors.
/// </summary>
public enum ValueChanged
{
/// <summary>
/// X value changed.
/// </summary>
X = 0,
/// <summary>
/// Y value changed.
/// </summary>
Y = 1,
/// <summary>
/// Z value changed.
/// </summary>
Z = 2
}
/// <summary>
/// Default implementation of the inspector used to edit Vector3 value type properties.
/// </summary>
@@ -49,14 +70,14 @@ namespace FlaxEditor.CustomEditors.Editors
/// </summary>
public bool LinkValues = false;
private enum ValueChanged
{
X = 0,
Y = 1,
Z = 2
}
/// <summary>
/// Whether to use the average for sliding different values.
/// </summary>
public virtual bool AllowSlidingForDifferentValues => true;
private ValueChanged _valueChanged;
private float _defaultSlidingSpeed;
private bool _slidingEnded = false;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
@@ -83,19 +104,32 @@ namespace FlaxEditor.CustomEditors.Editors
XElement.SetLimits(limit);
XElement.SetCategory(category);
XElement.ValueBox.ValueChanged += OnXValueChanged;
XElement.ValueBox.SlidingEnd += ClearToken;
XElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
_defaultSlidingSpeed = XElement.ValueBox.SlideSpeed;
YElement = grid.FloatValue();
YElement.SetLimits(limit);
YElement.SetCategory(category);
YElement.ValueBox.ValueChanged += OnYValueChanged;
YElement.ValueBox.SlidingEnd += ClearToken;
YElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
ZElement = grid.FloatValue();
ZElement.SetLimits(limit);
ZElement.SetCategory(category);
ZElement.ValueBox.ValueChanged += OnZValueChanged;
ZElement.ValueBox.SlidingEnd += ClearToken;
ZElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
if (LinkedLabel != null)
{
@@ -118,8 +152,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
if (IsSetBlocked)
return;
if (LinkValues)
_valueChanged = ValueChanged.X;
_valueChanged = ValueChanged.X;
OnValueChanged();
}
@@ -127,8 +160,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
if (IsSetBlocked)
return;
if (LinkValues)
_valueChanged = ValueChanged.Y;
_valueChanged = ValueChanged.Y;
OnValueChanged();
}
@@ -136,8 +168,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
if (IsSetBlocked)
return;
if (LinkValues)
_valueChanged = ValueChanged.Z;
_valueChanged = ValueChanged.Z;
OnValueChanged();
}
@@ -191,14 +222,79 @@ namespace FlaxEditor.CustomEditors.Editors
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding;
var token = isSliding ? this : null;
var value = new Float3(xValue, yValue, zValue);
object v = Values[0];
if (v is Vector3)
v = (Vector3)value;
else if (v is Float3)
v = (Float3)value;
else if (v is Double3)
v = (Double3)value;
SetValue(v, token);
if (HasDifferentValues && Values.Count > 1)
{
var newObjects = new object[Values.Count];
// Handle Sliding
if (AllowSlidingForDifferentValues && (isSliding || _slidingEnded))
{
// TODO: handle linked values
Float3 average = Float3.Zero;
for (int i = 0; i < Values.Count; i++)
{
var v = Values[i];
var castedValue = Float3.Zero;
if (v is Vector3 asVector3)
castedValue = asVector3;
else if (v is Float3 asFloat3)
castedValue = asFloat3;
else if (v is Double3 asDouble3)
castedValue = asDouble3;
average += castedValue;
}
average /= Values.Count;
var newValue = value - average;
for (int i = 0; i < Values.Count; i++)
{
var v = Values[i];
if (v is Vector3 asVector3)
v = asVector3 + new Vector3(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0);
else if (v is Float3 asFloat3)
v = asFloat3 + new Float3(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0);
else if (v is Double3 asDouble3)
v = asDouble3 + new Double3(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0);
newObjects[i] = v;
}
// Capture last sliding value
if (_slidingEnded)
_slidingEnded = false;
}
else
{
// TODO: handle linked values
for (int i = 0; i < Values.Count; i++)
{
object v = Values[i];
if (v is Vector3 asVector3)
v = new Vector3(_valueChanged == ValueChanged.X ? xValue : asVector3.X, _valueChanged == ValueChanged.Y ? yValue : asVector3.Y, _valueChanged == ValueChanged.Z ? zValue : asVector3.Z);
else if (v is Float3 asFloat3)
v = new Float3(_valueChanged == ValueChanged.X ? xValue : asFloat3.X, _valueChanged == ValueChanged.Y ? yValue : asFloat3.Y, _valueChanged == ValueChanged.Z ? zValue : asFloat3.Z);
else if (v is Double3 asDouble3)
v = new Double3(_valueChanged == ValueChanged.X ? xValue : asDouble3.X, _valueChanged == ValueChanged.Y ? yValue : asDouble3.Y, _valueChanged == ValueChanged.Z ? zValue : asDouble3.Z);
newObjects[i] = v;
}
}
SetValue(newObjects, token);
}
else
{
object v = Values[0];
if (v is Vector3)
v = (Vector3)value;
else if (v is Float3)
v = (Float3)value;
else if (v is Double3)
v = (Double3)value;
SetValue(v, token);
}
}
private float GetRatio(float value, float initialValue)
@@ -218,7 +314,79 @@ namespace FlaxEditor.CustomEditors.Editors
if (HasDifferentValues)
{
// TODO: support different values for ValueBox<T>
// Get which values are different
bool xDifferent = false;
bool yDifferent = false;
bool zDifferent = false;
Float3 cachedFirstValue = Float3.Zero;
Float3 average = Float3.Zero;
for (int i = 0; i < Values.Count; i++)
{
var v = Values[i];
var value = Float3.Zero;
if (v is Vector3 asVector3)
value = asVector3;
else if (v is Float3 asFloat3)
value = asFloat3;
else if (v is Double3 asDouble3)
value = asDouble3;
average += value;
if (i == 0)
{
cachedFirstValue = value;
continue;
}
if (!Mathf.NearEqual(cachedFirstValue.X, value.X))
xDifferent = true;
if (!Mathf.NearEqual(cachedFirstValue.Y, value.Y))
yDifferent = true;
if (!Mathf.NearEqual(cachedFirstValue.Z, value.Z))
zDifferent = true;
}
average /= Values.Count;
if (!xDifferent)
{
XElement.ValueBox.Value = cachedFirstValue.X;
}
else
{
if (AllowSlidingForDifferentValues)
XElement.ValueBox.Value = average.X;
else
XElement.ValueBox.SlideSpeed = 0;
XElement.ValueBox.Text = "---";
}
if (!yDifferent)
{
YElement.ValueBox.Value = cachedFirstValue.Y;
}
else
{
if (AllowSlidingForDifferentValues)
YElement.ValueBox.Value = average.Y;
else
YElement.ValueBox.SlideSpeed = 0;
YElement.ValueBox.Text = "---";
}
if (!zDifferent)
{
ZElement.ValueBox.Value = cachedFirstValue.Z;
}
else
{
if (AllowSlidingForDifferentValues)
ZElement.ValueBox.Value = average.Z;
else
ZElement.ValueBox.SlideSpeed = 0;
ZElement.ValueBox.Text = "---";
}
}
else
{
@@ -232,6 +400,13 @@ namespace FlaxEditor.CustomEditors.Editors
XElement.ValueBox.Value = value.X;
YElement.ValueBox.Value = value.Y;
ZElement.ValueBox.Value = value.Z;
if (!Mathf.NearEqual(XElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
XElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
if (!Mathf.NearEqual(YElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
YElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
if (!Mathf.NearEqual(ZElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
ZElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
}
}
}
@@ -259,6 +434,15 @@ namespace FlaxEditor.CustomEditors.Editors
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline;
/// <summary>
/// Whether to use the average for sliding different values.
/// </summary>
public virtual bool AllowSlidingForDifferentValues => true;
private ValueChanged _valueChanged;
private float _defaultSlidingSpeed;
private bool _slidingEnded = false;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
@@ -284,20 +468,33 @@ namespace FlaxEditor.CustomEditors.Editors
XElement = grid.DoubleValue();
XElement.SetLimits(limit);
XElement.SetCategory(category);
XElement.ValueBox.ValueChanged += OnValueChanged;
XElement.ValueBox.SlidingEnd += ClearToken;
XElement.ValueBox.ValueChanged += OnXValueChanged;
XElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
_defaultSlidingSpeed = XElement.ValueBox.SlideSpeed;
YElement = grid.DoubleValue();
YElement.SetLimits(limit);
YElement.SetCategory(category);
YElement.ValueBox.ValueChanged += OnValueChanged;
YElement.ValueBox.SlidingEnd += ClearToken;
YElement.ValueBox.ValueChanged += OnYValueChanged;
YElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
ZElement = grid.DoubleValue();
ZElement.SetLimits(limit);
ZElement.SetCategory(category);
ZElement.ValueBox.ValueChanged += OnValueChanged;
ZElement.ValueBox.SlidingEnd += ClearToken;
ZElement.ValueBox.ValueChanged += OnZValueChanged;
ZElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
if (LinkedLabel != null)
{
@@ -316,22 +513,115 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
private void OnXValueChanged()
{
if (IsSetBlocked)
return;
_valueChanged = ValueChanged.X;
OnValueChanged();
}
private void OnYValueChanged()
{
if (IsSetBlocked)
return;
_valueChanged = ValueChanged.Y;
OnValueChanged();
}
private void OnZValueChanged()
{
if (IsSetBlocked)
return;
_valueChanged = ValueChanged.Z;
OnValueChanged();
}
private void OnValueChanged()
{
if (IsSetBlocked || Values == null)
return;
var xValue = XElement.ValueBox.Value;
var yValue = YElement.ValueBox.Value;
var zValue = ZElement.ValueBox.Value;
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding;
var token = isSliding ? this : null;
var value = new Double3(XElement.ValueBox.Value, YElement.ValueBox.Value, ZElement.ValueBox.Value);
object v = Values[0];
if (v is Vector3)
v = (Vector3)value;
else if (v is Float3)
v = (Float3)value;
else if (v is Double3)
v = (Double3)value;
SetValue(v, token);
var value = new Double3(xValue, yValue, zValue);
if (HasDifferentValues && Values.Count > 1)
{
var newObjects = new object[Values.Count];
// Handle Sliding
if (AllowSlidingForDifferentValues && (isSliding || _slidingEnded))
{
// TODO: handle linked values
Double3 average = Double3.Zero;
for (int i = 0; i < Values.Count; i++)
{
var v = Values[i];
var castedValue = Double3.Zero;
if (v is Vector3 asVector3)
castedValue = asVector3;
else if (v is Float3 asFloat3)
castedValue = asFloat3;
else if (v is Double3 asDouble3)
castedValue = asDouble3;
average += castedValue;
}
average /= Values.Count;
var newValue = value - average;
for (int i = 0; i < Values.Count; i++)
{
var v = Values[i];
if (v is Vector3 asVector3)
v = asVector3 + new Vector3(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0);
else if (v is Float3 asFloat3)
v = asFloat3 + new Float3(_valueChanged == ValueChanged.X ? (float)newValue.X : 0, _valueChanged == ValueChanged.Y ? (float)newValue.Y : 0, _valueChanged == ValueChanged.Z ? (float)newValue.Z : 0);
else if (v is Double3 asDouble3)
v = asDouble3 + new Double3(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0);
newObjects[i] = v;
}
// Capture last sliding value
if (_slidingEnded)
_slidingEnded = false;
}
else
{
// TODO: handle linked values
for (int i = 0; i < Values.Count; i++)
{
object v = Values[i];
if (v is Vector3 asVector3)
v = new Vector3(_valueChanged == ValueChanged.X ? xValue : asVector3.X, _valueChanged == ValueChanged.Y ? yValue : asVector3.Y, _valueChanged == ValueChanged.Z ? zValue : asVector3.Z);
else if (v is Float3 asFloat3)
v = new Float3(_valueChanged == ValueChanged.X ? (float)xValue : asFloat3.X, _valueChanged == ValueChanged.Y ? (float)yValue : asFloat3.Y, _valueChanged == ValueChanged.Z ? (float)zValue : asFloat3.Z);
else if (v is Double3 asDouble3)
v = new Double3(_valueChanged == ValueChanged.X ? xValue : asDouble3.X, _valueChanged == ValueChanged.Y ? yValue : asDouble3.Y, _valueChanged == ValueChanged.Z ? zValue : asDouble3.Z);
newObjects[i] = v;
}
}
SetValue(newObjects, token);
}
else
{
object v = Values[0];
if (v is Vector3)
v = (Vector3)value;
else if (v is Float3)
v = (Float3)value;
else if (v is Double3)
v = (Double3)value;
SetValue(v, token);
}
}
/// <inheritdoc />
@@ -341,7 +631,79 @@ namespace FlaxEditor.CustomEditors.Editors
if (HasDifferentValues)
{
// TODO: support different values for ValueBox<T>
// Get which values are different
bool xDifferent = false;
bool yDifferent = false;
bool zDifferent = false;
Double3 cachedFirstValue = Double3.Zero;
Double3 average = Double3.Zero;
for (int i = 0; i < Values.Count; i++)
{
var v = Values[i];
var value = Double3.Zero;
if (v is Vector3 asVector3)
value = asVector3;
else if (v is Float3 asFloat3)
value = asFloat3;
else if (v is Double3 asDouble3)
value = asDouble3;
average += value;
if (i == 0)
{
cachedFirstValue = value;
continue;
}
if (!Mathd.NearEqual(cachedFirstValue.X, value.X))
xDifferent = true;
if (!Mathd.NearEqual(cachedFirstValue.Y, value.Y))
yDifferent = true;
if (!Mathd.NearEqual(cachedFirstValue.Z, value.Z))
zDifferent = true;
}
average /= Values.Count;
if (!xDifferent)
{
XElement.ValueBox.Value = cachedFirstValue.X;
}
else
{
if (AllowSlidingForDifferentValues)
XElement.ValueBox.Value = average.X;
else
XElement.ValueBox.SlideSpeed = 0;
XElement.ValueBox.Text = "---";
}
if (!yDifferent)
{
YElement.ValueBox.Value = cachedFirstValue.Y;
}
else
{
if (AllowSlidingForDifferentValues)
YElement.ValueBox.Value = average.Y;
else
YElement.ValueBox.SlideSpeed = 0;
YElement.ValueBox.Text = "---";
}
if (!zDifferent)
{
ZElement.ValueBox.Value = cachedFirstValue.Z;
}
else
{
if (AllowSlidingForDifferentValues)
ZElement.ValueBox.Value = average.Z;
else
ZElement.ValueBox.SlideSpeed = 0;
ZElement.ValueBox.Text = "---";
}
}
else
{
@@ -355,6 +717,19 @@ namespace FlaxEditor.CustomEditors.Editors
XElement.ValueBox.Value = value.X;
YElement.ValueBox.Value = value.Y;
ZElement.ValueBox.Value = value.Z;
if (!Mathf.NearEqual(XElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
{
XElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
}
if (!Mathf.NearEqual(YElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
{
YElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
}
if (!Mathf.NearEqual(ZElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
{
ZElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
}
}
}
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
@@ -385,10 +386,21 @@ namespace FlaxEditor.CustomEditors
if (instanceValues.Count != Count)
throw new ArgumentException();
for (int i = 0; i < Count; i++)
if (value is IList l && l.Count == Count)
{
Info.SetValue(instanceValues[i], value);
this[i] = Info.GetValue(instanceValues[i]);
for (int i = 0; i < Count; i++)
{
Info.SetValue(instanceValues[i], l[i]);
this[i] = Info.GetValue(instanceValues[i]);
}
}
else
{
for (int i = 0; i < Count; i++)
{
Info.SetValue(instanceValues[i], value);
this[i] = Info.GetValue(instanceValues[i]);
}
}
}

View File

@@ -673,6 +673,7 @@ bool Editor::Init()
Managed = New<ManagedEditor>();
// Show splash screen
if (!CommandLine::Options.Headless.IsTrue())
{
PROFILE_CPU_NAMED("Splash");
if (EditorImpl::Splash == nullptr)

View File

@@ -49,9 +49,9 @@ namespace FlaxEditor
}
private readonly List<EditorModule> _modules = new List<EditorModule>(16);
private bool _isAfterInit, _areModulesInited, _areModulesAfterInitEnd, _isHeadlessMode;
private bool _isAfterInit, _areModulesInited, _areModulesAfterInitEnd, _isHeadlessMode, _autoExit;
private string _projectToOpen;
private float _lastAutoSaveTimer;
private float _lastAutoSaveTimer, _autoExitTimeout = 0.1f;
private Button _saveNowButton;
private Button _cancelSaveButton;
private bool _autoSaveNow;
@@ -258,10 +258,11 @@ namespace FlaxEditor
Instance = this;
}
internal void Init(bool isHeadless, bool skipCompile, bool newProject, Guid startupScene)
internal void Init(StartupFlags flags, Guid startupScene)
{
Log("Setting up C# Editor...");
_isHeadlessMode = isHeadless;
_isHeadlessMode = flags.HasFlag(StartupFlags.Headless);
_autoExit = flags.HasFlag(StartupFlags.Exit);
_startupSceneCmdLine = startupScene;
Profiler.BeginEvent("Projects");
@@ -297,11 +298,11 @@ namespace FlaxEditor
StateMachine = new EditorStateMachine(this);
Undo = new EditorUndo(this);
if (newProject)
if (flags.HasFlag(StartupFlags.NewProject))
InitProject();
EnsureState<LoadingState>();
Log("Editor init");
if (isHeadless)
if (_isHeadlessMode)
Log("Running in headless mode");
// Note: we don't sort modules before Init (optimized)
@@ -357,7 +358,7 @@ namespace FlaxEditor
InitializationStart?.Invoke();
// Start Editor initialization ending phrase (will wait for scripts compilation result)
StateMachine.LoadingState.StartInitEnding(skipCompile);
StateMachine.LoadingState.StartInitEnding(flags.HasFlag(StartupFlags.SkipCompile));
}
internal void RegisterModule(EditorModule module)
@@ -486,6 +487,15 @@ namespace FlaxEditor
{
StateMachine.Update();
UpdateAutoSave();
if (_autoExit && StateMachine.CurrentState == StateMachine.EditingSceneState)
{
_autoExitTimeout -= Time.UnscaledGameTime;
if (_autoExitTimeout < 0.0f)
{
Log("Auto exit");
Engine.RequestExit(0);
}
}
if (!StateMachine.IsPlayMode)
{

View File

@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
@@ -29,6 +28,7 @@ namespace FlaxEditor.GUI
internal Float2 _mousePos = Float2.Minimum;
internal bool _isMovingSelection;
internal bool _isMovingTangent;
internal bool _movedView;
internal bool _movedKeyframes;
private TangentPoint _movingTangent;
private Float2 _movingSelectionStart;
@@ -190,31 +190,28 @@ namespace FlaxEditor.GUI
// Moving view
if (_rightMouseDown)
{
var delta = location - _movingViewLastPos;
var movingViewPos = Parent.PointToParent(PointToParent(location));
var delta = movingViewPos - _movingViewLastPos;
if (_editor.CustomViewPanning != null)
delta = _editor.CustomViewPanning(delta);
delta *= GetUseModeMask(_editor.EnablePanning) * _editor.ViewScale;
delta *= GetUseModeMask(_editor.EnablePanning);
if (delta.LengthSquared > 0.01f)
{
_editor._mainPanel.ViewOffset += delta;
_movingViewLastPos = location;
_movingViewLastPos = movingViewPos;
_movedView = true;
if (_editor.CustomViewPanning != null)
{
Cursor = CursorType.SizeAll;
if (Cursor == CursorType.Default)
Cursor = CursorType.SizeAll;
}
else
{
switch (_editor.EnablePanning)
{
case UseMode.Vertical:
Cursor = CursorType.SizeNS;
break;
case UseMode.Horizontal:
Cursor = CursorType.SizeWE;
break;
case UseMode.On:
Cursor = CursorType.SizeAll;
break;
case UseMode.Vertical: Cursor = CursorType.SizeNS; break;
case UseMode.Horizontal: Cursor = CursorType.SizeWE; break;
case UseMode.On: Cursor = CursorType.SizeAll; break;
}
}
}
@@ -234,11 +231,10 @@ namespace FlaxEditor.GUI
else if (_isMovingTangent)
{
var viewRect = _editor._mainPanel.GetClientArea();
var direction = _movingTangent.IsIn ? -1.0f : 1.0f;
var k = _editor.GetKeyframe(_movingTangent.Index);
var kv = _editor.GetKeyframeValue(k);
var value = _editor.Accessor.GetCurveValue(ref kv, _movingTangent.Component);
_movingTangent.TangentValue = direction * (PointToKeyframes(location, ref viewRect).Y - value);
_movingTangent.TangentValue = PointToKeyframes(location, ref viewRect).Y - value;
_editor.UpdateTangents();
Cursor = CursorType.SizeNS;
_movedKeyframes = true;
@@ -299,7 +295,8 @@ namespace FlaxEditor.GUI
{
_rightMouseDown = true;
_rightMouseDownPos = location;
_movingViewLastPos = location;
_movedView = false;
_movingViewLastPos = Parent.PointToParent(PointToParent(location));
}
// Check if any node is under the mouse
@@ -444,7 +441,7 @@ namespace FlaxEditor.GUI
Cursor = CursorType.Default;
// Check if no move has been made at all
if (Float2.Distance(ref location, ref _rightMouseDownPos) < 2.0f)
if (!_movedView)
{
var selectionCount = _editor.SelectionCount;
var point = GetChildAt(location) as KeyframePoint;
@@ -512,6 +509,27 @@ namespace FlaxEditor.GUI
return true;
}
/// <inheritdoc />
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
if (base.OnMouseDoubleClick(location, button))
return true;
// Add keyframe on double click
var child = GetChildAt(location);
if (child is not KeyframePoint &&
child is not TangentPoint &&
_editor.KeyframesCount < _editor.MaxKeyframes)
{
var viewRect = _editor._mainPanel.GetClientArea();
var pos = PointToKeyframes(location, ref viewRect);
_editor.AddKeyframe(pos);
return true;
}
return false;
}
/// <inheritdoc />
public override bool OnMouseWheel(Float2 location, float delta)
{
@@ -519,10 +537,29 @@ namespace FlaxEditor.GUI
return true;
// Zoom in/out
if (_editor.EnableZoom != UseMode.Off && IsMouseOver && !_leftMouseDown && RootWindow.GetKey(KeyboardKeys.Control))
var zoom = RootWindow.GetKey(KeyboardKeys.Control);
var zoomAlt = RootWindow.GetKey(KeyboardKeys.Shift);
if (_editor.EnableZoom != UseMode.Off && IsMouseOver && !_leftMouseDown && (zoom || zoomAlt))
{
// TODO: preserve the view center point for easier zooming
_editor.ViewScale += GetUseModeMask(_editor.EnableZoom) * (delta * 0.1f);
// Cache mouse location in curve-space
var viewRect = _editor._mainPanel.GetClientArea();
var locationInKeyframes = PointToKeyframes(location, ref viewRect);
var locationInEditorBefore = _editor.PointFromKeyframes(locationInKeyframes, ref viewRect);
// Scale relative to the curve size
var scale = new Float2(delta * 0.1f);
_editor._mainPanel.GetDesireClientArea(out var mainPanelArea);
var curveScale = mainPanelArea.Size / _editor._contents.Size;
scale *= curveScale;
if (zoomAlt)
scale.X = 0; // Scale Y axis only
scale *= GetUseModeMask(_editor.EnableZoom); // Mask scale depending on allowed usage
_editor.ViewScale += scale;
// Zoom towards the mouse position
var locationInEditorAfter = _editor.PointFromKeyframes(locationInKeyframes, ref viewRect);
var locationInEditorDelta = locationInEditorAfter - locationInEditorBefore;
_editor.ViewOffset -= locationInEditorDelta;
return true;
}

View File

@@ -671,8 +671,22 @@ namespace FlaxEditor.GUI
/// <inheritdoc />
public override void ShowWholeCurve()
{
ViewScale = ApplyUseModeMask(EnableZoom, _mainPanel.Size / _contents.Size, ViewScale);
ViewOffset = ApplyUseModeMask(EnablePanning, -_mainPanel.ControlsBounds.Location, ViewOffset);
_mainPanel.GetDesireClientArea(out var mainPanelArea);
ViewScale = ApplyUseModeMask(EnableZoom, mainPanelArea.Size / _contents.Size, ViewScale);
Float2 minPos = Float2.Maximum;
foreach (var point in _points)
{
var pos = point.PointToParent(point.Location);
Float2.Min(ref minPos, ref pos, out minPos);
}
var minPosPoint = _contents.PointToParent(ref minPos);
var scroll = new Float2(_mainPanel.HScrollBar?.TargetValue ?? 0, _mainPanel.VScrollBar?.TargetValue ?? 0);
scroll = ApplyUseModeMask(EnablePanning, minPosPoint, scroll);
if (_mainPanel.HScrollBar != null)
_mainPanel.HScrollBar.TargetValue = scroll.X;
if (_mainPanel.VScrollBar != null)
_mainPanel.VScrollBar.TargetValue = scroll.Y;
UpdateKeyframes();
}
@@ -923,6 +937,11 @@ namespace FlaxEditor.GUI
KeyframesEditorUtils.Paste(this);
return true;
}
else if (options.FocusSelection.Process(this))
{
ShowWholeCurve();
return true;
}
return false;
}
@@ -1384,9 +1403,7 @@ namespace FlaxEditor.GUI
// Calculate bounds
var bounds = _points[0].Bounds;
for (var i = 1; i < _points.Count; i++)
{
bounds = Rectangle.Union(bounds, _points[i].Bounds);
}
// Adjust contents bounds to fill the curve area
if (EnablePanning != UseMode.Off || !ShowCollapsed)
@@ -1632,6 +1649,7 @@ namespace FlaxEditor.GUI
var o = _keyframes[p.Index - 1];
var oValue = Accessor.GetCurveValue(ref o.Value, p.Component);
var slope = (value - oValue) / (k.Time - o.Time);
slope = -slope;
Accessor.SetCurveValue(slope, ref k.TangentIn, p.Component);
}
@@ -2116,9 +2134,7 @@ namespace FlaxEditor.GUI
// Calculate bounds
var bounds = _points[0].Bounds;
for (int i = 1; i < _points.Count; i++)
{
bounds = Rectangle.Union(bounds, _points[i].Bounds);
}
// Adjust contents bounds to fill the curve area
if (EnablePanning != UseMode.Off || !ShowCollapsed)
@@ -2184,12 +2200,12 @@ namespace FlaxEditor.GUI
var tangent = t.TangentValue;
var direction = t.IsIn ? -1.0f : 1.0f;
var offset = 30.0f * direction;
var offset = 30.0f;
var location = GetKeyframePoint(ref k, selectedComponent);
t.Size = KeyframesSize / ViewScale;
t.Location = new Float2
(
location.X * UnitsPerSecond - t.Width * 0.5f + offset,
location.X * UnitsPerSecond - t.Width * 0.5f + offset * direction,
location.Y * -UnitsPerSecond - t.Height * 0.5f + curveContentAreaBounds.Height - offset * tangent
);
@@ -2265,14 +2281,13 @@ namespace FlaxEditor.GUI
var startTangent = Accessor.GetCurveValue(ref startK.TangentOut, component);
var endTangent = Accessor.GetCurveValue(ref endK.TangentIn, component);
var offset = (end.X - start.X) * 0.5f;
var tangentScale = (endK.Time - startK.Time) / 3.0f;
var p1 = PointFromKeyframes(start, ref viewRect);
var p2 = PointFromKeyframes(start + new Float2(offset, startTangent * offset), ref viewRect);
var p3 = PointFromKeyframes(end - new Float2(offset, endTangent * offset), ref viewRect);
var p2 = PointFromKeyframes(start + new Float2(0, startTangent * tangentScale), ref viewRect);
var p3 = PointFromKeyframes(end + new Float2(0, endTangent * tangentScale), ref viewRect);
var p4 = PointFromKeyframes(end, ref viewRect);
Render2D.DrawBezier(p1, p2, p3, p4, color);
Render2D.DrawSpline(p1, p2, p3, p4, color);
}
}
}

View File

@@ -0,0 +1,86 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.GUI
{
sealed class Splitter : Control
{
private bool _clicked;
public Action<Float2> Moved;
public const float DefaultHeight = 5.0f;
public override void Draw()
{
var style = Style.Current;
if (IsMouseOver || _clicked)
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), _clicked ? style.BackgroundSelected : style.BackgroundHighlighted);
}
public override void OnEndMouseCapture()
{
base.OnEndMouseCapture();
_clicked = false;
}
public override void Defocus()
{
base.Defocus();
_clicked = false;
}
public override void OnMouseEnter(Float2 location)
{
base.OnMouseEnter(location);
Cursor = CursorType.SizeNS;
}
public override void OnMouseLeave()
{
Cursor = CursorType.Default;
base.OnMouseLeave();
}
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (button == MouseButton.Left)
{
_clicked = true;
Focus();
StartMouseCapture();
return true;
}
return base.OnMouseDown(location, button);
}
public override void OnMouseMove(Float2 location)
{
base.OnMouseMove(location);
if (_clicked)
{
Moved(location);
}
}
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _clicked)
{
_clicked = false;
EndMouseCapture();
return true;
}
return base.OnMouseUp(location, button);
}
}
}

View File

@@ -230,7 +230,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
continue;
// Draw all ticks
int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 1);
int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 2);
var lStep = _tickSteps[l];
var lNextStep = _tickSteps[l + 1];
int startTick = Mathf.FloorToInt(min / lStep);

View File

@@ -1446,6 +1446,17 @@ namespace FlaxEditor.GUI.Timeline
{
GetTracks(SelectedTracks[i], tracks);
}
// Find the lowest track position for selection
int lowestTrackLocation = Tracks.Count - 1;
for (int i = 0; i < tracks.Count; i++)
{
var trackToDelete = tracks[i];
if (trackToDelete.TrackIndex < lowestTrackLocation)
{
lowestTrackLocation = trackToDelete.TrackIndex;
}
}
SelectedTracks.Clear();
if (withUndo && Undo != null && Undo.Enabled)
{
@@ -1468,6 +1479,18 @@ namespace FlaxEditor.GUI.Timeline
}
OnTracksChanged();
MarkAsEdited();
// Select track above deleted tracks unless track is first track
if (Tracks.Count > 0)
{
if (lowestTrackLocation - 1 >= 0)
Select(Tracks[lowestTrackLocation - 1]);
else
Select(Tracks[0]);
SelectedTracks[0].Focus();
}
}
/// <summary>
@@ -1655,6 +1678,14 @@ namespace FlaxEditor.GUI.Timeline
}
OnTracksChanged();
MarkAsEdited();
// Deselect and select new clones.
Deselect();
foreach (var clone in clones)
{
Select(clone, true);
}
SelectedTracks[0].Focus();
}

View File

@@ -19,87 +19,6 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// <seealso cref="MemberTrack" />
public abstract class CurvePropertyTrackBase : MemberTrack, IKeyframesEditorContext
{
private sealed class Splitter : Control
{
private bool _clicked;
internal CurvePropertyTrackBase _track;
public override void Draw()
{
var style = Style.Current;
if (IsMouseOver || _clicked)
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), _clicked ? style.BackgroundSelected : style.BackgroundHighlighted);
}
public override void OnEndMouseCapture()
{
base.OnEndMouseCapture();
_clicked = false;
}
public override void Defocus()
{
base.Defocus();
_clicked = false;
}
public override void OnMouseEnter(Float2 location)
{
base.OnMouseEnter(location);
Cursor = CursorType.SizeNS;
}
public override void OnMouseLeave()
{
Cursor = CursorType.Default;
base.OnMouseLeave();
}
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (button == MouseButton.Left)
{
_clicked = true;
Focus();
StartMouseCapture();
return true;
}
return base.OnMouseDown(location, button);
}
public override void OnMouseMove(Float2 location)
{
base.OnMouseMove(location);
if (_clicked)
{
var height = Mathf.Clamp(PointToParent(location).Y, 40.0f, 1000.0f);
if (!Mathf.NearEqual(height, _track._expandedHeight))
{
_track.Height = _track._expandedHeight = height;
_track.Timeline.ArrangeTracks();
}
}
}
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _clicked)
{
_clicked = false;
EndMouseCapture();
return true;
}
return base.OnMouseUp(location, button);
}
}
private byte[] _curveEditingStartData;
private float _expandedHeight = 120.0f;
private Splitter _splitter;
@@ -251,12 +170,21 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
_splitter = new Splitter
{
_track = this,
Moved = OnSplitterMoved,
Parent = Curve,
};
}
var splitterHeight = 5.0f;
_splitter.Bounds = new Rectangle(0, Curve.Height - splitterHeight, Curve.Width, splitterHeight);
_splitter.Bounds = new Rectangle(0, Curve.Height - Splitter.DefaultHeight, Curve.Width, Splitter.DefaultHeight);
}
}
private void OnSplitterMoved(Float2 location)
{
var height = Mathf.Clamp(PointToParent(location).Y, 40.0f, 1000.0f);
if (!Mathf.NearEqual(height, _expandedHeight))
{
Height = _expandedHeight = height;
Timeline.ArrangeTracks();
}
}

View File

@@ -109,6 +109,19 @@ namespace FlaxEditor.GUI.Timeline.Tracks
MaxMediaCount = 1;
}
/// <inheritdoc />
public override void OnDuplicated(Track clone)
{
base.OnDuplicated(clone);
// Clone overriden parameters
if (clone is ParticleEmitterTrack cloneTrack)
{
foreach (var e in ParametersOverrides)
cloneTrack.ParametersOverrides.Add(e.Key, e.Value);
}
}
/// <inheritdoc />
public override void OnDestroy()
{

View File

@@ -61,6 +61,9 @@ namespace FlaxEditor.GUI.Timeline.Undo
_timeline.AddTrack(track, false);
track.TrackIndex = _order;
_timeline.OnTracksOrderChanged();
_timeline.Focus();
_timeline.Select(track);
track.Focus();
}
private void Remove()
@@ -68,10 +71,11 @@ namespace FlaxEditor.GUI.Timeline.Undo
var track = _timeline.FindTrack(_name);
if (track == null)
{
Editor.LogWarning($"Cannot remove track {_name}. It doesn't already exists.");
Editor.LogWarning($"Cannot remove track {_name}. It doesn't exist.");
return;
}
_timeline.Delete(track, false);
_timeline.Focus();
}
public string ActionString => _isAdd ? "Add track" : "Remove track";

View File

@@ -176,7 +176,7 @@ ManagedEditor::~ManagedEditor()
void ManagedEditor::Init()
{
// Note: editor modules should perform quite fast init, any longer things should be done in async during 'editor splash screen time
void* args[4];
void* args[2];
MClass* mclass = GetClass();
if (mclass == nullptr)
{
@@ -193,18 +193,22 @@ void ManagedEditor::Init()
LOG(Fatal, "Failed to create editor instance.");
}
MObject* exception = nullptr;
bool isHeadless = CommandLine::Options.Headless.IsTrue();
bool skipCompile = CommandLine::Options.SkipCompile.IsTrue();
bool newProject = CommandLine::Options.NewProject.IsTrue();
args[0] = &isHeadless;
args[1] = &skipCompile;
args[2] = &newProject;
StartupFlags flags = StartupFlags::None;
if (CommandLine::Options.Headless.IsTrue())
flags |= StartupFlags::Headless;
if (CommandLine::Options.SkipCompile.IsTrue())
flags |= StartupFlags::SkipCompile;
if (CommandLine::Options.NewProject.IsTrue())
flags |= StartupFlags::NewProject;
if (CommandLine::Options.Exit.IsTrue())
flags |= StartupFlags::Exit;
args[0] = &flags;
Guid sceneId;
if (!CommandLine::Options.Play.HasValue() || (CommandLine::Options.Play.HasValue() && Guid::Parse(CommandLine::Options.Play.GetValue(), sceneId)))
{
sceneId = Guid::Empty;
}
args[3] = &sceneId;
args[1] = &sceneId;
initMethod->Invoke(instance, args, &exception);
if (exception)
{
@@ -219,7 +223,7 @@ void ManagedEditor::Init()
WasExitCalled = false;
// Load scripts if auto-load on startup is disabled
if (!ManagedEditorOptions.ForceScriptCompilationOnStartup || skipCompile)
if (!ManagedEditorOptions.ForceScriptCompilationOnStartup || EnumHasAllFlags(flags, StartupFlags::SkipCompile))
{
LOG(Info, "Loading managed assemblies (due to disabled compilation on startup)");
Scripting::Load();

View File

@@ -22,6 +22,15 @@ API_CLASS(Namespace="FlaxEditor", Name="Editor", NoSpawn, NoConstructor) class M
DECLARE_SCRIPTING_TYPE_NO_SPAWN(ManagedEditor);
static Guid ObjectID;
API_ENUM(Attributes="Flags", Internal) enum class StartupFlags
{
None = 0,
Headless = 1,
SkipCompile = 2,
NewProject = 4,
Exit = 8,
};
struct InternalOptions
{
byte AutoReloadScriptsOnMainWindowFocus = 1;
@@ -258,3 +267,5 @@ public:
// [ScriptingObject]
void DestroyManaged() override;
};
DECLARE_ENUM_OPERATORS(ManagedEditor::StartupFlags);

View File

@@ -274,11 +274,7 @@ namespace FlaxEditor.Modules
private void Load()
{
if (!File.Exists(_cachePath))
{
Editor.LogWarning("Missing editor cache file.");
return;
}
_lastSaveTime = DateTime.UtcNow;
try

View File

@@ -950,7 +950,10 @@ namespace FlaxEditor.Modules
MainWindow = null;
// Capture project icon screenshot (not in play mode and if editor was used for some time)
if (!Editor.StateMachine.IsPlayMode && Time.TimeSinceStartup >= 5.0f && GPUDevice.Instance?.RendererType != RendererType.Null)
if (!Editor.StateMachine.IsPlayMode &&
Time.TimeSinceStartup >= 5.0f &&
!Editor.IsHeadlessMode &&
GPUDevice.Instance?.RendererType != RendererType.Null)
{
Editor.Log("Capture project icon screenshot");
_projectIconScreenshotTimeout = Time.TimeSinceStartup + 0.8f; // wait 800ms for a screenshot task

View File

@@ -449,6 +449,13 @@ namespace FlaxEditor.Options
[EditorDisplay("Visject", "Grid Snapping Size"), EditorOrder(551), Tooltip("Defines the size of the grid for nodes snapping."), VisibleIf(nameof(SurfaceGridSnapping))]
public float SurfaceGridSnappingSize { get; set; } = 20.0f;
/// <summary>
/// Gets or sets a value that indicates if a warning should be displayed when deleting a Visject parameter that is used in a graph.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Visject", "Warn when deleting used parameter"), EditorOrder(552)]
public bool WarnOnDeletingUsedVisjectParameter { get; set; } = true;
private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.PrimaryFont);
private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont);

View File

@@ -81,6 +81,7 @@ namespace FlaxEditor.SceneGraph.Actors
if (Level.ScenesCount > 1)
contextMenu.AddButton("Unload all but this scene", OnUnloadAllButSelectedScene).LinkTooltip("Unloads all of the active scenes except for the selected scene.").Enabled = Editor.Instance.StateMachine.CurrentState.CanChangeScene;
contextMenu.MaximumItemsInViewCount += 3;
base.OnContextMenu(contextMenu, window);
}

View File

@@ -293,6 +293,14 @@ namespace FlaxEditor.Scripting
/// <param name="obj">The object whose member value will be modified.</param>
/// <param name="value">The new member value.</param>
void SetValue(object obj, object value);
/// <summary>
/// Invokes the method on a specific object (null if static) using the provided parameters.
/// </summary>
/// <param name="obj">The instance of the object to invoke its method. Use null for static methods.</param>
/// <param name="parameters">List of parameters to provide.</param>
/// <returns>The value returned by the method.</returns>
object Invoke(object obj, object[] parameters);
}
/// <summary>

View File

@@ -691,6 +691,23 @@ namespace FlaxEditor.Scripting
else
_custom.SetValue(obj, value);
}
/// <summary>
/// Invokes the method on a specific object (null if static) using the provided parameters.
/// </summary>
/// <param name="obj">The instance of the object to invoke its method. Use null for static methods.</param>
/// <param name="parameters">List of parameters to provide.</param>
/// <returns>The value returned by the method.</returns>
public object Invoke(object obj = null, object[] parameters = null)
{
if (parameters == null)
parameters = Array.Empty<object>();
if (_managed is MethodInfo methodInfo)
return methodInfo.Invoke(obj, parameters);
if (_managed != null)
throw new NotSupportedException();
return _custom.Invoke(obj, parameters);
}
}
/// <summary>

View File

@@ -30,7 +30,7 @@ namespace FlaxEditor.Surface.Archetypes
/// Represents single blend point.
/// </summary>
/// <seealso cref="FlaxEngine.GUI.Control" />
protected class BlendPoint : Control
internal class BlendPoint : Control
{
private readonly BlendPointsEditor _editor;
private readonly int _index;
@@ -48,6 +48,11 @@ namespace FlaxEditor.Surface.Archetypes
/// </summary>
public int Index => _index;
/// <summary>
/// Flag that indicates that user is moving this point with a mouse.
/// </summary>
public bool IsMouseDown => _isMouseDown;
/// <summary>
/// Initializes a new instance of the <see cref="BlendPoint"/> class.
/// </summary>
@@ -211,6 +216,11 @@ namespace FlaxEditor.Surface.Archetypes
/// </summary>
public int PointsCount => (_node.Values.Length - 4) / 2; // 4 node values + 2 per blend point
/// <summary>
/// BLend points array.
/// </summary>
internal IReadOnlyList<BlendPoint> BlendPoints => _blendPoints;
/// <summary>
/// Initializes a new instance of the <see cref="BlendPointsEditor"/> class.
/// </summary>
@@ -374,6 +384,12 @@ namespace FlaxEditor.Surface.Archetypes
/// <returns>The blend point control position.</returns>
public Float2 BlendSpacePosToBlendPointPos(Float2 pos)
{
if (_rangeX.IsZero)
{
var data0 = (Float4)_node.Values[0];
_rangeX = new Float2(data0.X, data0.Y);
_rangeY = _is2D ? new Float2(data0.Z, data0.W) : Float2.Zero;
}
GetPointsArea(out var pointsArea);
if (_is2D)
{
@@ -389,7 +405,7 @@ namespace FlaxEditor.Surface.Archetypes
pointsArea.Center.Y
);
}
return pos - new Float2(BlendPoint.DefaultSize * 0.5f);
return pos;
}
/// <inheritdoc />
@@ -424,7 +440,7 @@ namespace FlaxEditor.Surface.Archetypes
}
// Update blend point
_blendPoints[i].Location = BlendSpacePosToBlendPointPos(location);
_blendPoints[i].Location = BlendSpacePosToBlendPointPos(location) - BlendPoint.DefaultSize * 0.5f;
var asset = Editor.Instance.ContentDatabase.FindAsset(animId);
var tooltip = asset?.ShortName ?? string.Empty;
tooltip += "\nX: " + location.X;
@@ -532,81 +548,18 @@ namespace FlaxEditor.Surface.Archetypes
SetAsset((int)b.Tag, Guid.Empty);
}
private void DrawAxis(bool vertical, Float2 start, Float2 end, ref Color gridColor, ref Color labelColor, Font labelFont, float value, bool isLast)
{
// Draw line
Render2D.DrawLine(start, end, gridColor);
// Draw label
var labelWidth = 50.0f;
var labelHeight = 10.0f;
var labelMargin = 2.0f;
string label = Utils.RoundTo2DecimalPlaces(value).ToString(System.Globalization.CultureInfo.InvariantCulture);
var hAlign = TextAlignment.Near;
Rectangle labelRect;
if (vertical)
{
labelRect = new Rectangle(start.X + labelMargin * 2, start.Y, labelWidth, labelHeight);
if (isLast)
return; // Don't overlap with the first horizontal label
}
else
{
labelRect = new Rectangle(start.X + labelMargin, start.Y - labelHeight - labelMargin, labelWidth, labelHeight);
if (isLast)
{
labelRect.X = start.X - labelMargin - labelRect.Width;
hAlign = TextAlignment.Far;
}
}
Render2D.DrawText(labelFont, label, labelRect, labelColor, hAlign, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
}
/// <inheritdoc />
public override void Draw()
{
var style = Style.Current;
var rect = new Rectangle(Float2.Zero, Size);
var containsFocus = ContainsFocus;
GetPointsArea(out var pointsArea);
var data0 = (Float4)_node.Values[0];
var rangeX = new Float2(data0.X, data0.Y);
// Background
Render2D.DrawRectangle(rect, IsMouseOver ? style.TextBoxBackgroundSelected : style.TextBoxBackground);
//Render2D.DrawRectangle(pointsArea, Color.Red);
_node.DrawEditorBackground(ref rect);
// Grid
int splits = 10;
var gridColor = style.TextBoxBackgroundSelected * 1.1f;
var labelColor = style.ForegroundDisabled;
var labelFont = style.FontSmall;
//var blendArea = BlendAreaRect;
var blendArea = pointsArea;
for (int i = 0; i <= splits; i++)
{
float alpha = (float)i / splits;
float x = blendArea.Left + blendArea.Width * alpha;
float value = Mathf.Lerp(rangeX.X, rangeX.Y, alpha);
DrawAxis(false, new Float2(x, rect.Height - 2), new Float2(x, 1), ref gridColor, ref labelColor, labelFont, value, i == splits);
}
if (_is2D)
{
var rangeY = new Float2(data0.Z, data0.W);
for (int i = 0; i <= splits; i++)
{
float alpha = (float)i / splits;
float y = blendArea.Top + blendArea.Height * alpha;
float value = Mathf.Lerp(rangeY.X, rangeY.Y, alpha);
DrawAxis(true, new Float2(1, y), new Float2(rect.Width - 2, y), ref gridColor, ref labelColor, labelFont, value, i == splits);
}
}
else
{
float y = blendArea.Center.Y;
Render2D.DrawLine(new Float2(1, y), new Float2(rect.Width - 2, y), gridColor);
}
_node.DrawEditorGrid(ref rect);
base.Draw();
@@ -808,6 +761,87 @@ namespace FlaxEditor.Surface.Archetypes
_editor.SetAsset(SelectedAnimationIndex, Guid.Empty);
}
private void DrawAxis(bool vertical, Float2 start, Float2 end, ref Color gridColor, ref Color labelColor, Font labelFont, float value, bool isLast)
{
// Draw line
Render2D.DrawLine(start, end, gridColor);
// Draw label
var labelWidth = 50.0f;
var labelHeight = 10.0f;
var labelMargin = 2.0f;
string label = Utils.RoundTo2DecimalPlaces(value).ToString(System.Globalization.CultureInfo.InvariantCulture);
var hAlign = TextAlignment.Near;
Rectangle labelRect;
if (vertical)
{
labelRect = new Rectangle(start.X + labelMargin * 2, start.Y, labelWidth, labelHeight);
if (isLast)
return; // Don't overlap with the first horizontal label
}
else
{
labelRect = new Rectangle(start.X + labelMargin, start.Y - labelHeight - labelMargin, labelWidth, labelHeight);
if (isLast)
{
labelRect.X = start.X - labelMargin - labelRect.Width;
hAlign = TextAlignment.Far;
}
}
Render2D.DrawText(labelFont, label, labelRect, labelColor, hAlign, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
}
/// <summary>
/// Custom drawing logic for blend space background.
/// </summary>
public virtual void DrawEditorBackground(ref Rectangle rect)
{
var style = Style.Current;
Render2D.FillRectangle(rect, style.Background.AlphaMultiplied(0.5f));
Render2D.DrawRectangle(rect, IsMouseOver ? style.TextBoxBackgroundSelected : style.TextBoxBackground);
}
/// <summary>
/// Custom drawing logic for blend space grid.
/// </summary>
public virtual void DrawEditorGrid(ref Rectangle rect)
{
var style = Style.Current;
_editor.GetPointsArea(out var pointsArea);
var data0 = (Float4)Values[0];
var rangeX = new Float2(data0.X, data0.Y);
int splits = 10;
var gridColor = style.TextBoxBackgroundSelected * 1.1f;
var labelColor = style.ForegroundDisabled;
var labelFont = style.FontSmall;
//var blendArea = BlendAreaRect;
var blendArea = pointsArea;
for (int i = 0; i <= splits; i++)
{
float alpha = (float)i / splits;
float x = blendArea.Left + blendArea.Width * alpha;
float value = Mathf.Lerp(rangeX.X, rangeX.Y, alpha);
DrawAxis(false, new Float2(x, rect.Height - 2), new Float2(x, 1), ref gridColor, ref labelColor, labelFont, value, i == splits);
}
if (_editor.Is2D)
{
var rangeY = new Float2(data0.Z, data0.W);
for (int i = 0; i <= splits; i++)
{
float alpha = (float)i / splits;
float y = blendArea.Top + blendArea.Height * alpha;
float value = Mathf.Lerp(rangeY.X, rangeY.Y, 1.0f - alpha);
DrawAxis(true, new Float2(1, y), new Float2(rect.Width - 2, y), ref gridColor, ref labelColor, labelFont, value, i == splits);
}
}
else
{
float y = blendArea.Center.Y;
Render2D.DrawLine(new Float2(1, y), new Float2(rect.Width - 2, y), gridColor);
}
}
/// <summary>
/// Updates the editor UI.
/// </summary>
@@ -983,6 +1017,10 @@ namespace FlaxEditor.Surface.Archetypes
private readonly FloatValueBox _animationX;
private readonly Label _animationYLabel;
private readonly FloatValueBox _animationY;
private Float2[] _triangles;
private Color[] _triangleColors;
private Float2[] _selectedTriangles;
private Color[] _selectedColors;
/// <inheritdoc />
public MultiBlend2D(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
@@ -1051,6 +1089,143 @@ namespace FlaxEditor.Surface.Archetypes
}
}
private void ClearTriangles()
{
// Remove cache
_triangles = null;
_triangleColors = null;
_selectedTriangles = null;
_selectedColors = null;
}
private void CacheTriangles()
{
// Get locations of blend point vertices
int pointsCount = _editor.PointsCount;
int count = 0, j = 0;
for (int i = 0; i < pointsCount; i++)
{
var animId = (Guid)Values[5 + i * 2];
if (animId != Guid.Empty)
count++;
}
var vertices = new Float2[count];
for (int i = 0; i < pointsCount; i++)
{
var animId = (Guid)Values[5 + i * 2];
if (animId != Guid.Empty)
{
var dataA = (Float4)Values[4 + i * 2];
vertices[j++] = new Float2(dataA.X, dataA.Y);
}
}
// Triangulate
_triangles = FlaxEngine.Utilities.Delaunay2D.Triangulate(vertices);
_triangleColors = null;
// Fix incorrect triangles (mirror logic in AnimGraphBase::onNodeLoaded)
if (_triangles == null || _triangles.Length == 0)
{
// Insert dummy triangles to have something working (eg. blend points are on the same axis)
var triangles = new List<Float2>();
int verticesLeft = vertices.Length;
while (verticesLeft >= 3)
{
verticesLeft -= 3;
triangles.Add(vertices[verticesLeft + 0]);
triangles.Add(vertices[verticesLeft + 1]);
triangles.Add(vertices[verticesLeft + 2]);
}
if (verticesLeft == 1)
{
triangles.Add(vertices[0]);
triangles.Add(vertices[0]);
triangles.Add(vertices[0]);
}
else if (verticesLeft == 2)
{
triangles.Add(vertices[0]);
triangles.Add(vertices[1]);
triangles.Add(vertices[0]);
}
_triangles = triangles.ToArray();
}
// Project to the blend space for drawing
for (int i = 0; i < _triangles.Length; i++)
_triangles[i] = _editor.BlendSpacePosToBlendPointPos(_triangles[i]);
// Check if anything is selected
var selectedIndex = _selectedAnimation.SelectedIndex;
if (selectedIndex != -1)
{
// Find triangles that contain selected point
var dataA = (Float4)Values[4 + selectedIndex * 2];
var pos = _editor.BlendSpacePosToBlendPointPos(new Float2(dataA.X, dataA.Y));
var selectedTriangles = new List<Float2>();
var selectedColors = new List<Color>();
var style = Style.Current;
var triangleColor = style.TextBoxBackgroundSelected.AlphaMultiplied(0.6f);
var selectedTriangleColor = style.BackgroundSelected.AlphaMultiplied(0.6f);
_triangleColors = new Color[_triangles.Length];
for (int i = 0; i < _triangles.Length; i += 3)
{
var is0 = Float2.NearEqual(ref _triangles[i + 0], ref pos);
var is1 = Float2.NearEqual(ref _triangles[i + 1], ref pos);
var is2 = Float2.NearEqual(ref _triangles[i + 2], ref pos);
if (is0 || is1 || is2)
{
selectedTriangles.Add(_triangles[i + 0]);
selectedTriangles.Add(_triangles[i + 1]);
selectedTriangles.Add(_triangles[i + 2]);
selectedColors.Add(is0 ? Color.White : Color.Transparent);
selectedColors.Add(is1 ? Color.White : Color.Transparent);
selectedColors.Add(is2 ? Color.White : Color.Transparent);
}
_triangleColors[i + 0] = is0 ? selectedTriangleColor : triangleColor;
_triangleColors[i + 1] = is1 ? selectedTriangleColor : triangleColor;
_triangleColors[i + 2] = is2 ? selectedTriangleColor : triangleColor;
}
_selectedTriangles = selectedTriangles.ToArray();
_selectedColors = selectedColors.ToArray();
}
}
/// <inheritdoc />
public override void DrawEditorBackground(ref Rectangle rect)
{
base.DrawEditorBackground(ref rect);
// Draw triangulated multi blend space
var style = Style.Current;
if (_triangles == null)
CacheTriangles();
if (_triangleColors != null && (ContainsFocus || IsMouseOver))
Render2D.FillTriangles(_triangles, _triangleColors);
else
Render2D.FillTriangles(_triangles, style.TextBoxBackgroundSelected.AlphaMultiplied(0.6f));
Render2D.DrawTriangles(_triangles, style.Foreground);
}
/// <inheritdoc />
public override void DrawEditorGrid(ref Rectangle rect)
{
base.DrawEditorGrid(ref rect);
// Highlight selected blend point
var style = Style.Current;
var selectedIndex = _selectedAnimation.SelectedIndex;
if (selectedIndex != -1 && (ContainsFocus || IsMouseOver))
{
var point = _editor.BlendPoints[selectedIndex];
var highlightColor = point.IsMouseDown ? style.SelectionBorder : style.BackgroundSelected;
Render2D.PushTint(ref highlightColor);
Render2D.DrawTriangles(_selectedTriangles, _selectedColors);
Render2D.PopTint();
}
}
/// <inheritdoc />
public override void UpdateUI(int selectedIndex, bool isValid, ref Float4 data0, ref Guid data1)
{
@@ -1075,6 +1250,23 @@ namespace FlaxEditor.Surface.Archetypes
_animationX.Enabled = isValid;
_animationYLabel.Enabled = isValid;
_animationY.Enabled = isValid;
ClearTriangles();
}
/// <inheritdoc />
public override void OnValuesChanged()
{
base.OnValuesChanged();
ClearTriangles();
}
/// <inheritdoc />
public override void OnLoaded(SurfaceNodeActions action)
{
base.OnLoaded(action);
ClearTriangles();
}
}
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using FlaxEditor.Content.Settings;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEditor.Windows.Assets;
@@ -590,7 +591,7 @@ namespace FlaxEditor.Surface.Archetypes
},
Elements = new[]
{
NodeElementArchetype.Factory.ComboBox(0, 0, 70.0f, 0, FlaxEditor.Tools.Terrain.PaintTerrainGizmoMode.TerrainLayerNames),
NodeElementArchetype.Factory.ComboBox(0, 0, 70.0f, 0, LayersAndTagsSettings.GetCurrentTerrainLayers()),
NodeElementArchetype.Factory.Output(0, "", typeof(float), 0),
}
},

View File

@@ -425,6 +425,12 @@ namespace FlaxEditor.Surface.Archetypes
UpdateCombo();
}
/// <inheritdoc />
public bool IsParamUsed(SurfaceParameter param)
{
return (Guid)Values[0] == param.ID;
}
/// <inheritdoc />
public override void OnLoaded(SurfaceNodeActions action)
{
@@ -937,13 +943,17 @@ namespace FlaxEditor.Surface.Archetypes
{
// Deselect if that parameter is selected
if ((Guid)Values[0] == param.ID)
{
_combobox.SelectedIndex = -1;
}
UpdateCombo();
}
/// <inheritdoc />
public bool IsParamUsed(SurfaceParameter param)
{
return (Guid)Values[0] == param.ID;
}
/// <inheritdoc />
public override void OnLoaded(SurfaceNodeActions action)
{

View File

@@ -33,5 +33,12 @@ namespace FlaxEditor.Surface
/// </summary>
/// <param name="param">The parameter.</param>
void OnParamDeleted(SurfaceParameter param);
/// <summary>
/// Get if the parameter is referenced in a graph. Referenced in this case means in a graph and at least one node in-/output connected to another node.
/// </summary>
/// <param name="param">The parameter.</param>
/// <returns>If the parameter is referenced.</returns>
bool IsParamUsed(SurfaceParameter param);
}
}

View File

@@ -39,6 +39,7 @@ namespace FlaxEditor.Surface
typeof(TooltipAttribute),
typeof(HideInEditorAttribute),
typeof(NoAnimateAttribute),
typeof(ButtonAttribute),
};
/// <summary>

View File

@@ -84,5 +84,16 @@ namespace FlaxEditor.Surface
}
MarkAsEdited();
}
/// <inheritdoc />
public bool IsParamUsed(SurfaceParameter param)
{
for (int i = 0; i < Nodes.Count; i++)
{
if (Nodes[i] is IParametersDependantNode node && node.IsParamUsed(param))
return true;
}
return false;
}
}
}

View File

@@ -776,6 +776,15 @@ namespace FlaxEditor.Surface
private void DeleteParameter(int index)
{
var window = (IVisjectSurfaceWindow)Values[0];
SurfaceParameter param = window.VisjectSurface.Parameters[index];
if (Editor.Instance.Options.Options.Interface.WarnOnDeletingUsedVisjectParameter && window.VisjectSurface.IsParamUsed(param))
{
string msg = $"Delete parameter {param.Name}?\nParameter is being used in a graph.\n\nYou can disable this warning in the editor settings.";
if (MessageBox.Show(msg, "Delete parameter", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK)
return;
}
var action = new AddRemoveParamAction
{
Window = window,

View File

@@ -60,7 +60,7 @@ namespace FlaxEditor.Tools.Terrain.Paint
/// <summary>
/// The layer to paint with it.
/// </summary>
[EditorOrder(10), Tooltip("The layer to paint with it. Terrain material can access per-layer blend weight to perform materials or textures blending.")]
[EditorOrder(10), Tooltip("The layer to paint on. Terrain material can access a per-layer blend weight to perform material or texture blending."), CustomEditorAlias("FlaxEditor.CustomEditors.Editors.TerrainLayerEditor")]
public Layers Layer = Layers.Layer0;
/// <inheritdoc />

View File

@@ -21,21 +21,6 @@ namespace FlaxEditor.Tools.Terrain
[HideInEditor]
public class PaintTerrainGizmoMode : EditorGizmoMode
{
/// <summary>
/// The terrain layer names.
/// </summary>
public static readonly string[] TerrainLayerNames =
{
"Layer 0",
"Layer 1",
"Layer 2",
"Layer 3",
"Layer 4",
"Layer 5",
"Layer 6",
"Layer 7",
};
private struct SplatmapData
{
public IntPtr DataPtr;

View File

@@ -250,6 +250,8 @@ namespace FlaxEditor.Utilities
internal static Int2 DrawCurveTicks(DrawCurveTick drawTick, float[] tickSteps, ref float[] tickStrengths, float min, float max, float pixelRange, float minDistanceBetweenTicks = 20, float maxDistanceBetweenTicks = 60)
{
if (pixelRange <= Mathf.Epsilon || maxDistanceBetweenTicks <= minDistanceBetweenTicks)
return Int2.Zero;
if (tickStrengths == null || tickStrengths.Length != tickSteps.Length)
tickStrengths = new float[tickSteps.Length];
@@ -286,7 +288,7 @@ namespace FlaxEditor.Utilities
continue;
// Draw all ticks
int l = Mathf.Clamp(smallestTick + level, 0, tickSteps.Length - 1);
int l = Mathf.Clamp(smallestTick + level, 0, tickSteps.Length - 2);
var lStep = tickSteps[l];
var lNextStep = tickSteps[l + 1];
int startTick = Mathf.FloorToInt(min / lStep);

View File

@@ -79,6 +79,13 @@ namespace FlaxEditor.Viewport.Previews
_uiControlLinked.Control.Parent = null;
_uiControlLinked = null;
}
foreach (var child in _uiParentLink.Children.ToArray())
{
if (child is CanvasRootControl canvasRoot)
{
canvasRoot.Canvas.EditorOverride(null, null);
}
}
// Remove for the preview
Task.RemoveCustomActor(_instance);

View File

@@ -54,24 +54,24 @@ namespace FlaxEditor.Windows.Assets
[EditorOrder(10), EditorDisplay("General"), Tooltip("Material domain type.")]
public MaterialDomain Domain;
[EditorOrder(20), EditorDisplay("General"), Tooltip("Defines how material inputs and properties are combined to result the final surface color.")]
[EditorOrder(20), VisibleIf(nameof(IsStandard)), EditorDisplay("General"), Tooltip("Defines how material inputs and properties are combined to result the final surface color.")]
public MaterialShadingModel ShadingModel;
[EditorOrder(30), EditorDisplay("General"), Tooltip("Determinates how materials' color should be blended with the background colors.")]
[EditorOrder(30), VisibleIf(nameof(IsStandard)), EditorDisplay("General"), Tooltip("Determinates how materials' color should be blended with the background colors.")]
public MaterialBlendMode BlendMode;
// Rendering
[EditorOrder(100), DefaultValue(CullMode.Normal), EditorDisplay("Rendering"), Tooltip("Defines the primitives culling mode used during geometry rendering.")]
[EditorOrder(100), DefaultValue(CullMode.Normal), VisibleIf(nameof(IsStandard)), EditorDisplay("Rendering"), Tooltip("Defines the primitives culling mode used during geometry rendering.")]
public CullMode CullMode;
[EditorOrder(110), DefaultValue(false), EditorDisplay("Rendering"), Tooltip("If checked, geometry will be rendered in wireframe mode without solid triangles fill.")]
[EditorOrder(110), DefaultValue(false), VisibleIf(nameof(IsStandardOrGUI)), EditorDisplay("Rendering"), Tooltip("If checked, geometry will be rendered in wireframe mode without solid triangles fill.")]
public bool Wireframe;
[EditorOrder(120), DefaultValue(true), EditorDisplay("Rendering"), Tooltip("Enables performing depth test during material rendering.")]
[EditorOrder(120), DefaultValue(true), VisibleIf(nameof(IsStandard)), EditorDisplay("Rendering"), Tooltip("Enables performing depth test during material rendering.")]
public bool DepthTest;
[EditorOrder(130), DefaultValue(true), EditorDisplay("Rendering"), Tooltip("Enable writing to the depth buffer during material rendering.")]
[EditorOrder(130), DefaultValue(true), VisibleIf(nameof(IsStandard)), EditorDisplay("Rendering"), Tooltip("Enable writing to the depth buffer during material rendering.")]
public bool DepthWrite;
// Transparency
@@ -111,13 +111,13 @@ namespace FlaxEditor.Windows.Assets
// Misc
[EditorOrder(400), DefaultValue(false), EditorDisplay("Misc"), Tooltip("If checked, material input normal will be assumed as world-space rather than tangent-space.")]
[EditorOrder(400), DefaultValue(false), VisibleIf(nameof(IsStandard)), EditorDisplay("Misc"), Tooltip("If checked, material input normal will be assumed as world-space rather than tangent-space.")]
public bool InputWorldSpaceNormal;
[EditorOrder(410), DefaultValue(false), EditorDisplay("Misc", "Dithered LOD Transition"), Tooltip("If checked, material uses dithered model LOD transition for smoother LODs switching.")]
[EditorOrder(410), DefaultValue(false), VisibleIf(nameof(IsStandard)), EditorDisplay("Misc", "Dithered LOD Transition"), Tooltip("If checked, material uses dithered model LOD transition for smoother LODs switching.")]
public bool DitheredLODTransition;
[EditorOrder(420), DefaultValue(0.3f), EditorDisplay("Misc"), Tooltip("Controls mask values clipping point."), Limit(0.0f, 1.0f, 0.01f)]
[EditorOrder(420), DefaultValue(0.3f), VisibleIf(nameof(IsStandard)), EditorDisplay("Misc"), Tooltip("Controls mask values clipping point."), Limit(0.0f, 1.0f, 0.01f)]
public float MaskThreshold;
[EditorOrder(430), DefaultValue(MaterialDecalBlendingMode.Translucent), VisibleIf(nameof(IsDecal)), EditorDisplay("Misc"), Tooltip("The decal material blending mode.")]
@@ -144,7 +144,9 @@ namespace FlaxEditor.Windows.Assets
private bool IsPostProcess => Domain == MaterialDomain.PostProcess;
private bool IsDecal => Domain == MaterialDomain.Decal;
private bool IsGUI => Domain == MaterialDomain.GUI;
private bool IsStandard => Domain == MaterialDomain.Surface || Domain == MaterialDomain.Terrain || Domain == MaterialDomain.Particle || Domain == MaterialDomain.Deformable;
private bool IsStandardOrGUI => IsStandard || IsGUI;
/// <summary>
/// Gathers parameters from the specified material.

View File

@@ -52,12 +52,13 @@ namespace FlaxEditor.Windows
contextMenu.AddSeparator();
// Basic editing options
var firstSelection = hasSthSelected ? Editor.SceneEditing.Selection[0] as ActorNode : null;
b = contextMenu.AddButton("Rename", inputOptions.Rename, Rename);
b = contextMenu.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate);
b.Enabled = hasSthSelected;
b = contextMenu.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate);
b.Enabled = hasSthSelected && (firstSelection != null ? firstSelection.CanDuplicate : true);
if (isSingleActorSelected)
if (isSingleActorSelected && firstSelection?.Actor is not Scene)
{
var convertMenu = contextMenu.AddChildMenu("Convert");
convertMenu.ContextMenu.AutoSort = true;
@@ -117,31 +118,31 @@ namespace FlaxEditor.Windows
}
}
b = contextMenu.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete);
b.Enabled = hasSthSelected;
b.Enabled = hasSthSelected && (firstSelection != null ? firstSelection.CanDelete : true);
contextMenu.AddSeparator();
b = contextMenu.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy);
b.Enabled = hasSthSelected && (firstSelection != null ? firstSelection.CanCopyPaste : true);
b.Enabled = hasSthSelected;
contextMenu.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste);
b = contextMenu.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut);
b.Enabled = canEditScene;
b.Enabled = canEditScene && hasSthSelected && (firstSelection != null ? firstSelection.CanCopyPaste : true);
// Create option
contextMenu.AddSeparator();
b = contextMenu.AddButton("Parent to new Actor", inputOptions.GroupSelectedActors, Editor.SceneEditing.CreateParentForSelectedActors);
b.Enabled = canEditScene && hasSthSelected;
b.Enabled = canEditScene && hasSthSelected && firstSelection?.Actor is not Scene;
b = contextMenu.AddButton("Create Prefab", Editor.Prefabs.CreatePrefab);
b.Enabled = isSingleActorSelected &&
((ActorNode)Editor.SceneEditing.Selection[0]).CanCreatePrefab &&
(firstSelection != null ? firstSelection.CanCreatePrefab : false) &&
Editor.Windows.ContentWin.CurrentViewFolder.CanHaveAssets;
bool hasPrefabLink = canEditScene && isSingleActorSelected && (Editor.SceneEditing.Selection[0] as ActorNode).HasPrefabLink;
bool hasPrefabLink = canEditScene && isSingleActorSelected && (firstSelection != null ? firstSelection.HasPrefabLink : false);
if (hasPrefabLink)
{
contextMenu.AddButton("Select Prefab", Editor.Prefabs.SelectPrefab);