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

# Conflicts:
#	Source/Engine/UI/GUI/Common/Button.cs
This commit is contained in:
Wojtek Figat
2024-09-23 14:11:05 +02:00
59 changed files with 675 additions and 319 deletions

View File

@@ -296,6 +296,16 @@ namespace FlaxEditor.CustomEditors
_values.Set(_parent.Values, value);
}
private bool SyncParent()
{
// TODO: add attribute for types that want to sync their contents with a parent
var type = Values.Type.Type;
if (type == typeof(LocalizedString) ||
type == typeof(FontReference))
return true;
return _parent != null && !(_parent is SyncPointEditor);
}
internal virtual void RefreshInternal()
{
if (_values == null)
@@ -317,7 +327,7 @@ namespace FlaxEditor.CustomEditors
// Propagate values up (eg. when member of structure gets modified, also structure should be updated as a part of the other object)
var obj = _parent;
while (obj._parent != null && !(obj._parent is SyncPointEditor))
while (obj.SyncParent())
{
obj.Values.Set(obj._parent.Values, obj.Values);
obj = obj._parent;

View File

@@ -72,6 +72,8 @@ namespace FlaxEditor.CustomEditors
return new GenericEditor();
if (targetType.IsArray)
{
if (targetType.Type == null)
return new ArrayEditor();
if (targetType.Type.GetArrayRank() != 1)
return new GenericEditor(); // Not-supported multidimensional array

View File

@@ -7,6 +7,7 @@ using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -634,26 +635,29 @@ namespace FlaxEditor.CustomEditors.Dedicated
LayoutElementsContainer vEl;
Color axisColorX = ActorTransformEditor.AxisColorX;
Color axisColorY = ActorTransformEditor.AxisColorY;
FloatValueBox xV, yV, wV, hV;
if (xEq)
{
xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values), axisColorX);
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values), axisColorX);
xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values), axisColorX, out xV);
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values), axisColorX, out wV);
}
else
{
xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values), axisColorX);
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values), axisColorX);
xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values), axisColorX, out xV);
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values), axisColorX, out wV);
}
if (yEq)
{
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values), axisColorY);
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values), axisColorY);
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values), axisColorY, out yV);
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values), axisColorY, out hV);
}
else
{
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values), axisColorY);
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values), axisColorY);
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values), axisColorY, out yV);
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values), axisColorY, out hV);
}
// Anchors
xEl.Control.AnchorMin = new Float2(0, xEl.Control.AnchorMin.Y);
xEl.Control.AnchorMax = new Float2(0.5f, xEl.Control.AnchorMax.Y);
@@ -665,6 +669,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
hEl.Control.AnchorMin = new Float2(0.5f, xEl.Control.AnchorMin.Y);
hEl.Control.AnchorMax = new Float2(1, xEl.Control.AnchorMax.Y);
// Navigation path
xV.NavTargetRight = yV;
yV.NavTargetRight = wV;
wV.NavTargetRight = hV;
yV.NavTargetLeft = xV;
wV.NavTargetLeft = yV;
hV.NavTargetLeft = wV;
}
private VerticalPanelElement VerticalPanelWithoutMargin(LayoutElementsContainer cont)
@@ -684,17 +697,19 @@ namespace FlaxEditor.CustomEditors.Dedicated
return grid;
}
private CustomElementsContainer<UniformGridPanel> UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values, Color borderColor)
private CustomElementsContainer<UniformGridPanel> UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values, Color borderColor, out FloatValueBox valueBox)
{
valueBox = null;
var grid = UniformGridTwoByOne(el);
grid.CustomControl.SlotPadding = new Margin(5, 5, 1, 1);
var label = grid.Label(text, TextAlignment.Far);
var editor = grid.Object(values);
if (editor is FloatEditor floatEditor && floatEditor.Element is FloatValueElement floatEditorElement)
{
valueBox = floatEditorElement.ValueBox;
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
floatEditorElement.ValueBox.BorderColor = Color.Lerp(borderColor, back, ActorTransformEditor.AxisGreyOutFactor);
floatEditorElement.ValueBox.BorderSelectedColor = borderColor;
valueBox.BorderColor = Color.Lerp(borderColor, back, ActorTransformEditor.AxisGreyOutFactor);
valueBox.BorderSelectedColor = borderColor;
}
return grid;
}

View File

@@ -69,7 +69,7 @@ namespace FlaxEditor.CustomEditors.Editors
private void OnValueChanged()
{
if (IsSetBlocked)
if (IsSetBlocked || Values == null)
return;
var isSliding = XElement.IsSliding || YElement.IsSliding;
@@ -158,7 +158,7 @@ namespace FlaxEditor.CustomEditors.Editors
private void OnValueChanged()
{
if (IsSetBlocked)
if (IsSetBlocked || Values == null)
return;
var isSliding = XElement.IsSliding || YElement.IsSliding;
@@ -247,7 +247,7 @@ namespace FlaxEditor.CustomEditors.Editors
private void OnValueChanged()
{
if (IsSetBlocked)
if (IsSetBlocked || Values == null)
return;
var isSliding = XElement.IsSliding || YElement.IsSliding;

View File

@@ -143,7 +143,7 @@ namespace FlaxEditor.CustomEditors.Editors
private void OnValueChanged()
{
if (IsSetBlocked)
if (IsSetBlocked || Values == null)
return;
var xValue = XElement.ValueBox.Value;
@@ -318,7 +318,7 @@ namespace FlaxEditor.CustomEditors.Editors
private void OnValueChanged()
{
if (IsSetBlocked)
if (IsSetBlocked || Values == null)
return;
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding;
@@ -418,7 +418,7 @@ namespace FlaxEditor.CustomEditors.Editors
private void OnValueChanged()
{
if (IsSetBlocked)
if (IsSetBlocked || Values == null)
return;
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding;

View File

@@ -89,7 +89,7 @@ namespace FlaxEditor.CustomEditors.Editors
private void OnValueChanged()
{
if (IsSetBlocked)
if (IsSetBlocked || Values == null)
return;
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding || WElement.IsSliding;
@@ -200,7 +200,7 @@ namespace FlaxEditor.CustomEditors.Editors
private void OnValueChanged()
{
if (IsSetBlocked)
if (IsSetBlocked || Values == null)
return;
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding || WElement.IsSliding;
@@ -311,7 +311,7 @@ namespace FlaxEditor.CustomEditors.Editors
private void OnValueChanged()
{
if (IsSetBlocked)
if (IsSetBlocked || Values == null)
return;
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding || WElement.IsSliding;

View File

@@ -218,6 +218,13 @@ namespace FlaxEditor.GUI
Render2D.FillRectangle(bounds, style.Selection);
Render2D.DrawRectangle(bounds, style.SelectionBorder);
}
// Navigation focus highlight
if (IsNavFocused)
{
var bounds = new Rectangle(Float2.Zero, Size);
Render2D.DrawRectangle(bounds, style.BackgroundSelected);
}
}
/// <inheritdoc />
@@ -286,35 +293,7 @@ namespace FlaxEditor.GUI
else if (Button1Rect.Contains(location))
{
Focus();
if (Validator.AssetType != ScriptType.Null)
{
// Show asset picker popup
var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item =>
{
Validator.SelectedItem = item;
RootWindow.Focus();
Focus();
});
if (Validator.SelectedAsset != null)
{
var selectedAssetName = Path.GetFileNameWithoutExtension(Validator.SelectedAsset.Path);
popup.ScrollToAndHighlightItemByName(selectedAssetName);
}
}
else
{
// Show content item picker popup
var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item =>
{
Validator.SelectedItem = item;
RootWindow.Focus();
Focus();
});
if (Validator.SelectedItem != null)
{
popup.ScrollToAndHighlightItemByName(Validator.SelectedItem.ShortName);
}
}
OnSubmit();
}
else if (Validator.SelectedAsset != null || Validator.SelectedItem != null)
{
@@ -412,5 +391,41 @@ namespace FlaxEditor.GUI
return DragDropEffect.Move;
}
/// <inheritdoc />
public override void OnSubmit()
{
base.OnSubmit();
if (Validator.AssetType != ScriptType.Null)
{
// Show asset picker popup
var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item =>
{
Validator.SelectedItem = item;
RootWindow.Focus();
Focus();
});
if (Validator.SelectedAsset != null)
{
var selectedAssetName = Path.GetFileNameWithoutExtension(Validator.SelectedAsset.Path);
popup.ScrollToAndHighlightItemByName(selectedAssetName);
}
}
else
{
// Show content item picker popup
var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item =>
{
Validator.SelectedItem = item;
RootWindow.Focus();
Focus();
});
if (Validator.SelectedItem != null)
{
popup.ScrollToAndHighlightItemByName(Validator.SelectedItem.ShortName);
}
}
}
}
}

View File

@@ -155,6 +155,11 @@ namespace FlaxEditor.Modules.SourceCodeEditing
/// </summary>
public readonly CachedTypesCollection All = new CachedAllTypesCollection(8096, ScriptType.Null, type => true, HasAssemblyValidAnyTypes);
/// <summary>
/// The all types collection from all assemblies (including C# system libraries).
/// </summary>
public readonly CachedTypesCollection AllWithStd = new CachedTypesCollection(8096, ScriptType.Null, type => true, assembly => true);
/// <summary>
/// The all valid types collection for the Visual Script property types (includes basic types like int/float, structures, object references).
/// </summary>
@@ -574,21 +579,17 @@ namespace FlaxEditor.Modules.SourceCodeEditing
private static bool HasAssemblyValidAnyTypes(Assembly assembly)
{
var codeBase = Utils.GetAssemblyLocation(assembly);
if (string.IsNullOrEmpty(codeBase))
return true;
#if USE_NETCORE
if (assembly.ManifestModule.FullyQualifiedName == "<In Memory Module>")
return false;
if (string.IsNullOrEmpty(codeBase))
return true;
// Skip runtime related assemblies
string repositoryUrl = assembly.GetCustomAttributes<AssemblyMetadataAttribute>().FirstOrDefault(x => x.Key == "RepositoryUrl")?.Value ?? "";
if (repositoryUrl != "https://github.com/dotnet/runtime")
return true;
#else
if (string.IsNullOrEmpty(codeBase))
return true;
// Skip assemblies from in-build Mono directory
if (!codeBase.Contains("/Mono/lib/mono/"))
return true;

View File

@@ -211,10 +211,10 @@ namespace FlaxEditor.Options
public bool SeparateValueAndUnit { get; set; }
/// <summary>
/// Gets or sets the option to put a space between numbers and units for unit formatting.
/// Gets or sets tree line visibility.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Interface"), EditorOrder(320)]
[EditorDisplay("Interface"), EditorOrder(320), Tooltip("Toggles tree line visibility in places like the Scene or Content Panel.")]
public bool ShowTreeLines { get; set; } = true;
/// <summary>
@@ -369,7 +369,7 @@ namespace FlaxEditor.Options
public int NumberOfGameClientsToLaunch = 1;
/// <summary>
/// Gets or sets the visject connection curvature.
/// Gets or sets the curvature of the line connecting to connected visject nodes.
/// </summary>
[DefaultValue(1.0f), Range(0.0f, 2.0f)]
[EditorDisplay("Visject"), EditorOrder(550)]

View File

@@ -74,6 +74,7 @@ namespace FlaxEditor.Surface
Visible = false,
Parent = this,
EndEditOnClick = false, // We have to handle this ourselves, otherwise the textbox instantly loses focus when double-clicking the header
HorizontalAlignment = TextAlignment.Center,
};
}

View File

@@ -448,6 +448,8 @@ namespace FlaxEditor.Surface
sb.Append("virtual ");
sb.Append(valueType.Name);
sb.Append(' ');
if (member.IsMethod)
sb.Append(member.DeclaringType.Namespace).Append('.');
sb.Append(declaringType.Name);
sb.Append('.');
sb.Append(name);

View File

@@ -140,12 +140,12 @@ namespace FlaxEditor.Surface
var searchStartTime = DateTime.Now;
#endif
foreach (var scriptType in Editor.Instance.CodeEditing.All.Get())
foreach (var scriptType in Editor.Instance.CodeEditing.AllWithStd.Get())
{
if (!SurfaceUtils.IsValidVisualScriptType(scriptType))
continue;
_iterator(scriptType, _cache, _version);
if (SurfaceUtils.IsValidVisualScriptType(scriptType))
{
_iterator(scriptType, _cache, _version);
}
}
// Add group to context menu (on a main thread)

View File

@@ -17,7 +17,6 @@ using FlaxEditor.Surface.Elements;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Surface
{
@@ -36,6 +35,14 @@ namespace FlaxEditor.Surface
Archetypes = new List<NodeArchetype>(),
};
private static readonly string[] _blacklistedTypeNames =
{
"Newtonsoft.Json.",
"System.Array",
"System.Linq.Expressions.",
"System.Reflection.",
};
private static NodesCache _nodesCache = new NodesCache(IterateNodesCache);
private DragActors _dragActors;
@@ -269,8 +276,11 @@ namespace FlaxEditor.Surface
{
// Skip Newtonsoft.Json stuff
var scriptTypeTypeName = scriptType.TypeName;
if (scriptTypeTypeName.StartsWith("Newtonsoft.Json."))
return;
for (var i = 0; i < _blacklistedTypeNames.Length; i++)
{
if (scriptTypeTypeName.StartsWith(_blacklistedTypeNames[i]))
return;
}
var scriptTypeName = scriptType.Name;
// Enum

View File

@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEditor.SceneGraph;
using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Widgets;
@@ -298,9 +299,33 @@ namespace FlaxEditor.Viewport
}
var buttonBB = translateSnappingCM.AddButton("Bounding Box").LinkTooltip("Snaps the selection based on it's bounding volume");
buttonBB.Tag = -1.0f;
translateSnappingCM.ButtonClicked += button =>
var buttonCustom = translateSnappingCM.AddButton("Custom");
buttonCustom.LinkTooltip("Custom grid size");
const float defaultCustomTranslateSnappingValue = 250.0f;
float customTranslateSnappingValue = transformGizmo.TranslationSnapValue;
if (customTranslateSnappingValue < 0.0f)
customTranslateSnappingValue = defaultCustomTranslateSnappingValue;
foreach (var v in TranslateSnapValues)
{
var v = (float)button.Tag;
if (Mathf.Abs(transformGizmo.TranslationSnapValue - v) < 0.001f)
{
customTranslateSnappingValue = defaultCustomTranslateSnappingValue;
break;
}
}
buttonCustom.Tag = customTranslateSnappingValue;
var customValue = new FloatValueBox(customTranslateSnappingValue, Style.Current.FontMedium.MeasureText(buttonCustom.Text).X + 5, 2, 70.0f, 0.001f, float.MaxValue, 0.1f)
{
Parent = buttonCustom
};
customValue.ValueChanged += () =>
{
buttonCustom.Tag = customValue.Value;
buttonCustom.Click();
};
translateSnappingCM.ButtonClicked += b =>
{
var v = (float)b.Tag;
transformGizmo.TranslationSnapValue = v;
if (v < 0.0f)
translateSnapping.Text = "Bounding Box";

View File

@@ -221,7 +221,7 @@ namespace FlaxEditor.Viewport
editor.SceneEditing.SelectionChanged += OnSelectionChanged;
// Gizmo widgets
AddGizmoViewportWidgets(this, TransformGizmo);
AddGizmoViewportWidgets(this, TransformGizmo, true);
// Show grid widget
_showGridButton = ViewWidgetShowMenu.AddButton("Grid", () => Grid.Enabled = !Grid.Enabled);

View File

@@ -32,6 +32,7 @@ namespace FlaxEditor.Windows.Assets
_surface.ContextChanged += OnSurfaceContextChanged;
// Toolstrip
SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/anim-graph/index.html")).LinkTooltip("See documentation to learn more");

View File

@@ -28,6 +28,7 @@ namespace FlaxEditor.Windows.Assets
};
// Toolstrip
SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/materials/index.html")).LinkTooltip("See documentation to learn more");
}

View File

@@ -28,6 +28,7 @@ namespace FlaxEditor.Windows.Assets
};
// Toolstrip
SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more");
}

View File

@@ -153,6 +153,14 @@ namespace FlaxEditor.Windows.Assets
{
var menu = new ContextMenu();
var copySprite = menu.AddButton("Copy sprite");
copySprite.Tag = groupPanel.Tag;
copySprite.ButtonClicked += OnCopySpriteClicked;
var pasteSprite = menu.AddButton("Paste sprite");
pasteSprite.Tag = groupPanel.Tag;
pasteSprite.ButtonClicked += OnPasteSpriteClicked;
var deleteSprite = menu.AddButton("Delete sprite");
deleteSprite.Tag = groupPanel.Tag;
deleteSprite.ButtonClicked += OnDeleteSpriteClicked;
@@ -160,6 +168,24 @@ namespace FlaxEditor.Windows.Assets
menu.Show(groupPanel, location);
}
private void OnCopySpriteClicked(ContextMenuButton button)
{
var window = ((PropertiesProxy)ParentEditor.Values[0])._window;
var index = (int)button.Tag;
var sprite = window.Asset.GetSprite(index);
Clipboard.Text = FlaxEngine.Json.JsonSerializer.Serialize(sprite, typeof(Sprite));
}
private void OnPasteSpriteClicked(ContextMenuButton button)
{
var window = ((PropertiesProxy)ParentEditor.Values[0])._window;
var index = (int)button.Tag;
var sprite = window.Asset.GetSprite(index);
var pasted = FlaxEngine.Json.JsonSerializer.Deserialize<Sprite>(Clipboard.Text);
sprite.Area = pasted.Area;
window.Asset.SetSprite(index, ref sprite);
}
private void OnDeleteSpriteClicked(ContextMenuButton button)
{
var window = ((PropertiesProxy)ParentEditor.Values[0])._window;

View File

@@ -27,9 +27,21 @@ namespace FlaxEditor.Windows.Assets
/// </summary>
protected readonly Panel _panel;
private readonly ToolStripButton _saveButton;
private readonly ToolStripButton _undoButton;
private readonly ToolStripButton _redoButton;
/// <summary>
/// Save button.
/// </summary>
protected ToolStripButton _saveButton;
/// <summary>
/// Undo button.
/// </summary>
protected ToolStripButton _undoButton;
/// <summary>
/// Redo button.
/// </summary>
protected ToolStripButton _redoButton;
private bool _showWholeGraphOnLoad = true;
/// <summary>
@@ -61,17 +73,12 @@ namespace FlaxEditor.Windows.Assets
protected VisjectFunctionSurfaceWindow(Editor editor, AssetItem item)
: base(editor, item)
{
var inputOptions = Editor.Options.Options.Input;
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
_undo.RedoDone += OnUndoRedo;
_undo.ActionDone += OnUndoRedo;
// Toolstrip
SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
// Panel
_panel = new Panel(ScrollBars.None)
{

View File

@@ -135,6 +135,7 @@ const Char* SplashScreenQuotes[] =
TEXT("Drum roll please"),
TEXT("Good Luck Have Fun"),
TEXT("GG Well Played"),
TEXT("Now with documentation."),
};
SplashScreen::~SplashScreen()

View File

@@ -396,8 +396,8 @@ void BehaviorTreeMoveToNode::GetAgentSize(Actor* agent, float& outRadius, float&
// Estimate actor bounds to extract capsule information
const BoundingBox box = agent->GetBox();
const BoundingSphere sphere = agent->GetSphere();
outRadius = sphere.Radius;
outHeight = box.GetSize().Y;
outRadius = (float)sphere.Radius;
outHeight = (float)box.GetSize().Y;
}
int32 BehaviorTreeMoveToNode::GetStateSize() const
@@ -522,7 +522,7 @@ String BehaviorTreeMoveToNode::GetDebugInfo(const BehaviorUpdateContext& context
goal = state->GoalLocation.ToString();
const Vector3 agentLocation = state->Agent->GetPosition();
const Vector3 agentLocationOnPath = agentLocation + state->AgentOffset;
float distanceLeft = state->Path.Count() > state->TargetPathIndex ? Vector3::Distance(state->Path[state->TargetPathIndex], agentLocationOnPath) : 0;
Real distanceLeft = state->Path.Count() > state->TargetPathIndex ? Vector3::Distance(state->Path[state->TargetPathIndex], agentLocationOnPath) : 0;
for (int32 i = state->TargetPathIndex; i < state->Path.Count(); i++)
distanceLeft += Vector3::Distance(state->Path[i - 1], state->Path[i]);
return String::Format(TEXT("Agent: '{}'\nGoal: '{}'\nDistance: {}"), agent, goal, (int32)distanceLeft);
@@ -559,7 +559,7 @@ void BehaviorTreeMoveToNode::State::OnUpdate()
const float acceptableHeightPercentage = 1.05f;
const float testHeight = agentHeight * acceptableHeightPercentage;
const Vector3 toGoal = agentLocationOnPath - pathSegmentEnd;
const float toGoalHeightDiff = (toGoal * UpVector).SumValues();
const Real toGoalHeightDiff = (toGoal * UpVector).SumValues();
if (toGoal.Length() <= testRadius && toGoalHeightDiff <= testHeight)
{
TargetPathIndex++;

View File

@@ -465,7 +465,7 @@ namespace FlaxEngine
/// <summary>
/// A single keyframe that can be injected into linear curve.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct Keyframe : IComparable, IComparable<Keyframe>
{
/// <summary>
@@ -720,7 +720,7 @@ namespace FlaxEngine
/// <summary>
/// A single keyframe that can be injected into Bezier curve.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct Keyframe : IComparable, IComparable<Keyframe>
{
/// <summary>

View File

@@ -113,7 +113,7 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ
newMidJointPos = rootTransform.Translation + projJointDist * toTargetDir + jointLineDist * bendDirection;
}
// TODO: fix the new IK impl (https://github.com/FlaxEngine/FlaxEngine/pull/2421) to properly work for character from https://github.com/PrecisionRender/CharacterControllerPro
#define OLD 0
#define OLD 1
// Update root joint orientation
{
#if OLD

View File

@@ -7,6 +7,7 @@
#include "Loading/ContentLoadingManager.h"
#include "Loading/Tasks/LoadAssetTask.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/LogContext.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Profiler/ProfilerCPU.h"
@@ -596,9 +597,10 @@ bool Asset::IsInternalType() const
bool Asset::onLoad(LoadAssetTask* task)
{
if (task->Asset.Get() != this || Platform::AtomicRead(&_loadingTask) == 0)
// It may fail when task is cancelled and new one was created later (don't crash but just end with an error)
if (task->Asset.Get() != this || Platform::AtomicRead(&_loadingTask) == 0)
return true;
LogContextScope logContext(GetID());
Locker.Lock();

View File

@@ -9,6 +9,7 @@
#include "Storage/JsonStorageProxy.h"
#include "Factories/IAssetFactory.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/LogContext.h"
#include "Engine/Core/Types/String.h"
#include "Engine/Core/ObjectsRemovalService.h"
#include "Engine/Engine/EngineService.h"
@@ -970,6 +971,7 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
if (IsAssetTypeIdInvalid(type, result->GetTypeHandle()) && !result->Is(type))
{
LOG(Warning, "Different loaded asset type! Asset: \'{0}\'. Expected type: {1}", result->ToString(), type.ToString());
LogContext::Print(LogType::Warning);
return nullptr;
}
return result;
@@ -1004,6 +1006,7 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
if (!GetAssetInfo(id, assetInfo))
{
LOG(Warning, "Invalid or missing asset ({0}, {1}).", id, type.ToString());
LogContext::Print(LogType::Warning);
LOAD_FAILED();
}
#if ASSETS_LOADING_EXTRA_VERIFICATION

View File

@@ -5,6 +5,7 @@
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Core/Log.h"
#include "Engine/Core/Cache.h"
#include "Engine/Core/Collections/Sorting.h"
#include "Engine/Core/Collections/ArrayExtensions.h"
#include "Engine/Serialization/MemoryWriteStream.h"
@@ -766,6 +767,7 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, ModelDa
// Link with object from prefab (if reimporting)
if (prefab)
{
rapidjson_flax::StringBuffer buffer;
for (Actor* a : nodeActors)
{
for (const auto& i : prefab->ObjectsCache)
@@ -776,6 +778,32 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, ModelDa
if (o->GetName() != a->GetName()) // Name match
continue;
// Preserve local changes made in the prefab
{
// Serialize
buffer.Clear();
CompactJsonWriter writer(buffer);
writer.StartObject();
const void* defaultInstance = o->GetType().GetDefaultInstance();
o->Serialize(writer, defaultInstance);
writer.EndObject();
// Parse json
rapidjson_flax::Document document;
document.Parse(buffer.GetString(), buffer.GetSize());
// Strip unwanted data
document.RemoveMember("ID");
document.RemoveMember("ParentID");
document.RemoveMember("PrefabID");
document.RemoveMember("PrefabObjectID");
document.RemoveMember("Name");
// Deserialize object
auto modifier = Cache::ISerializeModifier.Get();
a->Deserialize(document, &*modifier);
}
// Mark as this object already exists in prefab so will be preserved when updating it
a->LinkPrefab(o->GetPrefabID(), o->GetPrefabObjectID());
break;

View File

@@ -1,9 +1,16 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "LogContext.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Types/Guid.h"
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/StringBuilder.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/Script.h"
#include "Engine/Content/Asset.h"
#include "Engine/Content/Content.h"
#include "Engine/Level/Actor.h"
#include "Engine/Threading/ThreadLocal.h"
struct LogContextThreadData
@@ -30,7 +37,7 @@ struct LogContextThreadData
Count--;
}
LogContextData Peek()
LogContextData Peek() const
{
return Count > 0 ? Ptr[Count - 1] : LogContextData();
}
@@ -38,12 +45,58 @@ struct LogContextThreadData
ThreadLocal<LogContextThreadData> GlobalLogContexts;
String LogContext::GetInfo()
void LogContext::Print(LogType verbosity)
{
LogContextData context = LogContext::Get();
if (context.ObjectID != Guid::Empty)
return String::Format(TEXT("(Loading source was {0})"), context.ObjectID);
return String::Empty;
auto& stack = GlobalLogContexts.Get();
if (stack.Count == 0)
return;
const StringView indentation(TEXT(" "));
StringBuilder msg;
for (int32 index = (int32)stack.Count - 1; index >= 0; index--)
{
// Build call hierarchy via indentation
msg.Clear();
for (uint32 i = index; i < stack.Count; i++)
msg.Append(indentation);
LogContextData& context = stack.Ptr[index];
if (context.ObjectID != Guid::Empty)
{
// Object reference context
msg.Append(TEXT(" Referenced by "));
if (ScriptingObject* object = Scripting::TryFindObject(context.ObjectID))
{
const StringAnsiView typeName(object->GetType().Fullname);
if (Asset* asset = ScriptingObject::Cast<Asset>(object))
{
msg.AppendFormat(TEXT("asset '{}' ({}, {})"), asset->GetPath(), asset->GetTypeName(), context.ObjectID);
}
else if (Actor* actor = ScriptingObject::Cast<Actor>(object))
{
msg.AppendFormat(TEXT("actor '{}' ({}, {})"), actor->GetNamePath(), String(typeName), context.ObjectID);
}
else if (Script* script = ScriptingObject::Cast<Script>(object))
{
msg.AppendFormat(TEXT("script '{}' ({}, {})"), script->GetNamePath(), String(typeName), context.ObjectID);
}
else
{
msg.AppendFormat(TEXT("object {} ({})"), String(typeName), context.ObjectID);
}
}
else if (Asset* asset = Content::GetAsset(context.ObjectID))
{
msg.AppendFormat(TEXT("asset '{}' ({}, {})"), asset->GetPath(), asset->GetTypeName(), context.ObjectID);
}
else
{
msg.AppendFormat(TEXT("object {}"), context.ObjectID);
}
}
// Print message
Log::Logger::Write(verbosity, msg.ToStringView());
}
}
void LogContext::Push(const Guid& id)

View File

@@ -2,6 +2,7 @@
#pragma once
#include "Engine/Core/Log.h"
#include "Engine/Core/Config.h"
#include "Engine/Scripting/ScriptingType.h"
@@ -9,7 +10,7 @@ class String;
struct Guid;
/// <summary>
/// Log context data structure. Contains different kinds of context data for different situtations.
/// Log context data structure. Contains different kinds of context data for different situations.
/// </summary>
API_STRUCT(NoDefault) struct FLAXENGINE_API LogContextData
{
@@ -54,10 +55,10 @@ API_CLASS(Static) class FLAXENGINE_API LogContext
API_FUNCTION() static LogContextData Get();
/// <summary>
/// Returns a string which represents the current log context on the stack.
/// Prints the current log context to the log. Does nothing it
/// </summary>
/// <returns>The formatted string representing the current log context.</returns>
API_FUNCTION() static String GetInfo();
/// <param name="verbosity">The verbosity of the log.</param>
API_FUNCTION() static void Print(LogType verbosity);
};
/// <summary>

View File

@@ -542,10 +542,8 @@ void Quaternion::RotationYawPitchRoll(float yaw, float pitch, float roll, Quater
Quaternion Quaternion::GetRotationFromNormal(const Vector3& normal, const Transform& reference)
{
Float3 up = reference.GetUp();
const float dot = Vector3::Dot(normal, up);
const Real dot = Vector3::Dot(normal, up);
if (Math::NearEqual(Math::Abs(dot), 1))
{
up = reference.GetRight();
}
return Quaternion::LookRotation(normal, up);
}

View File

@@ -15,8 +15,8 @@ namespace FlaxEngine.Interop
#if FLAX_EDITOR
[HideInEditor]
#endif
[CustomMarshaller(typeof(object), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedHandleMarshaller.ManagedToNative))]
[CustomMarshaller(typeof(object), MarshalMode.UnmanagedToManagedOut, typeof(ManagedHandleMarshaller.ManagedToNative))]
[CustomMarshaller(typeof(object), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedHandleMarshaller.ManagedToNativeState))]
[CustomMarshaller(typeof(object), MarshalMode.UnmanagedToManagedOut, typeof(ManagedHandleMarshaller.ManagedToNativeState))]
[CustomMarshaller(typeof(object), MarshalMode.ElementIn, typeof(ManagedHandleMarshaller.ManagedToNative))]
[CustomMarshaller(typeof(object), MarshalMode.ManagedToUnmanagedOut, typeof(ManagedHandleMarshaller.NativeToManaged))]
[CustomMarshaller(typeof(object), MarshalMode.UnmanagedToManagedIn, typeof(ManagedHandleMarshaller.NativeToManaged))]
@@ -31,7 +31,20 @@ namespace FlaxEngine.Interop
#endif
public static class NativeToManaged
{
public static object ConvertToManaged(IntPtr unmanaged) => unmanaged == IntPtr.Zero ? null : ManagedHandle.FromIntPtr(unmanaged).Target;
public static object ConvertToManaged(IntPtr unmanaged)
{
if (unmanaged == IntPtr.Zero)
return null;
object managed = ManagedHandle.FromIntPtr(unmanaged).Target;
if (managed is ManagedArray managedArray)
{
var managedArrayHandle = ManagedHandle.Alloc(managedArray, GCHandleType.Normal);
managed = NativeInterop.MarshalToManaged((IntPtr)managedArrayHandle, managedArray.ArrayType);
managedArrayHandle.Free();
}
return managed;
}
public static IntPtr ConvertToUnmanaged(object managed) => managed != null ? ManagedHandle.ToIntPtr(managed, GCHandleType.Weak) : IntPtr.Zero;
public static void Free(IntPtr unmanaged)
@@ -40,6 +53,52 @@ namespace FlaxEngine.Interop
}
}
#if FLAX_EDITOR
[HideInEditor]
#endif
public struct ManagedToNativeState
{
ManagedArray managedArray;
IntPtr handle;
public void FromManaged(object managed)
{
if (managed == null)
return;
if (managed is Array arr)
{
var type = managed.GetType();
var elementType = type.GetElementType();
if (NativeInterop.ArrayFactory.GetMarshalledType(elementType) == elementType)
{
// Use pooled managed array wrapper to be passed around as handle to it
(ManagedHandle tmp, managedArray) = ManagedArray.WrapPooledArray(arr);
handle = ManagedHandle.ToIntPtr(tmp);
}
else
{
// Convert array contents to be properly accessed by the native code (as GCHandles array)
managedArray = NativeInterop.ManagedArrayToGCHandleWrappedArray(arr);
handle = ManagedHandle.ToIntPtr(ManagedHandle.Alloc(managedArray));
managedArray = null; // It's not pooled
}
}
else
handle = ManagedHandle.ToIntPtr(managed, GCHandleType.Weak);
}
public IntPtr ToUnmanaged()
{
return handle;
}
public void Free()
{
managedArray?.FreePooled();
}
}
#if FLAX_EDITOR
[HideInEditor]
#endif
@@ -50,12 +109,6 @@ namespace FlaxEngine.Interop
public static void Free(IntPtr unmanaged)
{
// This is a weak handle, no need to free it
/*
if (unmanaged == IntPtr.Zero)
return;
ManagedHandle.FromIntPtr(unmanaged).Free();
*/
}
}
@@ -342,6 +395,7 @@ namespace FlaxEngine.Interop
{
public static Dictionary<T, U> ConvertToManaged(IntPtr unmanaged) => DictionaryMarshaller<T, U>.ToManaged(unmanaged);
public static IntPtr ConvertToUnmanaged(Dictionary<T, U> managed) => DictionaryMarshaller<T, U>.ToNative(managed, GCHandleType.Weak);
public static void Free(IntPtr unmanaged)
{
//DictionaryMarshaller<T, U>.Free(unmanaged); // No need to free weak handles
@@ -614,6 +668,7 @@ namespace FlaxEngine.Interop
{
public static string ConvertToManaged(IntPtr unmanaged) => ManagedString.ToManaged(unmanaged);
public static unsafe IntPtr ConvertToUnmanaged(string managed) => managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed, GCHandleType.Weak);
public static void Free(IntPtr unmanaged)
{
//ManagedString.Free(unmanaged); // No need to free weak handles

View File

@@ -14,6 +14,7 @@
#include "Engine/Core/Collections/CollectionPoolCache.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Core/Cache.h"
#include "Engine/Core/LogContext.h"
#include "Engine/Debug/Exceptions/ArgumentException.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Scripting/Script.h"
@@ -122,6 +123,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
}
auto& data = *prefab->Data;
SceneObjectsFactory::Context context(modifier.Value);
LogContextScope logContext(prefabId);
// Deserialize prefab objects
auto prevIdMapping = Scripting::ObjectsLookupIdMapping.Get();

View File

@@ -1302,6 +1302,11 @@ bool NetworkReplicator::HasObject(const ScriptingObject* obj)
return false;
}
void NetworkReplicator::MapObjectId(Guid& objectId)
{
IdsRemappingTable.TryGet(objectId, objectId);
}
ScriptingObject* NetworkReplicator::ResolveForeignObject(Guid objectId)
{
if (const auto& object = ResolveObject(objectId))

View File

@@ -116,6 +116,12 @@ public:
/// <param name="obj">The network object.</param>
/// <returns>True if object exists in networking, otherwise false.</returns>
API_FUNCTION() static bool HasObject(const ScriptingObject* obj);
/// <summary>
/// Maps object ID into server or client ID (depending on the source ID). Leaves source value unchanged if that specific ID is unused.
/// </summary>
/// <param name="objectId">The network object identifier to map. Contains result ID once the method completes.</param>
API_FUNCTION() static void MapObjectId(API_PARAM(Ref) Guid& objectId);
/// <summary>
/// Resolves foreign Guid into a local ScriptingObject

View File

@@ -494,28 +494,28 @@ void PhysicsScene::CollectResults()
bool PhysicsScene::LineCast(const Vector3& start, const Vector3& end, uint32 layerMask, bool hitTriggers)
{
Vector3 directionToEnd = end - start;
const float distanceToEnd = directionToEnd.Length();
const Real distanceToEnd = directionToEnd.Length();
if (distanceToEnd >= ZeroTolerance)
directionToEnd /= distanceToEnd;
return PhysicsBackend::RayCast(_scene, start, directionToEnd, distanceToEnd, layerMask, hitTriggers);
return PhysicsBackend::RayCast(_scene, start, directionToEnd, (float)distanceToEnd, layerMask, hitTriggers);
}
bool PhysicsScene::LineCast(const Vector3& start, const Vector3& end, RayCastHit& hitInfo, uint32 layerMask, bool hitTriggers)
{
Vector3 directionToEnd = end - start;
const float distanceToEnd = directionToEnd.Length();
const Real distanceToEnd = directionToEnd.Length();
if (distanceToEnd >= ZeroTolerance)
directionToEnd /= distanceToEnd;
return PhysicsBackend::RayCast(_scene, start, directionToEnd, hitInfo, distanceToEnd, layerMask, hitTriggers);
return PhysicsBackend::RayCast(_scene, start, directionToEnd, hitInfo, (float)distanceToEnd, layerMask, hitTriggers);
}
bool PhysicsScene::LineCastAll(const Vector3& start, const Vector3& end, Array<RayCastHit>& results, uint32 layerMask, bool hitTriggers)
{
Vector3 directionToEnd = end - start;
const float distanceToEnd = directionToEnd.Length();
const Real distanceToEnd = directionToEnd.Length();
if (distanceToEnd >= ZeroTolerance)
directionToEnd /= distanceToEnd;
return PhysicsBackend::RayCastAll(_scene, start, directionToEnd, results, distanceToEnd, layerMask, hitTriggers);
return PhysicsBackend::RayCastAll(_scene, start, directionToEnd, results, (float)distanceToEnd, layerMask, hitTriggers);
}
bool PhysicsScene::RayCast(const Vector3& origin, const Vector3& direction, const float maxDistance, uint32 layerMask, bool hitTriggers)

View File

@@ -339,11 +339,10 @@ int32 Font::HitTestText(const StringView& text, const Float2& location, const Te
float baseLinesDistance = static_cast<float>(_height) * layout.BaseLinesGapScale * scale;
// Offset position to match lines origin space
Float2 rootOffset = layout.Bounds.Location + lines.First().Location;
Float2 testPoint = location - rootOffset;
Float2 testPoint = location - layout.Bounds.Location;
// Get line which may intersect with the position (it's possible because lines have fixed height)
int32 lineIndex = Math::Clamp(Math::FloorToInt(testPoint.Y / baseLinesDistance), 0, lines.Count() - 1);
int32 lineIndex = Math::Clamp(Math::FloorToInt((testPoint.Y - lines.First().Location.Y) / baseLinesDistance), 0, lines.Count() - 1);
const FontLineCache& line = lines[lineIndex];
float x = line.Location.X;
@@ -411,7 +410,6 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo
ASSERT(lines.HasItems());
float scale = layout.Scale / FontManager::FontScale;
float baseLinesDistance = static_cast<float>(_height) * layout.BaseLinesGapScale * scale;
Float2 rootOffset = layout.Bounds.Location + lines.First().Location;
// Find line with that position
FontCharacterEntry previous;
@@ -423,7 +421,7 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo
// Check if desire position is somewhere inside characters in line range
if (Math::IsInRange(index, line.FirstCharIndex, line.LastCharIndex))
{
float x = line.Location.X;
Float2 charPos = line.Location;
// Check all characters in the line
for (int32 currentIndex = line.FirstCharIndex; currentIndex < index; currentIndex++)
@@ -436,21 +434,21 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo
// Apply kerning
if (!isWhitespace && previous.IsValid)
{
x += entry.Font->GetKerning(previous.Character, entry.Character);
charPos.X += entry.Font->GetKerning(previous.Character, entry.Character);
}
previous = entry;
// Move
x += entry.AdvanceX * scale;
charPos.X += entry.AdvanceX * scale;
}
// Upper left corner of the character
return rootOffset + Float2(x, static_cast<float>(lineIndex * baseLinesDistance));
return layout.Bounds.Location + charPos;
}
}
// Position after last character in the last line
return rootOffset + Float2(lines.Last().Size.X, static_cast<float>((lines.Count() - 1) * baseLinesDistance));
return layout.Bounds.Location + lines.Last().Location + Float2(lines.Last().Size.X, 0.0f);
}
void Font::FlushFaceSize() const

View File

@@ -90,7 +90,7 @@ namespace FlaxEngine
/// <summary>
/// The size of the font characters.
/// </summary>
[EditorOrder(10), Limit(1, 500, 0.1f), Tooltip("The size of the font characters.")]
[EditorOrder(10), Limit(1, 500, 0.5f), Tooltip("The size of the font characters.")]
public float Size
{
get => _size;

View File

@@ -841,10 +841,11 @@ void Render2D::PushClip(const Rectangle& clipRect)
{
RENDER2D_CHECK_RENDERING_STATE;
const auto& mask = ClipLayersStack.Peek();
RotatedRectangle clipRectTransformed;
ApplyTransform(clipRect, clipRectTransformed);
const Rectangle bounds = Rectangle::Shared(clipRectTransformed.ToBoundingRect(), ClipLayersStack.Peek().Bounds);
ClipLayersStack.Push({ clipRectTransformed, bounds });
const Rectangle bounds = Rectangle::Shared(clipRectTransformed.ToBoundingRect(), mask.Bounds);
ClipLayersStack.Push({ RotatedRectangle::Shared(clipRectTransformed, mask.Bounds), bounds });
OnClipScissors();
}

View File

@@ -10,7 +10,6 @@
struct RotatedRectangle
{
public:
/// <summary>
/// The transformed top left corner.
/// </summary>
@@ -27,7 +26,6 @@ public:
Float2 ExtentY;
public:
/// <summary>
/// Initializes a new instance of the <see cref="RotatedRectangle"/> struct.
/// </summary>
@@ -60,7 +58,6 @@ public:
}
public:
/// <summary>
/// Convert rotated rectangle to the axis-aligned rectangle that builds rotated rectangle bounding box.
/// </summary>
@@ -95,8 +92,24 @@ public:
return false;
}
public:
/// <summary>
/// Calculates a rectangle that contains the shared part of both rectangles.
/// </summary>
/// <param name="a">The first rectangle.</param>
/// <param name="b">The second rectangle.</param>
/// <returns>Rectangle that contains shared part of a and b rectangles.</returns>
static RotatedRectangle Shared(const RotatedRectangle& a, const Rectangle& b)
{
// Clip the rotated rectangle bounds within the given AABB
RotatedRectangle result = a;
result.TopLeft = Float2::Max(a.TopLeft, b.GetTopLeft());
// TODO: do a little better math below (in case of actually rotated rectangle)
result.ExtentX.X = Math::Min(result.TopLeft.X + result.ExtentX.X, b.GetRight()) - result.TopLeft.X;
result.ExtentY.Y = Math::Min(result.TopLeft.Y + result.ExtentY.Y, b.GetBottom()) - result.TopLeft.Y;
return result;
}
public:
bool operator ==(const RotatedRectangle& other) const
{
return

View File

@@ -1,10 +1,11 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Runtime.CompilerServices;
namespace FlaxEngine
{
partial struct SpriteHandle
partial struct SpriteHandle : IEquatable<SpriteHandle>
{
/// <summary>
/// Invalid sprite handle.
@@ -107,5 +108,53 @@ namespace FlaxEngine
Atlas.SetSprite(Index, ref sprite);
}
}
/// <summary>
/// Tests for equality between two objects.
/// </summary>
/// <param name="left">The first value to compare.</param>
/// <param name="right">The second value to compare.</param>
/// <returns><c>true</c> if <paramref name="left" /> has the same value as <paramref name="right" />; otherwise, <c>false</c>.</returns>
public static bool operator ==(SpriteHandle left, SpriteHandle right)
{
return left.Index == right.Index && left.Atlas == right.Atlas;
}
/// <summary>
/// Tests for inequality between two objects.
/// </summary>
/// <param name="left">The first value to compare.</param>
/// <param name="right">The second value to compare.</param>
/// <returns><c>true</c> if <paramref name="left" /> has a different value than <paramref name="right" />; otherwise, <c>false</c>.</returns>
public static bool operator !=(SpriteHandle left, SpriteHandle right)
{
return left.Index != right.Index || left.Atlas != right.Atlas;
}
/// <inheritdoc />
public bool Equals(SpriteHandle other)
{
return Index == other.Index && Atlas == other.Atlas;
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is SpriteHandle other && Equals(other);
}
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine(Atlas, Index);
}
/// <inheritdoc />
public override string ToString()
{
if (Atlas)
return $"{System.IO.Path.GetFileNameWithoutExtension(Atlas.Path)}[{Index}]";
return "Invalid";
}
}
}

View File

@@ -318,6 +318,11 @@ void ProbesRendererService::Update()
// Calculate time delta since last update
auto timeNow = Time::Update.UnscaledTime;
auto timeSinceUpdate = timeNow - _lastProbeUpdate;
if (timeSinceUpdate < 0)
{
_lastProbeUpdate = timeNow;
timeSinceUpdate = 0;
}
// Check if render job is done
if (isUpdateSynced())
@@ -351,8 +356,9 @@ void ProbesRendererService::Update()
auto dt = (float)Time::Update.UnscaledDeltaTime.GetTotalSeconds();
for (int32 i = 0; i < _probesToBake.Count(); i++)
{
_probesToBake[i].Timeout -= dt;
if (_probesToBake[i].Timeout <= 0)
auto& e = _probesToBake[i];
e.Timeout -= dt;
if (e.Timeout <= 0)
{
firstValidEntryIndex = i;
break;
@@ -417,6 +423,9 @@ void ProbesRenderer::OnRender(RenderTask* task, GPUContext* context)
if (_current.Actor == nullptr)
{
// Probe has been unlinked (or deleted)
_task->Enabled = false;
_updateFrameNumber = 0;
_current.Type = EntryType::Invalid;
return;
}
break;

View File

@@ -28,21 +28,9 @@ public:
struct Entry
{
EntryType Type;
EntryType Type = EntryType::Invalid;
ScriptingObjectReference<Actor> Actor;
float Timeout;
Entry()
{
Type = EntryType::Invalid;
}
Entry(const Entry& other)
{
Type = other.Type;
Actor = other.Actor;
Timeout = other.Timeout;
}
float Timeout = 0.0f;
bool UseTextureData() const;
int32 GetResolution() const;

View File

@@ -887,7 +887,8 @@ ScriptingObject* Scripting::FindObject(Guid id, const MClass* type)
// Check type
if (!type || result->Is(type))
return result;
LOG(Warning, "Found scripting object with ID={0} of type {1} that doesn't match type {2}. {3}", id, String(result->GetType().Fullname), String(type->GetFullName()), LogContext::GetInfo());
LOG(Warning, "Found scripting object with ID={0} of type {1} that doesn't match type {2}", id, String(result->GetType().Fullname), String(type->GetFullName()));
LogContext::Print(LogType::Warning);
return nullptr;
}
@@ -906,7 +907,8 @@ ScriptingObject* Scripting::FindObject(Guid id, const MClass* type)
return asset;
}
LOG(Warning, "Unable to find scripting object with ID={0}. Required type {1}. {2}", id, String(type->GetFullName()), LogContext::GetInfo());
LOG(Warning, "Unable to find scripting object with ID={0}. Required type {1}", id, String(type->GetFullName()));
LogContext::Print(LogType::Warning);
return nullptr;
}

View File

@@ -737,7 +737,10 @@ DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_FindObject(Guid* id, MTypeObject*
if (klass && !obj->Is(klass))
{
if (!skipLog)
LOG(Warning, "Found scripting object with ID={0} of type {1} that doesn't match type {2}. {3}", *id, String(obj->GetType().Fullname), String(klass->GetFullName()), LogContext::GetInfo());
{
LOG(Warning, "Found scripting object with ID={0} of type {1} that doesn't match type {2}", *id, String(obj->GetType().Fullname), String(klass->GetFullName()));
LogContext::Print(LogType::Warning);
}
return nullptr;
}
return obj->GetOrCreateManagedInstance();
@@ -746,9 +749,10 @@ DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_FindObject(Guid* id, MTypeObject*
if (!skipLog)
{
if (klass)
LOG(Warning, "Unable to find scripting object with ID={0}. Required type {1}. {2}", *id, String(klass->GetFullName()), LogContext::GetInfo());
LOG(Warning, "Unable to find scripting object with ID={0} of type {1}", *id, String(klass->GetFullName()));
else
LOG(Warning, "Unable to find scripting object with ID={0}", *id);
LogContext::Print(LogType::Warning);
}
return nullptr;
}

View File

@@ -183,11 +183,15 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
break;
// Pre-skinned Local Position
case 13:
value = _treeType == MaterialTreeType::VertexShader ? Value(VariantType::Float3, TEXT("input.PreSkinnedPosition")) : Value::Zero;
value = Value(VariantType::Float3, TEXT("input.PreSkinnedPosition"));
if (_treeType != MaterialTreeType::VertexShader)
value = VsToPs(node, box).AsFloat3();
break;
// Pre-skinned Local Normal
case 14:
value = _treeType == MaterialTreeType::VertexShader ? Value(VariantType::Float3, TEXT("input.PreSkinnedNormal")) : Value::Zero;
value = Value(VariantType::Float3, TEXT("input.PreSkinnedNormal"));
if (_treeType != MaterialTreeType::VertexShader)
value = VsToPs(node, box).AsFloat3();
break;
// Depth
case 15:
@@ -211,38 +215,8 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
break;
// Interpolate VS To PS
case 20:
{
const auto input = node->GetBox(0);
// If used in VS then pass the value from the input box
if (_treeType == MaterialTreeType::VertexShader)
{
value = tryGetValue(input, Value::Zero).AsFloat4();
break;
}
// Check if can use more interpolants
if (_vsToPsInterpolants.Count() == 16)
{
OnError(node, box, TEXT("Too many VS to PS interpolants used."));
value = Value::Zero;
break;
}
// Check if can use interpolants
const auto layer = GetRootLayer();
if (!layer || layer->Domain == MaterialDomain::Decal || layer->Domain == MaterialDomain::PostProcess)
{
OnError(node, box, TEXT("VS to PS interpolants are not supported in Decal or Post Process materials."));
value = Value::Zero;
break;
}
// Indicate the interpolator slot usage
value = Value(VariantType::Float4, String::Format(TEXT("input.CustomVSToPS[{0}]"), _vsToPsInterpolants.Count()));
_vsToPsInterpolants.Add(input);
value = VsToPs(node, node->GetBox(0));
break;
}
// Terrain Holes Mask
case 21:
{

View File

@@ -832,4 +832,32 @@ void MaterialGenerator::WriteCustomGlobalCode(const Array<const MaterialGraph::N
}
}
ShaderGenerator::Value MaterialGenerator::VsToPs(Node* node, Box* input)
{
// If used in VS then pass the value from the input box
if (_treeType == MaterialTreeType::VertexShader)
{
return tryGetValue(input, Value::Zero).AsFloat4();
}
// Check if can use more interpolants
if (_vsToPsInterpolants.Count() == 16)
{
OnError(node, input, TEXT("Too many VS to PS interpolants used."));
return Value::Zero;
}
// Check if can use interpolants
const auto layer = GetRootLayer();
if (!layer || layer->Domain == MaterialDomain::Decal || layer->Domain == MaterialDomain::PostProcess)
{
OnError(node, input, TEXT("VS to PS interpolants are not supported in Decal or Post Process materials."));
return Value::Zero;
}
// Indicate the interpolator slot usage
_vsToPsInterpolants.Add(input);
return Value(VariantType::Float4, String::Format(TEXT("input.CustomVSToPS[{0}]"), _vsToPsInterpolants.Count() - 1));
}
#endif

View File

@@ -205,6 +205,7 @@ private:
MaterialValue AccessParticleAttribute(Node* caller, const StringView& name, ParticleAttributeValueTypes valueType, const Char* index = nullptr, ParticleAttributeSpace space = ParticleAttributeSpace::AsIs);
void prepareLayer(MaterialLayer* layer, bool allowVisibleParams);
void WriteCustomGlobalCode(const Array<const MaterialGraph::Node*, InlinedAllocation<8>>& nodes, int32 templateInputsMapping);
Value VsToPs(Node* node, Box* input);
public:

View File

@@ -8,7 +8,7 @@ namespace FlaxEngine.GUI
/// Button control
/// </summary>
[ActorToolbox("GUI")]
public class Button : ContainerControl
public class Button : Label
{
/// <summary>
/// The default height for the buttons.
@@ -20,65 +20,16 @@ namespace FlaxEngine.GUI
/// </summary>
protected bool _isPressed;
/// <summary>
/// The font.
/// </summary>
protected FontReference _font;
/// <summary>
/// The text.
/// </summary>
protected LocalizedString _text = new LocalizedString();
/// <summary>
/// Button text property.
/// </summary>
[EditorOrder(10), Tooltip("The button label text.")]
public LocalizedString Text
{
get => _text;
set => _text = value;
}
/// <summary>
/// Gets or sets the font used to draw button text.
/// </summary>
[EditorDisplay("Text Style"), EditorOrder(2022), ExpandGroups]
public FontReference Font
{
get => _font;
set => _font = value;
}
/// <summary>
/// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data.
/// [Deprecated on 18.09.2024, expires on 18.09.2026]
/// </summary>
[EditorDisplay("Text Style"), EditorOrder(2021), Tooltip("Custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data.")]
public MaterialBase TextMaterial { get; set; }
/// <summary>
/// Gets or sets the color used to draw button text.
/// </summary>
[EditorDisplay("Text Style"), EditorOrder(2020)]
public Color TextColor;
/// <summary>
/// Gets or sets the horizontal text alignment within the control bounds.
/// </summary>
[EditorDisplay("Text Style"), EditorOrder(2027)]
public TextAlignment HorizontalAlignment { get; set; } = TextAlignment.Center;
/// <summary>
/// Gets or sets the vertical text alignment within the control bounds.
/// </summary>
[EditorDisplay("Text Style"), EditorOrder(2028)]
public TextAlignment VerticalAlignment { get; set; } = TextAlignment.Center;
/// <summary>
/// Gets or sets the brush used for background drawing.
/// </summary>
[EditorDisplay("Background Style"), EditorOrder(1999), Tooltip("The brush used for background drawing."), ExpandGroups]
public IBrush BackgroundBrush { get; set; }
[Serialize, Obsolete("Use Material property instead."), NoUndo]
public MaterialBase TextMaterial
{
get => Material;
set => Material = value;
}
/// <summary>
/// Gets or sets the background color when button is highlighted.
@@ -97,7 +48,7 @@ namespace FlaxEngine.GUI
/// </summary>
[EditorDisplay("Border Style"), EditorOrder(2010), ExpandGroups]
public bool HasBorder { get; set; } = true;
/// <summary>
/// Gets or sets the border thickness.
/// </summary>
@@ -165,6 +116,7 @@ namespace FlaxEngine.GUI
public Button(float x, float y, float width = 120, float height = DefaultHeight)
: base(x, y, width, height)
{
AutoFocus = true;
var style = Style.Current;
if (style != null)
{
@@ -242,17 +194,14 @@ namespace FlaxEngine.GUI
/// <inheritdoc />
public override void DrawSelf()
{
// Cache data
Rectangle clientRect = new Rectangle(Float2.Zero, Size);
bool enabled = EnabledInHierarchy;
Color backgroundColor = BackgroundColor;
Color borderColor = BorderColor;
Color textColor = TextColor;
if (!enabled)
{
backgroundColor *= 0.5f;
borderColor *= 0.5f;
textColor *= 0.6f;
}
else if (_isPressed)
{
@@ -274,7 +223,10 @@ namespace FlaxEngine.GUI
Render2D.DrawRectangle(clientRect, borderColor, BorderThickness);
// Draw text
Render2D.DrawText(_font?.GetFont(), TextMaterial, _text, clientRect, textColor, HorizontalAlignment, VerticalAlignment);
backgroundColor = BackgroundColor;
BackgroundColor = Color.Transparent; // Skip background drawing in Control
base.DrawSelf();
BackgroundColor = backgroundColor;
}
/// <inheritdoc />
@@ -339,7 +291,7 @@ namespace FlaxEngine.GUI
OnClick();
return true;
}
if (button == MouseButton.Left && !_isPressed)
{
OnPressBegin();

View File

@@ -135,5 +135,14 @@ namespace FlaxEngine.GUI
return false;
}
/// <inheritdoc />
public override void OnSubmit()
{
base.OnSubmit();
// Execute default user interaction via mouse click
Clicked?.Invoke(this, MouseButton.Left);
}
}
}

View File

@@ -42,6 +42,7 @@ namespace FlaxEngine.GUI
private bool _autoFitText;
private Float2 _textSize;
private Float2 _autoFitTextRange = new Float2(0.1f, 100.0f);
private Margin _margin;
/// <summary>
/// The font.
@@ -57,9 +58,9 @@ namespace FlaxEngine.GUI
get => _text;
set
{
if (_text != value)
_text = value;
if (_autoWidth || _autoHeight || _autoFitText)
{
_text = value;
_textSize = Float2.Zero;
PerformLayout();
}
@@ -129,15 +130,11 @@ namespace FlaxEngine.GUI
get => _font;
set
{
if (_font != value)
_font = value;
if (_autoWidth || _autoHeight || _autoFitText)
{
_font = value;
if (_autoWidth || _autoHeight || _autoFitText)
{
_textSize = Float2.Zero;
PerformLayout();
}
_textSize = Float2.Zero;
PerformLayout();
}
}
}
@@ -152,7 +149,18 @@ namespace FlaxEngine.GUI
/// Gets or sets the margin for the text within the control bounds.
/// </summary>
[EditorOrder(70), Tooltip("The margin for the text within the control bounds.")]
public Margin Margin { get; set; }
public Margin Margin
{
get => _margin;
set
{
_margin = value;
if (_autoWidth || _autoHeight)
{
PerformLayout();
}
}
}
/// <summary>
/// Gets or sets a value indicating whether clip text during rendering.
@@ -161,9 +169,9 @@ namespace FlaxEngine.GUI
public bool ClipText { get; set; }
/// <summary>
/// Gets or sets a value indicating whether set automatic width based on text contents.
/// Gets or sets a value indicating whether set automatic width based on text contents. Control size is modified relative to the Pivot.
/// </summary>
[EditorOrder(85), DefaultValue(false), Tooltip("If checked, the control width will be based on text contents.")]
[EditorOrder(85), DefaultValue(false), Tooltip("If checked, the control width will be based on text contents. Control size is modified relative to the Pivot.")]
public bool AutoWidth
{
get => _autoWidth;
@@ -178,9 +186,9 @@ namespace FlaxEngine.GUI
}
/// <summary>
/// Gets or sets a value indicating whether set automatic height based on text contents.
/// Gets or sets a value indicating whether set automatic height based on text contents. Control size is modified relative to the Pivot.
/// </summary>
[EditorOrder(90), DefaultValue(false), Tooltip("If checked, the control height will be based on text contents.")]
[EditorOrder(90), DefaultValue(false), Tooltip("If checked, the control height will be based on text contents. Control size is modified relative to the Pivot.")]
public bool AutoHeight
{
get => _autoHeight;
@@ -226,13 +234,8 @@ namespace FlaxEngine.GUI
/// Initializes a new instance of the <see cref="Label"/> class.
/// </summary>
public Label()
: base(0, 0, 100, 20)
: this(0, 0, 100, 20)
{
AutoFocus = false;
var style = Style.Current;
Font = new FontReference(style.FontMedium);
TextColor = style.Foreground;
TextColorHighlighted = style.Foreground;
}
/// <inheritdoc />
@@ -241,9 +244,12 @@ namespace FlaxEngine.GUI
{
AutoFocus = false;
var style = Style.Current;
Font = new FontReference(style.FontMedium);
TextColor = style.Foreground;
TextColorHighlighted = style.Foreground;
if (style != null)
{
Font = new FontReference(style.FontMedium);
TextColor = style.Foreground;
TextColorHighlighted = style.Foreground;
}
}
/// <inheritdoc />
@@ -251,31 +257,24 @@ namespace FlaxEngine.GUI
{
base.DrawSelf();
var rect = new Rectangle(new Float2(Margin.Left, Margin.Top), Size - Margin.Size);
if (ClipText)
Render2D.PushClip(new Rectangle(Float2.Zero, Size));
var rect = new Rectangle(new Float2(Margin.Left, Margin.Top), Size - Margin.Size);
var color = IsMouseOver || IsNavFocused ? TextColorHighlighted : TextColor;
if (!EnabledInHierarchy)
color *= 0.6f;
var scale = 1.0f;
var hAlignment = HorizontalAlignment;
var wAlignment = VerticalAlignment;
if (_autoFitText)
if (_autoFitText && !_textSize.IsZero)
{
if (!_textSize.IsZero)
{
scale = (rect.Size / _textSize).MinValue;
scale = Mathf.Clamp(scale, _autoFitTextRange.X, _autoFitTextRange.Y);
}
scale = (rect.Size / _textSize).MinValue;
scale = Mathf.Clamp(scale, _autoFitTextRange.X, _autoFitTextRange.Y);
}
Font font = GetFont();
var text = ConvertedText();
Render2D.DrawText(font, Material, text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale);
if (ClipText)
@@ -336,7 +335,9 @@ namespace FlaxEngine.GUI
size.X = _textSize.X + Margin.Width;
if (_autoHeight)
size.Y = _textSize.Y + Margin.Height;
var pivotRelative = PivotRelative;
Size = size;
PivotRelative = pivotRelative;
}
}
}

View File

@@ -291,24 +291,7 @@ namespace FlaxEngine.GUI
{
float alpha = Mathf.Saturate(Mathf.Cos(_animateTime * CaretFlashSpeed) * 0.5f + 0.7f);
alpha = alpha * alpha * alpha * alpha * alpha * alpha;
if (CaretPosition == 0)
{
var bounds = CaretBounds;
if (_layout.VerticalAlignment == TextAlignment.Center)
bounds.Y = _layout.Bounds.Y + _layout.Bounds.Height * 0.5f - bounds.Height * 0.5f;
else if (_layout.VerticalAlignment == TextAlignment.Far)
bounds.Y = _layout.Bounds.Y + _layout.Bounds.Height - bounds.Height;
if (_layout.HorizontalAlignment == TextAlignment.Center)
bounds.X = _layout.Bounds.X + _layout.Bounds.Width * 0.5f - bounds.Width * 0.5f;
else if (_layout.HorizontalAlignment == TextAlignment.Far)
bounds.X = _layout.Bounds.X + _layout.Bounds.Width - bounds.Width;
Render2D.FillRectangle(bounds, CaretColor * alpha);
}
else
{
Render2D.FillRectangle(CaretBounds, CaretColor * alpha);
}
Render2D.FillRectangle(CaretBounds, CaretColor * alpha);
}

View File

@@ -234,6 +234,11 @@ namespace FlaxEngine.GUI
if (_bounds.Size.Equals(ref value))
return;
var bounds = new Rectangle(_bounds.Location, value);
if (_pivotRelativeSizing)
{
var delta = _bounds.Size - value;
bounds.Location += delta * Pivot;
}
SetBounds(ref bounds);
}
}
@@ -566,7 +571,7 @@ namespace FlaxEngine.GUI
}
/// <summary>
/// Sets the anchor preset for the control. Can be use to auto-place the control for a given preset or can preserve the current control bounds.
/// Sets the anchor preset for the control. Can be used to auto-place the control for a given preset or can preserve the current control bounds.
/// </summary>
/// <param name="anchorPreset">The anchor preset to set.</param>
/// <param name="preserveBounds">True if preserve current control bounds, otherwise will align control position accordingly to the anchor location.</param>

View File

@@ -81,6 +81,7 @@ namespace FlaxEngine.GUI
// Style
private Color _backgroundColor = Color.Transparent;
private IBrush _backgroundBrush = null;
// Tooltip
@@ -174,6 +175,25 @@ namespace FlaxEngine.GUI
set => _backgroundColor = value;
}
/// <summary>
/// Gets or sets control background brush used to fill the contents. Uses Background Color property as tint color.
/// </summary>
[EditorDisplay("Background Style"), EditorOrder(2001)]
public IBrush BackgroundBrush
{
get => _backgroundBrush;
set
{
_backgroundBrush = value;
#if FLAX_EDITOR
// Auto-reset background color so brush is visible as it uses it for tint
if (value != null && _backgroundColor == Color.Transparent && FlaxEditor.CustomEditors.CustomEditor.IsSettingValue)
_backgroundColor = Color.White;
#endif
}
}
/// <summary>
/// Gets or sets the anchor preset used by the control anchors (based on <see cref="AnchorMin"/> and <see cref="AnchorMax"/>).
/// </summary>
@@ -416,9 +436,14 @@ namespace FlaxEngine.GUI
public virtual void Draw()
{
// Paint Background
if (_backgroundColor.A > 0.0f)
var rect = new Rectangle(Float2.Zero, _bounds.Size);
if (BackgroundBrush != null)
{
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), _backgroundColor);
BackgroundBrush.Draw(rect, _backgroundColor);
}
else if (_backgroundColor.A > 0.0f)
{
Render2D.FillRectangle(rect, _backgroundColor);
}
}
@@ -614,6 +639,18 @@ namespace FlaxEngine.GUI
case NavDirection.Down: return NavTargetDown;
case NavDirection.Left: return NavTargetLeft;
case NavDirection.Right: return NavTargetRight;
case NavDirection.Next:
if (NavTargetRight != null)
return NavTargetRight;
if (NavTargetDown != null)
return NavTargetDown;
return null;
case NavDirection.Previous:
if (NavTargetLeft != null)
return NavTargetLeft;
if (NavTargetUp != null)
return NavTargetUp;
return null;
default: return null;
}
}

View File

@@ -22,6 +22,8 @@ namespace FlaxEngine.GUI
base.PerformLayoutBeforeChildren();
// Pre-set height of all controls
if (!ControlChildSize)
return;
float h = Height - _margin.Height;
for (int i = 0; i < _children.Count; i++)
{
@@ -40,6 +42,7 @@ namespace FlaxEngine.GUI
float left = _margin.Left;
float right = _margin.Right;
float h = Height - _margin.Height;
float maxHeight = h;
bool hasAnyLeft = false, hasAnyRight = false;
for (int i = 0; i < _children.Count; i++)
{
@@ -47,18 +50,20 @@ namespace FlaxEngine.GUI
if (c.Visible)
{
var w = c.Width;
var ch = ControlChildSize ? h : c.Height;
if (Mathf.IsZero(c.AnchorMin.X) && Mathf.IsZero(c.AnchorMax.X))
{
c.Bounds = new Rectangle(left + _offset.X, _margin.Top + _offset.Y, w, h);
c.Bounds = new Rectangle(left + _offset.X, _margin.Top + _offset.Y, w, ch);
left = c.Right + _spacing;
hasAnyLeft = true;
}
else if (Mathf.IsOne(c.AnchorMin.X) && Mathf.IsOne(c.AnchorMax.X))
{
right += w + _spacing;
c.Bounds = new Rectangle(Width - right + _offset.X, _margin.Top + _offset.Y, w, h);
c.Bounds = new Rectangle(Width - right + _offset.X, _margin.Top + _offset.Y, w, ch);
hasAnyRight = true;
}
maxHeight = Mathf.Max(maxHeight, ch);
}
}
if (hasAnyLeft)
@@ -68,7 +73,13 @@ namespace FlaxEngine.GUI
// Update size
if (_autoSize)
Width = left + right;
{
var size = Size;
size.X = left + right;
if (!ControlChildSize)
size.Y = maxHeight;
Size = size;
}
}
}
}

View File

@@ -1,5 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System.ComponentModel;
namespace FlaxEngine.GUI
{
/// <summary>
@@ -133,7 +135,7 @@ namespace FlaxEngine.GUI
/// <summary>
/// Gets or sets the value indicating whenever the panel size will be based on a children dimensions.
/// </summary>
[EditorOrder(30), Tooltip("If checked, the panel size will be based on a children dimensions.")]
[EditorOrder(30), DefaultValue(true), Tooltip("If checked, the panel size will be based on a children dimensions.")]
public bool AutoSize
{
get => _autoSize;
@@ -147,6 +149,12 @@ namespace FlaxEngine.GUI
}
}
/// <summary>
/// Gets or sets the value indicating whenever the panel can resize children controls (eg. auto-fit width/height).
/// </summary>
[EditorOrder(35), DefaultValue(true), Tooltip("If checked, the panel can resize children controls (eg. auto-fit width/height).")]
public bool ControlChildSize { get; set; } = true;
/// <summary>
/// Gets or sets the panel area margin.
/// </summary>

View File

@@ -22,6 +22,8 @@ namespace FlaxEngine.GUI
base.PerformLayoutBeforeChildren();
// Pre-set width of all controls
if (!ControlChildSize)
return;
float w = Width - _margin.Width;
for (int i = 0; i < _children.Count; i++)
{
@@ -40,6 +42,7 @@ namespace FlaxEngine.GUI
float top = _margin.Top;
float bottom = _margin.Bottom;
float w = Width - _margin.Width;
float maxWidth = w;
bool hasAnyTop = false, hasAnyBottom = false;
for (int i = 0; i < _children.Count; i++)
{
@@ -47,18 +50,20 @@ namespace FlaxEngine.GUI
if (c.Visible)
{
var h = c.Height;
var cw = ControlChildSize ? w : c.Width;
if (Mathf.IsZero(c.AnchorMin.Y) && Mathf.IsZero(c.AnchorMax.Y))
{
c.Bounds = new Rectangle(_margin.Left + _offset.X, top + _offset.Y, w, h);
c.Bounds = new Rectangle(_margin.Left + _offset.X, top + _offset.Y, cw, h);
top = c.Bottom + _spacing;
hasAnyTop = true;
}
else if (Mathf.IsOne(c.AnchorMin.Y) && Mathf.IsOne(c.AnchorMax.Y))
{
bottom += h + _spacing;
c.Bounds = new Rectangle(_margin.Left + _offset.X, Height - bottom + _offset.Y, w, h);
c.Bounds = new Rectangle(_margin.Left + _offset.X, Height - bottom + _offset.Y, cw, h);
hasAnyBottom = true;
}
maxWidth = Mathf.Max(maxWidth, cw);
}
}
if (hasAnyTop)
@@ -68,7 +73,13 @@ namespace FlaxEngine.GUI
// Update size
if (_autoSize)
Height = top + bottom;
{
var size = Size;
size.Y = top + bottom;
if (!ControlChildSize)
size.X = maxWidth;
Size = size;
}
}
}
}

View File

@@ -43,13 +43,9 @@ float4 PointInParallelogram(float2 p, float2 a, float4 bc)
void PerformClipping(float2 clipOrigin, float2 windowPos, float4 clipExtents)
{
#if CLIPPING_ENABLE
// Clip pixels which are outside of the clipping rect
// Clip pixels which are outside the clipping rect
float4 clipTest = PointInParallelogram(windowPos, clipOrigin, clipExtents);
// Clip pixels which are outside of the clipping rect
clip(clipTest);
#endif
}

View File

@@ -1478,8 +1478,8 @@ namespace Flax.Build.Bindings
public static class NativeToManaged
{
public static {{classInfo.Name}} ConvertToManaged(IntPtr unmanaged) => Unsafe.As<{{classInfo.Name}}>(ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(unmanaged));
public static IntPtr ConvertToUnmanaged({{classInfo.Name}} managed) => ManagedHandleMarshaller.ManagedToNative.ConvertToUnmanaged(managed);
public static void Free(IntPtr unmanaged) => ManagedHandleMarshaller.NativeToManaged.Free(unmanaged);
public static IntPtr ConvertToUnmanaged({{classInfo.Name}} managed) => managed != null ? ManagedHandle.ToIntPtr(managed, GCHandleType.Weak) : IntPtr.Zero;
public static void Free(IntPtr unmanaged) {}
}
#if FLAX_EDITOR
[HideInEditor]
@@ -1487,8 +1487,8 @@ namespace Flax.Build.Bindings
public static class ManagedToNative
{
public static {{classInfo.Name}} ConvertToManaged(IntPtr unmanaged) => Unsafe.As<{{classInfo.Name}}>(ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(unmanaged));
public static IntPtr ConvertToUnmanaged({{classInfo.Name}} managed) => ManagedHandleMarshaller.ManagedToNative.ConvertToUnmanaged(managed);
public static void Free(IntPtr unmanaged) => ManagedHandleMarshaller.ManagedToNative.Free(unmanaged);
public static IntPtr ConvertToUnmanaged({{classInfo.Name}} managed) => managed != null ? ManagedHandle.ToIntPtr(managed, GCHandleType.Weak) : IntPtr.Zero;
public static void Free(IntPtr unmanaged) {}
}
#if FLAX_EDITOR
[HideInEditor]

View File

@@ -242,6 +242,11 @@ namespace Flax.Build.Bindings
}
// Forward type
if (token.Value == "enum")
{
context.Tokenizer.SkipUntil(TokenType.Identifier);
token = context.Tokenizer.CurrentToken;
}
if (token.Value == "class")
{
context.Tokenizer.SkipUntil(TokenType.Identifier);