Merge remote-tracking branch 'origin/master' into 1.8
# Conflicts: # Flax.flaxproj
This commit is contained in:
BIN
Content/Shaders/DebugDraw.flax
(Stored with Git LFS)
BIN
Content/Shaders/DebugDraw.flax
(Stored with Git LFS)
Binary file not shown.
@@ -4,7 +4,7 @@
|
||||
"Major": 1,
|
||||
"Minor": 8,
|
||||
"Revision": 0,
|
||||
"Build": 65045
|
||||
"Build": 65046
|
||||
},
|
||||
"Company": "Flax",
|
||||
"Copyright": "Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.",
|
||||
|
||||
@@ -46,10 +46,13 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
|
||||
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
|
||||
XElement.ValueBox.BorderSelectedColor = AxisColorX;
|
||||
XElement.ValueBox.Category = Utils.ValueCategory.Distance;
|
||||
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
|
||||
YElement.ValueBox.BorderSelectedColor = AxisColorY;
|
||||
YElement.ValueBox.Category = Utils.ValueCategory.Distance;
|
||||
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
|
||||
ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
|
||||
ZElement.ValueBox.Category = Utils.ValueCategory.Distance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,10 +71,13 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
|
||||
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
|
||||
XElement.ValueBox.BorderSelectedColor = AxisColorX;
|
||||
XElement.ValueBox.Category = Utils.ValueCategory.Angle;
|
||||
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
|
||||
YElement.ValueBox.BorderSelectedColor = AxisColorY;
|
||||
YElement.ValueBox.Category = Utils.ValueCategory.Angle;
|
||||
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
|
||||
ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
|
||||
ZElement.ValueBox.Category = Utils.ValueCategory.Angle;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,32 +21,28 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_element = null;
|
||||
|
||||
// Try get limit attribute for value min/max range setting and slider speed
|
||||
var doubleValue = layout.DoubleValue();
|
||||
doubleValue.ValueBox.ValueChanged += OnValueChanged;
|
||||
doubleValue.ValueBox.SlidingEnd += ClearToken;
|
||||
_element = doubleValue;
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
var limit = attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
if (limit != null)
|
||||
var limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
doubleValue.SetLimits(limit);
|
||||
var valueCategory = ((ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute))?.Category ?? Utils.ValueCategory.None;
|
||||
if (valueCategory != Utils.ValueCategory.None)
|
||||
{
|
||||
// Use double value editor with limit
|
||||
var doubleValue = layout.DoubleValue();
|
||||
doubleValue.SetLimits((LimitAttribute)limit);
|
||||
doubleValue.ValueBox.ValueChanged += OnValueChanged;
|
||||
doubleValue.ValueBox.SlidingEnd += ClearToken;
|
||||
_element = doubleValue;
|
||||
return;
|
||||
doubleValue.SetCategory(valueCategory);
|
||||
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
|
||||
{
|
||||
menu.AddSeparator();
|
||||
var mb = menu.AddButton("Show formatted", bt => { doubleValue.SetCategory(bt.Checked ? valueCategory : Utils.ValueCategory.None); });
|
||||
mb.AutoCheck = true;
|
||||
mb.Checked = doubleValue.ValueBox.Category != Utils.ValueCategory.None;
|
||||
};
|
||||
}
|
||||
}
|
||||
if (_element == null)
|
||||
{
|
||||
// Use double value editor
|
||||
var doubleValue = layout.DoubleValue();
|
||||
doubleValue.ValueBox.ValueChanged += OnValueChanged;
|
||||
doubleValue.ValueBox.SlidingEnd += ClearToken;
|
||||
_element = doubleValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
using Utils = FlaxEngine.Utils;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
@@ -27,41 +28,39 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_element = null;
|
||||
|
||||
// Try get limit attribute for value min/max range setting and slider speed
|
||||
var attributes = Values.GetAttributes();
|
||||
var range = (RangeAttribute)attributes?.FirstOrDefault(x => x is RangeAttribute);
|
||||
if (range != null)
|
||||
{
|
||||
// Use slider
|
||||
var slider = layout.Slider();
|
||||
slider.Slider.SetLimits(range);
|
||||
slider.Slider.ValueChanged += OnValueChanged;
|
||||
slider.Slider.SlidingEnd += ClearToken;
|
||||
_element = slider;
|
||||
return;
|
||||
}
|
||||
|
||||
var floatValue = layout.FloatValue();
|
||||
floatValue.ValueBox.ValueChanged += OnValueChanged;
|
||||
floatValue.ValueBox.SlidingEnd += ClearToken;
|
||||
_element = floatValue;
|
||||
if (attributes != null)
|
||||
{
|
||||
var range = attributes.FirstOrDefault(x => x is RangeAttribute);
|
||||
if (range != null)
|
||||
var limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
floatValue.SetLimits(limit);
|
||||
var valueCategory = ((ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute))?.Category ?? Utils.ValueCategory.None;
|
||||
if (valueCategory != Utils.ValueCategory.None)
|
||||
{
|
||||
// Use slider
|
||||
var slider = layout.Slider();
|
||||
slider.SetLimits((RangeAttribute)range);
|
||||
slider.Slider.ValueChanged += OnValueChanged;
|
||||
slider.Slider.SlidingEnd += ClearToken;
|
||||
_element = slider;
|
||||
return;
|
||||
floatValue.SetCategory(valueCategory);
|
||||
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
|
||||
{
|
||||
menu.AddSeparator();
|
||||
var mb = menu.AddButton("Show formatted", bt => { floatValue.SetCategory(bt.Checked ? valueCategory : Utils.ValueCategory.None); });
|
||||
mb.AutoCheck = true;
|
||||
mb.Checked = floatValue.ValueBox.Category != Utils.ValueCategory.None;
|
||||
};
|
||||
}
|
||||
var limit = attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
if (limit != null)
|
||||
{
|
||||
// Use float value editor with limit
|
||||
var floatValue = layout.FloatValue();
|
||||
floatValue.SetLimits((LimitAttribute)limit);
|
||||
floatValue.ValueBox.ValueChanged += OnValueChanged;
|
||||
floatValue.ValueBox.SlidingEnd += ClearToken;
|
||||
_element = floatValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_element == null)
|
||||
{
|
||||
// Use float value editor
|
||||
var floatValue = layout.FloatValue();
|
||||
floatValue.ValueBox.ValueChanged += OnValueChanged;
|
||||
floatValue.ValueBox.SlidingEnd += ClearToken;
|
||||
_element = floatValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,14 +45,17 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
gridControl.SlotsVertically = 1;
|
||||
|
||||
XElement = grid.FloatValue();
|
||||
XElement.ValueBox.Category = Utils.ValueCategory.Angle;
|
||||
XElement.ValueBox.ValueChanged += OnValueChanged;
|
||||
XElement.ValueBox.SlidingEnd += ClearToken;
|
||||
|
||||
YElement = grid.FloatValue();
|
||||
YElement.ValueBox.Category = Utils.ValueCategory.Angle;
|
||||
YElement.ValueBox.ValueChanged += OnValueChanged;
|
||||
YElement.ValueBox.SlidingEnd += ClearToken;
|
||||
|
||||
ZElement = grid.FloatValue();
|
||||
ZElement.ValueBox.Category = Utils.ValueCategory.Angle;
|
||||
ZElement.ValueBox.ValueChanged += OnValueChanged;
|
||||
ZElement.ValueBox.SlidingEnd += ClearToken;
|
||||
|
||||
|
||||
@@ -70,25 +70,44 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
|
||||
LimitAttribute limit = null;
|
||||
var attributes = Values.GetAttributes();
|
||||
var category = Utils.ValueCategory.None;
|
||||
if (attributes != null)
|
||||
{
|
||||
limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
var categoryAttribute = (ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute);
|
||||
if (categoryAttribute != null)
|
||||
category = categoryAttribute.Category;
|
||||
}
|
||||
|
||||
XElement = grid.FloatValue();
|
||||
XElement.SetLimits(limit);
|
||||
XElement.SetCategory(category);
|
||||
XElement.ValueBox.ValueChanged += OnXValueChanged;
|
||||
XElement.ValueBox.SlidingEnd += ClearToken;
|
||||
|
||||
YElement = grid.FloatValue();
|
||||
YElement.SetLimits(limit);
|
||||
YElement.SetCategory(category);
|
||||
YElement.ValueBox.ValueChanged += OnYValueChanged;
|
||||
YElement.ValueBox.SlidingEnd += ClearToken;
|
||||
|
||||
ZElement = grid.FloatValue();
|
||||
ZElement.SetLimits(limit);
|
||||
ZElement.SetCategory(category);
|
||||
ZElement.ValueBox.ValueChanged += OnZValueChanged;
|
||||
ZElement.ValueBox.SlidingEnd += ClearToken;
|
||||
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
|
||||
{
|
||||
menu.AddSeparator();
|
||||
var mb = menu.AddButton("Show formatted", bt =>
|
||||
{
|
||||
XElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
|
||||
YElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
|
||||
ZElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
|
||||
});
|
||||
mb.AutoCheck = true;
|
||||
mb.Checked = XElement.ValueBox.Category != Utils.ValueCategory.None;
|
||||
};
|
||||
}
|
||||
|
||||
private void OnXValueChanged()
|
||||
@@ -248,26 +267,45 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
gridControl.SlotsVertically = 1;
|
||||
|
||||
LimitAttribute limit = null;
|
||||
Utils.ValueCategory category = Utils.ValueCategory.None;
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
var categoryAttribute = (ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute);
|
||||
if (categoryAttribute != null)
|
||||
category = categoryAttribute.Category;
|
||||
}
|
||||
|
||||
XElement = grid.DoubleValue();
|
||||
XElement.SetLimits(limit);
|
||||
XElement.SetCategory(category);
|
||||
XElement.ValueBox.ValueChanged += OnValueChanged;
|
||||
XElement.ValueBox.SlidingEnd += ClearToken;
|
||||
|
||||
YElement = grid.DoubleValue();
|
||||
YElement.SetLimits(limit);
|
||||
YElement.SetCategory(category);
|
||||
YElement.ValueBox.ValueChanged += OnValueChanged;
|
||||
YElement.ValueBox.SlidingEnd += ClearToken;
|
||||
|
||||
ZElement = grid.DoubleValue();
|
||||
ZElement.SetLimits(limit);
|
||||
ZElement.SetCategory(category);
|
||||
ZElement.ValueBox.ValueChanged += OnValueChanged;
|
||||
ZElement.ValueBox.SlidingEnd += ClearToken;
|
||||
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
|
||||
{
|
||||
menu.AddSeparator();
|
||||
var mb = menu.AddButton("Show formatted", bt =>
|
||||
{
|
||||
XElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
|
||||
YElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
|
||||
ZElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
|
||||
});
|
||||
mb.AutoCheck = true;
|
||||
mb.Checked = XElement.ValueBox.Category != Utils.ValueCategory.None;
|
||||
};
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
|
||||
@@ -51,6 +51,15 @@ namespace FlaxEditor.CustomEditors.Elements
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor value category.
|
||||
/// </summary>
|
||||
/// <param name="category">The category.</param>
|
||||
public void SetCategory(Utils.ValueCategory category)
|
||||
{
|
||||
ValueBox.Category = category;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor limits from member <see cref="LimitAttribute"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -51,6 +51,15 @@ namespace FlaxEditor.CustomEditors.Elements
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor value category.
|
||||
/// </summary>
|
||||
/// <param name="category">The category.</param>
|
||||
public void SetCategory(Utils.ValueCategory category)
|
||||
{
|
||||
ValueBox.Category = category;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor limits from member <see cref="LimitAttribute"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -290,7 +290,6 @@ namespace FlaxEditor
|
||||
|
||||
StateMachine = new EditorStateMachine(this);
|
||||
Undo = new EditorUndo(this);
|
||||
UIControl.FallbackParentGetDelegate += OnUIControlFallbackParentGet;
|
||||
|
||||
if (newProject)
|
||||
InitProject();
|
||||
@@ -355,27 +354,6 @@ namespace FlaxEditor
|
||||
StateMachine.LoadingState.StartInitEnding(skipCompile);
|
||||
}
|
||||
|
||||
private ContainerControl OnUIControlFallbackParentGet(UIControl control)
|
||||
{
|
||||
// Check if prefab root control is this UIControl
|
||||
var loadingPreview = Viewport.Previews.PrefabPreview.LoadingPreview;
|
||||
var activePreviews = Viewport.Previews.PrefabPreview.ActivePreviews;
|
||||
if (activePreviews != null)
|
||||
{
|
||||
foreach (var preview in activePreviews)
|
||||
{
|
||||
if (preview == loadingPreview ||
|
||||
(preview.Instance != null && (preview.Instance == control || preview.Instance.HasActorInHierarchy(control))))
|
||||
{
|
||||
// Link it to the prefab preview to see it in the editor
|
||||
preview.customControlLinked = control;
|
||||
return preview;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
internal void RegisterModule(EditorModule module)
|
||||
{
|
||||
Log("Register Editor module " + module);
|
||||
|
||||
@@ -431,7 +431,6 @@ namespace FlaxEditor.GUI
|
||||
/// </summary>
|
||||
protected CurveEditor()
|
||||
{
|
||||
_tickStrengths = new float[TickSteps.Length];
|
||||
Accessor.GetDefaultValue(out DefaultValue);
|
||||
|
||||
var style = Style.Current;
|
||||
@@ -780,75 +779,31 @@ namespace FlaxEditor.GUI
|
||||
return _mainPanel.PointToParent(point);
|
||||
}
|
||||
|
||||
private void DrawAxis(Float2 axis, ref Rectangle viewRect, float min, float max, float pixelRange)
|
||||
private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange)
|
||||
{
|
||||
int minDistanceBetweenTicks = 20;
|
||||
int maxDistanceBetweenTicks = 60;
|
||||
var range = max - min;
|
||||
|
||||
// Find the strength for each modulo number tick marker
|
||||
int smallestTick = 0;
|
||||
int biggestTick = TickSteps.Length - 1;
|
||||
for (int i = TickSteps.Length - 1; i >= 0; i--)
|
||||
Utilities.Utils.DrawCurveTicks((float tick, float strength) =>
|
||||
{
|
||||
// Calculate how far apart these modulo tick steps are spaced
|
||||
float tickSpacing = TickSteps[i] * pixelRange / range;
|
||||
var p = PointFromKeyframes(axis * tick, ref viewRect);
|
||||
|
||||
// Calculate the strength of the tick markers based on the spacing
|
||||
_tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks));
|
||||
// Draw line
|
||||
var lineRect = new Rectangle
|
||||
(
|
||||
viewRect.Location + (p - 0.5f) * axis,
|
||||
Float2.Lerp(viewRect.Size, Float2.One, axis)
|
||||
);
|
||||
Render2D.FillRectangle(lineRect, _linesColor.AlphaMultiplied(strength));
|
||||
|
||||
// Beyond threshold the ticks don't get any bigger or fatter
|
||||
if (_tickStrengths[i] >= 1)
|
||||
biggestTick = i;
|
||||
|
||||
// Do not show small tick markers
|
||||
if (tickSpacing <= minDistanceBetweenTicks)
|
||||
{
|
||||
smallestTick = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw all tick levels
|
||||
int tickLevels = biggestTick - smallestTick + 1;
|
||||
for (int level = 0; level < tickLevels; level++)
|
||||
{
|
||||
float strength = _tickStrengths[smallestTick + level];
|
||||
if (strength <= Mathf.Epsilon)
|
||||
continue;
|
||||
|
||||
// Draw all ticks
|
||||
int l = Mathf.Clamp(smallestTick + level, 0, TickSteps.Length - 1);
|
||||
int startTick = Mathf.FloorToInt(min / TickSteps[l]);
|
||||
int endTick = Mathf.CeilToInt(max / TickSteps[l]);
|
||||
for (int i = startTick; i <= endTick; i++)
|
||||
{
|
||||
if (l < biggestTick && (i % Mathf.RoundToInt(TickSteps[l + 1] / TickSteps[l]) == 0))
|
||||
continue;
|
||||
|
||||
var tick = i * TickSteps[l];
|
||||
var p = PointFromKeyframes(axis * tick, ref viewRect);
|
||||
|
||||
// Draw line
|
||||
var lineRect = new Rectangle
|
||||
(
|
||||
viewRect.Location + (p - 0.5f) * axis,
|
||||
Float2.Lerp(viewRect.Size, Float2.One, axis)
|
||||
);
|
||||
Render2D.FillRectangle(lineRect, _linesColor.AlphaMultiplied(strength));
|
||||
|
||||
// Draw label
|
||||
string label = tick.ToString(CultureInfo.InvariantCulture);
|
||||
var labelRect = new Rectangle
|
||||
(
|
||||
viewRect.X + 4.0f + (p.X * axis.X),
|
||||
viewRect.Y - LabelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X),
|
||||
50,
|
||||
LabelsSize
|
||||
);
|
||||
Render2D.DrawText(_labelsFont, label, labelRect, _labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
|
||||
}
|
||||
}
|
||||
// Draw label
|
||||
string label = tick.ToString(CultureInfo.InvariantCulture);
|
||||
var labelRect = new Rectangle
|
||||
(
|
||||
viewRect.X + 4.0f + (p.X * axis.X),
|
||||
viewRect.Y - LabelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X),
|
||||
50,
|
||||
LabelsSize
|
||||
);
|
||||
Render2D.DrawText(_labelsFont, label, labelRect, _labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
|
||||
}, TickSteps, ref _tickStrengths, min, max, pixelRange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -890,9 +845,9 @@ namespace FlaxEditor.GUI
|
||||
Render2D.PushClip(ref viewRect);
|
||||
|
||||
if ((ShowAxes & UseMode.Vertical) == UseMode.Vertical)
|
||||
DrawAxis(Float2.UnitX, ref viewRect, min.X, max.X, pixelRange.X);
|
||||
DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X);
|
||||
if ((ShowAxes & UseMode.Horizontal) == UseMode.Horizontal)
|
||||
DrawAxis(Float2.UnitY, ref viewRect, min.Y, max.Y, pixelRange.Y);
|
||||
DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y);
|
||||
|
||||
Render2D.PopClip();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine;
|
||||
using Utils = FlaxEngine.Utils;
|
||||
|
||||
namespace FlaxEditor.GUI.Input
|
||||
{
|
||||
@@ -13,6 +14,8 @@ namespace FlaxEditor.GUI.Input
|
||||
[HideInEditor]
|
||||
public class DoubleValueBox : ValueBox<double>
|
||||
{
|
||||
private Utils.ValueCategory _category = Utils.ValueCategory.None;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override double Value
|
||||
{
|
||||
@@ -129,10 +132,25 @@ namespace FlaxEditor.GUI.Input
|
||||
Value = Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the category of the value. This can be none for just a number or a more specific one like a distance.
|
||||
/// </summary>
|
||||
public Utils.ValueCategory Category
|
||||
{
|
||||
get => _category;
|
||||
set
|
||||
{
|
||||
if (_category == value)
|
||||
return;
|
||||
_category = value;
|
||||
UpdateText();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected sealed override void UpdateText()
|
||||
{
|
||||
SetText(Utilities.Utils.FormatFloat(_value));
|
||||
SetText(Utilities.Utils.FormatFloat(_value, Category));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine;
|
||||
using Utils = FlaxEngine.Utils;
|
||||
|
||||
namespace FlaxEditor.GUI.Input
|
||||
{
|
||||
@@ -14,6 +14,8 @@ namespace FlaxEditor.GUI.Input
|
||||
[HideInEditor]
|
||||
public class FloatValueBox : ValueBox<float>
|
||||
{
|
||||
private Utils.ValueCategory _category = Utils.ValueCategory.None;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float Value
|
||||
{
|
||||
@@ -137,10 +139,25 @@ namespace FlaxEditor.GUI.Input
|
||||
Value = Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the category of the value. This can be none for just a number or a more specific one like a distance.
|
||||
/// </summary>
|
||||
public Utils.ValueCategory Category
|
||||
{
|
||||
get => _category;
|
||||
set
|
||||
{
|
||||
if (_category == value)
|
||||
return;
|
||||
_category = value;
|
||||
UpdateText();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected sealed override void UpdateText()
|
||||
{
|
||||
SetText(Utilities.Utils.FormatFloat(_value));
|
||||
SetText(Utilities.Utils.FormatFloat(_value, Category));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
{
|
||||
_timeline = timeline;
|
||||
_tickSteps = Utilities.Utils.CurveTickSteps;
|
||||
_tickStrengths = new float[_tickSteps.Length];
|
||||
}
|
||||
|
||||
private void UpdateSelectionRectangle()
|
||||
@@ -173,55 +172,20 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
var rightFrame = Mathf.Ceil((right - Timeline.StartOffset) / zoom) * _timeline.FramesPerSecond;
|
||||
var min = leftFrame;
|
||||
var max = rightFrame;
|
||||
int smallestTick = 0;
|
||||
int biggestTick = _tickSteps.Length - 1;
|
||||
for (int i = _tickSteps.Length - 1; i >= 0; i--)
|
||||
{
|
||||
// Calculate how far apart these modulo tick steps are spaced
|
||||
float tickSpacing = _tickSteps[i] * _timeline.Zoom;
|
||||
|
||||
// Calculate the strength of the tick markers based on the spacing
|
||||
_tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks));
|
||||
|
||||
// Beyond threshold the ticks don't get any bigger or fatter
|
||||
if (_tickStrengths[i] >= 1)
|
||||
biggestTick = i;
|
||||
|
||||
// Do not show small tick markers
|
||||
if (tickSpacing <= minDistanceBetweenTicks)
|
||||
{
|
||||
smallestTick = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int tickLevels = biggestTick - smallestTick + 1;
|
||||
|
||||
// Draw vertical lines for time axis
|
||||
for (int level = 0; level < tickLevels; level++)
|
||||
var pixelsInRange = _timeline.Zoom;
|
||||
var pixelRange = pixelsInRange * (max - min);
|
||||
var tickRange = Utilities.Utils.DrawCurveTicks((float tick, float strength) =>
|
||||
{
|
||||
float strength = _tickStrengths[smallestTick + level];
|
||||
if (strength <= Mathf.Epsilon)
|
||||
continue;
|
||||
|
||||
// Draw all ticks
|
||||
int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 1);
|
||||
var lStep = _tickSteps[l];
|
||||
var lNextStep = _tickSteps[l + 1];
|
||||
int startTick = Mathf.FloorToInt(min / lStep);
|
||||
int endTick = Mathf.CeilToInt(max / lStep);
|
||||
Color lineColor = style.ForegroundDisabled.RGBMultiplied(0.7f).AlphaMultiplied(strength);
|
||||
for (int i = startTick; i <= endTick; i++)
|
||||
{
|
||||
if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0))
|
||||
continue;
|
||||
var tick = i * lStep;
|
||||
var time = tick / _timeline.FramesPerSecond;
|
||||
var x = time * zoom + Timeline.StartOffset;
|
||||
|
||||
// Draw line
|
||||
Render2D.FillRectangle(new Rectangle(x - 0.5f, 0, 1.0f, height), lineColor);
|
||||
}
|
||||
}
|
||||
var time = tick / _timeline.FramesPerSecond;
|
||||
var x = time * zoom + Timeline.StartOffset;
|
||||
var lineColor = style.ForegroundDisabled.RGBMultiplied(0.7f).AlphaMultiplied(strength);
|
||||
Render2D.FillRectangle(new Rectangle(x - 0.5f, 0, 1.0f, height), lineColor);
|
||||
}, _tickSteps, ref _tickStrengths, min, max, pixelRange, minDistanceBetweenTicks, maxDistanceBetweenTicks);
|
||||
var smallestTick = tickRange.X;
|
||||
var biggestTick = tickRange.Y;
|
||||
var tickLevels = biggestTick - smallestTick + 1;
|
||||
|
||||
// Draw selection rectangle
|
||||
if (_isSelecting)
|
||||
|
||||
@@ -161,5 +161,20 @@ namespace FlaxEditor.Gizmo
|
||||
}
|
||||
throw new ArgumentException("Not added mode to activate.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the gizmo of a given type or returns null if not added.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the gizmo.</typeparam>
|
||||
/// <returns>Found gizmo or null.</returns>
|
||||
public T Get<T>() where T : GizmoBase
|
||||
{
|
||||
foreach (var e in this)
|
||||
{
|
||||
if (e is T asT)
|
||||
return asT;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace FlaxEditor.Gizmo
|
||||
{
|
||||
Order = -100;
|
||||
UseSingleTarget = true;
|
||||
Location = PostProcessEffectLocation.BeforeForwardPass;
|
||||
Location = PostProcessEffectLocation.AfterAntiAliasingPass;
|
||||
}
|
||||
|
||||
~Renderer()
|
||||
@@ -46,7 +46,8 @@ namespace FlaxEditor.Gizmo
|
||||
var plane = new Plane(Vector3.Zero, Vector3.UnitY);
|
||||
var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos);
|
||||
|
||||
float space = Editor.Instance.Options.Options.Viewport.ViewportGridScale, size;
|
||||
var options = Editor.Instance.Options.Options;
|
||||
float space = options.Viewport.ViewportGridScale, size;
|
||||
if (dst <= 500.0f)
|
||||
{
|
||||
size = 8000;
|
||||
@@ -62,8 +63,12 @@ namespace FlaxEditor.Gizmo
|
||||
size = 100000;
|
||||
}
|
||||
|
||||
Color color = Color.Gray * 0.7f;
|
||||
float bigLineIntensity = 0.8f;
|
||||
Color bigColor = Color.Gray * bigLineIntensity;
|
||||
Color color = bigColor * 0.8f;
|
||||
int count = (int)(size / space);
|
||||
int midLine = count / 2;
|
||||
int bigLinesMod = count / 8;
|
||||
|
||||
Vector3 start = new Vector3(0, 0, size * -0.5f);
|
||||
Vector3 end = new Vector3(0, 0, size * 0.5f);
|
||||
@@ -71,7 +76,12 @@ namespace FlaxEditor.Gizmo
|
||||
for (int i = 0; i <= count; i++)
|
||||
{
|
||||
start.X = end.X = i * space + start.Z;
|
||||
DebugDraw.DrawLine(start, end, color);
|
||||
Color lineColor = color;
|
||||
if (i == midLine)
|
||||
lineColor = Color.Blue * bigLineIntensity;
|
||||
else if (i % bigLinesMod == 0)
|
||||
lineColor = bigColor;
|
||||
DebugDraw.DrawLine(start, end, lineColor);
|
||||
}
|
||||
|
||||
start = new Vector3(size * -0.5f, 0, 0);
|
||||
@@ -80,7 +90,12 @@ namespace FlaxEditor.Gizmo
|
||||
for (int i = 0; i <= count; i++)
|
||||
{
|
||||
start.Z = end.Z = i * space + start.X;
|
||||
DebugDraw.DrawLine(start, end, color);
|
||||
Color lineColor = color;
|
||||
if (i == midLine)
|
||||
lineColor = Color.Red * bigLineIntensity;
|
||||
else if (i % bigLinesMod == 0)
|
||||
lineColor = bigColor;
|
||||
DebugDraw.DrawLine(start, end, lineColor);
|
||||
}
|
||||
|
||||
DebugDraw.Draw(ref renderContext, input.View(), null, true);
|
||||
|
||||
@@ -117,5 +117,10 @@ namespace FlaxEditor.Gizmo
|
||||
/// </summary>
|
||||
/// <param name="actor">The new actor to spawn.</param>
|
||||
void Spawn(Actor actor);
|
||||
|
||||
/// <summary>
|
||||
/// Opens the context menu at the current mouse location (using current selection).
|
||||
/// </summary>
|
||||
void OpenContextMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,11 @@ namespace FlaxEditor.Gizmo
|
||||
/// </summary>
|
||||
public Action Duplicate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the array of selected objects.
|
||||
/// </summary>
|
||||
public List<SceneGraphNode> Selection => _selection;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the array of selected parent objects (as actors).
|
||||
/// </summary>
|
||||
|
||||
891
Source/Editor/Gizmo/UIEditorGizmo.cs
Normal file
891
Source/Editor/Gizmo/UIEditorGizmo.cs
Normal file
@@ -0,0 +1,891 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.SceneGraph.Actors;
|
||||
using FlaxEditor.Viewport.Cameras;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// UI editor camera.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
internal sealed class UIEditorCamera : ViewportCamera
|
||||
{
|
||||
public UIEditorRoot UIEditor;
|
||||
|
||||
public void ShowActors(IEnumerable<Actor> actors)
|
||||
{
|
||||
var root = UIEditor.UIRoot;
|
||||
if (root == null)
|
||||
return;
|
||||
|
||||
// Calculate bounds of all selected objects
|
||||
var areaRect = Rectangle.Empty;
|
||||
foreach (var actor in actors)
|
||||
{
|
||||
Rectangle bounds;
|
||||
if (actor is UIControl uiControl && uiControl.HasControl && uiControl.IsActive)
|
||||
{
|
||||
var control = uiControl.Control;
|
||||
bounds = control.EditorBounds;
|
||||
|
||||
var ul = control.PointToParent(root, bounds.UpperLeft);
|
||||
var ur = control.PointToParent(root, bounds.UpperRight);
|
||||
var bl = control.PointToParent(root, bounds.BottomLeft);
|
||||
var br = control.PointToParent(root, bounds.BottomRight);
|
||||
|
||||
var min = Float2.Min(Float2.Min(ul, ur), Float2.Min(bl, br));
|
||||
var max = Float2.Max(Float2.Max(ul, ur), Float2.Max(bl, br));
|
||||
bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero));
|
||||
}
|
||||
else if (actor is UICanvas uiCanvas && uiCanvas.IsActive && uiCanvas.GUI.Parent == root)
|
||||
{
|
||||
bounds = uiCanvas.GUI.Bounds;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
|
||||
if (areaRect == Rectangle.Empty)
|
||||
areaRect = bounds;
|
||||
else
|
||||
areaRect = Rectangle.Union(areaRect, bounds);
|
||||
}
|
||||
if (areaRect == Rectangle.Empty)
|
||||
return;
|
||||
|
||||
// Add margin
|
||||
areaRect = areaRect.MakeExpanded(100.0f);
|
||||
|
||||
// Show bounds
|
||||
UIEditor.ViewScale = (UIEditor.Size / areaRect.Size).MinValue * 0.95f;
|
||||
UIEditor.ViewCenterPosition = areaRect.Center;
|
||||
}
|
||||
|
||||
public override void FocusSelection(GizmosCollection gizmos, ref Quaternion orientation)
|
||||
{
|
||||
ShowActors(gizmos.Get<TransformGizmo>().Selection, ref orientation);
|
||||
}
|
||||
|
||||
public override void ShowActor(Actor actor)
|
||||
{
|
||||
ShowActors(new[] { actor });
|
||||
}
|
||||
|
||||
public override void ShowActors(List<SceneGraphNode> selection, ref Quaternion orientation)
|
||||
{
|
||||
ShowActors(selection.ConvertAll(x => (Actor)x.EditableObject));
|
||||
}
|
||||
|
||||
public override void UpdateView(float dt, ref Vector3 moveDelta, ref Float2 mouseDelta, out bool centerMouse)
|
||||
{
|
||||
centerMouse = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Root control for UI Controls presentation in the game/prefab viewport.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
internal class UIEditorRoot : InputsPassThrough
|
||||
{
|
||||
/// <summary>
|
||||
/// View for the UI structure to be linked in for camera zoom and panning operations.
|
||||
/// </summary>
|
||||
private sealed class View : ContainerControl
|
||||
{
|
||||
public View(UIEditorRoot parent)
|
||||
{
|
||||
AutoFocus = false;
|
||||
ClipChildren = false;
|
||||
CullChildren = false;
|
||||
Pivot = Float2.Zero;
|
||||
Size = new Float2(1920, 1080);
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public override bool RayCast(ref Float2 location, out Control hit)
|
||||
{
|
||||
// Ignore self
|
||||
return RayCastChildren(ref location, out hit);
|
||||
}
|
||||
|
||||
public override bool IntersectsContent(ref Float2 locationParent, out Float2 location)
|
||||
{
|
||||
location = PointFromParent(ref locationParent);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void DrawSelf()
|
||||
{
|
||||
var uiRoot = (UIEditorRoot)Parent;
|
||||
if (!uiRoot.EnableBackground)
|
||||
return;
|
||||
|
||||
// Draw canvas area
|
||||
var bounds = new Rectangle(Float2.Zero, Size);
|
||||
Render2D.FillRectangle(bounds, new Color(0, 0, 0, 0.2f));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cached placement of the widget used to size/edit control
|
||||
/// </summary>
|
||||
private struct Widget
|
||||
{
|
||||
public UIControl UIControl;
|
||||
public Rectangle Bounds;
|
||||
public Float2 ResizeAxis;
|
||||
public CursorType Cursor;
|
||||
}
|
||||
|
||||
private bool _mouseMovesControl, _mouseMovesView, _mouseMovesWidget;
|
||||
private Float2 _mouseMovesPos, _moveSnapDelta;
|
||||
private float _mouseMoveSum;
|
||||
private UndoMultiBlock _undoBlock;
|
||||
private View _view;
|
||||
private float[] _gridTickSteps = Utilities.Utils.CurveTickSteps, _gridTickStrengths;
|
||||
private List<Widget> _widgets;
|
||||
private Widget _activeWidget;
|
||||
|
||||
/// <summary>
|
||||
/// True if enable displaying UI editing background and grid elements.
|
||||
/// </summary>
|
||||
public virtual bool EnableBackground => false;
|
||||
|
||||
/// <summary>
|
||||
/// True if enable selecting controls with mouse button.
|
||||
/// </summary>
|
||||
public virtual bool EnableSelecting => false;
|
||||
|
||||
/// <summary>
|
||||
/// True if enable panning and zooming the view.
|
||||
/// </summary>
|
||||
public bool EnableCamera => _view != null && EnableBackground;
|
||||
|
||||
/// <summary>
|
||||
/// Transform gizmo to use sync with (selection, snapping, transformation settings).
|
||||
/// </summary>
|
||||
public virtual TransformGizmo TransformGizmo => null;
|
||||
|
||||
/// <summary>
|
||||
/// The root control for controls to be linked in.
|
||||
/// </summary>
|
||||
public readonly ContainerControl UIRoot;
|
||||
|
||||
internal Float2 ViewPosition
|
||||
{
|
||||
get => _view.Location / -ViewScale;
|
||||
set => _view.Location = value * -ViewScale;
|
||||
}
|
||||
|
||||
internal Float2 ViewCenterPosition
|
||||
{
|
||||
get => (_view.Location - Size * 0.5f) / -ViewScale;
|
||||
set => _view.Location = Size * 0.5f + value * -ViewScale;
|
||||
}
|
||||
|
||||
internal float ViewScale
|
||||
{
|
||||
get => _view?.Scale.X ?? 1;
|
||||
set
|
||||
{
|
||||
if (_view == null)
|
||||
return;
|
||||
value = Mathf.Clamp(value, 0.1f, 4.0f);
|
||||
_view.Scale = new Float2(value);
|
||||
}
|
||||
}
|
||||
|
||||
public UIEditorRoot(bool enableCamera = false)
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll;
|
||||
Offsets = Margin.Zero;
|
||||
AutoFocus = false;
|
||||
UIRoot = this;
|
||||
CullChildren = false;
|
||||
ClipChildren = true;
|
||||
if (enableCamera)
|
||||
{
|
||||
_view = new View(this);
|
||||
UIRoot = _view;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDown(location, button))
|
||||
return true;
|
||||
|
||||
var transformGizmo = TransformGizmo;
|
||||
var owner = transformGizmo?.Owner;
|
||||
if (_widgets != null && _widgets.Count != 0 && button == MouseButton.Left)
|
||||
{
|
||||
foreach (var widget in _widgets)
|
||||
{
|
||||
if (widget.Bounds.Contains(ref location))
|
||||
{
|
||||
// Initialize widget movement
|
||||
_activeWidget = widget;
|
||||
_mouseMovesWidget = true;
|
||||
_mouseMovesPos = location;
|
||||
Cursor = widget.Cursor;
|
||||
StartUndo();
|
||||
Focus();
|
||||
StartMouseCapture();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (EnableSelecting && owner != null && !_mouseMovesControl && button == MouseButton.Left)
|
||||
{
|
||||
// Raycast the control under the mouse
|
||||
var mousePos = PointFromWindow(RootWindow.MousePosition);
|
||||
if (RayCastControl(ref mousePos, out var hitControl))
|
||||
{
|
||||
var uiControlNode = FindUIControlNode(hitControl);
|
||||
if (uiControlNode != null)
|
||||
{
|
||||
// Select node (with additive mode)
|
||||
var selection = new List<SceneGraphNode>();
|
||||
if (Root.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
// Add/remove from selection
|
||||
selection.AddRange(transformGizmo.Selection);
|
||||
if (transformGizmo.Selection.Contains(uiControlNode))
|
||||
selection.Remove(uiControlNode);
|
||||
else
|
||||
selection.Add(uiControlNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Select
|
||||
selection.Add(uiControlNode);
|
||||
}
|
||||
owner.Select(selection);
|
||||
|
||||
// Initialize control movement
|
||||
_mouseMovesControl = true;
|
||||
_mouseMovesPos = location;
|
||||
_mouseMoveSum = 0.0f;
|
||||
_moveSnapDelta = Float2.Zero;
|
||||
Focus();
|
||||
StartMouseCapture();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Allow deselecting if user clicks on nothing
|
||||
else
|
||||
{
|
||||
owner.Select(null);
|
||||
}
|
||||
}
|
||||
if (EnableCamera && (button == MouseButton.Right || button == MouseButton.Middle))
|
||||
{
|
||||
// Initialize surface movement
|
||||
_mouseMovesView = true;
|
||||
_mouseMovesPos = location;
|
||||
_mouseMoveSum = 0.0f;
|
||||
Focus();
|
||||
StartMouseCapture();
|
||||
return true;
|
||||
}
|
||||
|
||||
return Focus(this);
|
||||
}
|
||||
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
base.OnMouseMove(location);
|
||||
|
||||
// Change cursor if mouse is over active control widget
|
||||
bool cursorChanged = false;
|
||||
if (_widgets != null && _widgets.Count != 0 && !_mouseMovesControl && !_mouseMovesWidget && !_mouseMovesView)
|
||||
{
|
||||
foreach (var widget in _widgets)
|
||||
{
|
||||
if (widget.Bounds.Contains(ref location))
|
||||
{
|
||||
Cursor = widget.Cursor;
|
||||
cursorChanged = true;
|
||||
}
|
||||
else if (Cursor != CursorType.Default && !cursorChanged)
|
||||
{
|
||||
Cursor = CursorType.Default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var transformGizmo = TransformGizmo;
|
||||
if (_mouseMovesControl && transformGizmo != null)
|
||||
{
|
||||
// Calculate transform delta
|
||||
var delta = location - _mouseMovesPos;
|
||||
if (transformGizmo.TranslationSnapEnable || transformGizmo.Owner.UseSnapping)
|
||||
{
|
||||
_moveSnapDelta += delta;
|
||||
delta = Float2.SnapToGrid(_moveSnapDelta, new Float2(transformGizmo.TranslationSnapValue * ViewScale));
|
||||
_moveSnapDelta -= delta;
|
||||
}
|
||||
|
||||
// Move selected controls
|
||||
if (delta.LengthSquared > 0.0f)
|
||||
{
|
||||
StartUndo();
|
||||
var moved = false;
|
||||
var moveLocation = _mouseMovesPos + delta;
|
||||
var selection = transformGizmo.Selection;
|
||||
for (var i = 0; i < selection.Count; i++)
|
||||
{
|
||||
if (IsValidControl(selection[i], out var uiControl))
|
||||
{
|
||||
// Move control (handle any control transformations by moving in editor's local-space)
|
||||
var control = uiControl.Control;
|
||||
var localLocation = control.LocalLocation;
|
||||
var uiControlDelta = GetControlDelta(control, ref _mouseMovesPos, ref moveLocation);
|
||||
control.LocalLocation = localLocation + uiControlDelta;
|
||||
|
||||
// Don't move if layout doesn't allow it
|
||||
if (control.Parent != null)
|
||||
control.Parent.PerformLayout();
|
||||
else
|
||||
control.PerformLayout();
|
||||
|
||||
// Check if control was moved (parent container could block it)
|
||||
if (localLocation != control.LocalLocation)
|
||||
moved = true;
|
||||
}
|
||||
}
|
||||
_mouseMovesPos = location;
|
||||
_mouseMoveSum += delta.Length;
|
||||
if (moved)
|
||||
Cursor = CursorType.SizeAll;
|
||||
}
|
||||
}
|
||||
if (_mouseMovesWidget && _activeWidget.UIControl)
|
||||
{
|
||||
// Calculate transform delta
|
||||
var resizeAxisAbs = _activeWidget.ResizeAxis.Absolute;
|
||||
var resizeAxisPos = Float2.Clamp(_activeWidget.ResizeAxis, Float2.Zero, Float2.One);
|
||||
var resizeAxisNeg = Float2.Clamp(-_activeWidget.ResizeAxis, Float2.Zero, Float2.One);
|
||||
var delta = location - _mouseMovesPos;
|
||||
// TODO: scale/size snapping?
|
||||
delta *= resizeAxisAbs;
|
||||
|
||||
// Resize control via widget
|
||||
var moveLocation = _mouseMovesPos + delta;
|
||||
var control = _activeWidget.UIControl.Control;
|
||||
var uiControlDelta = GetControlDelta(control, ref _mouseMovesPos, ref moveLocation);
|
||||
control.LocalLocation += uiControlDelta * resizeAxisNeg;
|
||||
control.Size += uiControlDelta * resizeAxisPos - uiControlDelta * resizeAxisNeg;
|
||||
|
||||
// Don't move if layout doesn't allow it
|
||||
if (control.Parent != null)
|
||||
control.Parent.PerformLayout();
|
||||
else
|
||||
control.PerformLayout();
|
||||
|
||||
_mouseMovesPos = location;
|
||||
}
|
||||
if (_mouseMovesView)
|
||||
{
|
||||
// Move view
|
||||
var delta = location - _mouseMovesPos;
|
||||
if (delta.LengthSquared > 4.0f)
|
||||
{
|
||||
_mouseMovesPos = location;
|
||||
_mouseMoveSum += delta.Length;
|
||||
_view.Location += delta;
|
||||
Cursor = CursorType.SizeAll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
EndMovingControls();
|
||||
EndMovingWidget();
|
||||
if (_mouseMovesView)
|
||||
{
|
||||
EndMovingView();
|
||||
if (button == MouseButton.Right && _mouseMoveSum < 2.0f)
|
||||
TransformGizmo.Owner.OpenContextMenu();
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
EndMovingControls();
|
||||
EndMovingView();
|
||||
EndMovingWidget();
|
||||
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
public override void OnLostFocus()
|
||||
{
|
||||
EndMovingControls();
|
||||
EndMovingView();
|
||||
EndMovingWidget();
|
||||
|
||||
base.OnLostFocus();
|
||||
}
|
||||
|
||||
public override bool OnMouseWheel(Float2 location, float delta)
|
||||
{
|
||||
if (base.OnMouseWheel(location, delta))
|
||||
return true;
|
||||
|
||||
if (EnableCamera && !_mouseMovesControl)
|
||||
{
|
||||
// Zoom view
|
||||
var nextViewScale = ViewScale + delta * 0.1f;
|
||||
if (delta > 0 && !_mouseMovesControl)
|
||||
{
|
||||
// Scale towards mouse when zooming in
|
||||
var nextCenterPosition = ViewPosition + location / ViewScale;
|
||||
ViewScale = nextViewScale;
|
||||
ViewPosition = nextCenterPosition - (location / ViewScale);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scale while keeping center position when zooming out or when dragging view
|
||||
var viewCenter = ViewCenterPosition;
|
||||
ViewScale = nextViewScale;
|
||||
ViewCenterPosition = viewCenter;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
if (EnableBackground && _view != null)
|
||||
{
|
||||
// Draw background
|
||||
Surface.VisjectSurface.DrawBackgroundDefault(Editor.Instance.UI.VisjectSurfaceBackground, Width, Height);
|
||||
|
||||
// Draw grid
|
||||
var viewRect = GetClientArea();
|
||||
var upperLeft = _view.PointFromParent(viewRect.Location);
|
||||
var bottomRight = _view.PointFromParent(viewRect.Size);
|
||||
var min = Float2.Min(upperLeft, bottomRight);
|
||||
var max = Float2.Max(upperLeft, bottomRight);
|
||||
var pixelRange = (max - min) * ViewScale;
|
||||
Render2D.PushClip(ref viewRect);
|
||||
DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X);
|
||||
DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y);
|
||||
Render2D.PopClip();
|
||||
}
|
||||
|
||||
base.Draw();
|
||||
|
||||
if (!_mouseMovesWidget)
|
||||
{
|
||||
// Clear widgets to collect them during drawing
|
||||
_widgets?.Clear();
|
||||
}
|
||||
|
||||
bool drawAnySelectedControl = false;
|
||||
var transformGizmo = TransformGizmo;
|
||||
var mousePos = PointFromWindow(RootWindow.MousePosition);
|
||||
if (transformGizmo != null)
|
||||
{
|
||||
// Selected UI controls outline
|
||||
var selection = transformGizmo.Selection;
|
||||
for (var i = 0; i < selection.Count; i++)
|
||||
{
|
||||
if (IsValidControl(selection[i], out var controlActor))
|
||||
{
|
||||
DrawControl(controlActor, controlActor.Control, true, ref mousePos, ref drawAnySelectedControl, EnableSelecting);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (EnableSelecting && !_mouseMovesControl && !_mouseMovesWidget && IsMouseOver)
|
||||
{
|
||||
// Highlight control under mouse for easier selecting (except if already selected)
|
||||
if (RayCastControl(ref mousePos, out var hitControl) &&
|
||||
(transformGizmo == null || !transformGizmo.Selection.Any(x => x.EditableObject is UIControl controlActor && controlActor.Control == hitControl)))
|
||||
{
|
||||
DrawControl(null, hitControl, false, ref mousePos, ref drawAnySelectedControl);
|
||||
}
|
||||
}
|
||||
if (drawAnySelectedControl)
|
||||
Render2D.PopTransform();
|
||||
|
||||
if (EnableBackground)
|
||||
{
|
||||
// Draw border
|
||||
if (ContainsFocus)
|
||||
{
|
||||
Render2D.DrawRectangle(new Rectangle(1, 1, Width - 2, Height - 2), Editor.IsPlayMode ? Color.OrangeRed : Style.Current.BackgroundSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (IsDisposing)
|
||||
return;
|
||||
EndMovingControls();
|
||||
EndMovingView();
|
||||
EndMovingWidget();
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
private Float2 GetControlDelta(Control control, ref Float2 start, ref Float2 end)
|
||||
{
|
||||
var pointOrigin = control.Parent ?? control;
|
||||
var startPos = pointOrigin.PointFromParent(this, start);
|
||||
var endPos = pointOrigin.PointFromParent(this, end);
|
||||
return endPos - startPos;
|
||||
}
|
||||
|
||||
private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange)
|
||||
{
|
||||
var style = Style.Current;
|
||||
var linesColor = style.ForegroundDisabled.RGBMultiplied(0.5f);
|
||||
var labelsColor = style.ForegroundDisabled;
|
||||
var labelsSize = 10.0f;
|
||||
Utilities.Utils.DrawCurveTicks((float tick, float strength) =>
|
||||
{
|
||||
var p = _view.PointToParent(axis * tick);
|
||||
|
||||
// Draw line
|
||||
var lineRect = new Rectangle
|
||||
(
|
||||
viewRect.Location + (p - 0.5f) * axis,
|
||||
Float2.Lerp(viewRect.Size, Float2.One, axis)
|
||||
);
|
||||
Render2D.FillRectangle(lineRect, linesColor.AlphaMultiplied(strength));
|
||||
|
||||
// Draw label
|
||||
string label = tick.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||
var labelRect = new Rectangle
|
||||
(
|
||||
viewRect.X + 4.0f + (p.X * axis.X),
|
||||
viewRect.Y - labelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X),
|
||||
50,
|
||||
labelsSize
|
||||
);
|
||||
Render2D.DrawText(style.FontSmall, label, labelRect, labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
|
||||
}, _gridTickSteps, ref _gridTickStrengths, min, max, pixelRange);
|
||||
}
|
||||
|
||||
private void DrawControl(UIControl uiControl, Control control, bool selection, ref Float2 mousePos, ref bool drawAnySelectedControl, bool withWidgets = false)
|
||||
{
|
||||
if (!drawAnySelectedControl)
|
||||
{
|
||||
drawAnySelectedControl = true;
|
||||
Render2D.PushTransform(ref _cachedTransform);
|
||||
}
|
||||
var options = Editor.Instance.Options.Options.Visual;
|
||||
|
||||
// Draw bounds
|
||||
var bounds = control.EditorBounds;
|
||||
var ul = control.PointToParent(this, bounds.UpperLeft);
|
||||
var ur = control.PointToParent(this, bounds.UpperRight);
|
||||
var bl = control.PointToParent(this, bounds.BottomLeft);
|
||||
var br = control.PointToParent(this, bounds.BottomRight);
|
||||
var color = selection ? options.SelectionOutlineColor0 : Style.Current.SelectionBorder;
|
||||
#if false
|
||||
// AABB
|
||||
var min = Float2.Min(Float2.Min(ul, ur), Float2.Min(bl, br));
|
||||
var max = Float2.Max(Float2.Max(ul, ur), Float2.Max(bl, br));
|
||||
bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero));
|
||||
Render2D.DrawRectangle(bounds, color, options.UISelectionOutlineSize);
|
||||
#else
|
||||
// OBB
|
||||
Render2D.DrawLine(ul, ur, color, options.UISelectionOutlineSize);
|
||||
Render2D.DrawLine(ur, br, color, options.UISelectionOutlineSize);
|
||||
Render2D.DrawLine(br, bl, color, options.UISelectionOutlineSize);
|
||||
Render2D.DrawLine(bl, ul, color, options.UISelectionOutlineSize);
|
||||
#endif
|
||||
if (withWidgets)
|
||||
{
|
||||
// Draw sizing widgets
|
||||
if (_widgets == null)
|
||||
_widgets = new List<Widget>();
|
||||
var widgetSize = 8.0f;
|
||||
var viewScale = ViewScale;
|
||||
if (viewScale < 0.7f)
|
||||
widgetSize *= viewScale;
|
||||
var controlSize = control.Size.Absolute.MinValue / 50.0f;
|
||||
if (controlSize < 1.0f)
|
||||
widgetSize *= Mathf.Clamp(controlSize + 0.1f, 0.1f, 1.0f);
|
||||
var cornerSize = new Float2(widgetSize);
|
||||
DrawControlWidget(uiControl, ref ul, ref mousePos, ref cornerSize, new Float2(-1, -1), CursorType.SizeNWSE);
|
||||
DrawControlWidget(uiControl, ref ur, ref mousePos, ref cornerSize, new Float2(1, -1), CursorType.SizeNESW);
|
||||
DrawControlWidget(uiControl, ref bl, ref mousePos, ref cornerSize, new Float2(-1, 1), CursorType.SizeNESW);
|
||||
DrawControlWidget(uiControl, ref br, ref mousePos, ref cornerSize, new Float2(1, 1), CursorType.SizeNWSE);
|
||||
var edgeSizeV = new Float2(widgetSize * 2, widgetSize);
|
||||
var edgeSizeH = new Float2(edgeSizeV.Y, edgeSizeV.X);
|
||||
Float2.Lerp(ref ul, ref bl, 0.5f, out var el);
|
||||
Float2.Lerp(ref ur, ref br, 0.5f, out var er);
|
||||
Float2.Lerp(ref ul, ref ur, 0.5f, out var eu);
|
||||
Float2.Lerp(ref bl, ref br, 0.5f, out var eb);
|
||||
DrawControlWidget(uiControl, ref el, ref mousePos, ref edgeSizeH, new Float2(-1, 0), CursorType.SizeWE);
|
||||
DrawControlWidget(uiControl, ref er, ref mousePos, ref edgeSizeH, new Float2(1, 0), CursorType.SizeWE);
|
||||
DrawControlWidget(uiControl, ref eu, ref mousePos, ref edgeSizeV, new Float2(0, -1), CursorType.SizeNS);
|
||||
DrawControlWidget(uiControl, ref eb, ref mousePos, ref edgeSizeV, new Float2(0, 1), CursorType.SizeNS);
|
||||
|
||||
// TODO: draw anchors
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawControlWidget(UIControl uiControl, ref Float2 pos, ref Float2 mousePos, ref Float2 size, Float2 resizeAxis, CursorType cursor)
|
||||
{
|
||||
var style = Style.Current;
|
||||
var rect = new Rectangle(pos - size * 0.5f, size);
|
||||
if (rect.Contains(ref mousePos))
|
||||
{
|
||||
Render2D.FillRectangle(rect, style.Foreground);
|
||||
}
|
||||
else
|
||||
{
|
||||
Render2D.FillRectangle(rect, style.ForegroundGrey);
|
||||
Render2D.DrawRectangle(rect, style.Foreground);
|
||||
}
|
||||
if (!_mouseMovesWidget && uiControl != null)
|
||||
{
|
||||
// Collect widget
|
||||
_widgets.Add(new Widget
|
||||
{
|
||||
UIControl = uiControl,
|
||||
Bounds = rect,
|
||||
ResizeAxis = resizeAxis,
|
||||
Cursor = cursor,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidControl(SceneGraphNode node, out UIControl uiControl)
|
||||
{
|
||||
uiControl = null;
|
||||
if (node.EditableObject is UIControl controlActor)
|
||||
uiControl = controlActor;
|
||||
return uiControl != null &&
|
||||
uiControl.Control != null &&
|
||||
uiControl.Control.VisibleInHierarchy &&
|
||||
uiControl.Control.RootWindow != null;
|
||||
}
|
||||
|
||||
private bool RayCastControl(ref Float2 location, out Control hit)
|
||||
{
|
||||
#if false
|
||||
// Raycast only controls with content (eg. skips transparent panels)
|
||||
return RayCastChildren(ref location, out hit);
|
||||
#else
|
||||
// Find any control under mouse (hierarchical)
|
||||
hit = GetChildAtRecursive(location);
|
||||
if (hit is View || hit is CanvasContainer)
|
||||
hit = null;
|
||||
return hit != null;
|
||||
#endif
|
||||
}
|
||||
|
||||
private UIControlNode FindUIControlNode(Control control)
|
||||
{
|
||||
return FindUIControlNode(TransformGizmo.Owner.SceneGraphRoot, control);
|
||||
}
|
||||
|
||||
private UIControlNode FindUIControlNode(SceneGraphNode node, Control control)
|
||||
{
|
||||
var result = node as UIControlNode;
|
||||
if (result != null && ((UIControl)result.Actor).Control == control)
|
||||
return result;
|
||||
foreach (var e in node.ChildNodes)
|
||||
{
|
||||
result = FindUIControlNode(e, control);
|
||||
if (result != null)
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void StartUndo()
|
||||
{
|
||||
var undo = TransformGizmo?.Owner?.Undo;
|
||||
if (undo == null || _undoBlock != null)
|
||||
return;
|
||||
_undoBlock = new UndoMultiBlock(undo, TransformGizmo.Selection.ConvertAll(x => x.EditableObject), "Edit control");
|
||||
}
|
||||
|
||||
private void EndUndo()
|
||||
{
|
||||
if (_undoBlock == null)
|
||||
return;
|
||||
_undoBlock.Dispose();
|
||||
_undoBlock = null;
|
||||
}
|
||||
|
||||
private void EndMovingControls()
|
||||
{
|
||||
if (!_mouseMovesControl)
|
||||
return;
|
||||
_mouseMovesControl = false;
|
||||
EndMouseCapture();
|
||||
Cursor = CursorType.Default;
|
||||
EndUndo();
|
||||
}
|
||||
|
||||
private void EndMovingView()
|
||||
{
|
||||
if (!_mouseMovesView)
|
||||
return;
|
||||
_mouseMovesView = false;
|
||||
EndMouseCapture();
|
||||
Cursor = CursorType.Default;
|
||||
}
|
||||
|
||||
private void EndMovingWidget()
|
||||
{
|
||||
if (!_mouseMovesWidget)
|
||||
return;
|
||||
_mouseMovesWidget = false;
|
||||
_activeWidget = new Widget();
|
||||
EndMouseCapture();
|
||||
Cursor = CursorType.Default;
|
||||
EndUndo();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Control that can optionally disable inputs to the children.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
internal class InputsPassThrough : ContainerControl
|
||||
{
|
||||
private bool _isMouseOver;
|
||||
|
||||
/// <summary>
|
||||
/// True if enable input events passing to the UI.
|
||||
/// </summary>
|
||||
public virtual bool EnableInputs => true;
|
||||
|
||||
public override bool RayCast(ref Float2 location, out Control hit)
|
||||
{
|
||||
return RayCastChildren(ref location, out hit);
|
||||
}
|
||||
|
||||
public override bool ContainsPoint(ref Float2 location, bool precise = false)
|
||||
{
|
||||
if (precise)
|
||||
return false;
|
||||
return base.ContainsPoint(ref location, precise);
|
||||
}
|
||||
|
||||
public override bool OnCharInput(char c)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return false;
|
||||
return base.OnCharInput(c);
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return DragDropEffect.None;
|
||||
return base.OnDragDrop(ref location, data);
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return DragDropEffect.None;
|
||||
return base.OnDragEnter(ref location, data);
|
||||
}
|
||||
|
||||
public override void OnDragLeave()
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return;
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return DragDropEffect.None;
|
||||
return base.OnDragMove(ref location, data);
|
||||
}
|
||||
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return false;
|
||||
return base.OnKeyDown(key);
|
||||
}
|
||||
|
||||
public override void OnKeyUp(KeyboardKeys key)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return;
|
||||
base.OnKeyUp(key);
|
||||
}
|
||||
|
||||
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return false;
|
||||
return base.OnMouseDoubleClick(location, button);
|
||||
}
|
||||
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return false;
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
public override bool IsMouseOver => _isMouseOver;
|
||||
|
||||
public override void OnMouseEnter(Float2 location)
|
||||
{
|
||||
_isMouseOver = true;
|
||||
if (!EnableInputs)
|
||||
return;
|
||||
base.OnMouseEnter(location);
|
||||
}
|
||||
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
_isMouseOver = false;
|
||||
if (!EnableInputs)
|
||||
return;
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return;
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return false;
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
public override bool OnMouseWheel(Float2 location, float delta)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return false;
|
||||
return base.OnMouseWheel(location, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,8 +201,8 @@ namespace FlaxEditor.Options
|
||||
/// <returns>True if input has been processed, otherwise false.</returns>
|
||||
public bool Process(Control control)
|
||||
{
|
||||
var root = control.Root;
|
||||
return root.GetKey(Key) && ProcessModifiers(control);
|
||||
var root = control?.Root;
|
||||
return root != null && root.GetKey(Key) && ProcessModifiers(control);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using FlaxEditor.GUI.Docking;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Options
|
||||
@@ -116,6 +117,27 @@ namespace FlaxEditor.Options
|
||||
BorderlessWindow,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for formatting numerical values.
|
||||
/// </summary>
|
||||
public enum ValueFormattingType
|
||||
{
|
||||
/// <summary>
|
||||
/// No formatting.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Format using the base SI unit.
|
||||
/// </summary>
|
||||
BaseUnit,
|
||||
|
||||
/// <summary>
|
||||
/// Format using a unit that matches the value best.
|
||||
/// </summary>
|
||||
AutoUnit,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required.
|
||||
/// </summary>
|
||||
@@ -174,6 +196,20 @@ namespace FlaxEditor.Options
|
||||
[EditorDisplay("Interface"), EditorOrder(280), Tooltip("Editor content window orientation.")]
|
||||
public FlaxEngine.GUI.Orientation ContentWindowOrientation { get; set; } = FlaxEngine.GUI.Orientation.Horizontal;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the formatting option for numeric values in the editor.
|
||||
/// </summary>
|
||||
[DefaultValue(ValueFormattingType.None)]
|
||||
[EditorDisplay("Interface"), EditorOrder(300)]
|
||||
public ValueFormattingType ValueFormatting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the option to put a space between numbers and units for unit formatting.
|
||||
/// </summary>
|
||||
[DefaultValue(false)]
|
||||
[EditorDisplay("Interface"), EditorOrder(310)]
|
||||
public bool SeparateValueAndUnit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timestamps prefix mode for output log messages.
|
||||
/// </summary>
|
||||
|
||||
@@ -200,6 +200,27 @@ namespace FlaxEditor.Options
|
||||
|
||||
EditorAssets.Cache.OnEditorOptionsChanged(Options);
|
||||
|
||||
// Units formatting options
|
||||
bool useUnitsFormatting = Options.Interface.ValueFormatting != InterfaceOptions.ValueFormattingType.None;
|
||||
bool automaticUnitsFormatting = Options.Interface.ValueFormatting == InterfaceOptions.ValueFormattingType.AutoUnit;
|
||||
bool separateValueAndUnit = Options.Interface.SeparateValueAndUnit;
|
||||
if (useUnitsFormatting != Utilities.Units.UseUnitsFormatting ||
|
||||
automaticUnitsFormatting != Utilities.Units.AutomaticUnitsFormatting ||
|
||||
separateValueAndUnit != Utilities.Units.SeparateValueAndUnit)
|
||||
{
|
||||
Utilities.Units.UseUnitsFormatting = useUnitsFormatting;
|
||||
Utilities.Units.AutomaticUnitsFormatting = automaticUnitsFormatting;
|
||||
Utilities.Units.SeparateValueAndUnit = separateValueAndUnit;
|
||||
|
||||
// Refresh UI in property panels
|
||||
Editor.Windows.PropertiesWin?.Presenter.BuildLayoutOnUpdate();
|
||||
foreach (var window in Editor.Windows.Windows)
|
||||
{
|
||||
if (window is Windows.Assets.PrefabWindow prefabWindow)
|
||||
prefabWindow.Presenter.BuildLayoutOnUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
// Send event
|
||||
OptionsChanged?.Invoke(Options);
|
||||
}
|
||||
|
||||
@@ -617,8 +617,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
public override void SetLocation(int index, Float2 location)
|
||||
{
|
||||
var dataA = (Float4)_node.Values[4 + index * 2];
|
||||
var ranges = (Float4)_node.Values[0];
|
||||
|
||||
dataA.X = location.X;
|
||||
dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y);
|
||||
|
||||
_node.Values[4 + index * 2] = dataA;
|
||||
_node.Surface.MarkAsEdited();
|
||||
@@ -750,9 +751,10 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
public override void SetLocation(int index, Float2 location)
|
||||
{
|
||||
var dataA = (Float4)_node.Values[4 + index * 2];
|
||||
var ranges = (Float4)_node.Values[0];
|
||||
|
||||
dataA.X = location.X;
|
||||
dataA.Y = location.Y;
|
||||
dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y);
|
||||
dataA.Y = Mathf.Clamp(location.Y, ranges.Z, ranges.W);
|
||||
|
||||
_node.Values[4 + index * 2] = dataA;
|
||||
_node.Surface.MarkAsEdited();
|
||||
|
||||
@@ -64,7 +64,11 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
protected virtual void DrawBackground()
|
||||
{
|
||||
var background = Style.Background;
|
||||
DrawBackgroundDefault(Style.Background, Width, Height);
|
||||
}
|
||||
|
||||
internal static void DrawBackgroundDefault(Texture background, float width, float height)
|
||||
{
|
||||
if (background && background.ResidentMipLevels > 0)
|
||||
{
|
||||
var bSize = background.Size;
|
||||
@@ -77,8 +81,8 @@ namespace FlaxEditor.Surface
|
||||
if (pos.Y > 0)
|
||||
pos.Y -= bh;
|
||||
|
||||
int maxI = Mathf.CeilToInt(Width / bw + 1.0f);
|
||||
int maxJ = Mathf.CeilToInt(Height / bh + 1.0f);
|
||||
int maxI = Mathf.CeilToInt(width / bw + 1.0f);
|
||||
int maxJ = Mathf.CeilToInt(height / bh + 1.0f);
|
||||
|
||||
for (int i = 0; i < maxI; i++)
|
||||
{
|
||||
|
||||
@@ -121,6 +121,37 @@ namespace FlaxEditor.Utilities
|
||||
["e"] = Math.E,
|
||||
["infinity"] = double.MaxValue,
|
||||
["-infinity"] = -double.MaxValue,
|
||||
["m"] = Units.Meters2Units,
|
||||
["cm"] = Units.Meters2Units / 100,
|
||||
["km"] = Units.Meters2Units * 1000,
|
||||
["s"] = 1,
|
||||
["ms"] = 0.001,
|
||||
["min"] = 60,
|
||||
["h"] = 3600,
|
||||
["cm²"] = (Units.Meters2Units / 100) * (Units.Meters2Units / 100),
|
||||
["cm³"] = (Units.Meters2Units / 100) * (Units.Meters2Units / 100) * (Units.Meters2Units / 100),
|
||||
["dm²"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10),
|
||||
["dm³"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10) * (Units.Meters2Units / 10),
|
||||
["l"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10) * (Units.Meters2Units / 10),
|
||||
["m²"] = Units.Meters2Units * Units.Meters2Units,
|
||||
["m³"] = Units.Meters2Units * Units.Meters2Units * Units.Meters2Units,
|
||||
["kg"] = 1,
|
||||
["g"] = 0.001,
|
||||
["n"] = Units.Meters2Units
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// List known units which cannot be handled as a variable easily because they contain operator symbols (mostly a forward slash). The value is the factor to calculate game units.
|
||||
/// </summary>
|
||||
private static readonly IDictionary<string, double> UnitSymbols = new Dictionary<string, double>
|
||||
{
|
||||
["cm/s"] = Units.Meters2Units / 100,
|
||||
["cm/s²"] = Units.Meters2Units / 100,
|
||||
["m/s"] = Units.Meters2Units,
|
||||
["m/s²"] = Units.Meters2Units,
|
||||
["km/h"] = 1 / 3.6 * Units.Meters2Units,
|
||||
// Nm is here because these values are compared case-sensitive, and we don't want to confuse nanometers and Newtonmeters
|
||||
["Nm"] = Units.Meters2Units * Units.Meters2Units,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -156,7 +187,7 @@ namespace FlaxEditor.Utilities
|
||||
if (Operators.ContainsKey(str))
|
||||
return TokenType.Operator;
|
||||
|
||||
if (char.IsLetter(c))
|
||||
if (char.IsLetter(c) || c == '²' || c == '³')
|
||||
return TokenType.Variable;
|
||||
|
||||
throw new ParsingException("wrong character");
|
||||
@@ -170,7 +201,24 @@ namespace FlaxEditor.Utilities
|
||||
public static IEnumerable<Token> Tokenize(string text)
|
||||
{
|
||||
// Prepare text
|
||||
text = text.Replace(',', '.');
|
||||
text = text.Replace(',', '.').Replace("°", "");
|
||||
foreach (var kv in UnitSymbols)
|
||||
{
|
||||
int idx;
|
||||
do
|
||||
{
|
||||
idx = text.IndexOf(kv.Key, StringComparison.InvariantCulture);
|
||||
if (idx > 0)
|
||||
{
|
||||
if (DetermineType(text[idx - 1]) != TokenType.Number)
|
||||
throw new ParsingException($"unit found without a number: {kv.Key} at {idx} in {text}");
|
||||
if (Mathf.Abs(kv.Value - 1) < Mathf.Epsilon)
|
||||
text = text.Remove(idx, kv.Key.Length);
|
||||
else
|
||||
text = text.Replace(kv.Key, "*" + kv.Value);
|
||||
}
|
||||
} while (idx > 0);
|
||||
}
|
||||
|
||||
// Necessary to correctly parse negative numbers
|
||||
var previous = TokenType.WhiteSpace;
|
||||
@@ -240,6 +288,11 @@ namespace FlaxEditor.Utilities
|
||||
}
|
||||
else if (type == TokenType.Variable)
|
||||
{
|
||||
if (previous == TokenType.Number)
|
||||
{
|
||||
previous = TokenType.Operator;
|
||||
yield return new Token(TokenType.Operator, "*");
|
||||
}
|
||||
// Continue till the end of the variable
|
||||
while (i + 1 < text.Length && DetermineType(text[i + 1]) == TokenType.Variable)
|
||||
{
|
||||
@@ -335,7 +388,7 @@ namespace FlaxEditor.Utilities
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ParsingException("unknown variable");
|
||||
throw new ParsingException($"unknown variable : {token.Value}");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -372,6 +425,15 @@ namespace FlaxEditor.Utilities
|
||||
}
|
||||
}
|
||||
|
||||
// if stack has more than one item we're not finished with evaluating
|
||||
// we assume the remaining values are all factors to be multiplied
|
||||
if (stack.Count > 1)
|
||||
{
|
||||
var v1 = stack.Pop();
|
||||
while (stack.Count > 0)
|
||||
v1 *= stack.Pop();
|
||||
return v1;
|
||||
}
|
||||
return stack.Pop();
|
||||
}
|
||||
|
||||
|
||||
41
Source/Editor/Utilities/Units.cs
Normal file
41
Source/Editor/Utilities/Units.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
namespace FlaxEditor.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Units display utilities for Editor.
|
||||
/// </summary>
|
||||
public class Units
|
||||
{
|
||||
/// <summary>
|
||||
/// Factor of units per meter.
|
||||
/// </summary>
|
||||
public static readonly float Meters2Units = 100f;
|
||||
|
||||
/// <summary>
|
||||
/// False to always show game units without any postfix.
|
||||
/// </summary>
|
||||
public static bool UseUnitsFormatting = true;
|
||||
|
||||
/// <summary>
|
||||
/// Add a space between numbers and units for readability.
|
||||
/// </summary>
|
||||
public static bool SeparateValueAndUnit = true;
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, the distance unit is chosen on the magnitude, otherwise it's meters.
|
||||
/// </summary>
|
||||
public static bool AutomaticUnitsFormatting = true;
|
||||
|
||||
/// <summary>
|
||||
/// Return the unit according to user settings.
|
||||
/// </summary>
|
||||
/// <param name="unit">The unit name.</param>
|
||||
/// <returns>The formatted text.</returns>
|
||||
public static string Unit(string unit)
|
||||
{
|
||||
if (SeparateValueAndUnit)
|
||||
return $" {unit}";
|
||||
return unit;
|
||||
}
|
||||
}
|
||||
@@ -243,6 +243,63 @@ namespace FlaxEditor.Utilities
|
||||
500000, 1000000, 5000000, 10000000, 100000000
|
||||
};
|
||||
|
||||
internal delegate void DrawCurveTick(float tick, float strength);
|
||||
|
||||
internal static Int2 DrawCurveTicks(DrawCurveTick drawTick, float[] tickSteps, ref float[] tickStrengths, float min, float max, float pixelRange, float minDistanceBetweenTicks = 20, float maxDistanceBetweenTicks = 60)
|
||||
{
|
||||
if (tickStrengths == null || tickStrengths.Length != tickSteps.Length)
|
||||
tickStrengths = new float[tickSteps.Length];
|
||||
|
||||
// Find the strength for each modulo number tick marker
|
||||
var pixelsInRange = pixelRange / (max - min);
|
||||
var smallestTick = 0;
|
||||
var biggestTick = tickSteps.Length - 1;
|
||||
for (int i = tickSteps.Length - 1; i >= 0; i--)
|
||||
{
|
||||
// Calculate how far apart these modulo tick steps are spaced
|
||||
float tickSpacing = tickSteps[i] * pixelsInRange;
|
||||
|
||||
// Calculate the strength of the tick markers based on the spacing
|
||||
tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks));
|
||||
|
||||
// Beyond threshold the ticks don't get any bigger or fatter
|
||||
if (tickStrengths[i] >= 1)
|
||||
biggestTick = i;
|
||||
|
||||
// Do not show small tick markers
|
||||
if (tickSpacing <= minDistanceBetweenTicks)
|
||||
{
|
||||
smallestTick = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
var tickLevels = biggestTick - smallestTick + 1;
|
||||
|
||||
// Draw all tick levels
|
||||
for (int level = 0; level < tickLevels; level++)
|
||||
{
|
||||
float strength = tickStrengths[smallestTick + level];
|
||||
if (strength <= Mathf.Epsilon)
|
||||
continue;
|
||||
|
||||
// Draw all ticks
|
||||
int l = Mathf.Clamp(smallestTick + level, 0, tickSteps.Length - 1);
|
||||
var lStep = tickSteps[l];
|
||||
var lNextStep = tickSteps[l + 1];
|
||||
int startTick = Mathf.FloorToInt(min / lStep);
|
||||
int endTick = Mathf.CeilToInt(max / lStep);
|
||||
for (int i = startTick; i <= endTick; i++)
|
||||
{
|
||||
if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0))
|
||||
continue;
|
||||
var tick = i * lStep;
|
||||
drawTick(tick, strength);
|
||||
}
|
||||
}
|
||||
|
||||
return new Int2(smallestTick, biggestTick);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified path string contains any invalid character.
|
||||
/// </summary>
|
||||
@@ -1187,6 +1244,71 @@ namespace FlaxEditor.Utilities
|
||||
return StringUtils.GetPathWithoutExtension(path);
|
||||
}
|
||||
|
||||
private static string InternalFormat(double value, string format, FlaxEngine.Utils.ValueCategory category)
|
||||
{
|
||||
switch (category)
|
||||
{
|
||||
case FlaxEngine.Utils.ValueCategory.Distance:
|
||||
if (!Units.AutomaticUnitsFormatting)
|
||||
return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m");
|
||||
var absValue = Mathf.Abs(value);
|
||||
// in case a unit != cm this would be (value / Meters2Units * 100)
|
||||
if (absValue < Units.Meters2Units)
|
||||
return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("cm");
|
||||
if (absValue < Units.Meters2Units * 1000)
|
||||
return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m");
|
||||
return (value / 1000 / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("km");
|
||||
case FlaxEngine.Utils.ValueCategory.Angle: return value.ToString(format, CultureInfo.InvariantCulture) + "°";
|
||||
case FlaxEngine.Utils.ValueCategory.Time: return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("s");
|
||||
// some fonts have a symbol for that: "\u33A7"
|
||||
case FlaxEngine.Utils.ValueCategory.Speed: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m/s");
|
||||
case FlaxEngine.Utils.ValueCategory.Acceleration: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m/s²");
|
||||
case FlaxEngine.Utils.ValueCategory.Area: return (value / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m²");
|
||||
case FlaxEngine.Utils.ValueCategory.Volume: return (value / Units.Meters2Units / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m³");
|
||||
case FlaxEngine.Utils.ValueCategory.Mass: return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("kg");
|
||||
case FlaxEngine.Utils.ValueCategory.Force: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("N");
|
||||
case FlaxEngine.Utils.ValueCategory.Torque: return (value / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("Nm");
|
||||
case FlaxEngine.Utils.ValueCategory.None:
|
||||
default: return FormatFloat(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format a float value either as-is, with a distance unit or with a degree sign.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to format.</param>
|
||||
/// <param name="category">The value type: none means just a number, distance will format in cm/m/km, angle with an appended degree sign.</param>
|
||||
/// <returns>The formatted string.</returns>
|
||||
public static string FormatFloat(float value, FlaxEngine.Utils.ValueCategory category)
|
||||
{
|
||||
if (float.IsPositiveInfinity(value) || value == float.MaxValue)
|
||||
return "Infinity";
|
||||
if (float.IsNegativeInfinity(value) || value == float.MinValue)
|
||||
return "-Infinity";
|
||||
if (!Units.UseUnitsFormatting || category == FlaxEngine.Utils.ValueCategory.None)
|
||||
return FormatFloat(value);
|
||||
const string format = "G7";
|
||||
return InternalFormat(value, format, category);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format a double value either as-is, with a distance unit or with a degree sign
|
||||
/// </summary>
|
||||
/// <param name="value">The value to format.</param>
|
||||
/// <param name="category">The value type: none means just a number, distance will format in cm/m/km, angle with an appended degree sign.</param>
|
||||
/// <returns>The formatted string.</returns>
|
||||
public static string FormatFloat(double value, FlaxEngine.Utils.ValueCategory category)
|
||||
{
|
||||
if (double.IsPositiveInfinity(value) || value == double.MaxValue)
|
||||
return "Infinity";
|
||||
if (double.IsNegativeInfinity(value) || value == double.MinValue)
|
||||
return "-Infinity";
|
||||
if (!Units.UseUnitsFormatting || category == FlaxEngine.Utils.ValueCategory.None)
|
||||
return FormatFloat(value);
|
||||
const string format = "G15";
|
||||
return InternalFormat(value, format, category);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the floating point value (double precision) into the readable text representation.
|
||||
/// </summary>
|
||||
@@ -1198,7 +1320,7 @@ namespace FlaxEditor.Utilities
|
||||
return "Infinity";
|
||||
if (float.IsNegativeInfinity(value) || value == float.MinValue)
|
||||
return "-Infinity";
|
||||
string str = value.ToString("r", CultureInfo.InvariantCulture);
|
||||
string str = value.ToString("R", CultureInfo.InvariantCulture);
|
||||
return FormatFloat(str, value < 0);
|
||||
}
|
||||
|
||||
@@ -1213,7 +1335,7 @@ namespace FlaxEditor.Utilities
|
||||
return "Infinity";
|
||||
if (double.IsNegativeInfinity(value) || value == double.MinValue)
|
||||
return "-Infinity";
|
||||
string str = value.ToString("r", CultureInfo.InvariantCulture);
|
||||
string str = value.ToString("R", CultureInfo.InvariantCulture);
|
||||
return FormatFloat(str, value < 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -187,6 +187,8 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene
|
||||
|
||||
void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor* actor, Mesh::DrawInfo& draw)
|
||||
{
|
||||
if (!actor || !actor->IsActiveInHierarchy())
|
||||
return;
|
||||
auto& view = renderContext.View;
|
||||
const BoundingFrustum frustum = view.Frustum;
|
||||
Matrix m1, m2, world;
|
||||
@@ -208,8 +210,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor
|
||||
draw.DrawState = &drawState;
|
||||
draw.Deformation = nullptr;
|
||||
|
||||
// Support custom icons through types, but not onces that were added through actors,
|
||||
// since they cant register while in prefab view anyway
|
||||
// Support custom icons through types, but not ones that were added through actors, since they cant register while in prefab view anyway
|
||||
if (ActorTypeToTexture.TryGet(actor->GetTypeHandle(), texture))
|
||||
{
|
||||
// Use custom texture
|
||||
|
||||
@@ -6,9 +6,7 @@ using Real = System.Double;
|
||||
using Real = System.Single;
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Viewport.Cameras
|
||||
@@ -85,86 +83,8 @@ namespace FlaxEditor.Viewport.Cameras
|
||||
_moveStartTime = Time.UnscaledGameTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the viewport to visualize the actor.
|
||||
/// </summary>
|
||||
/// <param name="actor">The actor to preview.</param>
|
||||
public void ShowActor(Actor actor)
|
||||
{
|
||||
Editor.GetActorEditorSphere(actor, out BoundingSphere sphere);
|
||||
ShowSphere(ref sphere);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the viewport to visualize selected actors.
|
||||
/// </summary>
|
||||
/// <param name="actor">The actors to show.</param>
|
||||
/// <param name="orientation">The used orientation.</param>
|
||||
public void ShowActor(Actor actor, ref Quaternion orientation)
|
||||
{
|
||||
Editor.GetActorEditorSphere(actor, out BoundingSphere sphere);
|
||||
ShowSphere(ref sphere, ref orientation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the viewport to visualize selected actors.
|
||||
/// </summary>
|
||||
/// <param name="selection">The actors to show.</param>
|
||||
public void ShowActors(List<SceneGraphNode> selection)
|
||||
{
|
||||
if (selection.Count == 0)
|
||||
return;
|
||||
|
||||
BoundingSphere mergesSphere = BoundingSphere.Empty;
|
||||
for (int i = 0; i < selection.Count; i++)
|
||||
{
|
||||
selection[i].GetEditorSphere(out var sphere);
|
||||
BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere);
|
||||
}
|
||||
|
||||
if (mergesSphere == BoundingSphere.Empty)
|
||||
return;
|
||||
ShowSphere(ref mergesSphere);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the viewport to visualize selected actors.
|
||||
/// </summary>
|
||||
/// <param name="selection">The actors to show.</param>
|
||||
/// <param name="orientation">The used orientation.</param>
|
||||
public void ShowActors(List<SceneGraphNode> selection, ref Quaternion orientation)
|
||||
{
|
||||
if (selection.Count == 0)
|
||||
return;
|
||||
|
||||
BoundingSphere mergesSphere = BoundingSphere.Empty;
|
||||
for (int i = 0; i < selection.Count; i++)
|
||||
{
|
||||
selection[i].GetEditorSphere(out var sphere);
|
||||
BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere);
|
||||
}
|
||||
|
||||
if (mergesSphere == BoundingSphere.Empty)
|
||||
return;
|
||||
ShowSphere(ref mergesSphere, ref orientation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the camera to visualize given world area defined by the sphere.
|
||||
/// </summary>
|
||||
/// <param name="sphere">The sphere.</param>
|
||||
public void ShowSphere(ref BoundingSphere sphere)
|
||||
{
|
||||
var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f);
|
||||
ShowSphere(ref sphere, ref q);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the camera to visualize given world area defined by the sphere.
|
||||
/// </summary>
|
||||
/// <param name="sphere">The sphere.</param>
|
||||
/// <param name="orientation">The camera orientation.</param>
|
||||
public void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation)
|
||||
/// <inheritdoc />
|
||||
public override void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation)
|
||||
{
|
||||
Vector3 position;
|
||||
if (Viewport.UseOrthographicProjection)
|
||||
|
||||
@@ -6,6 +6,9 @@ using Real = System.Double;
|
||||
using Real = System.Single;
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Viewport.Cameras
|
||||
@@ -33,6 +36,90 @@ namespace FlaxEditor.Viewport.Cameras
|
||||
/// </summary>
|
||||
public virtual bool UseMovementSpeed => true;
|
||||
|
||||
/// <summary>
|
||||
/// Focuses the viewport on the current selection of the gizmo.
|
||||
/// </summary>
|
||||
/// <param name="gizmos">The gizmo collection (from viewport).</param>
|
||||
/// <param name="orientation">The target view orientation.</param>
|
||||
public virtual void FocusSelection(GizmosCollection gizmos, ref Quaternion orientation)
|
||||
{
|
||||
var transformGizmo = gizmos.Get<TransformGizmo>();
|
||||
if (transformGizmo == null || transformGizmo.SelectedParents.Count == 0)
|
||||
return;
|
||||
if (gizmos.Active != null)
|
||||
{
|
||||
var gizmoBounds = gizmos.Active.FocusBounds;
|
||||
if (gizmoBounds != BoundingSphere.Empty)
|
||||
{
|
||||
ShowSphere(ref gizmoBounds, ref orientation);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ShowActors(transformGizmo.SelectedParents, ref orientation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the viewport to visualize the actor.
|
||||
/// </summary>
|
||||
/// <param name="actor">The actor to preview.</param>
|
||||
public virtual void ShowActor(Actor actor)
|
||||
{
|
||||
Editor.GetActorEditorSphere(actor, out BoundingSphere sphere);
|
||||
ShowSphere(ref sphere);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the viewport to visualize selected actors.
|
||||
/// </summary>
|
||||
/// <param name="selection">The actors to show.</param>
|
||||
public void ShowActors(List<SceneGraphNode> selection)
|
||||
{
|
||||
var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f);
|
||||
ShowActors(selection, ref q);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the viewport to visualize selected actors.
|
||||
/// </summary>
|
||||
/// <param name="selection">The actors to show.</param>
|
||||
/// <param name="orientation">The used orientation.</param>
|
||||
public virtual void ShowActors(List<SceneGraphNode> selection, ref Quaternion orientation)
|
||||
{
|
||||
if (selection.Count == 0)
|
||||
return;
|
||||
|
||||
BoundingSphere mergesSphere = BoundingSphere.Empty;
|
||||
for (int i = 0; i < selection.Count; i++)
|
||||
{
|
||||
selection[i].GetEditorSphere(out var sphere);
|
||||
BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere);
|
||||
}
|
||||
|
||||
if (mergesSphere == BoundingSphere.Empty)
|
||||
return;
|
||||
ShowSphere(ref mergesSphere, ref orientation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the camera to visualize given world area defined by the sphere.
|
||||
/// </summary>
|
||||
/// <param name="sphere">The sphere.</param>
|
||||
public void ShowSphere(ref BoundingSphere sphere)
|
||||
{
|
||||
var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f);
|
||||
ShowSphere(ref sphere, ref q);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the camera to visualize given world area defined by the sphere.
|
||||
/// </summary>
|
||||
/// <param name="sphere">The sphere.</param>
|
||||
/// <param name="orientation">The camera orientation.</param>
|
||||
public virtual void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation)
|
||||
{
|
||||
SetArcBallView(orientation, sphere.Center, sphere.Radius);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets view orientation and position to match the arc ball camera style view for the given target object bounds.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.Viewport.Cameras;
|
||||
using FlaxEditor.Viewport.Widgets;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
@@ -41,6 +44,7 @@ namespace FlaxEditor.Viewport
|
||||
Gizmos[i].Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public EditorViewport Viewport => this;
|
||||
|
||||
@@ -75,10 +79,10 @@ namespace FlaxEditor.Viewport
|
||||
public Float2 MouseDelta => _mouseDelta * 1000;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool UseSnapping => Root.GetKey(KeyboardKeys.Control);
|
||||
public bool UseSnapping => Root?.GetKey(KeyboardKeys.Control) ?? false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool UseDuplicate => Root.GetKey(KeyboardKeys.Shift);
|
||||
public bool UseDuplicate => Root?.GetKey(KeyboardKeys.Shift) ?? false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Undo Undo { get; }
|
||||
@@ -92,6 +96,9 @@ namespace FlaxEditor.Viewport
|
||||
/// <inheritdoc />
|
||||
public abstract void Spawn(Actor actor);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void OpenContextMenu();
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false;
|
||||
|
||||
@@ -121,5 +128,284 @@ namespace FlaxEditor.Viewport
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
internal static void AddGizmoViewportWidgets(EditorViewport viewport, TransformGizmo transformGizmo, bool useProjectCache = false)
|
||||
{
|
||||
var editor = Editor.Instance;
|
||||
var inputOptions = editor.Options.Options.Input;
|
||||
|
||||
if (useProjectCache)
|
||||
{
|
||||
// Initialize snapping enabled from cached values
|
||||
if (editor.ProjectCache.TryGetCustomData("TranslateSnapState", out var cachedState))
|
||||
transformGizmo.TranslationSnapEnable = bool.Parse(cachedState);
|
||||
if (editor.ProjectCache.TryGetCustomData("RotationSnapState", out cachedState))
|
||||
transformGizmo.RotationSnapEnabled = bool.Parse(cachedState);
|
||||
if (editor.ProjectCache.TryGetCustomData("ScaleSnapState", out cachedState))
|
||||
transformGizmo.ScaleSnapEnabled = bool.Parse(cachedState);
|
||||
if (editor.ProjectCache.TryGetCustomData("TranslateSnapValue", out cachedState))
|
||||
transformGizmo.TranslationSnapValue = float.Parse(cachedState);
|
||||
if (editor.ProjectCache.TryGetCustomData("RotationSnapValue", out cachedState))
|
||||
transformGizmo.RotationSnapValue = float.Parse(cachedState);
|
||||
if (editor.ProjectCache.TryGetCustomData("ScaleSnapValue", out cachedState))
|
||||
transformGizmo.ScaleSnapValue = float.Parse(cachedState);
|
||||
if (editor.ProjectCache.TryGetCustomData("TransformSpaceState", out cachedState) && Enum.TryParse(cachedState, out TransformGizmoBase.TransformSpace space))
|
||||
transformGizmo.ActiveTransformSpace = space;
|
||||
}
|
||||
|
||||
// Transform space widget
|
||||
var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true)
|
||||
{
|
||||
Checked = transformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World,
|
||||
TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})",
|
||||
Parent = transformSpaceWidget
|
||||
};
|
||||
transformSpaceToggle.Toggled += _ =>
|
||||
{
|
||||
transformGizmo.ToggleTransformSpace();
|
||||
if (useProjectCache)
|
||||
editor.ProjectCache.SetCustomData("TransformSpaceState", transformGizmo.ActiveTransformSpace.ToString());
|
||||
};
|
||||
transformSpaceWidget.Parent = viewport;
|
||||
|
||||
// Scale snapping widget
|
||||
var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableScaleSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.ScaleSnap32, null, true)
|
||||
{
|
||||
Checked = transformGizmo.ScaleSnapEnabled,
|
||||
TooltipText = "Enable scale snapping",
|
||||
Parent = scaleSnappingWidget
|
||||
};
|
||||
enableScaleSnapping.Toggled += _ =>
|
||||
{
|
||||
transformGizmo.ScaleSnapEnabled = !transformGizmo.ScaleSnapEnabled;
|
||||
if (useProjectCache)
|
||||
editor.ProjectCache.SetCustomData("ScaleSnapState", transformGizmo.ScaleSnapEnabled.ToString());
|
||||
};
|
||||
var scaleSnappingCM = new ContextMenu();
|
||||
var scaleSnapping = new ViewportWidgetButton(transformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM)
|
||||
{
|
||||
TooltipText = "Scale snapping values"
|
||||
};
|
||||
for (int i = 0; i < ScaleSnapValues.Length; i++)
|
||||
{
|
||||
var v = ScaleSnapValues[i];
|
||||
var button = scaleSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
scaleSnappingCM.ButtonClicked += button =>
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
transformGizmo.ScaleSnapValue = v;
|
||||
scaleSnapping.Text = v.ToString();
|
||||
if (useProjectCache)
|
||||
editor.ProjectCache.SetCustomData("ScaleSnapValue", transformGizmo.ScaleSnapValue.ToString("N"));
|
||||
};
|
||||
scaleSnappingCM.VisibleChanged += control =>
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(transformGizmo.ScaleSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
};
|
||||
scaleSnapping.Parent = scaleSnappingWidget;
|
||||
scaleSnappingWidget.Parent = viewport;
|
||||
|
||||
// Rotation snapping widget
|
||||
var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableRotateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.RotateSnap32, null, true)
|
||||
{
|
||||
Checked = transformGizmo.RotationSnapEnabled,
|
||||
TooltipText = "Enable rotation snapping",
|
||||
Parent = rotateSnappingWidget
|
||||
};
|
||||
enableRotateSnapping.Toggled += _ =>
|
||||
{
|
||||
transformGizmo.RotationSnapEnabled = !transformGizmo.RotationSnapEnabled;
|
||||
if (useProjectCache)
|
||||
editor.ProjectCache.SetCustomData("RotationSnapState", transformGizmo.RotationSnapEnabled.ToString());
|
||||
};
|
||||
var rotateSnappingCM = new ContextMenu();
|
||||
var rotateSnapping = new ViewportWidgetButton(transformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM)
|
||||
{
|
||||
TooltipText = "Rotation snapping values"
|
||||
};
|
||||
for (int i = 0; i < RotateSnapValues.Length; i++)
|
||||
{
|
||||
var v = RotateSnapValues[i];
|
||||
var button = rotateSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
rotateSnappingCM.ButtonClicked += button =>
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
transformGizmo.RotationSnapValue = v;
|
||||
rotateSnapping.Text = v.ToString();
|
||||
if (useProjectCache)
|
||||
editor.ProjectCache.SetCustomData("RotationSnapValue", transformGizmo.RotationSnapValue.ToString("N"));
|
||||
};
|
||||
rotateSnappingCM.VisibleChanged += control =>
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(transformGizmo.RotationSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
};
|
||||
rotateSnapping.Parent = rotateSnappingWidget;
|
||||
rotateSnappingWidget.Parent = viewport;
|
||||
|
||||
// Translation snapping widget
|
||||
var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.Grid32, null, true)
|
||||
{
|
||||
Checked = transformGizmo.TranslationSnapEnable,
|
||||
TooltipText = "Enable position snapping",
|
||||
Parent = translateSnappingWidget
|
||||
};
|
||||
enableTranslateSnapping.Toggled += _ =>
|
||||
{
|
||||
transformGizmo.TranslationSnapEnable = !transformGizmo.TranslationSnapEnable;
|
||||
if (useProjectCache)
|
||||
editor.ProjectCache.SetCustomData("TranslateSnapState", transformGizmo.TranslationSnapEnable.ToString());
|
||||
};
|
||||
var translateSnappingCM = new ContextMenu();
|
||||
var translateSnapping = new ViewportWidgetButton(transformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM)
|
||||
{
|
||||
TooltipText = "Position snapping values"
|
||||
};
|
||||
if (transformGizmo.TranslationSnapValue < 0.0f)
|
||||
translateSnapping.Text = "Bounding Box";
|
||||
for (int i = 0; i < TranslateSnapValues.Length; i++)
|
||||
{
|
||||
var v = TranslateSnapValues[i];
|
||||
var button = translateSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
var buttonBB = translateSnappingCM.AddButton("Bounding Box").LinkTooltip("Snaps the selection based on it's bounding volume");
|
||||
buttonBB.Tag = -1.0f;
|
||||
translateSnappingCM.ButtonClicked += button =>
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
transformGizmo.TranslationSnapValue = v;
|
||||
if (v < 0.0f)
|
||||
translateSnapping.Text = "Bounding Box";
|
||||
else
|
||||
translateSnapping.Text = v.ToString();
|
||||
if (useProjectCache)
|
||||
editor.ProjectCache.SetCustomData("TranslateSnapValue", transformGizmo.TranslationSnapValue.ToString("N"));
|
||||
};
|
||||
translateSnappingCM.VisibleChanged += control =>
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(transformGizmo.TranslationSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
};
|
||||
translateSnapping.Parent = translateSnappingWidget;
|
||||
translateSnappingWidget.Parent = viewport;
|
||||
|
||||
// Gizmo mode widget
|
||||
var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Translate,
|
||||
TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})",
|
||||
Checked = true,
|
||||
Parent = gizmoMode
|
||||
};
|
||||
gizmoModeTranslate.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate;
|
||||
var gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Rotate,
|
||||
TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})",
|
||||
Parent = gizmoMode
|
||||
};
|
||||
gizmoModeRotate.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate;
|
||||
var gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Scale,
|
||||
TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})",
|
||||
Parent = gizmoMode
|
||||
};
|
||||
gizmoModeScale.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale;
|
||||
gizmoMode.Parent = viewport;
|
||||
transformGizmo.ModeChanged += () =>
|
||||
{
|
||||
var mode = transformGizmo.ActiveMode;
|
||||
gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate;
|
||||
gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate;
|
||||
gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale;
|
||||
};
|
||||
|
||||
// Setup input actions
|
||||
viewport.InputActions.Add(options => options.TranslateMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
|
||||
viewport.InputActions.Add(options => options.RotateMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
|
||||
viewport.InputActions.Add(options => options.ScaleMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
|
||||
viewport.InputActions.Add(options => options.ToggleTransformSpace, () =>
|
||||
{
|
||||
transformGizmo.ToggleTransformSpace();
|
||||
if (useProjectCache)
|
||||
editor.ProjectCache.SetCustomData("TransformSpaceState", transformGizmo.ActiveTransformSpace.ToString());
|
||||
transformSpaceToggle.Checked = !transformSpaceToggle.Checked;
|
||||
});
|
||||
}
|
||||
|
||||
internal static readonly float[] TranslateSnapValues =
|
||||
{
|
||||
0.1f,
|
||||
0.5f,
|
||||
1.0f,
|
||||
5.0f,
|
||||
10.0f,
|
||||
100.0f,
|
||||
1000.0f,
|
||||
};
|
||||
|
||||
internal static readonly float[] RotateSnapValues =
|
||||
{
|
||||
1.0f,
|
||||
5.0f,
|
||||
10.0f,
|
||||
15.0f,
|
||||
30.0f,
|
||||
45.0f,
|
||||
60.0f,
|
||||
90.0f,
|
||||
};
|
||||
|
||||
internal static readonly float[] ScaleSnapValues =
|
||||
{
|
||||
0.05f,
|
||||
0.1f,
|
||||
0.25f,
|
||||
0.5f,
|
||||
1.0f,
|
||||
2.0f,
|
||||
4.0f,
|
||||
6.0f,
|
||||
8.0f,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ using FlaxEditor.Viewport.Cameras;
|
||||
using FlaxEditor.Viewport.Widgets;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using Newtonsoft.Json;
|
||||
using JsonSerializer = FlaxEngine.Json.JsonSerializer;
|
||||
|
||||
namespace FlaxEditor.Viewport
|
||||
@@ -154,6 +153,7 @@ namespace FlaxEditor.Viewport
|
||||
|
||||
// Input
|
||||
|
||||
internal bool _disableInputUpdate;
|
||||
private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown;
|
||||
private int _deltaFilteringStep;
|
||||
private Float2 _startPos;
|
||||
@@ -704,9 +704,9 @@ namespace FlaxEditor.Viewport
|
||||
// Camera Viewpoints
|
||||
{
|
||||
var cameraView = cameraCM.AddChildMenu("Viewpoints").ContextMenu;
|
||||
for (int i = 0; i < EditorViewportCameraViewpointValues.Length; i++)
|
||||
for (int i = 0; i < CameraViewpointValues.Length; i++)
|
||||
{
|
||||
var co = EditorViewportCameraViewpointValues[i];
|
||||
var co = CameraViewpointValues[i];
|
||||
var button = cameraView.AddButton(co.Name);
|
||||
button.Tag = co.Orientation;
|
||||
}
|
||||
@@ -899,9 +899,9 @@ namespace FlaxEditor.Viewport
|
||||
viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate32;
|
||||
viewFlags.AddButton("Disable flags", () => Task.ViewFlags = ViewFlags.None).Icon = Editor.Instance.Icons.Rotate32;
|
||||
viewFlags.AddSeparator();
|
||||
for (int i = 0; i < EditorViewportViewFlagsValues.Length; i++)
|
||||
for (int i = 0; i < ViewFlagsValues.Length; i++)
|
||||
{
|
||||
var v = EditorViewportViewFlagsValues[i];
|
||||
var v = ViewFlagsValues[i];
|
||||
var button = viewFlags.AddButton(v.Name);
|
||||
button.CloseMenuOnClick = false;
|
||||
button.Tag = v.Mode;
|
||||
@@ -933,9 +933,9 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
});
|
||||
debugView.AddSeparator();
|
||||
for (int i = 0; i < EditorViewportViewModeValues.Length; i++)
|
||||
for (int i = 0; i < ViewModeValues.Length; i++)
|
||||
{
|
||||
ref var v = ref EditorViewportViewModeValues[i];
|
||||
ref var v = ref ViewModeValues[i];
|
||||
if (v.Options != null)
|
||||
{
|
||||
var childMenu = debugView.AddChildMenu(v.Name).ContextMenu;
|
||||
@@ -989,12 +989,12 @@ namespace FlaxEditor.Viewport
|
||||
#endregion View mode widget
|
||||
}
|
||||
|
||||
InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Top").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointBottom, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Bottom").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointFront, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Front").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Back").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Right").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Left").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Top").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointBottom, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Bottom").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointFront, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Front").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Back").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Right").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Left").Orientation)));
|
||||
InputActions.Add(options => options.CameraToggleRotation, () => _isVirtualMouseRightDown = !_isVirtualMouseRightDown);
|
||||
InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(1));
|
||||
InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(-1));
|
||||
@@ -1496,6 +1496,9 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
if (_disableInputUpdate)
|
||||
return;
|
||||
|
||||
// Update camera
|
||||
bool useMovementSpeed = false;
|
||||
if (_camera != null)
|
||||
@@ -1534,7 +1537,7 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
bool useMouse = IsControllingMouse || (Mathf.IsInRange(_viewMousePos.X, 0, Width) && Mathf.IsInRange(_viewMousePos.Y, 0, Height));
|
||||
_prevInput = _input;
|
||||
var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl));
|
||||
var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl) && !(c is UIEditorRoot));
|
||||
if (canUseInput && ContainsFocus && hit == null)
|
||||
_input.Gather(win.Window, useMouse);
|
||||
else
|
||||
@@ -1867,7 +1870,7 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
}
|
||||
|
||||
private readonly CameraViewpoint[] EditorViewportCameraViewpointValues =
|
||||
private readonly CameraViewpoint[] CameraViewpointValues =
|
||||
{
|
||||
new CameraViewpoint("Front", new Float3(0, 180, 0)),
|
||||
new CameraViewpoint("Back", new Float3(0, 0, 0)),
|
||||
@@ -1898,7 +1901,7 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ViewModeOptions[] EditorViewportViewModeValues =
|
||||
private static readonly ViewModeOptions[] ViewModeValues =
|
||||
{
|
||||
new ViewModeOptions(ViewMode.Default, "Default"),
|
||||
new ViewModeOptions(ViewMode.Unlit, "Unlit"),
|
||||
@@ -1970,7 +1973,7 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ViewFlagOptions[] EditorViewportViewFlagsValues =
|
||||
private static readonly ViewFlagOptions[] ViewFlagsValues =
|
||||
{
|
||||
new ViewFlagOptions(ViewFlags.AntiAliasing, "Anti Aliasing"),
|
||||
new ViewFlagOptions(ViewFlags.Shadows, "Shadows"),
|
||||
@@ -2005,16 +2008,13 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
if (cm.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)cm;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b && b.Tag != null)
|
||||
{
|
||||
var v = (ViewFlags)b.Tag;
|
||||
b.Icon = (Task.View.Flags & v) != 0
|
||||
? Style.Current.CheckBoxTick
|
||||
: SpriteHandle.Invalid;
|
||||
b.Icon = (Task.View.Flags & v) != 0 ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2023,7 +2023,6 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
if (cm.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)cm;
|
||||
var layersMask = Task.ViewLayersMask;
|
||||
foreach (var e in ccm.Items)
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Viewport.Cameras;
|
||||
using FlaxEditor.Viewport.Modes;
|
||||
using FlaxEditor.Viewport.Widgets;
|
||||
using FlaxEditor.Windows;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
@@ -28,13 +25,6 @@ namespace FlaxEditor.Viewport
|
||||
|
||||
private readonly ContextMenuButton _showGridButton;
|
||||
private readonly ContextMenuButton _showNavigationButton;
|
||||
private readonly ViewportWidgetButton _gizmoModeTranslate;
|
||||
private readonly ViewportWidgetButton _gizmoModeRotate;
|
||||
private readonly ViewportWidgetButton _gizmoModeScale;
|
||||
|
||||
private readonly ViewportWidgetButton _translateSnapping;
|
||||
private readonly ViewportWidgetButton _rotateSnapping;
|
||||
private readonly ViewportWidgetButton _scaleSnapping;
|
||||
|
||||
private SelectionOutline _customSelectionOutline;
|
||||
|
||||
@@ -196,7 +186,6 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
_editor = editor;
|
||||
DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType, ValidateDragScriptItem);
|
||||
var inputOptions = editor.Options.Options.Input;
|
||||
|
||||
// Prepare rendering task
|
||||
Task.ActorsSource = ActorsSources.Scenes;
|
||||
@@ -222,8 +211,7 @@ namespace FlaxEditor.Viewport
|
||||
// Add transformation gizmo
|
||||
TransformGizmo = new TransformGizmo(this);
|
||||
TransformGizmo.ApplyTransformation += ApplyTransform;
|
||||
TransformGizmo.ModeChanged += OnGizmoModeChanged;
|
||||
TransformGizmo.Duplicate += Editor.Instance.SceneEditing.Duplicate;
|
||||
TransformGizmo.Duplicate += _editor.SceneEditing.Duplicate;
|
||||
Gizmos.Active = TransformGizmo;
|
||||
|
||||
// Add grid
|
||||
@@ -232,144 +220,8 @@ namespace FlaxEditor.Viewport
|
||||
|
||||
editor.SceneEditing.SelectionChanged += OnSelectionChanged;
|
||||
|
||||
// Initialize snapping enabled from cached values
|
||||
if (_editor.ProjectCache.TryGetCustomData("TranslateSnapState", out var cachedState))
|
||||
TransformGizmo.TranslationSnapEnable = bool.Parse(cachedState);
|
||||
if (_editor.ProjectCache.TryGetCustomData("RotationSnapState", out cachedState))
|
||||
TransformGizmo.RotationSnapEnabled = bool.Parse(cachedState);
|
||||
if (_editor.ProjectCache.TryGetCustomData("ScaleSnapState", out cachedState))
|
||||
TransformGizmo.ScaleSnapEnabled = bool.Parse(cachedState);
|
||||
if (_editor.ProjectCache.TryGetCustomData("TranslateSnapValue", out cachedState))
|
||||
TransformGizmo.TranslationSnapValue = float.Parse(cachedState);
|
||||
if (_editor.ProjectCache.TryGetCustomData("RotationSnapValue", out cachedState))
|
||||
TransformGizmo.RotationSnapValue = float.Parse(cachedState);
|
||||
if (_editor.ProjectCache.TryGetCustomData("ScaleSnapValue", out cachedState))
|
||||
TransformGizmo.ScaleSnapValue = float.Parse(cachedState);
|
||||
if (_editor.ProjectCache.TryGetCustomData("TransformSpaceState", out cachedState) && Enum.TryParse(cachedState, out TransformGizmoBase.TransformSpace space))
|
||||
TransformGizmo.ActiveTransformSpace = space;
|
||||
|
||||
// Transform space widget
|
||||
var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World,
|
||||
TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})",
|
||||
Parent = transformSpaceWidget
|
||||
};
|
||||
transformSpaceToggle.Toggled += OnTransformSpaceToggle;
|
||||
transformSpaceWidget.Parent = this;
|
||||
|
||||
// Scale snapping widget
|
||||
var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableScaleSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.ScaleSnap32, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.ScaleSnapEnabled,
|
||||
TooltipText = "Enable scale snapping",
|
||||
Parent = scaleSnappingWidget
|
||||
};
|
||||
enableScaleSnapping.Toggled += OnScaleSnappingToggle;
|
||||
|
||||
var scaleSnappingCM = new ContextMenu();
|
||||
_scaleSnapping = new ViewportWidgetButton(TransformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM)
|
||||
{
|
||||
TooltipText = "Scale snapping values"
|
||||
};
|
||||
|
||||
for (int i = 0; i < EditorViewportScaleSnapValues.Length; i++)
|
||||
{
|
||||
var v = EditorViewportScaleSnapValues[i];
|
||||
var button = scaleSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
scaleSnappingCM.ButtonClicked += OnWidgetScaleSnapClick;
|
||||
scaleSnappingCM.VisibleChanged += OnWidgetScaleSnapShowHide;
|
||||
_scaleSnapping.Parent = scaleSnappingWidget;
|
||||
scaleSnappingWidget.Parent = this;
|
||||
|
||||
// Rotation snapping widget
|
||||
var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableRotateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.RotateSnap32, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.RotationSnapEnabled,
|
||||
TooltipText = "Enable rotation snapping",
|
||||
Parent = rotateSnappingWidget
|
||||
};
|
||||
enableRotateSnapping.Toggled += OnRotateSnappingToggle;
|
||||
|
||||
var rotateSnappingCM = new ContextMenu();
|
||||
_rotateSnapping = new ViewportWidgetButton(TransformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM)
|
||||
{
|
||||
TooltipText = "Rotation snapping values"
|
||||
};
|
||||
|
||||
for (int i = 0; i < EditorViewportRotateSnapValues.Length; i++)
|
||||
{
|
||||
var v = EditorViewportRotateSnapValues[i];
|
||||
var button = rotateSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
rotateSnappingCM.ButtonClicked += OnWidgetRotateSnapClick;
|
||||
rotateSnappingCM.VisibleChanged += OnWidgetRotateSnapShowHide;
|
||||
_rotateSnapping.Parent = rotateSnappingWidget;
|
||||
rotateSnappingWidget.Parent = this;
|
||||
|
||||
// Translation snapping widget
|
||||
var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.Grid32, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.TranslationSnapEnable,
|
||||
TooltipText = "Enable position snapping",
|
||||
Parent = translateSnappingWidget
|
||||
};
|
||||
enableTranslateSnapping.Toggled += OnTranslateSnappingToggle;
|
||||
|
||||
var translateSnappingCM = new ContextMenu();
|
||||
_translateSnapping = new ViewportWidgetButton(TransformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM)
|
||||
{
|
||||
TooltipText = "Position snapping values"
|
||||
};
|
||||
if (TransformGizmo.TranslationSnapValue < 0.0f)
|
||||
_translateSnapping.Text = "Bounding Box";
|
||||
|
||||
for (int i = 0; i < EditorViewportTranslateSnapValues.Length; i++)
|
||||
{
|
||||
var v = EditorViewportTranslateSnapValues[i];
|
||||
var button = translateSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
var buttonBB = translateSnappingCM.AddButton("Bounding Box").LinkTooltip("Snaps the selection based on it's bounding volume");
|
||||
buttonBB.Tag = -1.0f;
|
||||
|
||||
translateSnappingCM.ButtonClicked += OnWidgetTranslateSnapClick;
|
||||
translateSnappingCM.VisibleChanged += OnWidgetTranslateSnapShowHide;
|
||||
_translateSnapping.Parent = translateSnappingWidget;
|
||||
translateSnappingWidget.Parent = this;
|
||||
|
||||
// Gizmo mode widget
|
||||
var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
_gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Translate,
|
||||
TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})",
|
||||
Checked = true,
|
||||
Parent = gizmoMode
|
||||
};
|
||||
_gizmoModeTranslate.Toggled += OnGizmoModeToggle;
|
||||
_gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Rotate,
|
||||
TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})",
|
||||
Parent = gizmoMode
|
||||
};
|
||||
_gizmoModeRotate.Toggled += OnGizmoModeToggle;
|
||||
_gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Scale,
|
||||
TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})",
|
||||
Parent = gizmoMode
|
||||
};
|
||||
_gizmoModeScale.Toggled += OnGizmoModeToggle;
|
||||
gizmoMode.Parent = this;
|
||||
// Gizmo widgets
|
||||
AddGizmoViewportWidgets(this, TransformGizmo);
|
||||
|
||||
// Show grid widget
|
||||
_showGridButton = ViewWidgetShowMenu.AddButton("Grid", () => Grid.Enabled = !Grid.Enabled);
|
||||
@@ -400,14 +252,6 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
|
||||
// Setup input actions
|
||||
InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
|
||||
InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
|
||||
InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
|
||||
InputActions.Add(options => options.ToggleTransformSpace, () =>
|
||||
{
|
||||
OnTransformSpaceToggle(transformSpaceToggle);
|
||||
transformSpaceToggle.Checked = !transformSpaceToggle.Checked;
|
||||
});
|
||||
InputActions.Add(options => options.LockFocusSelection, LockFocusSelection);
|
||||
InputActions.Add(options => options.FocusSelection, FocusSelection);
|
||||
InputActions.Add(options => options.RotateSelection, RotateSelection);
|
||||
@@ -486,7 +330,7 @@ namespace FlaxEditor.Viewport
|
||||
};
|
||||
|
||||
// Spawn
|
||||
Editor.Instance.SceneEditing.Spawn(actor, parent);
|
||||
_editor.SceneEditing.Spawn(actor, parent);
|
||||
}
|
||||
|
||||
private void OnBegin(RenderTask task, GPUContext context)
|
||||
@@ -552,7 +396,7 @@ namespace FlaxEditor.Viewport
|
||||
var task = renderContext.Task;
|
||||
|
||||
// Render editor primitives, gizmo and debug shapes in debug view modes
|
||||
// Note: can use Output buffer as both input and output because EditorPrimitives is using a intermediate buffers
|
||||
// Note: can use Output buffer as both input and output because EditorPrimitives is using an intermediate buffer
|
||||
if (EditorPrimitives && EditorPrimitives.CanRender())
|
||||
{
|
||||
EditorPrimitives.Render(context, ref renderContext, task.Output, task.Output);
|
||||
@@ -581,161 +425,6 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGizmoModeToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.ActiveMode = (TransformGizmoBase.Mode)(int)button.Tag;
|
||||
}
|
||||
|
||||
private void OnTranslateSnappingToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.TranslationSnapEnable = !TransformGizmo.TranslationSnapEnable;
|
||||
_editor.ProjectCache.SetCustomData("TranslateSnapState", TransformGizmo.TranslationSnapEnable.ToString());
|
||||
}
|
||||
|
||||
private void OnRotateSnappingToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.RotationSnapEnabled = !TransformGizmo.RotationSnapEnabled;
|
||||
_editor.ProjectCache.SetCustomData("RotationSnapState", TransformGizmo.RotationSnapEnabled.ToString());
|
||||
}
|
||||
|
||||
private void OnScaleSnappingToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.ScaleSnapEnabled = !TransformGizmo.ScaleSnapEnabled;
|
||||
_editor.ProjectCache.SetCustomData("ScaleSnapState", TransformGizmo.ScaleSnapEnabled.ToString());
|
||||
}
|
||||
|
||||
private void OnTransformSpaceToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.ToggleTransformSpace();
|
||||
_editor.ProjectCache.SetCustomData("TransformSpaceState", TransformGizmo.ActiveTransformSpace.ToString());
|
||||
}
|
||||
|
||||
private void OnGizmoModeChanged()
|
||||
{
|
||||
// Update all viewport widgets status
|
||||
var mode = TransformGizmo.ActiveMode;
|
||||
_gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate;
|
||||
_gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate;
|
||||
_gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale;
|
||||
}
|
||||
|
||||
private static readonly float[] EditorViewportScaleSnapValues =
|
||||
{
|
||||
0.05f,
|
||||
0.1f,
|
||||
0.25f,
|
||||
0.5f,
|
||||
1.0f,
|
||||
2.0f,
|
||||
4.0f,
|
||||
6.0f,
|
||||
8.0f,
|
||||
};
|
||||
|
||||
private void OnWidgetScaleSnapClick(ContextMenuButton button)
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
TransformGizmo.ScaleSnapValue = v;
|
||||
_scaleSnapping.Text = v.ToString();
|
||||
_editor.ProjectCache.SetCustomData("ScaleSnapValue", TransformGizmo.ScaleSnapValue.ToString("N"));
|
||||
}
|
||||
|
||||
private void OnWidgetScaleSnapShowHide(Control control)
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(TransformGizmo.ScaleSnapValue - v) < 0.001f
|
||||
? Style.Current.CheckBoxTick
|
||||
: SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly float[] EditorViewportRotateSnapValues =
|
||||
{
|
||||
1.0f,
|
||||
5.0f,
|
||||
10.0f,
|
||||
15.0f,
|
||||
30.0f,
|
||||
45.0f,
|
||||
60.0f,
|
||||
90.0f,
|
||||
};
|
||||
|
||||
private void OnWidgetRotateSnapClick(ContextMenuButton button)
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
TransformGizmo.RotationSnapValue = v;
|
||||
_rotateSnapping.Text = v.ToString();
|
||||
_editor.ProjectCache.SetCustomData("RotationSnapValue", TransformGizmo.RotationSnapValue.ToString("N"));
|
||||
}
|
||||
|
||||
private void OnWidgetRotateSnapShowHide(Control control)
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(TransformGizmo.RotationSnapValue - v) < 0.001f
|
||||
? Style.Current.CheckBoxTick
|
||||
: SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly float[] EditorViewportTranslateSnapValues =
|
||||
{
|
||||
0.1f,
|
||||
0.5f,
|
||||
1.0f,
|
||||
5.0f,
|
||||
10.0f,
|
||||
100.0f,
|
||||
1000.0f,
|
||||
};
|
||||
|
||||
private void OnWidgetTranslateSnapClick(ContextMenuButton button)
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
TransformGizmo.TranslationSnapValue = v;
|
||||
if (v < 0.0f)
|
||||
_translateSnapping.Text = "Bounding Box";
|
||||
else
|
||||
_translateSnapping.Text = v.ToString();
|
||||
_editor.ProjectCache.SetCustomData("TranslateSnapValue", TransformGizmo.TranslationSnapValue.ToString("N"));
|
||||
}
|
||||
|
||||
private void OnWidgetTranslateSnapShowHide(Control control)
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(TransformGizmo.TranslationSnapValue - v) < 0.001f
|
||||
? Style.Current.CheckBoxTick
|
||||
: SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectionChanged()
|
||||
{
|
||||
var selection = _editor.SceneEditing.Selection;
|
||||
@@ -761,7 +450,7 @@ namespace FlaxEditor.Viewport
|
||||
Vector3 gizmoPosition = TransformGizmo.Position;
|
||||
|
||||
// Rotate selected objects
|
||||
bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode;
|
||||
bool isPlayMode = _editor.StateMachine.IsPlayMode;
|
||||
TransformGizmo.StartTransforming();
|
||||
for (int i = 0; i < selection.Count; i++)
|
||||
{
|
||||
@@ -819,14 +508,7 @@ namespace FlaxEditor.Viewport
|
||||
/// <param name="orientation">The target view orientation.</param>
|
||||
public void FocusSelection(ref Quaternion orientation)
|
||||
{
|
||||
if (TransformGizmo.SelectedParents.Count == 0)
|
||||
return;
|
||||
|
||||
var gizmoBounds = Gizmos.Active.FocusBounds;
|
||||
if (gizmoBounds != BoundingSphere.Empty)
|
||||
((FPSCamera)ViewportCamera).ShowSphere(ref gizmoBounds, ref orientation);
|
||||
else
|
||||
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation);
|
||||
ViewportCamera.FocusSelection(Gizmos, ref orientation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -843,7 +525,7 @@ namespace FlaxEditor.Viewport
|
||||
Vector3 gizmoPosition = TransformGizmo.Position;
|
||||
|
||||
// Transform selected objects
|
||||
bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode;
|
||||
bool isPlayMode = _editor.StateMachine.IsPlayMode;
|
||||
for (int i = 0; i < selection.Count; i++)
|
||||
{
|
||||
var obj = selection[i];
|
||||
@@ -985,7 +667,14 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
var parent = actor.Parent ?? Level.GetScene(0);
|
||||
actor.Name = Utilities.Utils.IncrementNameNumber(actor.Name, x => parent.GetChild(x) == null);
|
||||
Editor.Instance.SceneEditing.Spawn(actor);
|
||||
_editor.SceneEditing.Spawn(actor);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OpenContextMenu()
|
||||
{
|
||||
var mouse = PointFromWindow(Root.MousePosition);
|
||||
_editor.Windows.SceneWin.ShowContextMenu(this, mouse);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Viewport.Cameras;
|
||||
using FlaxEditor.Viewport.Previews;
|
||||
using FlaxEditor.Viewport.Widgets;
|
||||
using FlaxEditor.Windows.Assets;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
@@ -29,7 +28,6 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
public PrefabWindowViewport Viewport;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanRender()
|
||||
{
|
||||
return (Task.View.Flags & ViewFlags.EditorSprites) == ViewFlags.EditorSprites && Enabled;
|
||||
@@ -41,19 +39,34 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
}
|
||||
|
||||
[HideInEditor]
|
||||
private sealed class PrefabUIEditorRoot : UIEditorRoot
|
||||
{
|
||||
private readonly PrefabWindowViewport _viewport;
|
||||
private bool UI => _viewport._hasUILinkedCached;
|
||||
|
||||
public PrefabUIEditorRoot(PrefabWindowViewport viewport)
|
||||
: base(true)
|
||||
{
|
||||
_viewport = viewport;
|
||||
Parent = viewport;
|
||||
}
|
||||
|
||||
public override bool EnableInputs => !UI;
|
||||
public override bool EnableSelecting => UI;
|
||||
public override bool EnableBackground => UI;
|
||||
public override TransformGizmo TransformGizmo => _viewport.TransformGizmo;
|
||||
}
|
||||
|
||||
private readonly PrefabWindow _window;
|
||||
private UpdateDelegate _update;
|
||||
|
||||
private readonly ViewportWidgetButton _gizmoModeTranslate;
|
||||
private readonly ViewportWidgetButton _gizmoModeRotate;
|
||||
private readonly ViewportWidgetButton _gizmoModeScale;
|
||||
|
||||
private ViewportWidgetButton _translateSnappng;
|
||||
private ViewportWidgetButton _rotateSnapping;
|
||||
private ViewportWidgetButton _scaleSnapping;
|
||||
|
||||
private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32);
|
||||
private PrefabSpritesRenderer _spritesRenderer;
|
||||
private IntPtr _tempDebugDrawContext;
|
||||
|
||||
private bool _hasUILinkedCached;
|
||||
private PrefabUIEditorRoot _uiRoot;
|
||||
|
||||
/// <summary>
|
||||
/// Drag and drop handlers
|
||||
@@ -106,144 +119,74 @@ namespace FlaxEditor.Viewport
|
||||
// Add transformation gizmo
|
||||
TransformGizmo = new TransformGizmo(this);
|
||||
TransformGizmo.ApplyTransformation += ApplyTransform;
|
||||
TransformGizmo.ModeChanged += OnGizmoModeChanged;
|
||||
TransformGizmo.Duplicate += _window.Duplicate;
|
||||
Gizmos.Active = TransformGizmo;
|
||||
|
||||
// Transform space widget
|
||||
var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var transformSpaceToggle = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Globe32, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World,
|
||||
TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})",
|
||||
Parent = transformSpaceWidget
|
||||
};
|
||||
transformSpaceToggle.Toggled += OnTransformSpaceToggle;
|
||||
transformSpaceWidget.Parent = this;
|
||||
// Use custom root for UI controls
|
||||
_uiRoot = new PrefabUIEditorRoot(this);
|
||||
_uiRoot.IndexInParent = 0; // Move viewport down below other widgets in the viewport
|
||||
_uiParentLink = _uiRoot.UIRoot;
|
||||
|
||||
// Scale snapping widget
|
||||
var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableScaleSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.ScaleSnap32, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.ScaleSnapEnabled,
|
||||
TooltipText = "Enable scale snapping",
|
||||
Parent = scaleSnappingWidget
|
||||
};
|
||||
enableScaleSnapping.Toggled += OnScaleSnappingToggle;
|
||||
|
||||
var scaleSnappingCM = new ContextMenu();
|
||||
_scaleSnapping = new ViewportWidgetButton(TransformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM)
|
||||
{
|
||||
TooltipText = "Scale snapping values"
|
||||
};
|
||||
|
||||
for (int i = 0; i < EditorViewportScaleSnapValues.Length; i++)
|
||||
{
|
||||
var v = EditorViewportScaleSnapValues[i];
|
||||
var button = scaleSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
scaleSnappingCM.ButtonClicked += OnWidgetScaleSnapClick;
|
||||
scaleSnappingCM.VisibleChanged += OnWidgetScaleSnapShowHide;
|
||||
_scaleSnapping.Parent = scaleSnappingWidget;
|
||||
scaleSnappingWidget.Parent = this;
|
||||
|
||||
// Rotation snapping widget
|
||||
var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableRotateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.RotateSnap32, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.RotationSnapEnabled,
|
||||
TooltipText = "Enable rotation snapping",
|
||||
Parent = rotateSnappingWidget
|
||||
};
|
||||
enableRotateSnapping.Toggled += OnRotateSnappingToggle;
|
||||
|
||||
var rotateSnappingCM = new ContextMenu();
|
||||
_rotateSnapping = new ViewportWidgetButton(TransformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM)
|
||||
{
|
||||
TooltipText = "Rotation snapping values"
|
||||
};
|
||||
|
||||
for (int i = 0; i < EditorViewportRotateSnapValues.Length; i++)
|
||||
{
|
||||
var v = EditorViewportRotateSnapValues[i];
|
||||
var button = rotateSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
rotateSnappingCM.ButtonClicked += OnWidgetRotateSnapClick;
|
||||
rotateSnappingCM.VisibleChanged += OnWidgetRotateSnapShowHide;
|
||||
_rotateSnapping.Parent = rotateSnappingWidget;
|
||||
rotateSnappingWidget.Parent = this;
|
||||
|
||||
// Translation snapping widget
|
||||
var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Grid32, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.TranslationSnapEnable,
|
||||
TooltipText = "Enable position snapping",
|
||||
Parent = translateSnappingWidget
|
||||
};
|
||||
enableTranslateSnapping.Toggled += OnTranslateSnappingToggle;
|
||||
|
||||
var translateSnappingCM = new ContextMenu();
|
||||
_translateSnappng = new ViewportWidgetButton(TransformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM)
|
||||
{
|
||||
TooltipText = "Position snapping values"
|
||||
};
|
||||
|
||||
for (int i = 0; i < EditorViewportTranslateSnapValues.Length; i++)
|
||||
{
|
||||
var v = EditorViewportTranslateSnapValues[i];
|
||||
var button = translateSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
translateSnappingCM.ButtonClicked += OnWidgetTranslateSnapClick;
|
||||
translateSnappingCM.VisibleChanged += OnWidgetTranslateSnapShowHide;
|
||||
_translateSnappng.Parent = translateSnappingWidget;
|
||||
translateSnappingWidget.Parent = this;
|
||||
|
||||
// Gizmo mode widget
|
||||
var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
_gizmoModeTranslate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Translate32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Translate,
|
||||
TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})",
|
||||
Checked = true,
|
||||
Parent = gizmoMode
|
||||
};
|
||||
_gizmoModeTranslate.Toggled += OnGizmoModeToggle;
|
||||
_gizmoModeRotate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Rotate32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Rotate,
|
||||
TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})",
|
||||
Parent = gizmoMode
|
||||
};
|
||||
_gizmoModeRotate.Toggled += OnGizmoModeToggle;
|
||||
_gizmoModeScale = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Scale32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Scale,
|
||||
TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})",
|
||||
Parent = gizmoMode
|
||||
};
|
||||
_gizmoModeScale.Toggled += OnGizmoModeToggle;
|
||||
gizmoMode.Parent = this;
|
||||
EditorGizmoViewport.AddGizmoViewportWidgets(this, TransformGizmo);
|
||||
|
||||
// Setup input actions
|
||||
InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
|
||||
InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
|
||||
InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
|
||||
InputActions.Add(options => options.ToggleTransformSpace, () =>
|
||||
{
|
||||
OnTransformSpaceToggle(transformSpaceToggle);
|
||||
transformSpaceToggle.Checked = !transformSpaceToggle.Checked;
|
||||
});
|
||||
InputActions.Add(options => options.FocusSelection, ShowSelectedActors);
|
||||
|
||||
SetUpdate(ref _update, OnUpdate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the viewport's gizmos, especially to toggle between 3D and UI editing modes.
|
||||
/// </summary>
|
||||
internal void UpdateGizmoMode()
|
||||
{
|
||||
// Skip if gizmo mode was unmodified
|
||||
if (_hasUILinked == _hasUILinkedCached)
|
||||
return;
|
||||
_hasUILinkedCached = _hasUILinked;
|
||||
|
||||
if (_hasUILinked)
|
||||
{
|
||||
// UI widget
|
||||
Gizmos.Active = null;
|
||||
ViewportCamera = new UIEditorCamera { UIEditor = _uiRoot };
|
||||
|
||||
// Hide 3D visuals
|
||||
ShowEditorPrimitives = false;
|
||||
ShowDefaultSceneActors = false;
|
||||
ShowDebugDraw = false;
|
||||
|
||||
// Show whole UI on startup
|
||||
var canvas = (CanvasRootControl)_uiParentLink.Children.FirstOrDefault(x => x is CanvasRootControl);
|
||||
if (canvas != null)
|
||||
ViewportCamera.ShowActor(canvas.Canvas);
|
||||
else if (Instance is UIControl)
|
||||
ViewportCamera.ShowActor(Instance);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Generic prefab
|
||||
Gizmos.Active = TransformGizmo;
|
||||
ViewportCamera = new FPSCamera();
|
||||
}
|
||||
|
||||
// Update default components usage
|
||||
bool defaultFeatures = !_hasUILinked;
|
||||
_disableInputUpdate = _hasUILinked;
|
||||
_spritesRenderer.Enabled = defaultFeatures;
|
||||
SelectionOutline.Enabled = defaultFeatures;
|
||||
_showDefaultSceneButton.Visible = defaultFeatures;
|
||||
_cameraWidget.Visible = defaultFeatures;
|
||||
_cameraButton.Visible = defaultFeatures;
|
||||
_orthographicModeButton.Visible = defaultFeatures;
|
||||
Task.Enabled = defaultFeatures;
|
||||
UseAutomaticTaskManagement = defaultFeatures;
|
||||
TintColor = defaultFeatures ? Color.White : Color.Transparent;
|
||||
}
|
||||
|
||||
private void OnUpdate(float deltaTime)
|
||||
{
|
||||
UpdateGizmoMode();
|
||||
for (int i = 0; i < Gizmos.Count; i++)
|
||||
{
|
||||
Gizmos[i].Update(deltaTime);
|
||||
@@ -258,11 +201,19 @@ namespace FlaxEditor.Viewport
|
||||
var selectedParents = TransformGizmo.SelectedParents;
|
||||
if (selectedParents.Count > 0)
|
||||
{
|
||||
// Use temporary Debug Draw context to pull any debug shapes drawing in Scene Graph Nodes - those are used in OnDebugDraw down below
|
||||
if (_tempDebugDrawContext == IntPtr.Zero)
|
||||
_tempDebugDrawContext = DebugDraw.AllocateContext();
|
||||
DebugDraw.SetContext(_tempDebugDrawContext);
|
||||
DebugDraw.UpdateContext(_tempDebugDrawContext, 1.0f);
|
||||
|
||||
for (int i = 0; i < selectedParents.Count; i++)
|
||||
{
|
||||
if (selectedParents[i].IsActiveInHierarchy)
|
||||
selectedParents[i].OnDebugDraw(_debugDrawData);
|
||||
}
|
||||
|
||||
DebugDraw.SetContext(IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,7 +257,7 @@ namespace FlaxEditor.Viewport
|
||||
public void ShowSelectedActors()
|
||||
{
|
||||
var orient = ViewOrientation;
|
||||
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orient);
|
||||
ViewportCamera.ShowActors(TransformGizmo.SelectedParents, ref orient);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -366,6 +317,13 @@ namespace FlaxEditor.Viewport
|
||||
_window.Spawn(actor);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OpenContextMenu()
|
||||
{
|
||||
var mouse = PointFromWindow(Root.MousePosition);
|
||||
_window.ShowContextMenu(this, ref mouse);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false;
|
||||
|
||||
@@ -385,151 +343,6 @@ namespace FlaxEditor.Viewport
|
||||
root.UpdateCallbacksToRemove.Add(_update);
|
||||
}
|
||||
|
||||
private void OnGizmoModeToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.ActiveMode = (TransformGizmoBase.Mode)(int)button.Tag;
|
||||
}
|
||||
|
||||
private void OnTranslateSnappingToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.TranslationSnapEnable = !TransformGizmo.TranslationSnapEnable;
|
||||
}
|
||||
|
||||
private void OnRotateSnappingToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.RotationSnapEnabled = !TransformGizmo.RotationSnapEnabled;
|
||||
}
|
||||
|
||||
private void OnScaleSnappingToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.ScaleSnapEnabled = !TransformGizmo.ScaleSnapEnabled;
|
||||
}
|
||||
|
||||
private void OnTransformSpaceToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.ToggleTransformSpace();
|
||||
}
|
||||
|
||||
private void OnGizmoModeChanged()
|
||||
{
|
||||
// Update all viewport widgets status
|
||||
var mode = TransformGizmo.ActiveMode;
|
||||
_gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate;
|
||||
_gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate;
|
||||
_gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale;
|
||||
}
|
||||
|
||||
private static readonly float[] EditorViewportScaleSnapValues =
|
||||
{
|
||||
0.05f,
|
||||
0.1f,
|
||||
0.25f,
|
||||
0.5f,
|
||||
1.0f,
|
||||
2.0f,
|
||||
4.0f,
|
||||
6.0f,
|
||||
8.0f,
|
||||
};
|
||||
|
||||
private void OnWidgetScaleSnapClick(ContextMenuButton button)
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
TransformGizmo.ScaleSnapValue = v;
|
||||
_scaleSnapping.Text = v.ToString();
|
||||
}
|
||||
|
||||
private void OnWidgetScaleSnapShowHide(Control control)
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(TransformGizmo.ScaleSnapValue - v) < 0.001f
|
||||
? Style.Current.CheckBoxTick
|
||||
: SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly float[] EditorViewportRotateSnapValues =
|
||||
{
|
||||
1.0f,
|
||||
5.0f,
|
||||
10.0f,
|
||||
15.0f,
|
||||
30.0f,
|
||||
45.0f,
|
||||
60.0f,
|
||||
90.0f,
|
||||
};
|
||||
|
||||
private void OnWidgetRotateSnapClick(ContextMenuButton button)
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
TransformGizmo.RotationSnapValue = v;
|
||||
_rotateSnapping.Text = v.ToString();
|
||||
}
|
||||
|
||||
private void OnWidgetRotateSnapShowHide(Control control)
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(TransformGizmo.RotationSnapValue - v) < 0.001f
|
||||
? Style.Current.CheckBoxTick
|
||||
: SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly float[] EditorViewportTranslateSnapValues =
|
||||
{
|
||||
0.1f,
|
||||
0.5f,
|
||||
1.0f,
|
||||
5.0f,
|
||||
10.0f,
|
||||
100.0f,
|
||||
1000.0f,
|
||||
};
|
||||
|
||||
private void OnWidgetTranslateSnapClick(ContextMenuButton button)
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
TransformGizmo.TranslationSnapValue = v;
|
||||
_translateSnappng.Text = v.ToString();
|
||||
}
|
||||
|
||||
private void OnWidgetTranslateSnapShowHide(Control control)
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(TransformGizmo.TranslationSnapValue - v) < 0.001f
|
||||
? Style.Current.CheckBoxTick
|
||||
: SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectionChanged()
|
||||
{
|
||||
Gizmos.ForEach(x => x.OnSelectionChanged(_window.Selection));
|
||||
@@ -584,40 +397,6 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
// 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 < _window.Selection.Count; i++)
|
||||
{
|
||||
if (_window.Selection[i]?.EditableObject is UIControl controlActor && controlActor && controlActor.Control != null && controlActor.Control.VisibleInHierarchy && controlActor.Control.RootWindow != null)
|
||||
{
|
||||
if (!drawAnySelectedControl)
|
||||
{
|
||||
drawAnySelectedControl = true;
|
||||
Render2D.PushTransform(ref _cachedTransform);
|
||||
}
|
||||
var control = controlActor.Control;
|
||||
var bounds = control.EditorBounds;
|
||||
var p1 = control.PointToParent(this, bounds.UpperLeft);
|
||||
var p2 = control.PointToParent(this, bounds.UpperRight);
|
||||
var p3 = control.PointToParent(this, bounds.BottomLeft);
|
||||
var p4 = control.PointToParent(this, bounds.BottomRight);
|
||||
var min = Float2.Min(Float2.Min(p1, p2), Float2.Min(p3, p4));
|
||||
var max = Float2.Max(Float2.Max(p1, p2), Float2.Max(p3, p4));
|
||||
bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero));
|
||||
var options = Editor.Instance.Options.Options.Visual;
|
||||
Render2D.DrawRectangle(bounds, options.SelectionOutlineColor0, options.UISelectionOutlineSize);
|
||||
}
|
||||
}
|
||||
if (drawAnySelectedControl)
|
||||
Render2D.PopTransform();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnLeftMouseButtonUp()
|
||||
{
|
||||
@@ -760,14 +539,7 @@ namespace FlaxEditor.Viewport
|
||||
/// <param name="orientation">The target view orientation.</param>
|
||||
public void FocusSelection(ref Quaternion orientation)
|
||||
{
|
||||
if (TransformGizmo.SelectedParents.Count == 0)
|
||||
return;
|
||||
|
||||
var gizmoBounds = Gizmos.Active.FocusBounds;
|
||||
if (gizmoBounds != BoundingSphere.Empty)
|
||||
((FPSCamera)ViewportCamera).ShowSphere(ref gizmoBounds, ref orientation);
|
||||
else
|
||||
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation);
|
||||
ViewportCamera.FocusSelection(Gizmos, ref orientation);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -792,6 +564,13 @@ namespace FlaxEditor.Viewport
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (IsDisposing)
|
||||
return;
|
||||
if (_tempDebugDrawContext != IntPtr.Zero)
|
||||
{
|
||||
DebugDraw.FreeContext(_tempDebugDrawContext);
|
||||
_tempDebugDrawContext = IntPtr.Zero;
|
||||
}
|
||||
FlaxEngine.Object.Destroy(ref SelectionOutline);
|
||||
FlaxEngine.Object.Destroy(ref _spritesRenderer);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace FlaxEditor.Viewport.Previews
|
||||
/// <seealso cref="FlaxEditor.Viewport.EditorViewport" />
|
||||
public abstract class AssetPreview : EditorViewport, IEditorPrimitivesOwner
|
||||
{
|
||||
private ContextMenuButton _showDefaultSceneButton;
|
||||
internal ContextMenuButton _showDefaultSceneButton;
|
||||
private IntPtr _debugDrawContext;
|
||||
private bool _debugDrawEnable;
|
||||
private bool _editorPrimitivesEnable;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.Viewport.Previews
|
||||
@@ -13,19 +13,11 @@ namespace FlaxEditor.Viewport.Previews
|
||||
/// <seealso cref="AssetPreview" />
|
||||
public class PrefabPreview : AssetPreview
|
||||
{
|
||||
/// <summary>
|
||||
/// The currently spawned prefab instance owner. Used to link some actors such as UIControl to preview scene and view.
|
||||
/// </summary>
|
||||
internal static PrefabPreview LoadingPreview;
|
||||
|
||||
/// <summary>
|
||||
/// The list of active prefab previews. Used to link some actors such as UIControl to preview scene and view.
|
||||
/// </summary>
|
||||
internal static List<PrefabPreview> ActivePreviews;
|
||||
|
||||
private Prefab _prefab;
|
||||
private Actor _instance;
|
||||
internal UIControl customControlLinked;
|
||||
private UIControl _uiControlLinked;
|
||||
internal bool _hasUILinked;
|
||||
internal ContainerControl _uiParentLink;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the prefab asset to preview.
|
||||
@@ -54,13 +46,10 @@ namespace FlaxEditor.Viewport.Previews
|
||||
_prefab.WaitForLoaded();
|
||||
|
||||
// Spawn prefab
|
||||
LoadingPreview = this;
|
||||
var instance = PrefabManager.SpawnPrefab(_prefab, null);
|
||||
LoadingPreview = null;
|
||||
if (instance == null)
|
||||
{
|
||||
_prefab = null;
|
||||
ActivePreviews.Remove(this);
|
||||
throw new Exception("Failed to spawn a prefab for the preview.");
|
||||
}
|
||||
|
||||
@@ -84,11 +73,11 @@ namespace FlaxEditor.Viewport.Previews
|
||||
if (_instance)
|
||||
{
|
||||
// Unlink UI control
|
||||
if (customControlLinked)
|
||||
if (_uiControlLinked)
|
||||
{
|
||||
if (customControlLinked.Control?.Parent == this)
|
||||
customControlLinked.Control.Parent = null;
|
||||
customControlLinked = null;
|
||||
if (_uiControlLinked.Control?.Parent == _uiParentLink)
|
||||
_uiControlLinked.Control.Parent = null;
|
||||
_uiControlLinked = null;
|
||||
}
|
||||
|
||||
// Remove for the preview
|
||||
@@ -96,27 +85,51 @@ namespace FlaxEditor.Viewport.Previews
|
||||
}
|
||||
|
||||
_instance = value;
|
||||
_hasUILinked = false;
|
||||
|
||||
if (_instance)
|
||||
{
|
||||
// Add to the preview
|
||||
Task.AddCustomActor(_instance);
|
||||
|
||||
// Link UI canvases to the preview
|
||||
LinkCanvas(_instance);
|
||||
UpdateLinkage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLinkage()
|
||||
{
|
||||
// Clear flag
|
||||
_hasUILinked = false;
|
||||
|
||||
// Link UI canvases to the preview (eg. after canvas added to the prefab)
|
||||
LinkCanvas(_instance);
|
||||
|
||||
// Link UI control to the preview
|
||||
if (_uiControlLinked == null &&
|
||||
_instance is UIControl uiControl &&
|
||||
uiControl.Control != null &&
|
||||
uiControl.Control.Parent == null)
|
||||
{
|
||||
uiControl.Control.Parent = _uiParentLink;
|
||||
_uiControlLinked = uiControl;
|
||||
_hasUILinked = true;
|
||||
}
|
||||
else if (_uiControlLinked != null)
|
||||
_hasUILinked = true;
|
||||
}
|
||||
|
||||
private void LinkCanvas(Actor actor)
|
||||
{
|
||||
if (actor is UICanvas uiCanvas)
|
||||
uiCanvas.EditorOverride(Task, this);
|
||||
{
|
||||
uiCanvas.EditorOverride(Task, _uiParentLink);
|
||||
if (uiCanvas.GUI.Parent == _uiParentLink)
|
||||
_hasUILinked = true;
|
||||
}
|
||||
|
||||
var children = actor.ChildrenCount;
|
||||
for (int i = 0; i < children; i++)
|
||||
{
|
||||
LinkCanvas(actor.GetChild(i));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -126,9 +139,8 @@ namespace FlaxEditor.Viewport.Previews
|
||||
public PrefabPreview(bool useWidgets)
|
||||
: base(useWidgets)
|
||||
{
|
||||
if (ActivePreviews == null)
|
||||
ActivePreviews = new List<PrefabPreview>();
|
||||
ActivePreviews.Add(this);
|
||||
// Link to itself by default
|
||||
_uiParentLink = this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -138,15 +150,13 @@ namespace FlaxEditor.Viewport.Previews
|
||||
|
||||
if (_instance != null)
|
||||
{
|
||||
// Link UI canvases to the preview (eg. after canvas added to the prefab)
|
||||
LinkCanvas(_instance);
|
||||
UpdateLinkage();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
ActivePreviews.Remove(this);
|
||||
Prefab = null;
|
||||
|
||||
base.OnDestroy();
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace FlaxEditor.Viewport
|
||||
var gridPlane = new Plane(Vector3.Zero, Vector3.Up);
|
||||
var flags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives;
|
||||
hit = _owner.SceneGraphRoot.RayCast(ref ray, ref view, out var closest, out var normal, flags);
|
||||
var girdGizmo = (GridGizmo)_owner.Gizmos.FirstOrDefault(x => x is GridGizmo);
|
||||
var girdGizmo = _owner.Gizmos.Get<GridGizmo>();
|
||||
if (hit != null)
|
||||
{
|
||||
// Use hit location
|
||||
@@ -180,7 +180,7 @@ namespace FlaxEditor.Viewport
|
||||
var location = hitLocation + new Vector3(0, bottomToCenter, 0);
|
||||
|
||||
// Apply grid snapping if enabled
|
||||
var transformGizmo = (TransformGizmo)_owner.Gizmos.FirstOrDefault(x => x is TransformGizmo);
|
||||
var transformGizmo = _owner.Gizmos.Get<TransformGizmo>();
|
||||
if (transformGizmo != null && (_owner.UseSnapping || transformGizmo.TranslationSnapEnable))
|
||||
{
|
||||
float snapValue = transformGizmo.TranslationSnapValue;
|
||||
|
||||
@@ -186,6 +186,10 @@ namespace FlaxEditor.Windows.Assets
|
||||
|
||||
base.Initialize(layout);
|
||||
|
||||
// Ignore import settings GUI if the type is not animation. This removes the import UI if the animation asset was not created using an import.
|
||||
if (proxy.ImportSettings.Settings.Type != FlaxEngine.Tools.ModelTool.ModelType.Animation)
|
||||
return;
|
||||
|
||||
// Import Settings
|
||||
{
|
||||
var group = layout.Group("Import Settings");
|
||||
|
||||
@@ -360,10 +360,9 @@ namespace FlaxEditor.Windows.Assets
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent control.</param>
|
||||
/// <param name="location">The location (within a given control).</param>
|
||||
private void ShowContextMenu(Control parent, ref Float2 location)
|
||||
internal void ShowContextMenu(Control parent, ref Float2 location)
|
||||
{
|
||||
var contextMenu = CreateContextMenu();
|
||||
|
||||
contextMenu.Show(parent, location);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.SceneGraph.GUI;
|
||||
using FlaxEditor.Viewport.Cameras;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Windows.Assets
|
||||
@@ -64,8 +62,11 @@ namespace FlaxEditor.Windows.Assets
|
||||
private void OnSelectionUndo(SceneGraphNode[] toSelect)
|
||||
{
|
||||
Selection.Clear();
|
||||
Selection.AddRange(toSelect);
|
||||
|
||||
foreach (var e in toSelect)
|
||||
{
|
||||
if (e != null)
|
||||
Selection.Add(e);
|
||||
}
|
||||
OnSelectionChanges();
|
||||
}
|
||||
|
||||
@@ -118,11 +119,13 @@ namespace FlaxEditor.Windows.Assets
|
||||
/// <param name="nodes">The nodes.</param>
|
||||
public void Select(List<SceneGraphNode> nodes)
|
||||
{
|
||||
nodes?.RemoveAll(x => x == null);
|
||||
if (nodes == null || nodes.Count == 0)
|
||||
{
|
||||
Deselect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Utils.ArraysEqual(Selection, nodes))
|
||||
return;
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
IsScrollable = false,
|
||||
Offsets = new Margin(0, 0, 0, 18 + 6),
|
||||
};
|
||||
_searchBox = new SearchBox()
|
||||
_searchBox = new SearchBox
|
||||
{
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
|
||||
Parent = headerPanel,
|
||||
@@ -140,7 +140,8 @@ namespace FlaxEditor.Windows.Assets
|
||||
};
|
||||
_searchBox.TextChanged += OnSearchBoxTextChanged;
|
||||
|
||||
_treePanel = new Panel()
|
||||
// Prefab structure tree
|
||||
_treePanel = new Panel
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Offsets = new Margin(0.0f, 0.0f, headerPanel.Bottom, 0.0f),
|
||||
@@ -148,8 +149,6 @@ namespace FlaxEditor.Windows.Assets
|
||||
IsScrollable = true,
|
||||
Parent = sceneTreePanel,
|
||||
};
|
||||
|
||||
// Prefab structure tree
|
||||
Graph = new LocalSceneGraph(new CustomRootNode(this));
|
||||
Graph.Root.TreeNode.Expand(true);
|
||||
_tree = new PrefabTree
|
||||
@@ -316,11 +315,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
return;
|
||||
|
||||
// Restore
|
||||
_viewport.Prefab = _asset;
|
||||
Graph.MainActor = _viewport.Instance;
|
||||
Selection.Clear();
|
||||
Select(Graph.Main);
|
||||
Graph.Root.TreeNode.Expand(true);
|
||||
OnPrefabOpened();
|
||||
_undo.Clear();
|
||||
ClearEditedFlag();
|
||||
}
|
||||
@@ -346,6 +341,16 @@ namespace FlaxEditor.Windows.Assets
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPrefabOpened()
|
||||
{
|
||||
_viewport.Prefab = _asset;
|
||||
_viewport.UpdateGizmoMode();
|
||||
Graph.MainActor = _viewport.Instance;
|
||||
Selection.Clear();
|
||||
Select(Graph.Main);
|
||||
Graph.Root.TreeNode.Expand(true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Save()
|
||||
{
|
||||
@@ -355,7 +360,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
|
||||
try
|
||||
{
|
||||
Editor.Scene.OnSaveStart(_viewport);
|
||||
Editor.Scene.OnSaveStart(_viewport._uiParentLink);
|
||||
|
||||
// Simply update changes
|
||||
Editor.Prefabs.ApplyAll(_viewport.Instance);
|
||||
@@ -375,7 +380,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
}
|
||||
finally
|
||||
{
|
||||
Editor.Scene.OnSaveEnd(_viewport);
|
||||
Editor.Scene.OnSaveEnd(_viewport._uiParentLink);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,13 +422,8 @@ namespace FlaxEditor.Windows.Assets
|
||||
return;
|
||||
}
|
||||
|
||||
_viewport.Prefab = _asset;
|
||||
Graph.MainActor = _viewport.Instance;
|
||||
OnPrefabOpened();
|
||||
_focusCamera = true;
|
||||
Selection.Clear();
|
||||
Select(Graph.Main);
|
||||
Graph.Root.TreeNode.Expand(true);
|
||||
|
||||
_undo.Clear();
|
||||
ClearEditedFlag();
|
||||
|
||||
@@ -468,11 +468,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
_viewport.Prefab = null;
|
||||
if (_asset.IsLoaded)
|
||||
{
|
||||
_viewport.Prefab = _asset;
|
||||
Graph.MainActor = _viewport.Instance;
|
||||
Selection.Clear();
|
||||
Select(Graph.Main);
|
||||
Graph.Root.TreeNode.ExpandAll(true);
|
||||
OnPrefabOpened();
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -484,7 +480,6 @@ namespace FlaxEditor.Windows.Assets
|
||||
if (_focusCamera && _viewport.Task.FrameCount > 1)
|
||||
{
|
||||
_focusCamera = false;
|
||||
|
||||
Editor.GetActorEditorSphere(_viewport.Instance, out BoundingSphere bounds);
|
||||
_viewport.ViewPosition = bounds.Center - _viewport.ViewDirection * (bounds.Radius * 1.2f);
|
||||
}
|
||||
|
||||
@@ -281,6 +281,9 @@ namespace FlaxEditor.Windows
|
||||
_view.OnDelete += Delete;
|
||||
_view.OnDuplicate += Duplicate;
|
||||
_view.OnPaste += Paste;
|
||||
|
||||
_view.InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus());
|
||||
InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus());
|
||||
}
|
||||
|
||||
private ContextMenu OnViewDropdownPopupCreate(ComboBox comboBox)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEditor.Options;
|
||||
@@ -194,133 +195,14 @@ namespace FlaxEditor.Windows
|
||||
public bool Active;
|
||||
}
|
||||
|
||||
private class GameRoot : ContainerControl
|
||||
/// <summary>
|
||||
/// Root control for game UI preview in Editor. Supports basic UI editing via <see cref="UIEditorRoot"/>.
|
||||
/// </summary>
|
||||
private class GameRoot : UIEditorRoot
|
||||
{
|
||||
public bool EnableEvents => !Time.GamePaused;
|
||||
|
||||
public override bool RayCast(ref Float2 location, out Control hit)
|
||||
{
|
||||
return RayCastChildren(ref location, out hit);
|
||||
}
|
||||
|
||||
public override bool ContainsPoint(ref Float2 location, bool precise = false)
|
||||
{
|
||||
if (precise)
|
||||
return false;
|
||||
return base.ContainsPoint(ref location, precise);
|
||||
}
|
||||
|
||||
public override bool OnCharInput(char c)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return false;
|
||||
|
||||
return base.OnCharInput(c);
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return DragDropEffect.None;
|
||||
|
||||
return base.OnDragDrop(ref location, data);
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return DragDropEffect.None;
|
||||
|
||||
return base.OnDragEnter(ref location, data);
|
||||
}
|
||||
|
||||
public override void OnDragLeave()
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return;
|
||||
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return DragDropEffect.None;
|
||||
|
||||
return base.OnDragMove(ref location, data);
|
||||
}
|
||||
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return false;
|
||||
|
||||
return base.OnKeyDown(key);
|
||||
}
|
||||
|
||||
public override void OnKeyUp(KeyboardKeys key)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return;
|
||||
|
||||
base.OnKeyUp(key);
|
||||
}
|
||||
|
||||
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return false;
|
||||
|
||||
return base.OnMouseDoubleClick(location, button);
|
||||
}
|
||||
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return false;
|
||||
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
public override void OnMouseEnter(Float2 location)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return;
|
||||
|
||||
base.OnMouseEnter(location);
|
||||
}
|
||||
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return;
|
||||
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return;
|
||||
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return false;
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
public override bool OnMouseWheel(Float2 location, float delta)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return false;
|
||||
|
||||
return base.OnMouseWheel(location, delta);
|
||||
}
|
||||
public override bool EnableInputs => !Time.GamePaused && Editor.IsPlayMode;
|
||||
public override bool EnableSelecting => !Editor.IsPlayMode || Time.GamePaused;
|
||||
public override TransformGizmo TransformGizmo => Editor.Instance.MainTransformGizmo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -348,13 +230,9 @@ namespace FlaxEditor.Windows
|
||||
// Override the game GUI root
|
||||
_guiRoot = new GameRoot
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Offsets = Margin.Zero,
|
||||
//Visible = false,
|
||||
AutoFocus = false,
|
||||
Parent = _viewport
|
||||
};
|
||||
RootControl.GameRoot = _guiRoot;
|
||||
RootControl.GameRoot = _guiRoot.UIRoot;
|
||||
|
||||
SizeChanged += control => { ResizeViewport(); };
|
||||
|
||||
@@ -382,6 +260,56 @@ namespace FlaxEditor.Windows
|
||||
Editor.Instance.Windows.ProfilerWin.Clear();
|
||||
Editor.Instance.UI.AddStatusMessage($"Profiling results cleared.");
|
||||
});
|
||||
InputActions.Add(options => options.Save, () =>
|
||||
{
|
||||
if (Editor.IsPlayMode)
|
||||
return;
|
||||
Editor.Instance.SaveAll();
|
||||
});
|
||||
InputActions.Add(options => options.Undo, () =>
|
||||
{
|
||||
if (Editor.IsPlayMode)
|
||||
return;
|
||||
Editor.Instance.PerformUndo();
|
||||
Focus();
|
||||
});
|
||||
InputActions.Add(options => options.Redo, () =>
|
||||
{
|
||||
if (Editor.IsPlayMode)
|
||||
return;
|
||||
Editor.Instance.PerformRedo();
|
||||
Focus();
|
||||
});
|
||||
InputActions.Add(options => options.Cut, () =>
|
||||
{
|
||||
if (Editor.IsPlayMode)
|
||||
return;
|
||||
Editor.Instance.SceneEditing.Cut();
|
||||
});
|
||||
InputActions.Add(options => options.Copy, () =>
|
||||
{
|
||||
if (Editor.IsPlayMode)
|
||||
return;
|
||||
Editor.Instance.SceneEditing.Copy();
|
||||
});
|
||||
InputActions.Add(options => options.Paste, () =>
|
||||
{
|
||||
if (Editor.IsPlayMode)
|
||||
return;
|
||||
Editor.Instance.SceneEditing.Paste();
|
||||
});
|
||||
InputActions.Add(options => options.Duplicate, () =>
|
||||
{
|
||||
if (Editor.IsPlayMode)
|
||||
return;
|
||||
Editor.Instance.SceneEditing.Duplicate();
|
||||
});
|
||||
InputActions.Add(options => options.Delete, () =>
|
||||
{
|
||||
if (Editor.IsPlayMode)
|
||||
return;
|
||||
Editor.Instance.SceneEditing.Delete();
|
||||
});
|
||||
}
|
||||
|
||||
private void ChangeViewportRatio(ViewportScaleOptions v)
|
||||
@@ -916,35 +844,6 @@ namespace FlaxEditor.Windows
|
||||
Render2D.DrawText(style.FontLarge, "No camera", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
|
||||
}
|
||||
|
||||
// Selected UI controls outline
|
||||
bool drawAnySelectedControl = false;
|
||||
// TODO: optimize this (eg. cache list of selected UIControl's when selection gets changed)
|
||||
var selection = Editor.SceneEditing.Selection;
|
||||
for (var i = 0; i < selection.Count; i++)
|
||||
{
|
||||
if (selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null && controlActor.Control.VisibleInHierarchy && controlActor.Control.RootWindow != null)
|
||||
{
|
||||
if (!drawAnySelectedControl)
|
||||
{
|
||||
drawAnySelectedControl = true;
|
||||
Render2D.PushTransform(ref _viewport._cachedTransform);
|
||||
}
|
||||
var options = Editor.Options.Options.Visual;
|
||||
var control = controlActor.Control;
|
||||
var bounds = control.EditorBounds;
|
||||
var p1 = control.PointToParent(_viewport, bounds.UpperLeft);
|
||||
var p2 = control.PointToParent(_viewport, bounds.UpperRight);
|
||||
var p3 = control.PointToParent(_viewport, bounds.BottomLeft);
|
||||
var p4 = control.PointToParent(_viewport, bounds.BottomRight);
|
||||
var min = Float2.Min(Float2.Min(p1, p2), Float2.Min(p3, p4));
|
||||
var max = Float2.Max(Float2.Max(p1, p2), Float2.Max(p3, p4));
|
||||
bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero));
|
||||
Render2D.DrawRectangle(bounds, options.SelectionOutlineColor0, options.UISelectionOutlineSize);
|
||||
}
|
||||
}
|
||||
if (drawAnySelectedControl)
|
||||
Render2D.PopTransform();
|
||||
|
||||
// Play mode hints and overlay
|
||||
if (Editor.StateMachine.IsPlayMode)
|
||||
{
|
||||
|
||||
@@ -65,6 +65,11 @@ namespace FlaxEditor.Windows
|
||||
/// </summary>
|
||||
public OutputLogWindow Window;
|
||||
|
||||
/// <summary>
|
||||
/// The input actions collection to processed during user input.
|
||||
/// </summary>
|
||||
public InputActionsContainer InputActions = new InputActionsContainer();
|
||||
|
||||
/// <summary>
|
||||
/// The default text style.
|
||||
/// </summary>
|
||||
@@ -80,6 +85,14 @@ namespace FlaxEditor.Windows
|
||||
/// </summary>
|
||||
public TextBlockStyle ErrorStyle;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (InputActions.Process(Editor.Instance, this, key))
|
||||
return true;
|
||||
return base.OnKeyDown(key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnParseTextBlocks()
|
||||
{
|
||||
@@ -201,6 +214,9 @@ namespace FlaxEditor.Windows
|
||||
// Setup editor options
|
||||
Editor.Options.OptionsChanged += OnEditorOptionsChanged;
|
||||
OnEditorOptionsChanged(Editor.Options.Options);
|
||||
|
||||
_output.InputActions.Add(options => options.Search, () => _searchBox.Focus());
|
||||
InputActions.Add(options => options.Search, () => _searchBox.Focus());
|
||||
|
||||
GameCooker.Event += OnGameCookerEvent;
|
||||
ScriptsBuilder.CompilationFailed += OnScriptsCompilationFailed;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.SceneGraph;
|
||||
@@ -258,10 +257,9 @@ namespace FlaxEditor.Windows
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent control.</param>
|
||||
/// <param name="location">The location (within a given control).</param>
|
||||
private void ShowContextMenu(Control parent, Float2 location)
|
||||
internal void ShowContextMenu(Control parent, Float2 location)
|
||||
{
|
||||
var contextMenu = CreateContextMenu();
|
||||
|
||||
contextMenu.Show(parent, location);
|
||||
}
|
||||
|
||||
|
||||
@@ -221,7 +221,6 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
if (!Editor.StateMachine.CurrentState.CanEditScene)
|
||||
return;
|
||||
|
||||
ShowContextMenu(node, location);
|
||||
}
|
||||
|
||||
|
||||
@@ -584,7 +584,43 @@ Asset::LoadResult BinaryAsset::loadAsset()
|
||||
ASSERT(Storage && _header.ID.IsValid() && _header.TypeName.HasChars());
|
||||
|
||||
auto lock = Storage->Lock();
|
||||
return load();
|
||||
auto chunksToPreload = getChunksToPreload();
|
||||
if (chunksToPreload != 0)
|
||||
{
|
||||
// Ensure that any chunks that were requested before are loaded in memory (in case streaming flushed them out after timeout)
|
||||
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
|
||||
{
|
||||
const auto chunk = _header.Chunks[i];
|
||||
if (GET_CHUNK_FLAG(i) & chunksToPreload && chunk && chunk->IsMissing())
|
||||
Storage->LoadAssetChunk(chunk);
|
||||
}
|
||||
}
|
||||
const LoadResult result = load();
|
||||
#if !BUILD_RELEASE
|
||||
if (result == LoadResult::MissingDataChunk)
|
||||
{
|
||||
// Provide more insights on potentially missing asset data chunk
|
||||
Char chunksBitMask[ASSET_FILE_DATA_CHUNKS + 1];
|
||||
Char chunksExistBitMask[ASSET_FILE_DATA_CHUNKS + 1];
|
||||
Char chunksLoadBitMask[ASSET_FILE_DATA_CHUNKS + 1];
|
||||
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
|
||||
{
|
||||
if (const FlaxChunk* chunk = _header.Chunks[i])
|
||||
{
|
||||
chunksBitMask[i] = '1';
|
||||
chunksExistBitMask[i] = chunk->ExistsInFile() ? '1' : '0';
|
||||
chunksLoadBitMask[i] = chunk->IsLoaded() ? '1' : '0';
|
||||
}
|
||||
else
|
||||
{
|
||||
chunksBitMask[i] = chunksExistBitMask[i] = chunksLoadBitMask[i] = '0';
|
||||
}
|
||||
}
|
||||
chunksBitMask[ASSET_FILE_DATA_CHUNKS] = chunksExistBitMask[ASSET_FILE_DATA_CHUNKS] = chunksLoadBitMask[ASSET_FILE_DATA_CHUNKS] = 0;
|
||||
LOG(Warning, "Asset reports missing data chunk. Chunks bitmask: {}, existing chunks: {} loaded chunks: {}. '{}'", chunksBitMask, chunksExistBitMask, chunksLoadBitMask, ToString());
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
void BinaryAsset::releaseStorage()
|
||||
|
||||
@@ -82,36 +82,17 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the amount of created asset chunks.
|
||||
/// </summary>
|
||||
/// <returns>Created asset chunks</returns>
|
||||
int32 GetChunksCount() const
|
||||
{
|
||||
int32 result = 0;
|
||||
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
|
||||
{
|
||||
if (Chunks[i] != nullptr)
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
int32 GetChunksCount() const;
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all chunks. Warning! Chunks are managed internally, use with caution!
|
||||
/// </summary>
|
||||
void DeleteChunks()
|
||||
{
|
||||
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
|
||||
{
|
||||
SAFE_DELETE(Chunks[i]);
|
||||
}
|
||||
}
|
||||
void DeleteChunks();
|
||||
|
||||
/// <summary>
|
||||
/// Unlinks all chunks.
|
||||
/// </summary>
|
||||
void UnlinkChunks()
|
||||
{
|
||||
Platform::MemoryClear(Chunks, sizeof(Chunks));
|
||||
}
|
||||
void UnlinkChunks();
|
||||
|
||||
/// <summary>
|
||||
/// Gets string with a human-readable info about that header
|
||||
|
||||
@@ -20,6 +20,30 @@
|
||||
#endif
|
||||
#include <ThirdParty/LZ4/lz4.h>
|
||||
|
||||
int32 AssetHeader::GetChunksCount() const
|
||||
{
|
||||
int32 result = 0;
|
||||
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
|
||||
{
|
||||
if (Chunks[i] != nullptr)
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void AssetHeader::DeleteChunks()
|
||||
{
|
||||
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
|
||||
{
|
||||
SAFE_DELETE(Chunks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetHeader::UnlinkChunks()
|
||||
{
|
||||
Platform::MemoryClear(Chunks, sizeof(Chunks));
|
||||
}
|
||||
|
||||
String AssetHeader::ToString() const
|
||||
{
|
||||
return String::Format(TEXT("ID: {0}, TypeName: {1}, Chunks Count: {2}"), ID, TypeName, GetChunksCount());
|
||||
|
||||
@@ -18,13 +18,13 @@ API_STRUCT() struct FLAXENGINE_API Transform
|
||||
/// <summary>
|
||||
/// The translation vector of the transform.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(10), EditorDisplay(null, \"Position\")")
|
||||
API_FIELD(Attributes="EditorOrder(10), EditorDisplay(null, \"Position\"), ValueCategory(Utils.ValueCategory.Distance)")
|
||||
Vector3 Translation;
|
||||
|
||||
/// <summary>
|
||||
/// The rotation of the transform.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(20), EditorDisplay(null, \"Rotation\")")
|
||||
API_FIELD(Attributes="EditorOrder(20), EditorDisplay(null, \"Rotation\"), ValueCategory(Utils.ValueCategory.Angle)")
|
||||
Quaternion Orientation;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -127,7 +127,8 @@ PACK_STRUCT(struct Vertex {
|
||||
|
||||
PACK_STRUCT(struct Data {
|
||||
Matrix ViewProjection;
|
||||
Float3 Padding;
|
||||
Float2 Padding;
|
||||
float ClipPosZBias;
|
||||
bool EnableDepthTest;
|
||||
});
|
||||
|
||||
@@ -356,6 +357,19 @@ namespace
|
||||
Float3 CircleCache[DEBUG_DRAW_CIRCLE_VERTICES];
|
||||
Array<Float3> SphereTriangleCache;
|
||||
DebugSphereCache SphereCache[3];
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
DebugDrawPsLinesDefault.Release();
|
||||
DebugDrawPsLinesDepthTest.Release();
|
||||
DebugDrawPsWireTrianglesDefault.Release();
|
||||
DebugDrawPsWireTrianglesDepthTest.Release();
|
||||
DebugDrawPsTrianglesDefault.Release();
|
||||
DebugDrawPsTrianglesDepthTest.Release();
|
||||
}
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
extern int32 BoxTrianglesIndicesCache[];
|
||||
@@ -615,17 +629,19 @@ void DebugDrawService::Update()
|
||||
GlobalContext.DebugDrawDefault.Update(deltaTime);
|
||||
GlobalContext.DebugDrawDepthTest.Update(deltaTime);
|
||||
|
||||
// Check if need to setup a resources
|
||||
// Lazy-init resources
|
||||
if (DebugDrawShader == nullptr)
|
||||
{
|
||||
// Shader
|
||||
DebugDrawShader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/DebugDraw"));
|
||||
if (DebugDrawShader == nullptr)
|
||||
{
|
||||
LOG(Fatal, "Cannot load DebugDraw shader");
|
||||
}
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
DebugDrawShader->OnReloading.Bind(&OnShaderReloading);
|
||||
#endif
|
||||
}
|
||||
if (DebugDrawVB == nullptr && DebugDrawShader && DebugDrawShader->IsLoaded())
|
||||
if (DebugDrawPsWireTrianglesDepthTest.Depth == nullptr && DebugDrawShader && DebugDrawShader->IsLoaded())
|
||||
{
|
||||
bool failed = false;
|
||||
const auto shader = DebugDrawShader->GetShader();
|
||||
@@ -661,10 +677,11 @@ void DebugDrawService::Update()
|
||||
{
|
||||
LOG(Fatal, "Cannot setup DebugDraw service!");
|
||||
}
|
||||
|
||||
// Vertex buffer
|
||||
DebugDrawVB = New<DynamicVertexBuffer>((uint32)(DEBUG_DRAW_INITIAL_VB_CAPACITY * sizeof(Vertex)), (uint32)sizeof(Vertex), TEXT("DebugDraw.VB"));
|
||||
}
|
||||
|
||||
// Vertex buffer
|
||||
if (DebugDrawVB == nullptr)
|
||||
DebugDrawVB = New<DynamicVertexBuffer>((uint32)(DEBUG_DRAW_INITIAL_VB_CAPACITY * sizeof(Vertex)), (uint32)sizeof(Vertex), TEXT("DebugDraw.VB"));
|
||||
}
|
||||
|
||||
void DebugDrawService::Dispose()
|
||||
@@ -723,7 +740,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
|
||||
// Ensure to have shader loaded and any lines to render
|
||||
const int32 debugDrawDepthTestCount = Context->DebugDrawDepthTest.Count();
|
||||
const int32 debugDrawDefaultCount = Context->DebugDrawDefault.Count();
|
||||
if (DebugDrawShader == nullptr || !DebugDrawShader->IsLoaded() || debugDrawDepthTestCount + debugDrawDefaultCount == 0)
|
||||
if (DebugDrawShader == nullptr || !DebugDrawShader->IsLoaded() || debugDrawDepthTestCount + debugDrawDefaultCount == 0 || DebugDrawPsWireTrianglesDepthTest.Depth == nullptr)
|
||||
return;
|
||||
if (renderContext.Buffers == nullptr || !DebugDrawVB)
|
||||
return;
|
||||
@@ -739,6 +756,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
|
||||
}
|
||||
Context->LastViewPos = view.Position;
|
||||
Context->LastViewProj = view.Projection;
|
||||
TaaJitterRemoveContext taaJitterRemove(view);
|
||||
|
||||
// Fallback to task buffers
|
||||
if (target == nullptr && renderContext.Task)
|
||||
@@ -766,8 +784,9 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
|
||||
const auto cb = DebugDrawShader->GetShader()->GetCB(0);
|
||||
Data data;
|
||||
Matrix vp;
|
||||
Matrix::Multiply(view.View, view.NonJitteredProjection, vp);
|
||||
Matrix::Multiply(view.View, view.Projection, vp);
|
||||
Matrix::Transpose(vp, data.ViewProjection);
|
||||
data.ClipPosZBias = -0.2f; // Reduce Z-fighting artifacts (eg. editor grid)
|
||||
data.EnableDepthTest = enableDepthTest;
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
@@ -848,6 +867,8 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
|
||||
{
|
||||
PROFILE_GPU_CPU_NAMED("Text");
|
||||
auto features = Render2D::Features;
|
||||
|
||||
// Disable vertex snapping when rendering 3D text
|
||||
Render2D::Features = (Render2D::RenderingFeatures)((uint32)features & ~(uint32)Render2D::RenderingFeatures::VertexSnapping);
|
||||
|
||||
if (!DebugDrawFont)
|
||||
|
||||
@@ -960,7 +960,7 @@ API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable
|
||||
/// The speed at which the exposure changes when the scene brightness moves from a bright area to a dark area (brightness goes down).
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(2), PostProcessSetting((int)EyeAdaptationSettingsOverride.SpeedDown)")
|
||||
float SpeedDown = 1.0f;
|
||||
float SpeedDown = 10.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The pre-exposure value applied to the scene color before performing post-processing (such as bloom, lens flares, etc.).
|
||||
@@ -984,7 +984,7 @@ API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable
|
||||
/// The maximum brightness for the auto exposure which limits the upper brightness the eye can adapt within.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(6), PostProcessSetting((int)EyeAdaptationSettingsOverride.MaxBrightness), EditorDisplay(null, \"Maximum Brightness\")")
|
||||
float MaxBrightness = 2.0f;
|
||||
float MaxBrightness = 15.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The lower bound for the luminance histogram of the scene color. This value is in percent and limits the pixels below this brightness. Use values in the range of 60-80. Used only in AutomaticHistogram mode.
|
||||
@@ -996,7 +996,7 @@ API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable
|
||||
/// The upper bound for the luminance histogram of the scene color. This value is in percent and limits the pixels above this brightness. Use values in the range of 80-95. Used only in AutomaticHistogram mode.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="Limit(1, 99, 0.001f), EditorOrder(3), PostProcessSetting((int)EyeAdaptationSettingsOverride.HistogramHighPercent)")
|
||||
float HistogramHighPercent = 98.0f;
|
||||
float HistogramHighPercent = 90.0f;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
@@ -1079,10 +1079,10 @@ API_STRUCT() struct FLAXENGINE_API CameraArtifactsSettings : ISerializable
|
||||
CameraArtifactsSettingsOverride OverrideFlags = Override::None;
|
||||
|
||||
/// <summary>
|
||||
/// Strength of the vignette effect. Value 0 hides it. The default value is 0.8.
|
||||
/// Strength of the vignette effect. Value 0 hides it. The default value is 0.4.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="Limit(0, 2, 0.001f), EditorOrder(0), PostProcessSetting((int)CameraArtifactsSettingsOverride.VignetteIntensity)")
|
||||
float VignetteIntensity = 0.8f;
|
||||
float VignetteIntensity = 0.4f;
|
||||
|
||||
/// <summary>
|
||||
/// Color of the vignette.
|
||||
@@ -1230,25 +1230,25 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable
|
||||
/// Strength of the effect. A value of 0 disables it.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(0), PostProcessSetting((int)LensFlaresSettingsOverride.Intensity)")
|
||||
float Intensity = 1.0f;
|
||||
float Intensity = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of lens flares ghosts.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="Limit(0, 16), EditorOrder(1), PostProcessSetting((int)LensFlaresSettingsOverride.Ghosts)")
|
||||
int32 Ghosts = 8;
|
||||
int32 Ghosts = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Lens flares halo width.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(2), PostProcessSetting((int)LensFlaresSettingsOverride.HaloWidth)")
|
||||
float HaloWidth = 0.16f;
|
||||
float HaloWidth = 0.04f;
|
||||
|
||||
/// <summary>
|
||||
/// Lens flares halo intensity.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(3), PostProcessSetting((int)LensFlaresSettingsOverride.HaloIntensity)")
|
||||
float HaloIntensity = 0.666f;
|
||||
float HaloIntensity = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Ghost samples dispersal parameter.
|
||||
@@ -1584,7 +1584,7 @@ API_STRUCT() struct FLAXENGINE_API MotionBlurSettings : ISerializable
|
||||
/// The blur effect strength. A value of 0 disables it, while higher values increase the effect.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="Limit(0, 5, 0.01f), EditorOrder(1), PostProcessSetting((int)MotionBlurSettingsOverride.Scale)")
|
||||
float Scale = 1.0f;
|
||||
float Scale = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of sample points used during motion blur rendering. It affects blur quality and performance.
|
||||
|
||||
@@ -18,6 +18,7 @@ void RenderView::Prepare(RenderContext& renderContext)
|
||||
// Check if use TAA (need to modify the projection matrix)
|
||||
Float2 taaJitter;
|
||||
NonJitteredProjection = Projection;
|
||||
IsTaaResolved = false;
|
||||
if (renderContext.List->Setup.UseTemporalAAJitter)
|
||||
{
|
||||
// Move to the next frame
|
||||
@@ -82,6 +83,18 @@ void RenderView::PrepareCache(const RenderContext& renderContext, float width, f
|
||||
MainScreenSize = mainView->ScreenSize;
|
||||
}
|
||||
|
||||
void RenderView::UpdateCachedData()
|
||||
{
|
||||
Matrix::Invert(View, IV);
|
||||
Matrix::Invert(Projection, IP);
|
||||
Matrix viewProjection;
|
||||
Matrix::Multiply(View, Projection, viewProjection);
|
||||
Frustum.SetMatrix(viewProjection);
|
||||
Matrix::Invert(viewProjection, IVP);
|
||||
CullingFrustum = Frustum;
|
||||
NonJitteredProjection = Projection;
|
||||
}
|
||||
|
||||
void RenderView::SetUp(const Matrix& viewProjection)
|
||||
{
|
||||
// Copy data
|
||||
@@ -201,3 +214,27 @@ void RenderView::GetWorldMatrix(const Transform& transform, Matrix& world) const
|
||||
const Float3 translation = transform.Translation - Origin;
|
||||
Matrix::Transformation(transform.Scale, transform.Orientation, translation, world);
|
||||
}
|
||||
|
||||
TaaJitterRemoveContext::TaaJitterRemoveContext(const RenderView& view)
|
||||
{
|
||||
if (view.IsTaaResolved)
|
||||
{
|
||||
// Cancel-out sub-pixel jitter when drawing geometry after TAA has been resolved
|
||||
_view = (RenderView*)&view;
|
||||
_prevProjection = view.Projection;
|
||||
_prevNonJitteredProjection = view.NonJitteredProjection;
|
||||
_view->Projection = _prevNonJitteredProjection;
|
||||
_view->UpdateCachedData();
|
||||
}
|
||||
}
|
||||
|
||||
TaaJitterRemoveContext::~TaaJitterRemoveContext()
|
||||
{
|
||||
if (_view)
|
||||
{
|
||||
// Restore projection
|
||||
_view->Projection = _prevProjection;
|
||||
_view->UpdateCachedData();
|
||||
_view->NonJitteredProjection = _prevNonJitteredProjection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +117,11 @@ public:
|
||||
/// </summary>
|
||||
API_FIELD() bool IsCullingDisabled = false;
|
||||
|
||||
/// <summary>
|
||||
/// True if TAA has been resolved when rendering view and frame doesn't contain jitter anymore. Rendering geometry after this point should not use jitter anymore (eg. editor gizmos or custom geometry as overlay).
|
||||
/// </summary>
|
||||
API_FIELD() bool IsTaaResolved = false;
|
||||
|
||||
/// <summary>
|
||||
/// The static flags mask used to hide objects that don't have a given static flags. Eg. use StaticFlags::Lightmap to render only objects that can use lightmap.
|
||||
/// </summary>
|
||||
@@ -160,7 +165,7 @@ public:
|
||||
API_FIELD() DEPRECATED float ShadowModelLODDistanceFactor = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The Temporal Anti-Aliasing jitter frame index.
|
||||
/// Temporal Anti-Aliasing jitter frame index.
|
||||
/// </summary>
|
||||
API_FIELD() int32 TaaFrameIndex = 0;
|
||||
|
||||
@@ -261,6 +266,11 @@ public:
|
||||
RenderView& operator=(const RenderView& other) = default;
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
|
||||
/// <summary>
|
||||
/// Updates the cached data for the view (inverse matrices, etc.).
|
||||
/// </summary>
|
||||
void UpdateCachedData();
|
||||
|
||||
// Set up view with custom params
|
||||
// @param viewProjection View * Projection matrix
|
||||
void SetUp(const Matrix& viewProjection);
|
||||
@@ -344,3 +354,15 @@ public:
|
||||
world.M43 -= Origin.Z;
|
||||
}
|
||||
};
|
||||
|
||||
// Removes TAA jitter from the RenderView when drawing geometry after TAA has been resolved to prevent unwanted jittering.
|
||||
struct TaaJitterRemoveContext
|
||||
{
|
||||
private:
|
||||
RenderView* _view = nullptr;
|
||||
Matrix _prevProjection, _prevNonJitteredProjection;
|
||||
|
||||
public:
|
||||
TaaJitterRemoveContext(const RenderView& view);
|
||||
~TaaJitterRemoveContext();
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "Engine/Graphics/Shaders/GPUShader.h"
|
||||
#include "Engine/Graphics/Materials/MaterialShader.h"
|
||||
#include "Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h"
|
||||
#include "FlaxEngine.Gen.h"
|
||||
|
||||
const Char* ShaderProfileCacheDirNames[] =
|
||||
{
|
||||
@@ -179,6 +180,7 @@ bool ShaderCacheManagerService::Init()
|
||||
// Validate the database cache version (need to recompile all shaders on shader cache format change)
|
||||
struct CacheVersion
|
||||
{
|
||||
int32 EngineVersion = -1;
|
||||
int32 ShaderCacheVersion = -1;
|
||||
int32 MaterialGraphVersion = -1;
|
||||
int32 ParticleGraphVersion = -1;
|
||||
@@ -193,7 +195,8 @@ bool ShaderCacheManagerService::Init()
|
||||
LOG(Warning, "Failed to read the shaders cache database version file.");
|
||||
}
|
||||
}
|
||||
if (cacheVersion.ShaderCacheVersion != GPU_SHADER_CACHE_VERSION
|
||||
if (cacheVersion.EngineVersion != FLAXENGINE_VERSION_BUILD
|
||||
|| cacheVersion.ShaderCacheVersion != GPU_SHADER_CACHE_VERSION
|
||||
|| cacheVersion.MaterialGraphVersion != MATERIAL_GRAPH_VERSION
|
||||
|| cacheVersion.ParticleGraphVersion != PARTICLE_GPU_GRAPH_VERSION
|
||||
)
|
||||
@@ -209,6 +212,7 @@ bool ShaderCacheManagerService::Init()
|
||||
LOG(Error, "Failed to createe the shaders cache database directory.");
|
||||
}
|
||||
|
||||
cacheVersion.EngineVersion = FLAXENGINE_VERSION_BUILD;
|
||||
cacheVersion.ShaderCacheVersion = GPU_SHADER_CACHE_VERSION;
|
||||
cacheVersion.MaterialGraphVersion = MATERIAL_GRAPH_VERSION;
|
||||
cacheVersion.ParticleGraphVersion = PARTICLE_GPU_GRAPH_VERSION;
|
||||
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the camera's field of view (in degrees).
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), VisibleIf(nameof(UsePerspective))")
|
||||
API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), VisibleIf(nameof(UsePerspective)), ValueCategory(Utils.ValueCategory.Angle)")
|
||||
float GetFieldOfView() const;
|
||||
|
||||
/// <summary>
|
||||
@@ -99,7 +99,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets camera's near plane distance.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\"), ValueCategory(Utils.ValueCategory.Distance)")
|
||||
float GetNearPlane() const;
|
||||
|
||||
/// <summary>
|
||||
@@ -110,7 +110,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets camera's far plane distance.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\"), ValueCategory(Utils.ValueCategory.Distance)")
|
||||
float GetFarPlane() const;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -553,13 +553,11 @@ const Span<MaterialSlot> StaticModel::GetMaterialSlots() const
|
||||
|
||||
MaterialBase* StaticModel::GetMaterial(int32 entryIndex)
|
||||
{
|
||||
if (Model)
|
||||
Model->WaitForLoaded();
|
||||
else
|
||||
if (!Model || Model->WaitForLoaded())
|
||||
return nullptr;
|
||||
CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr);
|
||||
MaterialBase* material = Entries[entryIndex].Material.Get();
|
||||
if (!material)
|
||||
if (!material && entryIndex < Model->MaterialSlots.Count())
|
||||
{
|
||||
material = Model->MaterialSlots[entryIndex].Material.Get();
|
||||
if (!material)
|
||||
|
||||
@@ -569,7 +569,7 @@ void RigidBody::OnTransformChanged()
|
||||
|
||||
void RigidBody::OnPhysicsSceneChanged(PhysicsScene* previous)
|
||||
{
|
||||
PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _actor);
|
||||
PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _actor, true);
|
||||
void* scene = GetPhysicsScene()->GetPhysicsScene();
|
||||
PhysicsBackend::AddSceneActor(scene, _actor);
|
||||
const bool putToSleep = !_startAwake && GetEnableSimulation() && !GetIsKinematic() && IsActiveInHierarchy();
|
||||
|
||||
@@ -181,7 +181,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the mass value measured in kilograms (use override value only if OverrideMass is checked).
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(110), Limit(0), EditorDisplay(\"Rigid Body\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(110), Limit(0), EditorDisplay(\"Rigid Body\"), ValueCategory(Utils.ValueCategory.Mass)")
|
||||
float GetMass() const;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -23,7 +23,7 @@ public:
|
||||
/// Gets the size of the box, measured in the object's local space.
|
||||
/// </summary>
|
||||
/// <remarks>The box size will be scaled by the actor's world scale. </remarks>
|
||||
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(typeof(Float3), \"100,100,100\"), EditorDisplay(\"Collider\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(typeof(Float3), \"100,100,100\"), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
|
||||
FORCE_INLINE Float3 GetSize() const
|
||||
{
|
||||
return _size;
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
/// Gets the radius of the sphere, measured in the object's local space.
|
||||
/// </summary>
|
||||
/// <remarks>The sphere radius will be scaled by the actor's world scale.</remarks>
|
||||
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(20.0f), EditorDisplay(\"Collider\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(20.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
|
||||
FORCE_INLINE float GetRadius() const
|
||||
{
|
||||
return _radius;
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
/// Gets the height of the capsule, measured in the object's local space between the centers of the hemispherical ends.
|
||||
/// </summary>
|
||||
/// <remarks>The capsule height will be scaled by the actor's world scale.</remarks>
|
||||
API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(100.0f), EditorDisplay(\"Collider\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(100.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
|
||||
FORCE_INLINE float GetHeight() const
|
||||
{
|
||||
return _height;
|
||||
|
||||
@@ -73,7 +73,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the radius of the sphere, measured in the object's local space. The sphere radius will be scaled by the actor's world scale.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
|
||||
float GetRadius() const;
|
||||
|
||||
/// <summary>
|
||||
@@ -84,7 +84,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the height of the capsule, measured in the object's local space. The capsule height will be scaled by the actor's world scale.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(150.0f), EditorDisplay(\"Collider\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(150.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
|
||||
float GetHeight() const;
|
||||
|
||||
/// <summary>
|
||||
@@ -95,7 +95,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the slope limit (in degrees). Limits the collider to only climb slopes that are less steep (in degrees) than the indicated value.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(210), DefaultValue(45.0f), Limit(0, 100), EditorDisplay(\"Character Controller\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(210), DefaultValue(45.0f), Limit(0, 100), EditorDisplay(\"Character Controller\"), ValueCategory(Utils.ValueCategory.Angle)")
|
||||
float GetSlopeLimit() const;
|
||||
|
||||
/// <summary>
|
||||
@@ -117,7 +117,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the step height. The character will step up a stair only if it is closer to the ground than the indicated value. This should not be greater than the Character Controller’s height or it will generate an error.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(220), DefaultValue(30.0f), Limit(0), EditorDisplay(\"Character Controller\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(220), DefaultValue(30.0f), Limit(0), EditorDisplay(\"Character Controller\"), ValueCategory(Utils.ValueCategory.Distance)")
|
||||
float GetStepOffset() const;
|
||||
|
||||
/// <summary>
|
||||
@@ -139,7 +139,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the minimum move distance of the character controller. The minimum traveled distance to consider. If traveled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(230), DefaultValue(0.0f), Limit(0, 1000), EditorDisplay(\"Character Controller\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(230), DefaultValue(0.0f), Limit(0, 1000), EditorDisplay(\"Character Controller\"), ValueCategory(Utils.ValueCategory.Distance)")
|
||||
float GetMinMoveDistance() const;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -438,7 +438,7 @@ void Collider::OnPhysicsSceneChanged(PhysicsScene* previous)
|
||||
|
||||
if (_staticActor != nullptr)
|
||||
{
|
||||
PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _staticActor);
|
||||
PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _staticActor, true);
|
||||
void* scene = GetPhysicsScene()->GetPhysicsScene();
|
||||
PhysicsBackend::AddSceneActor(scene, _staticActor);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the center of the collider, measured in the object's local space.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorDisplay(\"Collider\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
|
||||
FORCE_INLINE Vector3 GetCenter() const
|
||||
{
|
||||
return _center;
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the contact offset. Colliders whose distance is less than the sum of their ContactOffset values will generate contacts. The contact offset must be positive. Contact offset allows the collision detection system to predictively enforce the contact constraint even when the objects are slightly separated.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(2.0f), Limit(0, 100), EditorDisplay(\"Collider\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(2.0f), Limit(0, 100), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
|
||||
FORCE_INLINE float GetContactOffset() const
|
||||
{
|
||||
return _contactOffset;
|
||||
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
/// Gets the radius of the sphere, measured in the object's local space.
|
||||
/// </summary>
|
||||
/// <remarks>The sphere radius will be scaled by the actor's world scale.</remarks>
|
||||
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
|
||||
FORCE_INLINE float GetRadius() const
|
||||
{
|
||||
return _radius;
|
||||
|
||||
@@ -67,7 +67,7 @@ public:
|
||||
/// Gets the allowed minimum distance for the joint.
|
||||
/// </summary>
|
||||
/// <remarks>Used only when DistanceJointFlag.MinDistance flag is set. The minimum distance must be no more than the maximum distance. Default: 0, Range: [0, float.MaxValue].</remarks>
|
||||
API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(0.0f), Limit(0.0f), EditorDisplay(\"Joint\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(0.0f), Limit(0.0f), EditorDisplay(\"Joint\"), ValueCategory(Utils.ValueCategory.Distance)")
|
||||
FORCE_INLINE float GetMinDistance() const
|
||||
{
|
||||
return _minDistance;
|
||||
@@ -83,7 +83,7 @@ public:
|
||||
/// Gets the allowed maximum distance for the joint.
|
||||
/// </summary>
|
||||
/// <remarks>Used only when DistanceJointFlag.MaxDistance flag is set. The maximum distance must be no less than the minimum distance. Default: 0, Range: [0, float.MaxValue].</remarks>
|
||||
API_PROPERTY(Attributes="EditorOrder(120), DefaultValue(10.0f), Limit(0.0f), EditorDisplay(\"Joint\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(120), DefaultValue(10.0f), Limit(0.0f), EditorDisplay(\"Joint\"), ValueCategory(Utils.ValueCategory.Distance)")
|
||||
FORCE_INLINE float GetMaxDistance() const
|
||||
{
|
||||
return _maxDistance;
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the break force. Determines the maximum force the joint can apply before breaking. Broken joints no longer participate in physics simulation.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(float.MaxValue), EditorDisplay(\"Joint\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(float.MaxValue), EditorDisplay(\"Joint\"), ValueCategory(Utils.ValueCategory.Force)")
|
||||
FORCE_INLINE float GetBreakForce() const
|
||||
{
|
||||
return _breakForce;
|
||||
@@ -52,7 +52,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the break torque. Determines the maximum torque the joint can apply before breaking. Broken joints no longer participate in physics simulation.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(float.MaxValue), EditorDisplay(\"Joint\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(float.MaxValue), EditorDisplay(\"Joint\"), ValueCategory(Utils.ValueCategory.Torque)")
|
||||
FORCE_INLINE float GetBreakTorque() const
|
||||
{
|
||||
return _breakTorque;
|
||||
|
||||
@@ -1998,11 +1998,14 @@ void PhysicsBackend::AddSceneActor(void* scene, void* actor)
|
||||
FlushLocker.Unlock();
|
||||
}
|
||||
|
||||
void PhysicsBackend::RemoveSceneActor(void* scene, void* actor)
|
||||
void PhysicsBackend::RemoveSceneActor(void* scene, void* actor, bool immediately)
|
||||
{
|
||||
auto scenePhysX = (ScenePhysX*)scene;
|
||||
FlushLocker.Lock();
|
||||
scenePhysX->RemoveActors.Add((PxActor*)actor);
|
||||
if (immediately)
|
||||
scenePhysX->Scene->removeActor(*(PxActor*)actor);
|
||||
else
|
||||
scenePhysX->RemoveActors.Add((PxActor*)actor);
|
||||
FlushLocker.Unlock();
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ public:
|
||||
static void SetSceneBounceThresholdVelocity(void* scene, float value);
|
||||
static void SetSceneOrigin(void* scene, const Vector3& oldOrigin, const Vector3& newOrigin);
|
||||
static void AddSceneActor(void* scene, void* actor);
|
||||
static void RemoveSceneActor(void* scene, void* actor);
|
||||
static void RemoveSceneActor(void* scene, void* actor, bool immediately = false);
|
||||
static void AddSceneActorAction(void* scene, void* actor, ActionType action);
|
||||
#if COMPILE_WITH_PROFILER
|
||||
static void GetSceneStatistics(void* scene, PhysicsStatistics& result);
|
||||
|
||||
@@ -115,7 +115,7 @@ void PhysicsBackend::AddSceneActor(void* scene, void* actor)
|
||||
{
|
||||
}
|
||||
|
||||
void PhysicsBackend::RemoveSceneActor(void* scene, void* actor)
|
||||
void PhysicsBackend::RemoveSceneActor(void* scene, void* actor, bool immediately)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -149,4 +149,7 @@ void TAA::Render(const RenderContext& renderContext, GPUTexture* input, GPUTextu
|
||||
context->Draw(output);
|
||||
renderContext.Buffers->TemporalAA = outputHistory;
|
||||
}
|
||||
|
||||
// Mark TAA jitter as resolved for future drawing
|
||||
(bool&)renderContext.View.IsTaaResolved = true;
|
||||
}
|
||||
|
||||
@@ -652,6 +652,7 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL
|
||||
const auto* batchesData = list.Batches.Get();
|
||||
const auto context = GPUDevice::Instance->GetMainContext();
|
||||
bool useInstancing = list.CanUseInstancing && CanUseInstancing(renderContext.View.Pass) && GPUDevice::Instance->Limits.HasInstancing;
|
||||
TaaJitterRemoveContext taaJitterRemove(renderContext.View);
|
||||
|
||||
// Clear SR slots to prevent any resources binding issues (leftovers from the previous passes)
|
||||
context->ResetSR();
|
||||
|
||||
@@ -612,7 +612,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
// Color Grading LUT generation
|
||||
auto colorGradingLUT = ColorGradingPass::Instance()->RenderLUT(renderContext);
|
||||
|
||||
// Post processing
|
||||
// Post-processing
|
||||
EyeAdaptationPass::Instance()->Render(renderContext, frameBuffer);
|
||||
PostProcessingPass::Instance()->Render(renderContext, frameBuffer, tempBuffer, colorGradingLUT);
|
||||
RenderTargetPool::Release(colorGradingLUT);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the value category of a numeric value as either as-is (a scalar), a distance (formatted as cm/m/km) or an angle (formatted with a degree sign).
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public sealed class ValueCategoryAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The value category used for formatting.
|
||||
/// </summary>
|
||||
public Utils.ValueCategory Category;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ValueCategoryAttribute"/> class.
|
||||
/// </summary>
|
||||
private ValueCategoryAttribute()
|
||||
{
|
||||
Category = Utils.ValueCategory.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ValueCategoryAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="category">The value category.</param>
|
||||
public ValueCategoryAttribute(Utils.ValueCategory category)
|
||||
{
|
||||
Category = category;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2148,7 +2148,9 @@ bool TerrainPatch::CreateHeightField()
|
||||
if (collisionHeader->CheckOldMagicNumber != MAX_int32 || collisionHeader->Version != TerrainCollisionDataHeader::CurrentVersion)
|
||||
{
|
||||
// Reset height map
|
||||
return InitializeHeightMap();
|
||||
PROFILE_CPU_NAMED("ResetHeightMap");
|
||||
const float* data = GetHeightmapData();
|
||||
return SetupHeightMap(_cachedHeightMap.Count(), data);
|
||||
}
|
||||
|
||||
// Create heightfield object from the data
|
||||
@@ -2580,7 +2582,7 @@ void TerrainPatch::Deserialize(DeserializeStream& stream, ISerializeModifier* mo
|
||||
|
||||
void TerrainPatch::OnPhysicsSceneChanged(PhysicsScene* previous)
|
||||
{
|
||||
PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _physicsActor);
|
||||
PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _physicsActor, true);
|
||||
void* scene = _terrain->GetPhysicsScene()->GetPhysicsScene();
|
||||
PhysicsBackend::AddSceneActor(scene, _physicsActor);
|
||||
}
|
||||
|
||||
@@ -429,6 +429,13 @@ namespace FlaxEngine.GUI
|
||||
return ContainsPoint(ref location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IntersectsChildContent(Control child, Float2 location, out Float2 childSpaceLocation)
|
||||
{
|
||||
location /= _scale;
|
||||
return base.IntersectsChildContent(child, location, out childSpaceLocation);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool ContainsPoint(ref Float2 location, bool precise = false)
|
||||
{
|
||||
@@ -462,97 +469,6 @@ namespace FlaxEngine.GUI
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
|
||||
{
|
||||
location /= _scale;
|
||||
return base.OnDragEnter(ref location, data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
|
||||
{
|
||||
location /= _scale;
|
||||
return base.OnDragMove(ref location, data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
|
||||
{
|
||||
location /= _scale;
|
||||
return base.OnDragDrop(ref location, data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseEnter(Float2 location)
|
||||
{
|
||||
location /= _scale;
|
||||
base.OnMouseEnter(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
location /= _scale;
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
location /= _scale;
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
location /= _scale;
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
||||
{
|
||||
location /= _scale;
|
||||
return base.OnMouseDoubleClick(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseWheel(Float2 location, float delta)
|
||||
{
|
||||
location /= _scale;
|
||||
return base.OnMouseWheel(location, delta);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnTouchEnter(Float2 location, int pointerId)
|
||||
{
|
||||
location /= _scale;
|
||||
base.OnTouchEnter(location, pointerId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnTouchMove(Float2 location, int pointerId)
|
||||
{
|
||||
location /= _scale;
|
||||
base.OnTouchMove(location, pointerId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnTouchDown(Float2 location, int pointerId)
|
||||
{
|
||||
location /= _scale;
|
||||
return base.OnTouchDown(location, pointerId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnTouchUp(Float2 location, int pointerId)
|
||||
{
|
||||
location /= _scale;
|
||||
return base.OnTouchUp(location, pointerId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,7 +356,7 @@ namespace FlaxEngine.GUI
|
||||
for (int i = _children.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var child = _children[i];
|
||||
if (IntersectsChildContent(child, point, out var childLocation))
|
||||
if (child.Visible && IntersectsChildContent(child, point, out var childLocation))
|
||||
{
|
||||
var containerControl = child as ContainerControl;
|
||||
var childAtRecursive = containerControl?.GetChildAtRecursive(childLocation);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace FlaxEngine.GUI
|
||||
{
|
||||
@@ -382,6 +383,7 @@ namespace FlaxEngine.GUI
|
||||
/// <summary>
|
||||
/// Gets or sets the shear transform angles (x, y). Defined in degrees. Shearing happens relative to the control pivot point.
|
||||
/// </summary>
|
||||
[DefaultValue(typeof(Float2), "0,0")]
|
||||
[ExpandGroups, EditorDisplay("Transform"), EditorOrder(1040), Tooltip("The shear transform angles (x, y). Defined in degrees. Shearing happens relative to the control pivot point.")]
|
||||
public Float2 Shear
|
||||
{
|
||||
@@ -398,6 +400,7 @@ namespace FlaxEngine.GUI
|
||||
/// <summary>
|
||||
/// Gets or sets the rotation angle (in degrees). Control is rotated around it's pivot point (middle of the control by default).
|
||||
/// </summary>
|
||||
[DefaultValue(0.0f)]
|
||||
[ExpandGroups, EditorDisplay("Transform"), EditorOrder(1050), Tooltip("The control rotation angle (in degrees). Control is rotated around it's pivot point (middle of the control by default).")]
|
||||
public float Rotation
|
||||
{
|
||||
|
||||
@@ -257,6 +257,7 @@ namespace FlaxEngine
|
||||
{
|
||||
public static FieldInfo itemsField;
|
||||
}
|
||||
|
||||
internal static T[] ExtractArrayFromList<T>(List<T> list)
|
||||
{
|
||||
if (list == null)
|
||||
@@ -1038,5 +1039,66 @@ namespace FlaxEngine
|
||||
parameterTypes = Array.Empty<Type>();
|
||||
return parameterTypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A category of number values used for formatting and input fields.
|
||||
/// </summary>
|
||||
public enum ValueCategory
|
||||
{
|
||||
/// <summary>
|
||||
/// Nothing.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Distance (eg. meters).
|
||||
/// </summary>
|
||||
Distance,
|
||||
|
||||
/// <summary>
|
||||
/// Area (eg. m^2).
|
||||
/// </summary>
|
||||
Area,
|
||||
|
||||
/// <summary>
|
||||
/// Volume (eg. m^3).
|
||||
/// </summary>
|
||||
Volume,
|
||||
|
||||
/// <summary>
|
||||
/// Mass (eg. kilograms).
|
||||
/// </summary>
|
||||
Mass,
|
||||
|
||||
/// <summary>
|
||||
/// Angle (eg. degrees).
|
||||
/// </summary>
|
||||
Angle,
|
||||
|
||||
/// <summary>
|
||||
/// Speed (distance / time).
|
||||
/// </summary>
|
||||
Speed,
|
||||
|
||||
/// <summary>
|
||||
/// Acceleration (distance^2 / time).
|
||||
/// </summary>
|
||||
Acceleration,
|
||||
|
||||
/// <summary>
|
||||
/// Time (eg. seconds).
|
||||
/// </summary>
|
||||
Time,
|
||||
|
||||
/// <summary>
|
||||
/// Force (mass * distance / time^2).
|
||||
/// </summary>
|
||||
Force,
|
||||
|
||||
/// <summary>
|
||||
/// Torque (mass * distance^2 / time^2).
|
||||
/// </summary>
|
||||
Torque,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
META_CB_BEGIN(0, Data)
|
||||
float4x4 ViewProjection;
|
||||
float3 Padding;
|
||||
float2 Padding;
|
||||
float ClipPosZBias;
|
||||
bool EnableDepthTest;
|
||||
META_CB_END
|
||||
|
||||
@@ -23,6 +24,7 @@ VS2PS VS(float3 Position : POSITION, float4 Color : COLOR)
|
||||
{
|
||||
VS2PS output;
|
||||
output.Position = mul(float4(Position, 1), ViewProjection);
|
||||
output.Position.z += ClipPosZBias;
|
||||
output.Color = Color;
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Flax.Build.Bindings
|
||||
private static readonly List<PropertyInfo> CppAutoSerializeProperties = new List<PropertyInfo>();
|
||||
public static readonly HashSet<string> CppIncludeFiles = new HashSet<string>();
|
||||
private static readonly List<string> CppIncludeFilesList = new List<string>();
|
||||
private static readonly HashSet<TypeInfo> CppVariantToTypes = new HashSet<TypeInfo>();
|
||||
private static readonly Dictionary<string, TypeInfo> CppVariantToTypes = new Dictionary<string, TypeInfo>();
|
||||
private static readonly Dictionary<string, TypeInfo> CppVariantFromTypes = new Dictionary<string, TypeInfo>();
|
||||
private static bool CppNonPodTypesConvertingGeneration = false;
|
||||
private static StringBuilder CppContentsEnd;
|
||||
@@ -231,13 +231,15 @@ namespace Flax.Build.Bindings
|
||||
throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'.");
|
||||
if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null)
|
||||
{
|
||||
CppVariantToTypes.Add(typeInfo);
|
||||
return $"MoveTemp(VariantTo{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}({value}))";
|
||||
var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo);
|
||||
CppVariantToTypes[wrapperName] = typeInfo;
|
||||
return $"MoveTemp(VariantTo{wrapperName}({value}))";
|
||||
}
|
||||
if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null)
|
||||
{
|
||||
CppVariantToTypes.Add(typeInfo);
|
||||
return $"MoveTemp(VariantTo{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}({value}))";
|
||||
var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo);
|
||||
CppVariantToTypes[wrapperName] = typeInfo;
|
||||
return $"MoveTemp(VariantTo{wrapperName}({value}))";
|
||||
}
|
||||
if (typeInfo.Type == "Span" && typeInfo.GenericArgs != null)
|
||||
{
|
||||
@@ -2790,12 +2792,14 @@ namespace Flax.Build.Bindings
|
||||
var header = GetStringBuilder();
|
||||
|
||||
// Variant converting helper methods
|
||||
foreach (var typeInfo in CppVariantToTypes)
|
||||
foreach (var e in CppVariantToTypes)
|
||||
{
|
||||
var wrapperName = e.Key;
|
||||
var typeInfo = e.Value;
|
||||
var name = typeInfo.ToString(false);
|
||||
header.AppendLine();
|
||||
header.AppendLine("namespace {");
|
||||
header.Append($"{name} VariantTo{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}(const Variant& v)").AppendLine();
|
||||
header.Append($"{name} VariantTo{wrapperName}(const Variant& v)").AppendLine();
|
||||
header.Append('{').AppendLine();
|
||||
header.Append($" {name} result;").AppendLine();
|
||||
if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null)
|
||||
|
||||
@@ -839,10 +839,11 @@ namespace Flax.Build.Plugins
|
||||
module.GetType("System.IntPtr", out var intPtrType);
|
||||
module.GetType("FlaxEngine.Object", out var scriptingObjectType);
|
||||
var fromUnmanagedPtr = scriptingObjectType.Resolve().GetMethod("FromUnmanagedPtr");
|
||||
TypeReference intPtr = module.ImportReference(intPtrType);
|
||||
|
||||
var m = new MethodDefinition(name + "Native", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, context.VoidType);
|
||||
m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtrType));
|
||||
m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, intPtrType));
|
||||
m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtr));
|
||||
m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, intPtr));
|
||||
TypeReference networkStream = module.ImportReference(context.NetworkStreamType);
|
||||
ILProcessor il = m.Body.GetILProcessor();
|
||||
il.Emit(OpCodes.Nop);
|
||||
@@ -1645,12 +1646,13 @@ namespace Flax.Build.Plugins
|
||||
module.GetType("FlaxEngine.Object", out var scriptingObjectType);
|
||||
var fromUnmanagedPtr = scriptingObjectType.Resolve().GetMethod("FromUnmanagedPtr");
|
||||
TypeReference networkStream = module.ImportReference(networkStreamType);
|
||||
TypeReference intPtr = module.ImportReference(intPtrType);
|
||||
|
||||
// Generate static method to execute RPC locally
|
||||
{
|
||||
var m = new MethodDefinition(method.Name + "_Execute", MethodAttributes.Static | MethodAttributes.Assembly | MethodAttributes.HideBySig, voidType);
|
||||
m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtrType));
|
||||
m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, module.ImportReference(intPtrType)));
|
||||
m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtr));
|
||||
m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, intPtr));
|
||||
ILProcessor ilp = m.Body.GetILProcessor();
|
||||
var il = new DotnetIlContext(ilp, method);
|
||||
il.Emit(OpCodes.Nop);
|
||||
|
||||
Reference in New Issue
Block a user