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

This commit is contained in:
Wojtek Figat
2024-03-11 23:49:33 +01:00
119 changed files with 1922 additions and 1150 deletions

View File

@@ -161,7 +161,7 @@ namespace FlaxEditor.Content.GUI
{
var style = Style.Current;
var rect = new Rectangle(Float2.Zero, Size);
var color = IsDragOver ? style.BackgroundSelected * 0.6f : (_mouseDown ? style.BackgroundSelected : (IsMouseOver ? style.BackgroundHighlighted : Color.Transparent));
var color = IsDragOver ? Color.Transparent : (_mouseDown ? style.BackgroundSelected : (IsMouseOver ? style.BackgroundHighlighted : Color.Transparent));
Render2D.FillRectangle(rect, color);
Render2D.DrawSprite(Editor.Instance.Icons.ArrowRight12, new Rectangle(rect.Location.X, rect.Y + rect.Size.Y * 0.25f, rect.Size.X, rect.Size.X), EnabledInHierarchy ? style.Foreground : style.ForegroundDisabled);
}

View File

@@ -1,123 +0,0 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using FlaxEditor.GUI.Drag;
using FlaxEditor.SceneGraph;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Content.GUI
{
public partial class ContentView
{
private bool _validDragOver;
private DragActors _dragActors;
/// <inheritdoc />
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
var result = base.OnDragEnter(ref location, data);
if (result != DragDropEffect.None)
return result;
// Check if drop file(s)
if (data is DragDataFiles)
{
_validDragOver = true;
return DragDropEffect.Copy;
}
// Check if drop actor(s)
if (_dragActors == null)
_dragActors = new DragActors(ValidateDragActors);
if (_dragActors.OnDragEnter(data))
{
_validDragOver = true;
return DragDropEffect.Move;
}
return DragDropEffect.None;
}
private bool ValidateDragActors(ActorNode actor)
{
return actor.CanCreatePrefab && Editor.Instance.Windows.ContentWin.CurrentViewFolder.CanHaveAssets;
}
private void ImportActors(DragActors actors, ContentFolder location)
{
foreach (var actorNode in actors.Objects)
{
var actor = actorNode.Actor;
if (actors.Objects.Contains(actorNode.ParentNode as ActorNode))
continue;
Editor.Instance.Prefabs.CreatePrefab(actor, false);
}
}
/// <inheritdoc />
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
_validDragOver = false;
var result = base.OnDragMove(ref location, data);
if (result != DragDropEffect.None)
return result;
if (data is DragDataFiles)
{
_validDragOver = true;
result = DragDropEffect.Copy;
}
else if (_dragActors != null && _dragActors.HasValidDrag)
{
_validDragOver = true;
result = DragDropEffect.Move;
}
return result;
}
/// <inheritdoc />
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
{
var result = base.OnDragDrop(ref location, data);
if (result != DragDropEffect.None)
return result;
// Check if drop file(s)
if (data is DragDataFiles files)
{
// Import files
var currentFolder = Editor.Instance.Windows.ContentWin.CurrentViewFolder;
if (currentFolder != null)
Editor.Instance.ContentImporting.Import(files.Files, currentFolder);
result = DragDropEffect.Copy;
}
// Check if drop actor(s)
else if (_dragActors != null && _dragActors.HasValidDrag)
{
// Import actors
var currentFolder = Editor.Instance.Windows.ContentWin.CurrentViewFolder;
if (currentFolder != null)
ImportActors(_dragActors, currentFolder);
_dragActors.OnDragDrop();
result = DragDropEffect.Move;
}
// Clear cache
_validDragOver = false;
return result;
}
/// <inheritdoc />
public override void OnDragLeave()
{
_validDragOver = false;
_dragActors?.OnDragLeave();
base.OnDragLeave();
}
}
}

View File

@@ -3,7 +3,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.GUI.Drag;
using FlaxEditor.Options;
using FlaxEditor.SceneGraph;
using FlaxEditor.Windows;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -52,14 +54,17 @@ namespace FlaxEditor.Content.GUI
public partial class ContentView : ContainerControl, IContentItemOwner
{
private readonly List<ContentItem> _items = new List<ContentItem>(256);
private readonly List<ContentItem> _selection = new List<ContentItem>(16);
private readonly List<ContentItem> _selection = new List<ContentItem>();
private float _viewScale = 1.0f;
private ContentViewType _viewType = ContentViewType.Tiles;
private bool _isRubberBandSpanning = false;
private Float2 _mousePresslocation;
private bool _isRubberBandSpanning;
private Float2 _mousePressLocation;
private Rectangle _rubberBandRectangle;
private bool _validDragOver;
private DragActors _dragActors;
#region External Events
/// <summary>
@@ -193,6 +198,7 @@ namespace FlaxEditor.Content.GUI
OnDelete?.Invoke(_selection);
}),
new InputActionsContainer.Binding(options => options.SelectAll, SelectAll),
new InputActionsContainer.Binding(options => options.DeselectAll, DeselectAll),
new InputActionsContainer.Binding(options => options.Rename, () =>
{
if (HasSelection && _selection[0].CanRename)
@@ -397,10 +403,7 @@ namespace FlaxEditor.Content.GUI
PerformLayout();
}
/// <summary>
/// Selects all the items.
/// </summary>
public void SelectAll()
private void BulkSelectUpdate(bool select = true)
{
// Lock layout
var wasLayoutLocked = IsLayoutLocked;
@@ -408,13 +411,30 @@ namespace FlaxEditor.Content.GUI
// Select items
_selection.Clear();
_selection.AddRange(_items);
if (select)
_selection.AddRange(_items);
// Unload and perform UI layout
IsLayoutLocked = wasLayoutLocked;
PerformLayout();
}
/// <summary>
/// Selects all the items.
/// </summary>
public void SelectAll()
{
BulkSelectUpdate(true);
}
/// <summary>
/// Deselects all the items.
/// </summary>
public void DeselectAll()
{
BulkSelectUpdate(false);
}
/// <summary>
/// Deselects the specified item.
/// </summary>
@@ -595,7 +615,9 @@ namespace FlaxEditor.Content.GUI
// Check if drag is over
if (IsDragOver && _validDragOver)
{
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundSelected * 0.4f);
var bounds = new Rectangle(Float2.One, Size - Float2.One * 2);
Render2D.FillRectangle(bounds, style.Selection);
Render2D.DrawRectangle(bounds, style.SelectionBorder);
}
// Check if it's an empty thing
@@ -604,10 +626,11 @@ namespace FlaxEditor.Content.GUI
Render2D.DrawText(style.FontSmall, IsSearching ? "No results" : "Empty", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
}
// Selection
if (_isRubberBandSpanning)
{
Render2D.FillRectangle(_rubberBandRectangle, Color.Orange * 0.4f);
Render2D.DrawRectangle(_rubberBandRectangle, Color.Orange);
Render2D.FillRectangle(_rubberBandRectangle, style.Selection);
Render2D.DrawRectangle(_rubberBandRectangle, style.SelectionBorder);
}
}
@@ -619,8 +642,8 @@ namespace FlaxEditor.Content.GUI
if (button == MouseButton.Left)
{
_mousePresslocation = location;
_rubberBandRectangle = new Rectangle(_mousePresslocation, 0, 0);
_mousePressLocation = location;
_rubberBandRectangle = new Rectangle(_mousePressLocation, 0, 0);
_isRubberBandSpanning = true;
StartMouseCapture();
}
@@ -632,8 +655,8 @@ namespace FlaxEditor.Content.GUI
{
if (_isRubberBandSpanning)
{
_rubberBandRectangle.Width = location.X - _mousePresslocation.X;
_rubberBandRectangle.Height = location.Y - _mousePresslocation.Y;
_rubberBandRectangle.Width = location.X - _mousePressLocation.X;
_rubberBandRectangle.Height = location.Y - _mousePressLocation.Y;
}
base.OnMouseMove(location);
@@ -768,6 +791,114 @@ namespace FlaxEditor.Content.GUI
return false;
}
/// <inheritdoc />
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
var result = base.OnDragEnter(ref location, data);
if (result != DragDropEffect.None)
return result;
// Check if drop file(s)
if (data is DragDataFiles)
{
_validDragOver = true;
return DragDropEffect.Copy;
}
// Check if drop actor(s)
if (_dragActors == null)
_dragActors = new DragActors(ValidateDragActors);
if (_dragActors.OnDragEnter(data))
{
_validDragOver = true;
return DragDropEffect.Move;
}
return DragDropEffect.None;
}
private bool ValidateDragActors(ActorNode actor)
{
return actor.CanCreatePrefab && Editor.Instance.Windows.ContentWin.CurrentViewFolder.CanHaveAssets;
}
private void ImportActors(DragActors actors, ContentFolder location)
{
foreach (var actorNode in actors.Objects)
{
var actor = actorNode.Actor;
if (actors.Objects.Contains(actorNode.ParentNode as ActorNode))
continue;
Editor.Instance.Prefabs.CreatePrefab(actor, false);
}
}
/// <inheritdoc />
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
_validDragOver = false;
var result = base.OnDragMove(ref location, data);
if (result != DragDropEffect.None)
return result;
if (data is DragDataFiles)
{
_validDragOver = true;
result = DragDropEffect.Copy;
}
else if (_dragActors != null && _dragActors.HasValidDrag)
{
_validDragOver = true;
result = DragDropEffect.Move;
}
return result;
}
/// <inheritdoc />
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
{
var result = base.OnDragDrop(ref location, data);
if (result != DragDropEffect.None)
return result;
// Check if drop file(s)
if (data is DragDataFiles files)
{
// Import files
var currentFolder = Editor.Instance.Windows.ContentWin.CurrentViewFolder;
if (currentFolder != null)
Editor.Instance.ContentImporting.Import(files.Files, currentFolder);
result = DragDropEffect.Copy;
}
// Check if drop actor(s)
else if (_dragActors != null && _dragActors.HasValidDrag)
{
// Import actors
var currentFolder = Editor.Instance.Windows.ContentWin.CurrentViewFolder;
if (currentFolder != null)
ImportActors(_dragActors, currentFolder);
_dragActors.OnDragDrop();
result = DragDropEffect.Move;
}
// Clear cache
_validDragOver = false;
return result;
}
/// <inheritdoc />
public override void OnDragLeave()
{
_validDragOver = false;
_dragActors?.OnDragLeave();
base.OnDragLeave();
}
/// <inheritdoc />
protected override void PerformLayoutBeforeChildren()
{
@@ -833,6 +964,9 @@ namespace FlaxEditor.Content.GUI
/// <inheritdoc />
public override void OnDestroy()
{
if (IsDisposing)
return;
// Ensure to unlink all items
ClearItems();

View File

@@ -241,7 +241,12 @@ namespace FlaxEditor.Content
// Check if drag is over
if (IsDragOver && _validDragOver)
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), Style.Current.BackgroundSelected * 0.6f);
{
var style = Style.Current;
var bounds = new Rectangle(Float2.Zero, Size);
Render2D.FillRectangle(bounds, style.Selection);
Render2D.DrawRectangle(bounds, style.SelectionBorder);
}
}
private bool ValidateDragItem(ContentItem item)

View File

@@ -75,6 +75,8 @@ namespace FlaxEditor.Content
/// <inheritdoc />
public override void Create(string outputPath, object arg)
{
bool resetTransform = false;
var transform = Transform.Identity;
if (!(arg is Actor actor))
{
// Create default prefab root object
@@ -86,8 +88,17 @@ namespace FlaxEditor.Content
// Cleanup it after usage
Object.Destroy(actor, 20.0f);
}
else if (actor.Scene != null)
{
// Create prefab with identity transform so the actor instance on a level will have it customized
resetTransform = true;
transform = actor.LocalTransform;
actor.LocalTransform = Transform.Identity;
}
PrefabManager.CreatePrefab(actor, outputPath, true);
if (resetTransform)
actor.LocalTransform = transform;
}
/// <inheritdoc />

View File

@@ -747,7 +747,7 @@ namespace FlaxEditor.CustomEditors
/// </summary>
public void SetValueToDefault()
{
SetValueCloned(Values.DefaultValue);
SetValue(Utilities.Utils.CloneValue(Values.DefaultValue));
}
/// <summary>
@@ -784,19 +784,7 @@ namespace FlaxEditor.CustomEditors
return;
}
SetValueCloned(Values.ReferenceValue);
}
private void SetValueCloned(object value)
{
// For objects (eg. arrays) we need to clone them to prevent editing default/reference value within editor
if (value != null && !value.GetType().IsValueType)
{
var json = JsonSerializer.Serialize(value);
value = JsonSerializer.Deserialize(json, value.GetType());
}
SetValue(value);
SetValue(Utilities.Utils.CloneValue(Values.ReferenceValue));
}
/// <summary>

View File

@@ -245,9 +245,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
// Special case for new Script added to actor
if (editor.Values[0] is Script script && !script.HasPrefabLink)
{
return CreateDiffNode(editor);
}
// Skip if no change detected
var isRefEdited = editor.Values.IsReferenceValueModified;
@@ -258,9 +256,16 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (editor.ChildrenEditors.Count == 0 || (isRefEdited && editor is CollectionEditor))
result = CreateDiffNode(editor);
bool isScriptEditorWithRefValue = editor is ScriptsEditor && editor.Values.HasReferenceValue;
bool isActorEditorInLevel = editor is ActorEditor && editor.Values[0] is Actor actor && actor.IsPrefabRoot && actor.Scene != null;
for (int i = 0; i < editor.ChildrenEditors.Count; i++)
{
var child = ProcessDiff(editor.ChildrenEditors[i], !isScriptEditorWithRefValue);
var childEditor = editor.ChildrenEditors[i];
// Special case for root actor transformation (can be applied only in Prefab editor, not in Level)
if (isActorEditorInLevel && childEditor.Values.Info.Name is "LocalPosition" or "LocalOrientation" or "LocalScale")
continue;
var child = ProcessDiff(childEditor, !isScriptEditorWithRefValue);
if (child != null)
{
if (result == null)
@@ -276,7 +281,6 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
var prefabObjectScript = prefabObjectScripts[j];
bool isRemoved = true;
for (int i = 0; i < editor.ChildrenEditors.Count; i++)
{
if (editor.ChildrenEditors[i].Values is ScriptsEditor.ScriptsContainer container && container.PrefabObjectId == prefabObjectScript.PrefabObjectID)
@@ -286,14 +290,12 @@ namespace FlaxEditor.CustomEditors.Dedicated
break;
}
}
if (isRemoved)
{
var dummy = new RemovedScriptDummy
{
PrefabObject = prefabObjectScript
};
var child = CreateDiffNode(dummy);
if (result == null)
result = CreateDiffNode(editor);

View File

@@ -67,25 +67,6 @@ public class MissingScriptEditor : GenericEditor
base.Initialize(layout);
}
private void FindActorsWithMatchingMissingScript(List<MissingScript> missingScripts)
{
foreach (Actor actor in Level.GetActors(typeof(Actor)))
{
for (int scriptIndex = 0; scriptIndex < actor.ScriptsCount; scriptIndex++)
{
Script actorScript = actor.Scripts[scriptIndex];
if (actorScript is not MissingScript missingActorScript)
continue;
MissingScript currentMissing = Values[0] as MissingScript;
if (missingActorScript.MissingTypeName != currentMissing.MissingTypeName)
continue;
missingScripts.Add(missingActorScript);
}
}
}
private void RunReplacementMultiCast(List<IUndoAction> actions)
{
if (actions.Count == 0)
@@ -104,16 +85,54 @@ public class MissingScriptEditor : GenericEditor
}
}
private void ReplaceScript(ScriptType script, bool replaceAllInScene)
private void GetMissingScripts(Actor actor, string currentMissingTypeName, List<MissingScript> missingScripts)
{
var actions = new List<IUndoAction>(4);
// Iterate over the scripts of this actor
for (int i = 0; i < actor.ScriptsCount; i++)
{
if (actor.GetScript(i) is MissingScript otherMissing &&
otherMissing.MissingTypeName == currentMissingTypeName)
{
missingScripts.Add(otherMissing);
}
}
// Iterate over this actor children (recursive)
for (int i = 0; i < actor.ChildrenCount; i++)
{
GetMissingScripts(actor.GetChild(i), currentMissingTypeName, missingScripts);
}
}
private void ReplaceScript(ScriptType script)
{
var missingScripts = new List<MissingScript>();
if (!replaceAllInScene)
missingScripts.Add((MissingScript)Values[0]);
var currentMissing = (MissingScript)Values[0];
var currentMissingTypeName = currentMissing.MissingTypeName;
if (_shouldReplaceAllCheckbox.Checked)
{
if (currentMissing.Scene == null && currentMissing.Actor != null)
{
// Find all missing scripts in prefab instance
GetMissingScripts(currentMissing.Actor.GetPrefabRoot(), currentMissingTypeName, missingScripts);
}
else
{
// Find all missing scripts in all loaded levels that match this type
for (int i = 0; i < Level.ScenesCount; i++)
{
GetMissingScripts(Level.GetScene(i), currentMissingTypeName, missingScripts);
}
}
}
else
FindActorsWithMatchingMissingScript(missingScripts);
{
// Use the current instance only
foreach (var value in Values)
missingScripts.Add((MissingScript)value);
}
var actions = new List<IUndoAction>(4);
foreach (var missingScript in missingScripts)
actions.Add(AddRemoveScript.Add(missingScript.Actor, script));
RunReplacementMultiCast(actions);
@@ -149,7 +168,7 @@ public class MissingScriptEditor : GenericEditor
var cm = new ItemsListContextMenu(180);
for (int i = 0; i < scripts.Count; i++)
cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i]));
cm.ItemClicked += item => ReplaceScript((ScriptType)item.Tag, _shouldReplaceAllCheckbox.Checked);
cm.ItemClicked += item => ReplaceScript((ScriptType)item.Tag);
cm.SortItems();
cm.Show(_dropPanel, _replaceScriptButton.BottomLeft - new Float2((cm.Width - _replaceScriptButton.Width) / 2, 0));
}

View File

@@ -160,7 +160,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
var actions = new List<IUndoAction>();
foreach (var body in bodies)
{
var action = new Actions.DeleteActorsAction(new List<SceneGraphNode> { SceneGraphFactory.FindNode(body.ID) });
var action = new Actions.DeleteActorsAction(body);
action.Do();
actions.Add(action);
}
@@ -185,7 +185,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
var body = bodies.FirstOrDefault(x => x.Name == name);
if (body != null)
{
var action = new Actions.DeleteActorsAction(new List<SceneGraphNode> { SceneGraphFactory.FindNode(body.ID) });
var action = new Actions.DeleteActorsAction(body);
action.Do();
Presenter.Undo?.AddAction(action);
}
@@ -224,7 +224,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
else
{
// Remove joint that will no longer be valid
var action = new Actions.DeleteActorsAction(new List<SceneGraphNode> { SceneGraphFactory.FindNode(joint.ID) });
var action = new Actions.DeleteActorsAction(joint);
action.Do();
Presenter.Undo?.AddAction(action);
}
@@ -233,7 +233,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
// Remove body
{
var action = new Actions.DeleteActorsAction(new List<SceneGraphNode> { SceneGraphFactory.FindNode(body.ID) });
var action = new Actions.DeleteActorsAction(body);
action.Do();
Presenter.Undo?.AddAction(action);
}

View File

@@ -151,8 +151,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (IsDragOver && _dragHandlers != null && _dragHandlers.HasValidDrag)
{
var area = new Rectangle(Float2.Zero, size);
Render2D.FillRectangle(area, Color.Orange * 0.5f);
Render2D.DrawRectangle(area, Color.Black);
Render2D.FillRectangle(area, style.Selection);
Render2D.DrawRectangle(area, style.SelectionBorder);
}
base.Draw();
@@ -520,7 +520,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
base.Draw();
var color = FlaxEngine.GUI.Style.Current.BackgroundSelected * (IsDragOver ? 0.9f : 0.1f);
var color = Style.Current.BackgroundSelected * (IsDragOver ? 0.9f : 0.1f);
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), color);
}

View File

@@ -589,25 +589,27 @@ namespace FlaxEditor.CustomEditors.Dedicated
LayoutElementsContainer yEl;
LayoutElementsContainer hEl;
LayoutElementsContainer vEl;
Color axisColorX = ActorTransformEditor.AxisColorX;
Color axisColorY = ActorTransformEditor.AxisColorY;
if (xEq)
{
xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values));
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values));
xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values), axisColorX);
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values), axisColorX);
}
else
{
xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values));
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values));
xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values), axisColorX);
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values), axisColorX);
}
if (yEq)
{
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values));
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values));
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values), axisColorY);
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values), axisColorY);
}
else
{
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values));
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values));
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values), axisColorY);
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values), axisColorY);
}
xEl.Control.AnchorMin = new Float2(0, xEl.Control.AnchorMin.Y);
xEl.Control.AnchorMax = new Float2(0.5f, xEl.Control.AnchorMax.Y);
@@ -624,28 +626,34 @@ namespace FlaxEditor.CustomEditors.Dedicated
private VerticalPanelElement VerticalPanelWithoutMargin(LayoutElementsContainer cont)
{
var horUp = cont.VerticalPanel();
horUp.Panel.Margin = Margin.Zero;
return horUp;
var panel = cont.VerticalPanel();
panel.Panel.Margin = Margin.Zero;
return panel;
}
private CustomElementsContainer<UniformGridPanel> UniformGridTwoByOne(LayoutElementsContainer cont)
{
var horUp = cont.CustomContainer<UniformGridPanel>();
horUp.CustomControl.SlotsHorizontally = 2;
horUp.CustomControl.SlotsVertically = 1;
horUp.CustomControl.SlotPadding = Margin.Zero;
horUp.CustomControl.ClipChildren = false;
return horUp;
var grid = cont.CustomContainer<UniformGridPanel>();
grid.CustomControl.SlotsHorizontally = 2;
grid.CustomControl.SlotsVertically = 1;
grid.CustomControl.SlotPadding = Margin.Zero;
grid.CustomControl.ClipChildren = false;
return grid;
}
private CustomElementsContainer<UniformGridPanel> UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values)
private CustomElementsContainer<UniformGridPanel> UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values, Color borderColor)
{
CustomElementsContainer<UniformGridPanel> hor = UniformGridTwoByOne(el);
hor.CustomControl.SlotPadding = new Margin(5, 5, 0, 0);
LabelElement lab = hor.Label(text);
hor.Object(values);
return hor;
var grid = UniformGridTwoByOne(el);
grid.CustomControl.SlotPadding = new Margin(5, 5, 1, 1);
var label = grid.Label(text);
var editor = grid.Object(values);
if (editor is FloatEditor floatEditor && floatEditor.Element is FloatValueElement floatEditorElement)
{
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
floatEditorElement.ValueBox.BorderColor = Color.Lerp(borderColor, back, ActorTransformEditor.AxisGreyOutFactor);
floatEditorElement.ValueBox.BorderSelectedColor = borderColor;
}
return grid;
}
private bool _cachedXEq;

View File

@@ -26,6 +26,11 @@ namespace FlaxEditor.CustomEditors.Editors
/// </summary>
public static Color AxisColorZ = new Color(0.0f, 0.0235294f, 1.0f, 1.0f);
/// <summary>
/// The axes colors grey out scale when input field is not focused.
/// </summary>
public static float AxisGreyOutFactor = 0.6f;
/// <summary>
/// Custom editor for actor position property.
/// </summary>
@@ -39,12 +44,11 @@ namespace FlaxEditor.CustomEditors.Editors
// Override colors
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
var grayOutFactor = 0.6f;
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor);
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
XElement.ValueBox.BorderSelectedColor = AxisColorX;
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor);
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
YElement.ValueBox.BorderSelectedColor = AxisColorY;
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor);
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
}
}
@@ -62,12 +66,11 @@ namespace FlaxEditor.CustomEditors.Editors
// Override colors
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
var grayOutFactor = 0.6f;
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor);
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
XElement.ValueBox.BorderSelectedColor = AxisColorX;
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor);
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
YElement.ValueBox.BorderSelectedColor = AxisColorY;
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor);
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
}
}

View File

@@ -2,7 +2,6 @@
using System;
using System.Collections;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.Utilities;
@@ -47,8 +46,9 @@ namespace FlaxEditor.CustomEditors.Editors
if (elementType.IsValueType || NotNullItems)
{
// Fill new entries with the last value
var lastValue = array.GetValue(oldSize - 1);
for (int i = oldSize; i < newSize; i++)
Array.Copy(array, oldSize - 1, newValues, i, 1);
newValues.SetValue(Utilities.Utils.CloneValue(lastValue), i);
}
else
{

View File

@@ -542,9 +542,10 @@ namespace FlaxEditor.CustomEditors.Editors
{
if (_dragHandlers is { HasValidDrag: true })
{
var style = FlaxEngine.GUI.Style.Current;
var area = new Rectangle(Float2.Zero, Size);
Render2D.FillRectangle(area, Color.Orange * 0.5f);
Render2D.DrawRectangle(area, Color.Black);
Render2D.FillRectangle(area, style.Selection);
Render2D.DrawRectangle(area, style.SelectionBorder);
}
base.Draw();

View File

@@ -220,7 +220,11 @@ namespace FlaxEditor.CustomEditors.Editors
// Check if drag is over
if (IsDragOver && _hasValidDragOver)
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundSelected * 0.4f);
{
var bounds = new Rectangle(Float2.Zero, Size);
Render2D.FillRectangle(bounds, style.Selection);
Render2D.DrawRectangle(bounds, style.SelectionBorder);
}
}
/// <inheritdoc />

View File

@@ -175,7 +175,11 @@ namespace FlaxEditor.CustomEditors.Editors
// Check if drag is over
if (IsDragOver && _hasValidDragOver)
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundSelected * 0.4f);
{
var bounds = new Rectangle(Float2.Zero, Size);
Render2D.FillRectangle(bounds, style.Selection);
Render2D.DrawRectangle(bounds, style.SelectionBorder);
}
}
/// <inheritdoc />

View File

@@ -1676,6 +1676,9 @@ namespace FlaxEditor
[return: MarshalAs(UnmanagedType.U1)]
internal static partial bool Internal_CanSetToRoot(IntPtr prefab, IntPtr newRoot);
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetPrefabNestedObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
internal static partial void Internal_GetPrefabNestedObject(IntPtr prefabId, IntPtr prefabObjectId, IntPtr outPrefabId, IntPtr outPrefabObjectId);
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetAnimationTime", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
internal static partial float Internal_GetAnimationTime(IntPtr animatedModel);

View File

@@ -182,7 +182,11 @@ namespace FlaxEditor.GUI
// Check if drag is over
if (IsDragOver && _dragOverElement != null && _dragOverElement.HasValidDrag)
Render2D.FillRectangle(iconRect, style.BackgroundSelected * 0.4f);
{
var bounds = new Rectangle(Float2.Zero, Size);
Render2D.FillRectangle(bounds, style.Selection);
Render2D.DrawRectangle(bounds, style.SelectionBorder);
}
}
/// <inheritdoc />

View File

@@ -359,6 +359,17 @@ namespace FlaxEditor.GUI.ContextMenu
ButtonClicked?.Invoke(button);
}
/// <inheritdoc />
public override void Show(Control parent, Float2 location)
{
// Remove last separator to make context menu look better
int lastIndex = _panel.Children.Count - 1;
if (lastIndex >= 0 && _panel.Children[lastIndex] is ContextMenuSeparator separator)
separator.Dispose();
base.Show(parent, location);
}
/// <inheritdoc />
public override bool ContainsPoint(ref Float2 location, bool precise)
{

View File

@@ -168,30 +168,30 @@ namespace FlaxEditor.GUI.ContextMenu
bool isUp = false, isLeft = false;
if (UseAutomaticDirectionFix)
{
var parentMenu = parent as ContextMenu;
if (monitorBounds.Bottom < rightBottomLocationSS.Y)
{
// Direction: up
isUp = true;
locationSS.Y -= dpiSize.Y;
// Offset to fix sub-menu location
if (parent is ContextMenu menu && menu._childCM != null)
if (parentMenu != null && parentMenu._childCM != null)
locationSS.Y += 30.0f * dpiScale;
}
if (monitorBounds.Right < rightBottomLocationSS.X || _parentCM?.Direction == ContextMenuDirection.LeftDown || _parentCM?.Direction == ContextMenuDirection.LeftUp)
if (parentMenu == null)
{
// Direction: left
isLeft = true;
if (IsSubMenu && _parentCM != null)
{
locationSS.X -= _parentCM.Width + dpiSize.X;
}
else
if (monitorBounds.Right < rightBottomLocationSS.X)
{
isLeft = true;
locationSS.X -= dpiSize.X;
}
}
else if (monitorBounds.Right < rightBottomLocationSS.X || _parentCM?.Direction == ContextMenuDirection.LeftDown || _parentCM?.Direction == ContextMenuDirection.LeftUp)
{
isLeft = true;
if (IsSubMenu && _parentCM != null)
locationSS.X -= _parentCM.Width + dpiSize.X;
else
locationSS.X -= dpiSize.X;
}
}
// Update direction flag

View File

@@ -484,6 +484,7 @@ namespace FlaxEditor.GUI
cm.AddSeparator();
cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location));
cm.AddButton("Select all keyframes", _editor.SelectAll);
cm.AddButton("Deselect all keyframes", _editor.DeselectAll);
cm.AddButton("Copy all keyframes", () =>
{
_editor.SelectAll();

View File

@@ -6,6 +6,7 @@ using System.Globalization;
using System.Linq;
using FlaxEditor.CustomEditors;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Options;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -713,15 +714,28 @@ namespace FlaxEditor.GUI
}
}
private void BulkSelectUpdate(bool select = true)
{
for (int i = 0; i < _points.Count; i++)
{
_points[i].IsSelected = select;
}
}
/// <summary>
/// Selects all keyframes.
/// </summary>
public void SelectAll()
{
for (int i = 0; i < _points.Count; i++)
{
_points[i].IsSelected = true;
}
BulkSelectUpdate(true);
}
/// <summary>
/// Deselects all keyframes.
/// </summary>
public void DeselectAll()
{
BulkSelectUpdate(false);
}
/// <summary>
@@ -899,8 +913,8 @@ namespace FlaxEditor.GUI
_mainPanel.PointToParent(_contents.PointToParent(_contents._leftMouseDownPos)),
_mainPanel.PointToParent(_contents.PointToParent(_contents._mousePos))
);
Render2D.FillRectangle(selectionRect, Color.Orange * 0.4f);
Render2D.DrawRectangle(selectionRect, Color.Orange);
Render2D.FillRectangle(selectionRect, style.Selection);
Render2D.DrawRectangle(selectionRect, style.SelectionBorder);
}
base.Draw();
@@ -926,34 +940,35 @@ namespace FlaxEditor.GUI
if (base.OnKeyDown(key))
return true;
switch (key)
InputOptions options = Editor.Instance.Options.Options.Input;
if (options.SelectAll.Process(this))
{
SelectAll();
UpdateTangents();
return true;
}
else if (options.DeselectAll.Process(this))
{
DeselectAll();
UpdateTangents();
return true;
}
else if (options.Delete.Process(this))
{
case KeyboardKeys.Delete:
RemoveKeyframes();
return true;
case KeyboardKeys.A:
if (Root.GetKey(KeyboardKeys.Control))
{
SelectAll();
UpdateTangents();
return true;
}
break;
case KeyboardKeys.C:
if (Root.GetKey(KeyboardKeys.Control))
{
CopyKeyframes();
return true;
}
break;
case KeyboardKeys.V:
if (Root.GetKey(KeyboardKeys.Control))
{
KeyframesEditorUtils.Paste(this);
return true;
}
break;
}
else if (options.Copy.Process(this))
{
CopyKeyframes();
return true;
}
else if (options.Paste.Process(this))
{
KeyframesEditorUtils.Paste(this);
return true;
}
return false;
}

View File

@@ -201,8 +201,9 @@ namespace FlaxEditor.GUI.Input
if (_isSliding)
{
// Draw overlay
// TODO: render nicer overlay with some glow from the borders (inside)
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), Color.Orange * 0.3f);
var bounds = new Rectangle(Float2.Zero, Size);
Render2D.FillRectangle(bounds, style.Selection);
Render2D.DrawRectangle(bounds, style.SelectionBorder);
}
}
}

View File

@@ -122,6 +122,10 @@ namespace FlaxEditor.GUI
if (IsMouseOver || IsFocused)
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundHighlighted);
// Indent for drop panel items is handled by drop panel margin
if (Parent is not DropPanel)
textRect.Location += new Float2(Editor.Instance.Icons.ArrowRight12.Size.X + 2, 0);
// Draw all highlights
if (_highlights != null)
{
@@ -262,6 +266,10 @@ namespace FlaxEditor.GUI
}
}
category.Visible = anyVisible;
if (string.IsNullOrEmpty(_searchBox.Text))
category.Close(false);
else
category.Open(false);
}
}
@@ -338,9 +346,14 @@ namespace FlaxEditor.GUI
var categoryPanel = new DropPanel
{
HeaderText = item.Category,
ArrowImageOpened = new SpriteBrush(Editor.Instance.Icons.ArrowDown12),
ArrowImageClosed = new SpriteBrush(Editor.Instance.Icons.ArrowRight12),
EnableDropDownIcon = true,
ItemsMargin = new Margin(28, 0, 2, 2),
HeaderColor = Style.Current.Background,
Parent = parent,
};
categoryPanel.Open(false);
categoryPanel.Close(false);
_categoryPanels.Add(categoryPanel);
parent = categoryPanel;
}
@@ -382,6 +395,7 @@ namespace FlaxEditor.GUI
item2.UpdateFilter(null);
}
category.Visible = true;
category.Close(false);
}
}

View File

@@ -45,7 +45,8 @@ namespace FlaxEditor.GUI
// Draw background
if (IsDragOver && _validDragOver)
{
Render2D.FillRectangle(clientRect, Style.Current.BackgroundSelected * 0.6f);
Render2D.FillRectangle(clientRect, style.Selection);
Render2D.DrawRectangle(clientRect, style.SelectionBorder);
}
else if (_isPressed)
{

View File

@@ -227,8 +227,8 @@ namespace FlaxEditor.GUI.Timeline.GUI
if (_isSelecting)
{
var selectionRect = Rectangle.FromPoints(_selectingStartPos, _mousePos);
Render2D.FillRectangle(selectionRect, Color.Orange * 0.4f);
Render2D.DrawRectangle(selectionRect, Color.Orange);
Render2D.FillRectangle(selectionRect, style.Selection);
Render2D.DrawRectangle(selectionRect, style.SelectionBorder);
}
DrawChildren();

View File

@@ -7,6 +7,7 @@ using System.Linq;
using System.Text;
using FlaxEditor.CustomEditors;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Options;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -432,6 +433,7 @@ namespace FlaxEditor.GUI
if (_editor.EnableKeyframesValueEdit)
cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location));
cm.AddButton("Select all keyframes", _editor.SelectAll).Enabled = _editor._points.Count > 0;
cm.AddButton("Deselect all keyframes", _editor.DeselectAll).Enabled = _editor._points.Count > 0;
cm.AddButton("Copy all keyframes", () =>
{
_editor.SelectAll();
@@ -902,7 +904,7 @@ namespace FlaxEditor.GUI
var k = new Keyframe
{
Time = keyframesPos.X,
Value = DefaultValue,
Value = Utilities.Utils.CloneValue(DefaultValue),
};
OnEditingStart();
AddKeyframe(k);
@@ -1209,15 +1211,28 @@ namespace FlaxEditor.GUI
}
}
private void BulkSelectUpdate(bool select = true)
{
for (int i = 0; i < _points.Count; i++)
{
_points[i].IsSelected = select;
}
}
/// <summary>
/// Selects all keyframes.
/// </summary>
public void SelectAll()
{
for (int i = 0; i < _points.Count; i++)
{
_points[i].IsSelected = true;
}
BulkSelectUpdate(true);
}
/// <summary>
/// Deselects all keyframes.
/// </summary>
public void DeselectAll()
{
BulkSelectUpdate(false);
}
/// <inheritdoc />
@@ -1241,8 +1256,8 @@ namespace FlaxEditor.GUI
_mainPanel.PointToParent(_contents.PointToParent(_contents._leftMouseDownPos)),
_mainPanel.PointToParent(_contents.PointToParent(_contents._mousePos))
);
Render2D.FillRectangle(selectionRect, Color.Orange * 0.4f);
Render2D.DrawRectangle(selectionRect, Color.Orange);
Render2D.FillRectangle(selectionRect, style.Selection);
Render2D.DrawRectangle(selectionRect, style.SelectionBorder);
}
base.Draw();
@@ -1268,33 +1283,33 @@ namespace FlaxEditor.GUI
if (base.OnKeyDown(key))
return true;
switch (key)
InputOptions options = Editor.Instance.Options.Options.Input;
if (options.SelectAll.Process(this))
{
SelectAll();
return true;
}
else if (options.DeselectAll.Process(this))
{
DeselectAll();
return true;
}
else if (options.Delete.Process(this))
{
case KeyboardKeys.Delete:
RemoveKeyframes();
return true;
case KeyboardKeys.A:
if (Root.GetKey(KeyboardKeys.Control))
{
SelectAll();
return true;
}
break;
case KeyboardKeys.C:
if (Root.GetKey(KeyboardKeys.Control))
{
CopyKeyframes();
return true;
}
break;
case KeyboardKeys.V:
if (Root.GetKey(KeyboardKeys.Control))
{
KeyframesEditorUtils.Paste(this);
return true;
}
break;
}
else if (options.Copy.Process(this))
{
CopyKeyframes();
return true;
}
else if (options.Paste.Process(this))
{
KeyframesEditorUtils.Paste(this);
return true;
}
return false;
}

View File

@@ -304,7 +304,9 @@ namespace FlaxEditor.GUI.Timeline
if (IsDragOver && _currentDragEffect != DragDropEffect.None)
{
var style = Style.Current;
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundSelected * 0.4f);
var bounds = new Rectangle(Float2.Zero, Size);
Render2D.FillRectangle(bounds, style.Selection);
Render2D.DrawRectangle(bounds, style.SelectionBorder);
}
base.Draw();

View File

@@ -187,6 +187,11 @@ namespace FlaxEditor.GUI.Timeline
private bool _showPreviewValues = true;
private PlaybackStates _state = PlaybackStates.Disabled;
/// <summary>
/// The Track that is being dragged over. This could have a value when not dragging.
/// </summary>
internal Track DraggedOverTrack = null;
/// <summary>
/// Flag used to mark modified timeline data.
/// </summary>

View File

@@ -774,14 +774,33 @@ namespace FlaxEditor.GUI.Timeline
/// Updates the drag over mode based on the given mouse location.
/// </summary>
/// <param name="location">The location.</param>
private void UpdateDrawPositioning(ref Float2 location)
private void UpdateDragPositioning(ref Float2 location)
{
// Check collision with drag areas
if (new Rectangle(0, 0 - DefaultDragInsertPositionMargin - DefaultNodeOffsetY, Width, DefaultDragInsertPositionMargin * 2.0f).Contains(location))
_dragOverMode = DragItemPositioning.Above;
else if (IsCollapsed && new Rectangle(0, Height - DefaultDragInsertPositionMargin, Width, DefaultDragInsertPositionMargin * 2.0f).Contains(location))
_dragOverMode = DragItemPositioning.Below;
else
_dragOverMode = DragItemPositioning.At;
// Update DraggedOverTrack
var timeline = Timeline;
if (_dragOverMode == DragItemPositioning.None)
{
if (timeline != null && timeline.DraggedOverTrack == this)
timeline.DraggedOverTrack = null;
}
else if (timeline != null)
timeline.DraggedOverTrack = this;
}
private void ClearDragPositioning()
{
_dragOverMode = DragItemPositioning.None;
var timeline = Timeline;
if (timeline != null && timeline.DraggedOverTrack == this)
timeline.DraggedOverTrack = null;
}
/// <summary>
@@ -975,26 +994,21 @@ namespace FlaxEditor.GUI.Timeline
}
// Draw drag and drop effect
if (IsDragOver && _isDragOverHeader)
if (IsDragOver && _timeline.DraggedOverTrack == this)
{
Color dragOverColor = style.BackgroundSelected * 0.6f;
Rectangle rect;
switch (_dragOverMode)
{
case DragItemPositioning.At:
rect = textRect;
Render2D.FillRectangle(textRect, style.Selection);
Render2D.DrawRectangle(textRect, style.SelectionBorder);
break;
case DragItemPositioning.Above:
rect = new Rectangle(textRect.X, textRect.Y - DefaultDragInsertPositionMargin - DefaultNodeOffsetY, textRect.Width, DefaultDragInsertPositionMargin * 2.0f);
Render2D.DrawRectangle(new Rectangle(textRect.X, textRect.Top - DefaultDragInsertPositionMargin * 0.5f - DefaultNodeOffsetY - _margin.Top, textRect.Width, DefaultDragInsertPositionMargin), style.SelectionBorder);
break;
case DragItemPositioning.Below:
rect = new Rectangle(textRect.X, textRect.Bottom - DefaultDragInsertPositionMargin, textRect.Width, DefaultDragInsertPositionMargin * 2.0f);
break;
default:
rect = Rectangle.Empty;
Render2D.DrawRectangle(new Rectangle(textRect.X, textRect.Bottom + _margin.Bottom - DefaultDragInsertPositionMargin * 0.5f, textRect.Width, DefaultDragInsertPositionMargin), style.SelectionBorder);
break;
}
Render2D.FillRectangle(rect, dragOverColor);
}
base.Draw();
@@ -1170,18 +1184,18 @@ namespace FlaxEditor.GUI.Timeline
_dragOverMode = DragItemPositioning.None;
if (result == DragDropEffect.None)
{
UpdateDrawPositioning(ref location);
UpdateDragPositioning(ref location);
// Check if mouse is over header
_isDragOverHeader = TestHeaderHit(ref location);
if (_isDragOverHeader)
{
// Check if mouse is over arrow
if (Timeline != null)
Timeline.DraggedOverTrack = this;
// Expand node if mouse goes over arrow
if (_children.Count > 0 && ArrowRect.Contains(location))
{
// Expand track
Expand();
}
result = OnDragEnterHeader(data);
}
@@ -1199,21 +1213,18 @@ namespace FlaxEditor.GUI.Timeline
var result = base.OnDragMove(ref location, data);
// Check if no children handled that event
_dragOverMode = DragItemPositioning.None;
ClearDragPositioning();
if (result == DragDropEffect.None)
{
UpdateDrawPositioning(ref location);
UpdateDragPositioning(ref location);
// Check if mouse is over header
bool isDragOverHeader = TestHeaderHit(ref location);
if (isDragOverHeader)
{
// Check if mouse is over arrow
// Expand node if mouse goes over arrow
if (_children.Count > 0 && ArrowRect.Contains(location))
{
// Expand track
Expand();
}
if (!_isDragOverHeader)
result = OnDragEnterHeader(data);
@@ -1226,10 +1237,8 @@ namespace FlaxEditor.GUI.Timeline
}
_isDragOverHeader = isDragOverHeader;
if (result == DragDropEffect.None || !isDragOverHeader)
{
if (result == DragDropEffect.None)
_dragOverMode = DragItemPositioning.None;
}
}
return result;
@@ -1243,7 +1252,7 @@ namespace FlaxEditor.GUI.Timeline
// Check if no children handled that event
if (result == DragDropEffect.None)
{
UpdateDrawPositioning(ref location);
UpdateDragPositioning(ref location);
// Check if mouse is over header
if (TestHeaderHit(ref location))
@@ -1254,7 +1263,7 @@ namespace FlaxEditor.GUI.Timeline
// Clear cache
_isDragOverHeader = false;
_dragOverMode = DragItemPositioning.None;
ClearDragPositioning();
return result;
}
@@ -1262,15 +1271,15 @@ namespace FlaxEditor.GUI.Timeline
/// <inheritdoc />
public override void OnDragLeave()
{
base.OnDragLeave();
// Clear cache
if (_isDragOverHeader)
{
_isDragOverHeader = false;
OnDragLeaveHeader();
}
_dragOverMode = DragItemPositioning.None;
ClearDragPositioning();
base.OnDragLeave();
}
/// <inheritdoc />

View File

@@ -204,7 +204,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
b.TooltipText = Utilities.Utils.GetTooltip(actorNode.Actor);
}
}
menu.AddButton("Select...", OnClickedSelect).TooltipText = "Opens actor picker dialog to select the target actor for this track";
menu.AddButton("Retarget...", OnClickedSelect).TooltipText = "Opens actor picker dialog to select the target actor for this track";
}
/// <summary>

View File

@@ -7,6 +7,8 @@ using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using FlaxEditor.GUI.Timeline.Undo;
using FlaxEditor.Scripting;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -56,7 +58,6 @@ namespace FlaxEditor.GUI.Timeline.Tracks
throw new Exception("Invalid track data.");
var keyframes = new KeyframesEditor.Keyframe[keyframesCount];
var dataBuffer = new byte[e.ValueSize];
var propertyType = TypeUtils.GetManagedType(e.MemberTypeName);
if (propertyType == null)
{
@@ -67,20 +68,40 @@ namespace FlaxEditor.GUI.Timeline.Tracks
return;
}
GCHandle handle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned);
for (int i = 0; i < keyframesCount; i++)
if (e.ValueSize != 0)
{
var time = stream.ReadSingle();
stream.Read(dataBuffer, 0, e.ValueSize);
var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType);
keyframes[i] = new KeyframesEditor.Keyframe
// POD value type - use raw memory
var dataBuffer = new byte[e.ValueSize];
GCHandle handle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned);
for (int i = 0; i < keyframesCount; i++)
{
Time = time,
Value = value,
};
var time = stream.ReadSingle();
stream.Read(dataBuffer, 0, e.ValueSize);
var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType);
keyframes[i] = new KeyframesEditor.Keyframe
{
Time = time,
Value = value,
};
}
handle.Free();
}
else
{
// Generic value - use Json storage (as UTF-8)
for (int i = 0; i < keyframesCount; i++)
{
var time = stream.ReadSingle();
var len = stream.ReadInt32();
var value = len != 0 ? FlaxEngine.Json.JsonSerializer.Deserialize(Encoding.UTF8.GetString(stream.ReadBytes(len)), propertyType) : null;
keyframes[i] = new KeyframesEditor.Keyframe
{
Time = time,
Value = value,
};
}
}
handle.Free();
e.Keyframes.DefaultValue = e.GetDefaultValue(propertyType);
e.Keyframes.SetKeyframes(keyframes);
@@ -113,17 +134,35 @@ namespace FlaxEditor.GUI.Timeline.Tracks
stream.Write(propertyTypeNameData);
stream.Write('\0');
var dataBuffer = new byte[e.ValueSize];
IntPtr ptr = Marshal.AllocHGlobal(e.ValueSize);
for (int i = 0; i < keyframes.Count; i++)
if (e.ValueSize != 0)
{
var keyframe = keyframes[i];
Marshal.StructureToPtr(keyframe.Value, ptr, true);
Marshal.Copy(ptr, dataBuffer, 0, e.ValueSize);
stream.Write(keyframe.Time);
stream.Write(dataBuffer);
// POD value type - use raw memory
var dataBuffer = new byte[e.ValueSize];
IntPtr ptr = Marshal.AllocHGlobal(e.ValueSize);
for (int i = 0; i < keyframes.Count; i++)
{
var keyframe = keyframes[i];
Marshal.StructureToPtr(keyframe.Value, ptr, true);
Marshal.Copy(ptr, dataBuffer, 0, e.ValueSize);
stream.Write(keyframe.Time);
stream.Write(dataBuffer);
}
Marshal.FreeHGlobal(ptr);
}
else
{
// Generic value - use Json storage (as UTF-8)
for (int i = 0; i < keyframes.Count; i++)
{
var keyframe = keyframes[i];
stream.Write(keyframe.Time);
var json = keyframe.Value != null ? FlaxEngine.Json.JsonSerializer.Serialize(keyframe.Value) : null;
var len = json?.Length ?? 0;
stream.Write(len);
if (len > 0)
stream.Write(Encoding.UTF8.GetBytes(json));
}
}
Marshal.FreeHGlobal(ptr);
}
private byte[] _keyframesEditingStartData;
@@ -281,7 +320,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private void OnKeyframesEditingEnd()
{
var after = EditTrackAction.CaptureData(this);
if (!Utils.ArraysEqual(_keyframesEditingStartData, after))
if (!FlaxEngine.Utils.ArraysEqual(_keyframesEditingStartData, after))
Timeline.AddBatchedUndoAction(new EditTrackAction(Timeline, this, _keyframesEditingStartData, after));
_keyframesEditingStartData = null;
}
@@ -308,7 +347,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// <returns>The default value.</returns>
protected virtual object GetDefaultValue(Type propertyType)
{
return Activator.CreateInstance(propertyType);
var value = TypeUtils.GetDefaultValue(new ScriptType(propertyType));
if (value == null)
value = Activator.CreateInstance(propertyType);
return value;
}
/// <inheritdoc />

View File

@@ -424,6 +424,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{ typeof(Guid), KeyframesPropertyTrack.GetArchetype() },
{ typeof(DateTime), KeyframesPropertyTrack.GetArchetype() },
{ typeof(TimeSpan), KeyframesPropertyTrack.GetArchetype() },
{ typeof(LocalizedString), KeyframesPropertyTrack.GetArchetype() },
{ typeof(string), StringPropertyTrack.GetArchetype() },
};
}

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Options;
using FlaxEngine;
using FlaxEngine.Assertions;
using FlaxEngine.GUI;
@@ -43,7 +44,7 @@ namespace FlaxEditor.GUI.Tree
/// <summary>
/// The TreeNode that is being dragged over. This could have a value when not dragging.
/// </summary>
public TreeNode DraggedOverNode = null;
internal TreeNode DraggedOverNode = null;
/// <summary>
/// Action fired when tree nodes selection gets changed.
@@ -315,10 +316,7 @@ namespace FlaxEditor.GUI.Tree
}
}
/// <summary>
/// Select all expanded nodes
/// </summary>
public void SelectAllExpanded()
private void BulkSelectUpdateExpanded(bool select = true)
{
if (_supportMultiSelect)
{
@@ -327,7 +325,8 @@ namespace FlaxEditor.GUI.Tree
// Update selection
Selection.Clear();
WalkSelectExpandedTree(Selection, _children[0] as TreeNode);
if (select)
WalkSelectExpandedTree(Selection, _children[0] as TreeNode);
// Check if changed
if (Selection.Count != prev.Count || !Selection.SequenceEqual(prev))
@@ -338,6 +337,22 @@ namespace FlaxEditor.GUI.Tree
}
}
/// <summary>
/// Select all expanded nodes
/// </summary>
public void SelectAllExpanded()
{
BulkSelectUpdateExpanded(true);
}
/// <summary>
/// Deselect all nodes
/// </summary>
public void DeselectAll()
{
BulkSelectUpdateExpanded(false);
}
/// <inheritdoc />
public override void Update(float deltaTime)
{
@@ -472,14 +487,19 @@ namespace FlaxEditor.GUI.Tree
// Check if can use multi selection
if (_supportMultiSelect)
{
bool isCtrlDown = Root.GetKey(KeyboardKeys.Control);
InputOptions options = Editor.Instance.Options.Options.Input;
// Select all expanded nodes
if (key == KeyboardKeys.A && isCtrlDown)
if (options.SelectAll.Process(this))
{
SelectAllExpanded();
return true;
}
else if (options.DeselectAll.Process(this))
{
DeselectAll();
return true;
}
}
return base.OnKeyDown(key);

View File

@@ -16,7 +16,7 @@ namespace FlaxEditor.GUI.Tree
/// <summary>
/// The default drag insert position margin.
/// </summary>
public const float DefaultDragInsertPositionMargin = 2.0f;
public const float DefaultDragInsertPositionMargin = 3.0f;
/// <summary>
/// The default node offset on Y axis.
@@ -42,6 +42,7 @@ namespace FlaxEditor.GUI.Tree
private DragItemPositioning _dragOverMode;
private bool _isDragOverHeader;
private static ulong _dragEndFrame;
/// <summary>
/// Gets or sets the text.
@@ -546,14 +547,33 @@ namespace FlaxEditor.GUI.Tree
/// Updates the drag over mode based on the given mouse location.
/// </summary>
/// <param name="location">The location.</param>
private void UpdateDrawPositioning(ref Float2 location)
private void UpdateDragPositioning(ref Float2 location)
{
// Check collision with drag areas
if (new Rectangle(_headerRect.X, _headerRect.Y - DefaultDragInsertPositionMargin - DefaultNodeOffsetY, _headerRect.Width, DefaultDragInsertPositionMargin * 2.0f).Contains(location))
_dragOverMode = DragItemPositioning.Above;
else if ((IsCollapsed || !HasAnyVisibleChild) && new Rectangle(_headerRect.X, _headerRect.Bottom - DefaultDragInsertPositionMargin, _headerRect.Width, DefaultDragInsertPositionMargin * 2.0f).Contains(location))
_dragOverMode = DragItemPositioning.Below;
else
_dragOverMode = DragItemPositioning.At;
// Update DraggedOverNode
var tree = ParentTree;
if (_dragOverMode == DragItemPositioning.None)
{
if (tree != null && tree.DraggedOverNode == this)
tree.DraggedOverNode = null;
}
else if (tree != null)
tree.DraggedOverNode = this;
}
private void ClearDragPositioning()
{
_dragOverMode = DragItemPositioning.None;
var tree = ParentTree;
if (tree != null && tree.DraggedOverNode == this)
tree.DraggedOverNode = null;
}
/// <summary>
@@ -661,27 +681,19 @@ namespace FlaxEditor.GUI.Tree
// Draw drag and drop effect
if (IsDragOver && _tree.DraggedOverNode == this)
{
Color dragOverColor = style.BackgroundSelected;
Rectangle rect;
switch (_dragOverMode)
{
case DragItemPositioning.At:
dragOverColor *= 0.6f;
rect = textRect;
Render2D.FillRectangle(textRect, style.Selection);
Render2D.DrawRectangle(textRect, style.SelectionBorder);
break;
case DragItemPositioning.Above:
dragOverColor *= 1.2f;
rect = new Rectangle(textRect.X, textRect.Top - DefaultDragInsertPositionMargin - DefaultNodeOffsetY - _margin.Top, textRect.Width, DefaultDragInsertPositionMargin * 2.0f);
Render2D.DrawRectangle(new Rectangle(textRect.X, textRect.Top - DefaultDragInsertPositionMargin * 0.5f - DefaultNodeOffsetY - _margin.Top, textRect.Width, DefaultDragInsertPositionMargin), style.SelectionBorder);
break;
case DragItemPositioning.Below:
dragOverColor *= 1.2f;
rect = new Rectangle(textRect.X, textRect.Bottom + _margin.Bottom - DefaultDragInsertPositionMargin, textRect.Width, DefaultDragInsertPositionMargin * 2.0f);
break;
default:
rect = Rectangle.Empty;
Render2D.DrawRectangle(new Rectangle(textRect.X, textRect.Bottom + _margin.Bottom - DefaultDragInsertPositionMargin * 0.5f, textRect.Width, DefaultDragInsertPositionMargin), style.SelectionBorder);
break;
}
Render2D.FillRectangle(rect, dragOverColor);
}
// Base
@@ -736,9 +748,8 @@ namespace FlaxEditor.GUI.Tree
UpdateMouseOverFlags(location);
// Clear flag for left button
if (button == MouseButton.Left)
if (button == MouseButton.Left && _isMouseDown)
{
// Clear flag
_isMouseDown = false;
_mouseDownTime = -1;
}
@@ -746,6 +757,10 @@ namespace FlaxEditor.GUI.Tree
// Check if mouse hits bar and node isn't a root
if (_mouseOverHeader)
{
// Skip mouse up event right after drag drop ends
if (button == MouseButton.Left && Engine.FrameCount - _dragEndFrame < 10)
return true;
// Prevent from selecting node when user is just clicking at an arrow
if (!_mouseOverArrow)
{
@@ -937,20 +952,18 @@ namespace FlaxEditor.GUI.Tree
_dragOverMode = DragItemPositioning.None;
if (result == DragDropEffect.None)
{
UpdateDrawPositioning(ref location);
if (ParentTree != null)
ParentTree.DraggedOverNode = this;
UpdateDragPositioning(ref location);
// Check if mouse is over header
_isDragOverHeader = TestHeaderHit(ref location);
if (_isDragOverHeader)
{
// Check if mouse is over arrow
if (ParentTree != null)
ParentTree.DraggedOverNode = this;
// Expand node if mouse goes over arrow
if (ArrowRect.Contains(location) && HasAnyVisibleChild)
{
// Expand node (no animation)
Expand(true);
}
result = OnDragEnterHeader(data);
}
@@ -968,26 +981,31 @@ namespace FlaxEditor.GUI.Tree
var result = base.OnDragMove(ref location, data);
// Check if no children handled that event
_dragOverMode = DragItemPositioning.None;
ClearDragPositioning();
if (result == DragDropEffect.None)
{
UpdateDrawPositioning(ref location);
UpdateDragPositioning(ref location);
// Check if mouse is over header
bool isDragOverHeader = TestHeaderHit(ref location);
if (isDragOverHeader)
{
if (ArrowRect.Contains(location) && HasAnyVisibleChild)
{
// Expand node (no animation)
Expand(true);
}
if (ParentTree != null)
ParentTree.DraggedOverNode = this;
// Expand node if mouse goes over arrow
if (ArrowRect.Contains(location) && HasAnyVisibleChild)
Expand(true);
if (!_isDragOverHeader)
result = OnDragEnterHeader(data);
else
result = OnDragMoveHeader(data);
}
else if (_isDragOverHeader)
{
OnDragLeaveHeader();
}
_isDragOverHeader = isDragOverHeader;
if (result == DragDropEffect.None)
@@ -1005,7 +1023,8 @@ namespace FlaxEditor.GUI.Tree
// Check if no children handled that event
if (result == DragDropEffect.None)
{
UpdateDrawPositioning(ref location);
UpdateDragPositioning(ref location);
_dragEndFrame = Engine.FrameCount;
// Check if mouse is over header
if (TestHeaderHit(ref location))
@@ -1016,9 +1035,7 @@ namespace FlaxEditor.GUI.Tree
// Clear cache
_isDragOverHeader = false;
_dragOverMode = DragItemPositioning.None;
if (ParentTree != null)
ParentTree.DraggedOverNode = null;
ClearDragPositioning();
return result;
}
@@ -1032,7 +1049,7 @@ namespace FlaxEditor.GUI.Tree
_isDragOverHeader = false;
OnDragLeaveHeader();
}
_dragOverMode = DragItemPositioning.None;
ClearDragPositioning();
base.OnDragLeave();
}

View File

@@ -263,6 +263,8 @@ namespace FlaxEditor.Gizmo
// Note: because selection may contain objects and their children we have to split them and get only parents.
// Later during transformation we apply translation/scale/rotation only on them (children inherit transformations)
SceneGraphTools.BuildNodesParents(_selection, _selectionParents);
base.OnSelectionChanged(newSelection);
}
/// <inheritdoc />

View File

@@ -437,8 +437,6 @@ namespace FlaxEditor.Gizmo
{
case Mode.Translate:
UpdateTranslateScale();
if (Owner.SnapToVertex)
UpdateVertexSnapping();
break;
case Mode.Scale:
UpdateTranslateScale();
@@ -447,6 +445,8 @@ namespace FlaxEditor.Gizmo
UpdateRotate(dt);
break;
}
if (Owner.SnapToVertex)
UpdateVertexSnapping();
}
else
{
@@ -553,41 +553,21 @@ namespace FlaxEditor.Gizmo
for (int i = 0; i < SelectionCount; i++)
{
var obj = GetSelectedObject(i);
if (obj.CanVertexSnap && obj.RayCastSelf(ref ray, out var distance, out _) && distance < closestDistance)
if (obj.RayCastSelf(ref ray, out var distance, out _) && distance < closestDistance)
{
closestDistance = distance;
closestObject = obj;
}
}
if (closestObject == null)
{
// Find the closest object in selection (in case ray didn't hit anything)
for (int i = 0; i < SelectionCount; i++)
{
var obj = GetSelectedObject(i);
if (obj.CanVertexSnap)
{
GetSelectedObjectsBounds(out var bounds, out _);
CollisionsHelper.ClosestPointBoxPoint(ref bounds, ref ray.Ray.Position, out var point);
var distance = Vector3.Distance(ref point, ref ray.Ray.Position);
if (distance < closestDistance)
{
closestDistance = distance;
closestObject = obj;
}
}
}
}
_vertexSnapObject = closestObject;
if (closestObject == null)
return;
return; // ignore it if there is nothing under the mouse closestObject is only null if ray caster missed everything or Selection Count == 0
// Find the closest vertex to bounding box point (collision detection approximation)
var closestPoint = ray.Ray.GetPoint(closestDistance);
if (!closestObject.OnVertexSnap(ref closestPoint, out _vertexSnapPoint))
_vertexSnapObject = closestObject;
if (!closestObject.OnVertexSnap(ref ray.Ray, closestDistance, out _vertexSnapPoint))
{
// Failed to get the closest point
_vertexSnapPoint = closestPoint;
// The OnVertexSnap is unimplemented or failed to get point return because there is nothing to do
_vertexSnapPoint = Vector3.Zero;
return;
}
// Transform back to the local space of the object to work when moving it
@@ -619,12 +599,11 @@ namespace FlaxEditor.Gizmo
for (int i = 0; i < SelectionCount; i++)
rayCast.ExcludeObjects.Add(GetSelectedObject(i));
var hit = Owner.SceneGraphRoot.RayCast(ref rayCast, out var distance, out var _);
if (hit != null && hit.CanVertexSnap)
if (hit != null)
{
var point = rayCast.Ray.GetPoint(distance);
if (hit.OnVertexSnap(ref point, out var pointSnapped)
if (hit.OnVertexSnap(ref rayCast.Ray, distance, out var pointSnapped)
//&& Vector3.Distance(point, pointSnapped) <= 25.0f
)
)
{
_vertexSnapObjectTo = hit;
_vertexSnapPointTo = hit.Transform.WorldToLocal(pointSnapped);
@@ -712,5 +691,12 @@ namespace FlaxEditor.Gizmo
protected virtual void OnDuplicate()
{
}
/// <inheritdoc />
public override void OnSelectionChanged(List<SceneGraphNode> newSelection)
{
EndVertexSnapping();
UpdateGizmoPosition();
}
}
}

View File

@@ -509,6 +509,21 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_CanSetToRoot(Prefab* prefab, Actor* ta
return true;
}
DEFINE_INTERNAL_CALL(void) EditorInternal_GetPrefabNestedObject(Guid* prefabId, Guid* prefabObjectId, Guid* outPrefabId, Guid* outPrefabObjectId)
{
*outPrefabId = Guid::Empty;
*outPrefabObjectId = Guid::Empty;
const auto prefab = Content::Load<Prefab>(*prefabId);
if (!prefab)
return;
const ISerializable::DeserializeStream** prefabObjectDataPtr = prefab->ObjectsDataCache.TryGet(*prefabObjectId);
if (!prefabObjectDataPtr)
return;
const ISerializable::DeserializeStream& prefabObjectData = **prefabObjectDataPtr;
JsonTools::GetGuidIfValid(*outPrefabId, prefabObjectData, "PrefabID");
JsonTools::GetGuidIfValid(*outPrefabObjectId, prefabObjectData, "PrefabObjectID");
}
DEFINE_INTERNAL_CALL(float) EditorInternal_GetAnimationTime(AnimatedModel* animatedModel)
{
return animatedModel && animatedModel->GraphInstance.State.Count() == 1 ? animatedModel->GraphInstance.State[0].Animation.TimePosition : 0.0f;

View File

@@ -278,13 +278,7 @@ void ManagedEditor::Update()
void ManagedEditor::Exit()
{
if (WasExitCalled)
{
// Ups xD
LOG(Warning, "Managed Editor exit called after exit or before init.");
return;
}
// Set flag
WasExitCalled = true;
// Skip if managed object is missing

View File

@@ -225,8 +225,15 @@ namespace FlaxEditor.Modules
throw new ArgumentException("Missing prefab to apply.");
PrefabApplying?.Invoke(prefab, instance);
// When applying changes to prefab from actor in level ignore it's root transformation (see ActorEditor.ProcessDiff)
var originalTransform = instance.LocalTransform;
if (instance.IsPrefabRoot && instance.Scene != null)
instance.LocalTransform = prefab.GetDefaultInstance().Transform;
// Call backend
if (PrefabManager.Internal_ApplyAll(FlaxEngine.Object.GetUnmanagedPtr(instance)))
var failed = PrefabManager.Internal_ApplyAll(FlaxEngine.Object.GetUnmanagedPtr(instance));
instance.LocalTransform = originalTransform;
if (failed)
throw new Exception("Failed to apply the prefab. See log to learn more.");
PrefabApplied?.Invoke(prefab, instance);

View File

@@ -60,13 +60,26 @@ namespace FlaxEditor.Modules
{
}
private void BulkScenesSelectUpdate(bool select = true)
{
// Blank list deselects all
Select(select ? Editor.Scene.Root.ChildNodes : new List<SceneGraphNode>());
}
/// <summary>
/// Selects all scenes.
/// </summary>
public void SelectAllScenes()
{
// Select all scenes (linked to the root node)
Select(Editor.Scene.Root.ChildNodes);
BulkScenesSelectUpdate(true);
}
/// <summary>
/// Deselects all scenes.
/// </summary>
public void DeselectAllScenes()
{
BulkScenesSelectUpdate(false);
}
/// <summary>
@@ -320,7 +333,7 @@ namespace FlaxEditor.Modules
actorNode.PostSpawn();
// Create undo action
IUndoAction action = new DeleteActorsAction(new List<SceneGraphNode>(1) { actorNode }, true);
IUndoAction action = new DeleteActorsAction(actorNode, true);
if (autoSelect)
{
var before = Selection.ToArray();

View File

@@ -6,6 +6,7 @@ using System.IO;
using FlaxEditor.SceneGraph;
using FlaxEditor.SceneGraph.Actors;
using FlaxEngine;
using FlaxEngine.GUI;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Modules
@@ -454,6 +455,41 @@ namespace FlaxEditor.Modules
Profiler.EndEvent();
}
private Dictionary<ContainerControl, Float2> _uiRootSizes;
internal void OnSaveStart(ContainerControl uiRoot)
{
// Force viewport UI to have fixed size during scene/prefabs saving to result in stable data (less mess in version control diffs)
if (_uiRootSizes == null)
_uiRootSizes = new Dictionary<ContainerControl, Float2>();
_uiRootSizes[uiRoot] = uiRoot.Size;
uiRoot.Size = new Float2(1920, 1080);
}
internal void OnSaveEnd(ContainerControl uiRoot)
{
// Restore cached size of the UI root container
if (_uiRootSizes != null && _uiRootSizes.Remove(uiRoot, out var size))
{
uiRoot.Size = size;
}
}
private void OnSceneSaving(Scene scene, Guid sceneId)
{
OnSaveStart(RootControl.GameRoot);
}
private void OnSceneSaved(Scene scene, Guid sceneId)
{
OnSaveEnd(RootControl.GameRoot);
}
private void OnSceneSaveError(Scene scene, Guid sceneId)
{
OnSaveEnd(RootControl.GameRoot);
}
private void OnSceneLoaded(Scene scene, Guid sceneId)
{
var startTime = DateTime.UtcNow;
@@ -659,6 +695,9 @@ namespace FlaxEditor.Modules
Root = new ScenesRootNode();
// Bind events
Level.SceneSaving += OnSceneSaving;
Level.SceneSaved += OnSceneSaved;
Level.SceneSaveError += OnSceneSaveError;
Level.SceneLoaded += OnSceneLoaded;
Level.SceneUnloading += OnSceneUnloading;
Level.ActorSpawned += OnActorSpawned;
@@ -673,6 +712,9 @@ namespace FlaxEditor.Modules
public override void OnExit()
{
// Unbind events
Level.SceneSaving -= OnSceneSaving;
Level.SceneSaved -= OnSceneSaved;
Level.SceneSaveError -= OnSceneSaveError;
Level.SceneLoaded -= OnSceneLoaded;
Level.SceneUnloading -= OnSceneUnloading;
Level.ActorSpawned -= OnActorSpawned;

View File

@@ -264,11 +264,14 @@ namespace FlaxEditor.Modules
_enterPlayFocusedWindow = gameWin;
// Show Game widow if hidden
if (gameWin != null && gameWin.FocusOnPlay)
if (gameWin != null)
{
gameWin.FocusGameViewport();
if (gameWin.FocusOnPlay)
gameWin.FocusGameViewport();
gameWin.SetWindowMode(Editor.Options.Options.Interface.DefaultGameWindowMode);
}
Editor.Log("[PlayMode] Enter");
}

View File

@@ -54,6 +54,7 @@ namespace FlaxEditor.Modules
private ContextMenuButton _menuEditDelete;
private ContextMenuButton _menuEditDuplicate;
private ContextMenuButton _menuEditSelectAll;
private ContextMenuButton _menuEditDeselectAll;
private ContextMenuButton _menuEditFind;
private ContextMenuButton _menuSceneMoveActorToViewport;
private ContextMenuButton _menuSceneAlignActorWithViewport;
@@ -554,6 +555,7 @@ namespace FlaxEditor.Modules
_menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate);
cm.AddSeparator();
_menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes);
_menuEditDeselectAll = cm.AddButton("Deselect all", inputOptions.DeselectAll, Editor.SceneEditing.DeselectAllScenes);
_menuCreateParentForSelectedActors = cm.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors);
_menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search);
cm.AddSeparator();
@@ -673,6 +675,7 @@ namespace FlaxEditor.Modules
_menuEditDelete.ShortKeys = inputOptions.Delete.ToString();
_menuEditDuplicate.ShortKeys = inputOptions.Duplicate.ToString();
_menuEditSelectAll.ShortKeys = inputOptions.SelectAll.ToString();
_menuEditDeselectAll.ShortKeys = inputOptions.DeselectAll.ToString();
_menuEditFind.ShortKeys = inputOptions.Search.ToString();
_menuGamePlayGame.ShortKeys = inputOptions.Play.ToString();
_menuGamePlayCurrentScenes.ShortKeys = inputOptions.PlayCurrentScenes.ToString();
@@ -739,6 +742,17 @@ namespace FlaxEditor.Modules
playActionGroup.SelectedChanged = SetPlayAction;
Editor.Options.OptionsChanged += options => { playActionGroup.Selected = options.Interface.PlayButtonAction; };
var windowModesGroup = new ContextMenuSingleSelectGroup<InterfaceOptions.GameWindowMode>();
var windowTypeMenu = _toolStripPlay.ContextMenu.AddChildMenu("Game window mode");
windowModesGroup.AddItem("Docked", InterfaceOptions.GameWindowMode.Docked, null, "Shows the game window docked, inside the editor");
windowModesGroup.AddItem("Popup", InterfaceOptions.GameWindowMode.PopupWindow, null, "Shows the game window as a popup");
windowModesGroup.AddItem("Maximized", InterfaceOptions.GameWindowMode.MaximizedWindow, null, "Shows the game window maximized (Same as pressing F11)");
windowModesGroup.AddItem("Borderless", InterfaceOptions.GameWindowMode.BorderlessWindow, null, "Shows the game window borderless");
windowModesGroup.AddItemsToContextMenu(windowTypeMenu.ContextMenu);
windowModesGroup.Selected = Editor.Options.Options.Interface.DefaultGameWindowMode;
windowModesGroup.SelectedChanged = SetGameWindowMode;
Editor.Options.OptionsChanged += options => { windowModesGroup.Selected = options.Interface.DefaultGameWindowMode; };
_toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game ({inputOptions.Pause})");
_toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game");
@@ -871,6 +885,7 @@ namespace FlaxEditor.Modules
_menuEditDelete.Enabled = hasSthSelected;
_menuEditDuplicate.Enabled = hasSthSelected;
_menuEditSelectAll.Enabled = Level.IsAnySceneLoaded;
_menuEditDeselectAll.Enabled = hasSthSelected;
control.PerformLayout();
}
@@ -1043,6 +1058,13 @@ namespace FlaxEditor.Modules
Editor.Options.Apply(options);
}
private void SetGameWindowMode(InterfaceOptions.GameWindowMode newGameWindowMode)
{
var options = Editor.Options.Options;
options.Interface.DefaultGameWindowMode = newGameWindowMode;
Editor.Options.Apply(options);
}
private void OnMainWindowClosing()
{
// Clear UI references (GUI cannot be used after window closing)

View File

@@ -56,6 +56,10 @@ namespace FlaxEditor.Options
[EditorDisplay("Common"), EditorOrder(190)]
public InputBinding SelectAll = new InputBinding(KeyboardKeys.A, KeyboardKeys.Control);
[DefaultValue(typeof(InputBinding), "Ctrl+Shift+A")]
[EditorDisplay("Common"), EditorOrder(195)]
public InputBinding DeselectAll = new InputBinding(KeyboardKeys.A, KeyboardKeys.Shift, KeyboardKeys.Control);
[DefaultValue(typeof(InputBinding), "F")]
[EditorDisplay("Common"), EditorOrder(200)]
public InputBinding FocusSelection = new InputBinding(KeyboardKeys.F);

View File

@@ -90,6 +90,32 @@ namespace FlaxEditor.Options
PlayScenes,
}
/// <summary>
/// Available window modes for the game window.
/// </summary>
public enum GameWindowMode
{
/// <summary>
/// Shows the game window docked, inside the editor.
/// </summary>
Docked,
/// <summary>
/// Shows the game window as a popup.
/// </summary>
PopupWindow,
/// <summary>
/// Shows the game window maximized. (Same as pressing F11)
/// </summary>
MaximizedWindow,
/// <summary>
/// Shows the game window borderless.
/// </summary>
BorderlessWindow,
}
/// <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>
@@ -229,6 +255,13 @@ namespace FlaxEditor.Options
[EditorDisplay("Play In-Editor", "Play Button Action"), EditorOrder(410)]
public PlayAction PlayButtonAction { get; set; } = PlayAction.PlayScenes;
/// <summary>
/// Gets or sets a value indicating how the game window should be displayed when the game is launched.
/// </summary>
[DefaultValue(GameWindowMode.Docked)]
[EditorDisplay("Play In-Editor", "Game Window Mode"), EditorOrder(420), Tooltip("Determines how the game window is displayed when the game is launched.")]
public GameWindowMode DefaultGameWindowMode { get; set; } = GameWindowMode.Docked;
/// <summary>
/// Gets or sets a value indicating the number of game clients to launch when building and/or running cooked game.
/// </summary>

View File

@@ -212,6 +212,14 @@ namespace FlaxEditor.Options
string styleName = themeOptions.SelectedStyle;
if (styleName != ThemeOptions.DefaultName && styleName != ThemeOptions.LightDefault && themeOptions.Styles.TryGetValue(styleName, out var style) && style != null)
{
// Setup defaults for newly added components that might be missing
if (style.Selection == Color.Transparent && style.SelectionBorder == Color.Transparent)
{
// [Deprecated on 6.03.2024, expires on 6.03.2026]
style.Selection = Color.Orange * 0.4f;
style.SelectionBorder = Color.Orange;
}
Style.Current = style;
}
else
@@ -258,6 +266,8 @@ namespace FlaxEditor.Options
TextBoxBackgroundSelected = Color.FromBgra(0xFF3F3F46),
CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC),
ProgressNormal = Color.FromBgra(0xFF0ad328),
Selection = Color.Orange * 0.4f,
SelectionBorder = Color.Orange,
Statusbar = new Style.StatusbarStyle
{
@@ -318,6 +328,8 @@ namespace FlaxEditor.Options
TextBoxBackgroundSelected = new Color(0.73f, 0.73f, 0.80f, 1f),
CollectionBackgroundColor = new Color(0.85f, 0.85f, 0.88f, 1f),
ProgressNormal = new Color(0.03f, 0.65f, 0.12f, 1f),
Selection = Color.Orange * 0.4f,
SelectionBorder = Color.Orange,
// Fonts
FontTitle = options.Interface.TitleFont.GetFont(),

View File

@@ -316,41 +316,7 @@ namespace FlaxEditor.SceneGraph
{
base.OnParentChanged();
// Update UI (special case if actor is spawned and added to existing scene tree)
var parentTreeNode = (parentNode as ActorNode)?.TreeNode;
if (parentTreeNode != null && !parentTreeNode.IsLayoutLocked)
{
parentTreeNode.IsLayoutLocked = true;
_treeNode.Parent = parentTreeNode;
_treeNode.IndexInParent = _actor.OrderInParent;
parentTreeNode.IsLayoutLocked = false;
// Skip UI update if node won't be in a view
if (parentTreeNode.IsCollapsed)
{
TreeNode.UnlockChildrenRecursive();
}
else
{
// Try to perform layout at the level where it makes it the most performant (the least computations)
var tree = parentTreeNode.ParentTree;
if (tree != null)
{
if (tree.Parent is FlaxEngine.GUI.Panel treeParent)
treeParent.PerformLayout();
else
tree.PerformLayout();
}
else
{
parentTreeNode.PerformLayout();
}
}
}
else
{
_treeNode.Parent = parentTreeNode;
}
_treeNode.OnParentChanged(_actor, parentNode as ActorNode);
}
/// <inheritdoc />

View File

@@ -1,5 +1,11 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#if USE_LARGE_WORLDS
using Real = System.Double;
#else
using Real = System.Single;
#endif
using System;
using System.Collections.Generic;
using FlaxEditor.Content;
@@ -25,18 +31,20 @@ namespace FlaxEditor.SceneGraph.Actors
}
/// <inheritdoc />
public override bool OnVertexSnap(ref Vector3 point, out Vector3 result)
public override bool OnVertexSnap(ref Ray ray, Real hitDistance, out Vector3 result)
{
result = point;
// Find the closest vertex to bounding box point (collision detection approximation)
result = ray.GetPoint(hitDistance);
var model = ((StaticModel)Actor).Model;
if (model && !model.WaitForLoaded())
{
// TODO: move to C++ and use cached vertex buffer internally inside the Mesh
if (_vertices == null)
_vertices = new();
var pointLocal = (Float3)Actor.Transform.WorldToLocal(point);
var minDistance = float.MaxValue;
foreach (var lod in model.LODs)
var pointLocal = (Float3)Actor.Transform.WorldToLocal(result);
var minDistance = Real.MaxValue;
var lodIndex = 0; // TODO: use LOD index based on the game view
var lod = model.LODs[lodIndex];
{
var hit = false;
foreach (var mesh in lod.Meshes)

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Actions;
using FlaxEditor.Content;
using FlaxEditor.GUI;
using FlaxEditor.GUI.Drag;
@@ -14,7 +15,6 @@ using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
namespace FlaxEditor.SceneGraph.GUI
{
@@ -82,8 +82,51 @@ namespace FlaxEditor.SceneGraph.GUI
UpdateText();
}
internal void OnParentChanged(Actor actor, ActorNode parentNode)
{
// Update cached value
_orderInParent = actor.OrderInParent;
// Update UI (special case if actor is spawned and added to existing scene tree)
var parentTreeNode = parentNode?.TreeNode;
if (parentTreeNode != null && !parentTreeNode.IsLayoutLocked)
{
parentTreeNode.IsLayoutLocked = true;
Parent = parentTreeNode;
IndexInParent = _orderInParent;
parentTreeNode.IsLayoutLocked = false;
// Skip UI update if node won't be in a view
if (parentTreeNode.IsCollapsed)
{
UnlockChildrenRecursive();
}
else
{
// Try to perform layout at the level where it makes it the most performant (the least computations)
var tree = parentTreeNode.ParentTree;
if (tree != null)
{
if (tree.Parent is Panel treeParent)
treeParent.PerformLayout();
else
tree.PerformLayout();
}
else
{
parentTreeNode.PerformLayout();
}
}
}
else
{
Parent = parentTreeNode;
}
}
internal void OnOrderInParentChanged()
{
// Use cached value to check if we need to update UI layout (and update siblings order at once)
if (Parent is ActorTreeNode parent)
{
var anyChanged = false;
@@ -419,134 +462,6 @@ namespace FlaxEditor.SceneGraph.GUI
_dragHandlers.OnDragLeave();
}
[Serializable]
private class ReparentAction : IUndoAction
{
[Serialize]
private Guid[] _ids;
[Serialize]
private int _actorsCount;
[Serialize]
private Guid[] _prefabIds;
[Serialize]
private Guid[] _prefabObjectIds;
public ReparentAction(Actor actor)
: this(new List<Actor> { actor })
{
}
public ReparentAction(List<Actor> actors)
{
var allActors = new List<Actor>(Mathf.NextPowerOfTwo(actors.Count));
for (int i = 0; i < actors.Count; i++)
{
GetAllActors(allActors, actors[i]);
}
var allScripts = new List<Script>(allActors.Capacity);
GetAllScripts(allActors, allScripts);
int allCount = allActors.Count + allScripts.Count;
_actorsCount = allActors.Count;
_ids = new Guid[allCount];
_prefabIds = new Guid[allCount];
_prefabObjectIds = new Guid[allCount];
for (int i = 0; i < allActors.Count; i++)
{
_ids[i] = allActors[i].ID;
_prefabIds[i] = allActors[i].PrefabID;
_prefabObjectIds[i] = allActors[i].PrefabObjectID;
}
for (int i = 0; i < allScripts.Count; i++)
{
int j = _actorsCount + i;
_ids[j] = allScripts[i].ID;
_prefabIds[j] = allScripts[i].PrefabID;
_prefabObjectIds[j] = allScripts[i].PrefabObjectID;
}
}
public ReparentAction(Script script)
{
_actorsCount = 0;
_ids = new Guid[] { script.ID };
_prefabIds = new Guid[] { script.PrefabID };
_prefabObjectIds = new Guid[] { script.PrefabObjectID };
}
private void GetAllActors(List<Actor> allActors, Actor actor)
{
allActors.Add(actor);
for (int i = 0; i < actor.ChildrenCount; i++)
{
var child = actor.GetChild(i);
if (!allActors.Contains(child))
{
GetAllActors(allActors, child);
}
}
}
private void GetAllScripts(List<Actor> allActors, List<Script> allScripts)
{
for (int i = 0; i < allActors.Count; i++)
{
var actor = allActors[i];
for (int j = 0; j < actor.ScriptsCount; j++)
{
allScripts.Add(actor.GetScript(j));
}
}
}
/// <inheritdoc />
public string ActionString => string.Empty;
/// <inheritdoc />
public void Do()
{
// Note: prefab links are broken by the C++ backend on actor reparenting
}
/// <inheritdoc />
public void Undo()
{
// Restore links
for (int i = 0; i < _actorsCount; i++)
{
var actor = Object.Find<Actor>(ref _ids[i]);
if (actor != null && _prefabIds[i] != Guid.Empty)
{
Actor.Internal_LinkPrefab(Object.GetUnmanagedPtr(actor), ref _prefabIds[i], ref _prefabObjectIds[i]);
}
}
for (int i = _actorsCount; i < _ids.Length; i++)
{
var script = Object.Find<Script>(ref _ids[i]);
if (script != null && _prefabIds[i] != Guid.Empty)
{
Script.Internal_LinkPrefab(Object.GetUnmanagedPtr(script), ref _prefabIds[i], ref _prefabObjectIds[i]);
}
}
}
/// <inheritdoc />
public void Dispose()
{
_ids = null;
_prefabIds = null;
_prefabObjectIds = null;
}
}
/// <inheritdoc />
protected override DragDropEffect OnDragDropHeader(DragData data)
{
@@ -593,46 +508,24 @@ namespace FlaxEditor.SceneGraph.GUI
// Drag actors
if (_dragActors != null && _dragActors.HasValidDrag)
{
bool worldPositionLock = Root.GetKey(KeyboardKeys.Control) == false;
var singleObject = _dragActors.Objects.Count == 1;
if (singleObject)
{
var targetActor = _dragActors.Objects[0].Actor;
var customAction = targetActor.HasPrefabLink ? new ReparentAction(targetActor) : null;
using (new UndoBlock(ActorNode.Root.Undo, targetActor, "Change actor parent", customAction))
{
targetActor.SetParent(newParent, worldPositionLock, true);
targetActor.OrderInParent = newOrder;
}
}
else
{
var targetActors = _dragActors.Objects.ConvertAll(x => x.Actor);
var customAction = targetActors.Any(x => x.HasPrefabLink) ? new ReparentAction(targetActors) : null;
using (new UndoMultiBlock(ActorNode.Root.Undo, targetActors, "Change actors parent", customAction))
{
for (int i = 0; i < targetActors.Count; i++)
{
var targetActor = targetActors[i];
targetActor.SetParent(newParent, worldPositionLock, true);
targetActor.OrderInParent = newOrder;
}
}
}
bool worldPositionsStays = Root.GetKey(KeyboardKeys.Control) == false;
var objects = new SceneObject[_dragActors.Objects.Count];
for (int i = 0; i < objects.Length; i++)
objects[i] = _dragActors.Objects[i].Actor;
var action = new ParentActorsAction(objects, newParent, newOrder, worldPositionsStays);
ActorNode.Root.Undo?.AddAction(action);
action.Do();
result = DragDropEffect.Move;
}
// Drag scripts
else if (_dragScripts != null && _dragScripts.HasValidDrag)
{
foreach (var script in _dragScripts.Objects)
{
var customAction = script.HasPrefabLink ? new ReparentAction(script) : null;
using (new UndoBlock(ActorNode.Root.Undo, script, "Change script parent", customAction))
{
script.SetParent(newParent, true);
}
}
var objects = new SceneObject[_dragScripts.Objects.Count];
for (int i = 0; i < objects.Length; i++)
objects[i] = _dragScripts.Objects[i];
var action = new ParentActorsAction(objects, newParent, newOrder);
ActorNode.Root.Undo?.AddAction(action);
action.Do();
Select();
result = DragDropEffect.Move;
}

View File

@@ -41,7 +41,11 @@ namespace FlaxEditor.SceneGraph
protected SceneGraphNode(Guid id)
{
ID = id;
SceneGraphFactory.Nodes.Add(id, this);
if (SceneGraphFactory.Nodes.TryGetValue(id, out var duplicate) && duplicate != null)
{
Editor.LogWarning($"Duplicated Scene Graph node with ID {FlaxEngine.Json.JsonSerializer.GetStringID(id)} of type '{duplicate.GetType().FullName}'");
}
SceneGraphFactory.Nodes[id] = this;
}
/// <summary>
@@ -94,18 +98,6 @@ namespace FlaxEditor.SceneGraph
/// </summary>
public virtual bool CanTransform => true;
/// <summary>
/// Gets a value indicating whether this node can be used for the vertex snapping feature.
/// </summary>
public bool CanVertexSnap
{
get
{
var v = Vector3.Zero;
return OnVertexSnap(ref v, out _);
}
}
/// <summary>
/// Gets a value indicating whether this <see cref="SceneGraphNode"/> is active.
/// </summary>
@@ -365,14 +357,15 @@ namespace FlaxEditor.SceneGraph
}
/// <summary>
/// Performs the vertex snapping of a given point on the object surface that is closest to a given location.
/// Performs the vertex snapping for a given ray and hitDistance.
/// </summary>
/// <param name="point">The position to snap.</param>
/// <param name="ray">The ray to raycast.</param>
/// <param name="hitDistance">Hit distance from ray to object bounding box.</param>
/// <param name="result">The result point on the object mesh that is closest to the specified location.</param>
/// <returns>True if got a valid result value, otherwise false (eg. if missing data or not initialized).</returns>
public virtual bool OnVertexSnap(ref Vector3 point, out Vector3 result)
public virtual bool OnVertexSnap(ref Ray ray, Real hitDistance, out Vector3 result)
{
result = point;
result = Vector3.Zero;
return false;
}

View File

@@ -92,11 +92,12 @@ namespace FlaxEditor.SceneGraph
private static void FillTree(SceneGraphNode node, List<SceneGraphNode> result)
{
result.AddRange(node.ChildNodes);
for (int i = 0; i < node.ChildNodes.Count; i++)
{
FillTree(node.ChildNodes[i], result);
}
if (result.Contains(node))
return;
result.Add(node);
var children = node.ChildNodes;
for (int i = 0; i < children.Count; i++)
FillTree(children[i], result);
}
/// <summary>
@@ -109,21 +110,9 @@ namespace FlaxEditor.SceneGraph
{
if (nodes == null || result == null)
throw new ArgumentNullException();
result.Clear();
for (var i = 0; i < nodes.Count; i++)
{
var target = nodes[i];
// Check if has been already added
if (result.Contains(target))
continue;
// Add whole child tree to the results
result.Add(target);
FillTree(target, result);
}
FillTree(nodes[i], result);
}
/// <summary>
@@ -150,7 +139,6 @@ namespace FlaxEditor.SceneGraph
if (node == null || result == null)
throw new ArgumentNullException();
result.Clear();
result.Add(node);
FillTree(node, result);
}
}

View File

@@ -602,7 +602,7 @@ namespace FlaxEditor.Surface.Archetypes
var dataB = (Guid)_node.Values[5 + i * 2];
pointsAnims[i] = dataB;
pointsLocations[i] = new Float2(dataA.X, 0.0f);
pointsLocations[i] = new Float2(Mathf.Clamp(dataA.X, rangeX.X, rangeX.Y), 0.0f);
}
}
@@ -681,6 +681,9 @@ namespace FlaxEditor.Surface.Archetypes
{
_animationX.Value = 0.0f;
}
var ranges = (Float4)Values[0];
_animationX.MinValue = ranges.X;
_animationX.MaxValue = ranges.Y;
_animationXLabel.Enabled = isValid;
_animationX.Enabled = isValid;
}
@@ -732,7 +735,7 @@ namespace FlaxEditor.Surface.Archetypes
var dataB = (Guid)_node.Values[5 + i * 2];
pointsAnims[i] = dataB;
pointsLocations[i] = new Float2(dataA.X, dataA.Y);
pointsLocations[i] = new Float2(Mathf.Clamp(dataA.X, rangeX.X, rangeX.Y), Mathf.Clamp(dataA.Y, rangeY.X, rangeY.Y));
}
}
@@ -843,6 +846,11 @@ namespace FlaxEditor.Surface.Archetypes
_animationX.Value = 0.0f;
_animationY.Value = 0.0f;
}
var ranges = (Float4)Values[0];
_animationX.MinValue = ranges.X;
_animationX.MaxValue = ranges.Y;
_animationY.MinValue = ranges.Z;
_animationY.MaxValue = ranges.W;
_animationXLabel.Enabled = isValid;
_animationX.Enabled = isValid;
_animationYLabel.Enabled = isValid;

View File

@@ -350,7 +350,7 @@ namespace FlaxEditor.Surface.Archetypes
// Paint Background
if (_isSelected)
Render2D.DrawRectangle(_textRect, Color.Orange);
Render2D.DrawRectangle(_textRect, style.SelectionBorder);
BackgroundColor = style.BackgroundNormal;
var dragAreaColor = BackgroundColor / 2.0f;
@@ -1078,7 +1078,7 @@ namespace FlaxEditor.Surface.Archetypes
// Paint Background
if (_isSelected)
Render2D.DrawRectangle(_textRect, Color.Orange);
Render2D.DrawRectangle(_textRect, style.SelectionBorder);
BackgroundColor = style.BackgroundNormal;
var dragAreaColor = BackgroundColor / 2.0f;

View File

@@ -239,6 +239,7 @@ namespace FlaxEditor.Surface.Archetypes
ConnectionsHints = ConnectionsHint.Numeric,
IndependentBoxes = new[] { 0, 1, 2 },
DependentBoxes = new[] { 3 },
SortScore = -1, // Lower sort score to not go above Multiply node
DefaultValues = new object[]
{
1.0f,

View File

@@ -128,7 +128,7 @@ namespace FlaxEditor.Surface.Archetypes
Render2D.DrawSprite(Editor.Instance.Icons.DragBar12, _arrangeButtonRect, _arrangeButtonInUse ? Color.Orange : dragBarColor);
if (_arrangeButtonInUse && ArrangeAreaCheck(out _, out var arrangeTargetRect))
{
Render2D.FillRectangle(arrangeTargetRect, Color.Orange * 0.8f);
Render2D.FillRectangle(arrangeTargetRect, style.Selection);
}
// Disabled overlay

View File

@@ -583,7 +583,7 @@ namespace FlaxEditor.Surface.ContextMenu
// Check if surface has any parameters
var parameters = _parametersGetter?.Invoke();
int count = parameters?.Count(x => x.IsPublic) ?? 0;
int count = parameters?.Count ?? 0;
if (count > 0)
{
// TODO: cache the allocated memory to reduce dynamic allocations
@@ -592,28 +592,6 @@ namespace FlaxEditor.Surface.ContextMenu
var archetypes = new NodeArchetype[count];
int archetypeIndex = 0;
// ReSharper disable once PossibleNullReferenceException
for (int i = 0; i < parameters.Count; i++)
{
var param = parameters[i];
if (!param.IsPublic)
continue;
var node = (NodeArchetype)_parameterGetNodeArchetype.Clone();
node.Title = "Get " + param.Name;
node.DefaultValues[0] = param.ID;
archetypes[archetypeIndex++] = node;
if (_parameterSetNodeArchetype != null)
{
node = (NodeArchetype)_parameterSetNodeArchetype.Clone();
node.Title = "Set " + param.Name;
node.DefaultValues[0] = param.ID;
node.DefaultValues[1] = TypeUtils.GetDefaultValue(param.Type);
archetypes[archetypeIndex++] = node;
}
}
var groupArchetype = new GroupArchetype
{
GroupID = 6,
@@ -626,26 +604,39 @@ namespace FlaxEditor.Surface.ContextMenu
group.ArrowImageOpened = new SpriteBrush(Style.Current.ArrowDown);
group.ArrowImageClosed = new SpriteBrush(Style.Current.ArrowRight);
group.Close(false);
archetypeIndex = 0;
// ReSharper disable once PossibleNullReferenceException
for (int i = 0; i < parameters.Count; i++)
{
var param = parameters[i];
if (!param.IsPublic)
continue;
var item = new VisjectCMItem(group, groupArchetype, archetypes[archetypeIndex++])
// Define Getter node and create CM item
var node = (NodeArchetype)_parameterGetNodeArchetype.Clone();
node.Title = "Get " + param.Name;
node.DefaultValues[0] = param.ID;
archetypes[archetypeIndex++] = node;
var item = new VisjectCMItem(group, groupArchetype, node)
{
Parent = group
};
// Define Setter node and create CM item if parameter has a setter
if (_parameterSetNodeArchetype != null)
{
item = new VisjectCMItem(group, groupArchetype, archetypes[archetypeIndex++])
node = (NodeArchetype)_parameterSetNodeArchetype.Clone();
node.Title = "Set " + param.Name;
node.DefaultValues[0] = param.ID;
node.DefaultValues[1] = TypeUtils.GetDefaultValue(param.Type);
archetypes[archetypeIndex++] = node;
item = new VisjectCMItem(group, groupArchetype, node)
{
Parent = group
};
}
}
group.SortChildren();
group.UnlockChildrenRecursive();
group.Parent = _groupsPanel;

View File

@@ -181,7 +181,8 @@ namespace FlaxEditor.Surface
if (_isResizing)
{
// Draw overlay
Render2D.FillRectangle(_resizeButtonRect, Color.Orange * 0.3f);
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
}
// Resize button

View File

@@ -96,9 +96,10 @@ namespace FlaxEditor.Surface
/// <remarks>Called only when user is selecting nodes using rectangle tool.</remarks>
protected virtual void DrawSelection()
{
var style = FlaxEngine.GUI.Style.Current;
var selectionRect = Rectangle.FromPoints(_leftMouseDownPos, _mousePos);
Render2D.FillRectangle(selectionRect, Color.Orange * 0.4f);
Render2D.DrawRectangle(selectionRect, Color.Orange);
Render2D.FillRectangle(selectionRect, style.Selection);
Render2D.DrawRectangle(selectionRect, style.SelectionBorder);
}
/// <summary>

View File

@@ -382,6 +382,7 @@ namespace FlaxEditor.Surface
{
new InputActionsContainer.Binding(options => options.Delete, Delete),
new InputActionsContainer.Binding(options => options.SelectAll, SelectAll),
new InputActionsContainer.Binding(options => options.DeselectAll, DeselectAll),
new InputActionsContainer.Binding(options => options.Copy, Copy),
new InputActionsContainer.Binding(options => options.Paste, Paste),
new InputActionsContainer.Binding(options => options.Cut, Cut),
@@ -611,17 +612,14 @@ namespace FlaxEditor.Surface
_context.MarkAsModified(graphEdited);
}
/// <summary>
/// Selects all the nodes.
/// </summary>
public void SelectAll()
private void BulkSelectUpdate(bool select = true)
{
bool selectionChanged = false;
for (int i = 0; i < _rootControl.Children.Count; i++)
{
if (_rootControl.Children[i] is SurfaceControl control && !control.IsSelected)
if (_rootControl.Children[i] is SurfaceControl control && control.IsSelected != select)
{
control.IsSelected = true;
control.IsSelected = select;
selectionChanged = true;
}
}
@@ -629,6 +627,22 @@ namespace FlaxEditor.Surface
SelectionChanged?.Invoke();
}
/// <summary>
/// Selects all the nodes.
/// </summary>
public void SelectAll()
{
BulkSelectUpdate(true);
}
/// <summary>
/// Deelects all the nodes.
/// </summary>
public void DeselectAll()
{
BulkSelectUpdate(false);
}
/// <summary>
/// Clears the selection.
/// </summary>

View File

@@ -17,6 +17,30 @@ namespace FlaxEditor.Actions
[Serializable]
sealed class BreakPrefabLinkAction : IUndoAction
{
private struct Item
{
public Guid ID;
public Guid PrefabID;
public Guid PrefabObjectID;
public unsafe Item(SceneObject obj, List<Item> nestedPrefabLinks)
{
ID = obj.ID;
PrefabID = obj.PrefabID;
PrefabObjectID = obj.PrefabObjectID;
if (nestedPrefabLinks != null)
{
// Check if this object comes from another nested prefab (to break link only from the top-level prefab)
Item nested;
nested.ID = ID;
fixed (Item* i = &this)
Editor.Internal_GetPrefabNestedObject(new IntPtr(&i->PrefabID), new IntPtr(&i->PrefabObjectID), new IntPtr(&nested.PrefabID), new IntPtr(&nested.PrefabObjectID));
if (nested.PrefabID != Guid.Empty && nested.PrefabObjectID != Guid.Empty)
nestedPrefabLinks.Add(nested);
}
}
}
[Serialize]
private readonly bool _isBreak;
@@ -24,25 +48,18 @@ namespace FlaxEditor.Actions
private Guid _actorId;
[Serialize]
private Guid _prefabId;
private List<Item> _items = new();
[Serialize]
private Dictionary<Guid, Guid> _prefabObjectIds;
private BreakPrefabLinkAction(bool isBreak, Guid actorId, Guid prefabId)
private BreakPrefabLinkAction(bool isBreak, Guid actorId)
{
_isBreak = isBreak;
_actorId = actorId;
_prefabId = prefabId;
}
private BreakPrefabLinkAction(bool isBreak, Actor actor)
{
_isBreak = isBreak;
_actorId = actor.ID;
_prefabId = actor.PrefabID;
_prefabObjectIds = new Dictionary<Guid, Guid>(1024);
CollectIds(actor);
}
@@ -55,7 +72,7 @@ namespace FlaxEditor.Actions
{
if (actor == null)
throw new ArgumentNullException(nameof(actor));
return new BreakPrefabLinkAction(true, actor.ID, Guid.Empty);
return new BreakPrefabLinkAction(true, actor.ID);
}
/// <summary>
@@ -96,53 +113,45 @@ namespace FlaxEditor.Actions
/// <inheritdoc />
public void Dispose()
{
_prefabObjectIds.Clear();
_items.Clear();
}
private void DoLink()
{
if (_prefabObjectIds == null)
throw new Exception("Cannot link prefab. Missing objects Ids mapping.");
var actor = Object.Find<Actor>(ref _actorId);
if (actor == null)
throw new Exception("Cannot link prefab. Missing actor.");
// Restore cached links
foreach (var e in _prefabObjectIds)
{
var objId = e.Key;
var prefabObjId = e.Value;
var obj = Object.Find<Object>(ref objId);
if (obj is Actor)
{
Actor.Internal_LinkPrefab(Object.GetUnmanagedPtr(obj), ref _prefabId, ref prefabObjId);
}
else if (obj is Script)
{
Script.Internal_LinkPrefab(Object.GetUnmanagedPtr(obj), ref _prefabId, ref prefabObjId);
}
}
Editor.Instance.Scene.MarkSceneEdited(actor.Scene);
Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout();
Link(_items);
Refresh(actor);
}
private void CollectIds(Actor actor)
private void Link(List<Item> items)
{
_prefabObjectIds.Add(actor.ID, actor.PrefabObjectID);
for (int i = 0; i < items.Count; i++)
{
var item = items[i];
var obj = Object.Find<Object>(ref item.ID);
if (obj != null)
SceneObject.Internal_LinkPrefab(Object.GetUnmanagedPtr(obj), ref item.PrefabID, ref item.PrefabObjectID);
}
}
private void CollectIds(Actor actor, List<Item> nestedPrefabLinks = null)
{
_items.Add(new Item(actor, nestedPrefabLinks));
for (int i = 0; i < actor.ChildrenCount; i++)
{
CollectIds(actor.GetChild(i));
}
CollectIds(actor.GetChild(i), nestedPrefabLinks);
for (int i = 0; i < actor.ScriptsCount; i++)
{
var script = actor.GetScript(i);
_prefabObjectIds.Add(script.ID, script.PrefabObjectID);
}
_items.Add(new Item(actor.GetScript(i), nestedPrefabLinks));
}
private void Refresh(Actor actor)
{
Editor.Instance.Scene.MarkSceneEdited(actor.Scene);
Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout();
}
private void DoBreak()
@@ -153,18 +162,18 @@ namespace FlaxEditor.Actions
if (!actor.HasPrefabLink)
throw new Exception("Cannot break missing prefab link.");
if (_prefabObjectIds == null)
_prefabObjectIds = new Dictionary<Guid, Guid>(1024);
else
_prefabObjectIds.Clear();
CollectIds(actor);
_prefabId = actor.PrefabID;
// Cache 'prev' state and extract any nested prefab instances to remain
_items.Clear();
var nestedPrefabLinks = new List<Item>();
CollectIds(actor, nestedPrefabLinks);
// Break prefab linkage
actor.BreakPrefabLink();
Editor.Instance.Scene.MarkSceneEdited(actor.Scene);
Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout();
// Restore prefab link for nested instances
Link(nestedPrefabLinks);
Refresh(actor);
}
}
}

View File

@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.Utilities;
@@ -25,6 +24,9 @@ namespace FlaxEditor.Actions
[Serialize]
private Guid[] _nodeParentsIDs;
[Serialize]
private int[] _nodeParentsOrders;
[Serialize]
private Guid[] _prefabIds;
@@ -43,12 +45,35 @@ namespace FlaxEditor.Actions
[Serialize]
protected List<SceneGraphNode> _nodeParents;
/// <summary>
/// Initializes a new instance of the <see cref="DeleteActorsAction"/> class.
/// </summary>
/// <param name="actor">The actor.</param>
/// <param name="isInverted">If set to <c>true</c> action will be inverted - instead of delete it will create actors.</param>
/// <param name="preserveOrder">If set to <c>true</c> action will be preserve actors order when performing undo.</param>
internal DeleteActorsAction(Actor actor, bool isInverted = false, bool preserveOrder = true)
: this(new List<SceneGraphNode>(1) { SceneGraphFactory.FindNode(actor.ID) }, isInverted, preserveOrder)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DeleteActorsAction"/> class.
/// </summary>
/// <param name="node">The object.</param>
/// <param name="isInverted">If set to <c>true</c> action will be inverted - instead of delete it will create actors.</param>
/// <param name="preserveOrder">If set to <c>true</c> action will be preserve actors order when performing undo.</param>
internal DeleteActorsAction(SceneGraphNode node, bool isInverted = false, bool preserveOrder = true)
: this(new List<SceneGraphNode>(1) { node }, isInverted, preserveOrder)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DeleteActorsAction"/> class.
/// </summary>
/// <param name="nodes">The objects.</param>
/// <param name="isInverted">If set to <c>true</c> action will be inverted - instead of delete it will be create actors.</param>
internal DeleteActorsAction(List<SceneGraphNode> nodes, bool isInverted = false)
/// <param name="isInverted">If set to <c>true</c> action will be inverted - instead of delete it will create actors.</param>
/// <param name="preserveOrder">If set to <c>true</c> action will be preserve actors order when performing undo.</param>
internal DeleteActorsAction(List<SceneGraphNode> nodes, bool isInverted = false, bool preserveOrder = true)
{
_isInverted = isInverted;
@@ -82,6 +107,12 @@ namespace FlaxEditor.Actions
_nodeParentsIDs = new Guid[_nodeParents.Count];
for (int i = 0; i < _nodeParentsIDs.Length; i++)
_nodeParentsIDs[i] = _nodeParents[i].ID;
if (preserveOrder)
{
_nodeParentsOrders = new int[_nodeParents.Count];
for (int i = 0; i < _nodeParentsOrders.Length; i++)
_nodeParentsOrders[i] = _nodeParents[i].OrderInParent;
}
// Serialize actors
_actorsData = Actor.ToBytes(actors.ToArray());
@@ -122,6 +153,7 @@ namespace FlaxEditor.Actions
{
_actorsData = null;
_nodeParentsIDs = null;
_nodeParentsOrders = null;
_prefabIds = null;
_prefabObjectIds = null;
_nodeParents.Clear();
@@ -211,6 +243,8 @@ namespace FlaxEditor.Actions
if (foundNode is ActorNode node)
{
nodes.Add(node);
if (_nodeParentsOrders != null)
node.Actor.OrderInParent = _nodeParentsOrders[i];
}
}
nodes.BuildNodesParents(_nodeParents);

View File

@@ -0,0 +1,199 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using FlaxEngine;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Actions
{
/// <summary>
/// Implementation of <see cref="IUndoAction"/> used to change parent for <see cref="Actor"/> or <see cref="Script"/>.
/// </summary>
/// <seealso cref="FlaxEditor.IUndoAction" />
[Serializable]
class ParentActorsAction : IUndoAction
{
private struct Item
{
public Guid ID;
public Guid Parent;
public int OrderInParent;
public Transform LocalTransform;
}
[Serialize]
private bool _worldPositionsStays;
[Serialize]
private Guid _newParent;
[Serialize]
private int _newOrder;
[Serialize]
private Item[] _items;
[Serialize]
private Guid[] _idsForPrefab;
[Serialize]
private Guid[] _prefabIds;
[Serialize]
private Guid[] _prefabObjectIds;
public ParentActorsAction(SceneObject[] objects, Actor newParent, int newOrder, bool worldPositionsStays = true)
{
// Sort source objects to provide deterministic behavior
Array.Sort(objects, SortObjects);
// Cache initial state for undo
_worldPositionsStays = worldPositionsStays;
_newParent = newParent.ID;
_newOrder = newOrder;
_items = new Item[objects.Length];
for (int i = 0; i < objects.Length; i++)
{
var obj = objects[i];
_items[i] = new Item
{
ID = obj.ID,
Parent = obj.Parent?.ID ?? Guid.Empty,
OrderInParent = obj.OrderInParent,
LocalTransform = obj is Actor actor ? actor.LocalTransform : Transform.Identity,
};
}
// Collect all objects that have prefab links so they can be restored on undo
var prefabs = new List<SceneObject>();
for (int i = 0; i < objects.Length; i++)
GetAllPrefabs(prefabs, objects[i]);
if (prefabs.Count != 0)
{
// Cache ids of all objects
_idsForPrefab = new Guid[prefabs.Count];
_prefabIds = new Guid[prefabs.Count];
_prefabObjectIds = new Guid[prefabs.Count];
for (int i = 0; i < prefabs.Count; i++)
{
var obj = prefabs[i];
_idsForPrefab[i] = obj.ID;
_prefabIds[i] = obj.PrefabID;
_prefabObjectIds[i] = obj.PrefabObjectID;
}
}
}
private static int SortObjects(SceneObject a, SceneObject b)
{
// By parent
var aParent = Object.GetUnmanagedPtr(a.Parent);
var bParent = Object.GetUnmanagedPtr(b.Parent);
if (aParent == bParent)
{
// By index in parent
var aOrder = a.OrderInParent;
var bOrder = b.OrderInParent;
return aOrder.CompareTo(bOrder);
}
return aParent.CompareTo(bParent);
}
private static void GetAllPrefabs(List<SceneObject> result, SceneObject obj)
{
if (result.Contains(obj))
return;
if (obj.HasPrefabLink)
result.Add(obj);
if (obj is Actor actor)
{
for (int i = 0; i < actor.ScriptsCount; i++)
GetAllPrefabs(result, actor.GetScript(i));
for (int i = 0; i < actor.ChildrenCount; i++)
GetAllPrefabs(result, actor.GetChild(i));
}
}
public string ActionString => "Change parent";
public void Do()
{
// Perform action
var newParent = Object.Find<Actor>(ref _newParent);
if (newParent == null)
{
Editor.LogError("Missing actor to change objects parent.");
return;
}
var order = _newOrder;
var scenes = new HashSet<Scene> { newParent.Scene };
for (int i = 0; i < _items.Length; i++)
{
var item = _items[i];
var obj = Object.Find<SceneObject>(ref item.ID);
if (obj != null)
{
scenes.Add(obj.Parent.Scene);
if (obj is Actor actor)
actor.SetParent(newParent, _worldPositionsStays, true);
else
obj.Parent = newParent;
if (order != -1)
obj.OrderInParent = order++;
}
}
// Prefab links are broken by the C++ backend on actor reparenting
// Mark scenes as edited
foreach (var scene in scenes)
Editor.Instance.Scene.MarkSceneEdited(scene);
}
public void Undo()
{
// Restore state
for (int i = 0; i < _items.Length; i++)
{
var item = _items[i];
var obj = Object.Find<SceneObject>(ref item.ID);
if (obj != null)
{
var parent = Object.Find<Actor>(ref item.Parent);
if (parent != null)
obj.Parent = parent;
if (obj is Actor actor)
actor.LocalTransform = item.LocalTransform;
}
}
for (int j = 0; j < _items.Length; j++) // TODO: find a better way ensure the order is properly restored when moving back multiple objects
for (int i = 0; i < _items.Length; i++)
{
var item = _items[i];
var obj = Object.Find<SceneObject>(ref item.ID);
if (obj != null)
obj.OrderInParent = item.OrderInParent;
}
// Restore prefab links (if any was in use)
if (_idsForPrefab != null)
{
for (int i = 0; i < _idsForPrefab.Length; i++)
{
var obj = Object.Find<SceneObject>(ref _idsForPrefab[i]);
if (obj != null && _prefabIds[i] != Guid.Empty)
SceneObject.Internal_LinkPrefab(Object.GetUnmanagedPtr(obj), ref _prefabIds[i], ref _prefabObjectIds[i]);
}
}
}
public void Dispose()
{
_items = null;
_idsForPrefab = null;
_prefabIds = null;
_prefabObjectIds = null;
}
}
}

View File

@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.SceneGraph;
using FlaxEngine;
@@ -165,7 +164,7 @@ namespace FlaxEditor.Actions
var child = children[j];
if (child != actor && child.Name == name)
{
string newName = Utilities.Utils.IncrementNameNumber(name, x => IsNameValid(x));
string newName = Utilities.Utils.IncrementNameNumber(name, IsNameValid);
foundNamesResults[newName] = true;
actor.Name = newName;
// Multiple actors may have the same name, continue

View File

@@ -38,9 +38,6 @@ namespace FlaxEditor
/// <summary>
/// Gets the undo operations stack.
/// </summary>
/// <value>
/// The undo operations stack.
/// </value>
public HistoryStack UndoOperationsStack { get; }
/// <summary>
@@ -138,6 +135,7 @@ namespace FlaxEditor
return;
_snapshots.Add(snapshotInstance, new UndoInternal(snapshotInstance, actionString));
}
/// <summary>
@@ -152,9 +150,7 @@ namespace FlaxEditor
return;
if (snapshotInstance == null)
{
snapshotInstance = _snapshots.Last().Key;
}
var action = _snapshots[snapshotInstance].End(snapshotInstance);
_snapshots.Remove(snapshotInstance);
@@ -249,9 +245,7 @@ namespace FlaxEditor
return;
if (snapshotInstance == null)
{
snapshotInstance = (object[])_snapshots.Last().Key;
}
var action = _snapshots[snapshotInstance].End(snapshotInstance);
_snapshots.Remove(snapshotInstance);
@@ -325,12 +319,10 @@ namespace FlaxEditor
{
if (action == null)
throw new ArgumentNullException();
if (!Enabled)
return;
UndoOperationsStack.Push(action);
OnAction(action);
}

View File

@@ -11,7 +11,6 @@ using System.Globalization;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
@@ -20,6 +19,7 @@ using FlaxEditor.GUI.Input;
using FlaxEditor.GUI.Tree;
using FlaxEditor.SceneGraph;
using FlaxEngine;
using FlaxEngine.Json;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
using FlaxEditor.Windows;
@@ -203,6 +203,22 @@ namespace FlaxEditor.Utilities
}
}
/// <summary>
/// Clones the value. handles non-value types (such as arrays) that need deep value cloning.
/// </summary>
/// <param name="value">The source value to clone.</param>
/// <returns>The duplicated value.</returns>
internal static object CloneValue(object value)
{
// For objects (eg. arrays) we need to clone them to prevent editing default/reference value within editor
if (value != null && (!value.GetType().IsValueType || !value.GetType().IsClass))
{
var json = JsonSerializer.Serialize(value);
value = JsonSerializer.Deserialize(json, value.GetType());
}
return value;
}
/// <summary>
/// The colors for the keyframes used by the curve editor.
/// </summary>
@@ -1289,6 +1305,7 @@ namespace FlaxEditor.Utilities
inputActions.Add(options => options.Paste, Editor.Instance.SceneEditing.Paste);
inputActions.Add(options => options.Duplicate, Editor.Instance.SceneEditing.Duplicate);
inputActions.Add(options => options.SelectAll, Editor.Instance.SceneEditing.SelectAllScenes);
inputActions.Add(options => options.DeselectAll, Editor.Instance.SceneEditing.DeselectAllScenes);
inputActions.Add(options => options.Delete, Editor.Instance.SceneEditing.Delete);
inputActions.Add(options => options.Search, () => Editor.Instance.Windows.SceneWin.Search());
inputActions.Add(options => options.MoveActorToViewport, Editor.Instance.UI.MoveActorToViewport);

View File

@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Content;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu;
@@ -591,15 +590,32 @@ namespace FlaxEditor.Viewport
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)
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 = Rectangle.FromPoints(control.PointToParent(this, Float2.Zero), control.PointToParent(this, control.Size));
Render2D.DrawRectangle(bounds, Editor.Instance.Options.Options.Visual.SelectionOutlineColor0, Editor.Instance.Options.Options.Visual.UISelectionOutlineSize);
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 />
@@ -799,6 +815,15 @@ namespace FlaxEditor.Viewport
{
base.OnDebugDraw(context, ref renderContext);
// Collect selected objects debug shapes again when DebugDraw is active with a custom context
_debugDrawData.Clear();
var selectedParents = TransformGizmo.SelectedParents;
for (int i = 0; i < selectedParents.Count; i++)
{
if (selectedParents[i].IsActiveInHierarchy)
selectedParents[i].OnDebugDraw(_debugDrawData);
}
unsafe
{
fixed (IntPtr* actors = _debugDrawData.ActorsPtrs)

View File

@@ -239,6 +239,8 @@ namespace FlaxEditor.Viewport.Previews
/// <inheritdoc />
public override void OnDestroy()
{
if (IsDisposing)
return;
Object.Destroy(ref PreviewLight);
Object.Destroy(ref EnvProbe);
Object.Destroy(ref Sky);

View File

@@ -184,6 +184,7 @@ namespace FlaxEditor.Viewport.Previews
break;
default: throw new ArgumentOutOfRangeException();
}
samplesPerIndex = Math.Max(samplesPerIndex, info.NumChannels);
const uint maxSamplesPerIndex = 64;
uint samplesPerIndexDiff = Math.Max(1, samplesPerIndex / Math.Min(samplesPerIndex, maxSamplesPerIndex));

View File

@@ -355,6 +355,8 @@ namespace FlaxEditor.Windows.Assets
try
{
Editor.Scene.OnSaveStart(_viewport);
// Simply update changes
Editor.Prefabs.ApplyAll(_viewport.Instance);
@@ -371,6 +373,10 @@ namespace FlaxEditor.Windows.Assets
throw;
}
finally
{
Editor.Scene.OnSaveEnd(_viewport);
}
}
/// <inheritdoc />

View File

@@ -170,6 +170,8 @@ namespace FlaxEditor.Windows
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
InputOptions options = FlaxEditor.Editor.Instance.Options.Options.Input;
// Up
if (key == KeyboardKeys.ArrowUp)
{
@@ -200,7 +202,7 @@ namespace FlaxEditor.Windows
Open();
}
// Ctrl+C
else if (key == KeyboardKeys.C && Root.GetKey(KeyboardKeys.Control))
else if (options.Copy.Process(this))
{
Copy();
return true;

View File

@@ -23,6 +23,7 @@ namespace FlaxEditor.Windows
private bool _showGUI = true;
private bool _showDebugDraw = false;
private bool _isMaximized = false, _isUnlockingMouse = false;
private bool _isFloating = false, _isBorderless = false;
private bool _cursorVisible = true;
private float _gameStartTime;
private GUI.Docking.DockState _maximizeRestoreDockState;
@@ -68,7 +69,7 @@ namespace FlaxEditor.Windows
}
/// <summary>
/// Gets or sets a value indicating whether game window is maximized (only in play mode).
/// Gets or sets a value indicating whether the game window is maximized (only in play mode).
/// </summary>
private bool IsMaximized
{
@@ -78,20 +79,42 @@ namespace FlaxEditor.Windows
if (_isMaximized == value)
return;
_isMaximized = value;
if (value)
{
IsFloating = true;
var rootWindow = RootWindow;
rootWindow.Maximize();
}
else
{
IsFloating = false;
}
}
}
/// <summary>
/// Gets or sets a value indicating whether the game window is floating (popup, only in play mode).
/// </summary>
private bool IsFloating
{
get => _isFloating;
set
{
if (_isFloating == value)
return;
_isFloating = value;
var rootWindow = RootWindow;
if (value)
{
// Maximize
_maximizeRestoreDockTo = _dockedTo;
_maximizeRestoreDockState = _dockedTo.TryGetDockState(out _);
if (_maximizeRestoreDockState != GUI.Docking.DockState.Float)
{
var monitorBounds = Platform.GetMonitorBounds(PointToScreen(Size * 0.5f));
ShowFloating(monitorBounds.Location + new Float2(200, 200), Float2.Zero, WindowStartPosition.Manual);
rootWindow = RootWindow;
var size = DefaultSize;
var location = monitorBounds.Location + monitorBounds.Size * 0.5f - size * 0.5f;
ShowFloating(location, size, WindowStartPosition.Manual);
}
if (rootWindow != null && !rootWindow.IsMaximized)
rootWindow.Maximize();
}
else
{
@@ -105,6 +128,33 @@ namespace FlaxEditor.Windows
}
}
/// <summary>
/// Gets or sets a value indicating whether the game window is borderless (only in play mode).
/// </summary>
private bool IsBorderless
{
get => _isBorderless;
set
{
if (_isBorderless == value)
return;
_isBorderless = value;
if (value)
{
IsFloating = true;
var rootWindow = RootWindow;
var monitorBounds = Platform.GetMonitorBounds(rootWindow.RootWindow.Window.ClientPosition);
rootWindow.Window.Position = monitorBounds.Location;
rootWindow.Window.SetBorderless(true);
rootWindow.Window.ClientSize = monitorBounds.Size;
}
else
{
IsFloating = false;
}
}
}
/// <summary>
/// Gets or sets a value indicating whether center mouse position on window focus in play mode. Helps when working with games that lock mouse cursor.
/// </summary>
@@ -318,6 +368,20 @@ namespace FlaxEditor.Windows
InputActions.Add(options => options.TakeScreenshot, () => Screenshot.Capture(string.Empty));
InputActions.Add(options => options.DebuggerUnlockMouse, UnlockMouseInPlay);
InputActions.Add(options => options.ToggleFullscreen, () => { if (Editor.IsPlayMode) IsMaximized = !IsMaximized; });
InputActions.Add(options => options.Play, Editor.Instance.Simulation.DelegatePlayOrStopPlayInEditor);
InputActions.Add(options => options.Pause, Editor.Instance.Simulation.RequestResumeOrPause);
InputActions.Add(options => options.StepFrame, Editor.Instance.Simulation.RequestPlayOneFrame);
InputActions.Add(options => options.ProfilerStartStop, () =>
{
bool recording = !Editor.Instance.Windows.ProfilerWin.LiveRecording;
Editor.Instance.Windows.ProfilerWin.LiveRecording = recording;
Editor.Instance.UI.AddStatusMessage($"Profiling {(recording ? "started" : "stopped")}.");
});
InputActions.Add(options => options.ProfilerClear, () =>
{
Editor.Instance.Windows.ProfilerWin.Clear();
Editor.Instance.UI.AddStatusMessage($"Profiling results cleared.");
});
}
private void ChangeViewportRatio(ViewportScaleOptions v)
@@ -474,7 +538,9 @@ namespace FlaxEditor.Windows
/// <inheritdoc />
public override void OnPlayEnd()
{
IsFloating = false;
IsMaximized = false;
IsBorderless = false;
Cursor = CursorType.Default;
}
@@ -856,7 +922,7 @@ namespace FlaxEditor.Windows
var selection = Editor.SceneEditing.Selection;
for (var i = 0; i < selection.Count; i++)
{
if (selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null)
if (selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null && controlActor.Control.VisibleInHierarchy && controlActor.Control.RootWindow != null)
{
if (!drawAnySelectedControl)
{
@@ -902,7 +968,7 @@ namespace FlaxEditor.Windows
if (animTime < 0)
{
float alpha = Mathf.Saturate(-animTime / fadeOutTime);
Render2D.DrawRectangle(new Rectangle(new Float2(4), Size - 8), Color.Orange * alpha);
Render2D.DrawRectangle(new Rectangle(new Float2(4), Size - 8), style.SelectionBorder * alpha);
}
// Add overlay during debugger breakpoint hang
@@ -956,6 +1022,29 @@ namespace FlaxEditor.Windows
Focus();
}
/// <summary>
/// Apply the selected window mode to the game window.
/// </summary>
/// <param name="mode"></param>
public void SetWindowMode(InterfaceOptions.GameWindowMode mode)
{
switch (mode)
{
case InterfaceOptions.GameWindowMode.Docked:
break;
case InterfaceOptions.GameWindowMode.PopupWindow:
IsFloating = true;
break;
case InterfaceOptions.GameWindowMode.MaximizedWindow:
IsMaximized = true;
break;
case InterfaceOptions.GameWindowMode.BorderlessWindow:
IsBorderless = true;
break;
default: throw new ArgumentOutOfRangeException(nameof(mode), mode, null);
}
}
/// <summary>
/// Takes the screenshot of the current viewport.
/// </summary>

View File

@@ -12,6 +12,7 @@ using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Docking;
using FlaxEditor.GUI.Input;
using FlaxEditor.GUI.Tree;
using FlaxEditor.Options;
using FlaxEditor.Scripting;
using FlaxEditor.Surface;
using FlaxEditor.Windows;
@@ -142,6 +143,7 @@ namespace FlaxEngine.Windows.Search
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
InputOptions options = FlaxEditor.Editor.Instance.Options.Options.Input;
if (IsFocused)
{
if (key == KeyboardKeys.Return && Navigate != null)
@@ -149,7 +151,7 @@ namespace FlaxEngine.Windows.Search
Navigate.Invoke(this);
return true;
}
if (key == KeyboardKeys.C && Root.GetKey(KeyboardKeys.Control))
if (options.Copy.Process(this))
{
Clipboard.Text = Text;
return true;

View File

@@ -11,7 +11,7 @@
/// <summary>
/// Behavior instance script that runs Behavior Tree execution.
/// </summary>
API_CLASS() class FLAXENGINE_API Behavior : public Script
API_CLASS(Attributes="Category(\"Flax Engine\")") class FLAXENGINE_API Behavior : public Script
{
API_AUTO_SERIALIZATION();
DECLARE_SCRIPTING_TYPE(Behavior);

View File

@@ -706,6 +706,14 @@ bool BehaviorTreeKnowledgeValuesConditionalDecorator::CanUpdate(const BehaviorUp
return BehaviorKnowledge::CompareValues((float)ValueA.Get(context.Knowledge), (float)ValueB.Get(context.Knowledge), Comparison);
}
bool BehaviorTreeKnowledgeBooleanDecorator::CanUpdate(const BehaviorUpdateContext& context)
{
Variant value = Value.Get(context.Knowledge);
bool result = (bool)value;
result ^= Invert;
return result;
}
bool BehaviorTreeHasTagDecorator::CanUpdate(const BehaviorUpdateContext& context)
{
bool result = false;

View File

@@ -445,6 +445,27 @@ public:
bool CanUpdate(const BehaviorUpdateContext& context) override;
};
/// <summary>
/// Checks certain knowledge value to conditionally enter the node if the value is set (eg. not-null object reference or boolean value).
/// </summary>
API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeKnowledgeBooleanDecorator : public BehaviorTreeDecorator
{
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeKnowledgeBooleanDecorator, BehaviorTreeDecorator);
API_AUTO_SERIALIZATION();
// The value from behavior's knowledge (blackboard, goal or sensor) to check if it's set (eg. not-null object reference or boolean value).
API_FIELD(Attributes="EditorOrder(0)")
BehaviorKnowledgeSelectorAny Value;
// If checked, the condition will be inverted.
API_FIELD(Attributes="EditorOrder(10)")
bool Invert = false;
public:
// [BehaviorTreeNode]
bool CanUpdate(const BehaviorUpdateContext& context) override;
};
/// <summary>
/// Checks if certain actor (from knowledge) has a given tag assigned.
/// </summary>

View File

@@ -900,5 +900,6 @@ private:
Variant SampleState(AnimGraphContext& context, const AnimGraphNode* state);
void InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition = nullptr);
AnimGraphStateTransition* UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState = nullptr);
AnimGraphStateTransition* UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, const AnimGraphNode::StateBaseData& stateData, AnimGraphNode* state, AnimGraphNode* ignoreState = nullptr);
void UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData);
};

View File

@@ -563,9 +563,13 @@ void AnimGraphExecutor::InitStateTransition(AnimGraphContext& context, AnimGraph
}
AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState)
{
return UpdateStateTransitions(context, stateMachineData, state->Data.State, state, ignoreState);
}
AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, const AnimGraphNode::StateBaseData& stateData, AnimGraphNode* state, AnimGraphNode* ignoreState)
{
int32 transitionIndex = 0;
const AnimGraphNode::StateBaseData& stateData = state->Data.State;
while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex)
{
const uint16 idx = stateData.Transitions[transitionIndex];
@@ -640,7 +644,7 @@ AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphCon
void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData)
{
AnimGraphStateTransition* transition = UpdateStateTransitions(context, stateMachineData, stateMachineBucket.CurrentState);
AnimGraphStateTransition* transition = UpdateStateTransitions(context, stateMachineData, stateData, stateMachineBucket.CurrentState);
if (transition)
{
InitStateTransition(context, stateMachineBucket, transition);

View File

@@ -281,10 +281,26 @@ Asset::LoadResult SceneAnimation::load()
track.TrackStateIndex = TrackStatesCount++;
trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1);
trackRuntime->PropertyTypeName = stream.Move<char>(trackData->PropertyTypeNameLength + 1);
const int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize);
int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize);
if (trackData->ValueSize == 0)
{
// When using json data (from non-POD types) read the sum of all keyframes data
const int32 keyframesDataStart = stream.GetPosition();
for (int32 j = 0; j < trackData->KeyframesCount; j++)
{
stream.Move<float>(); // Time
int32 jsonLen;
stream.ReadInt32(&jsonLen);
stream.Move(jsonLen);
}
const int32 keyframesDataEnd = stream.GetPosition();
stream.SetPosition(keyframesDataStart);
keyframesDataSize = keyframesDataEnd - keyframesDataStart;
}
trackRuntime->ValueSize = trackData->ValueSize;
trackRuntime->KeyframesCount = trackData->KeyframesCount;
trackRuntime->Keyframes = stream.Move(keyframesDataSize);
trackRuntime->KeyframesSize = keyframesDataSize;
needsParent = true;
break;
}
@@ -298,6 +314,7 @@ Asset::LoadResult SceneAnimation::load()
trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1);
trackRuntime->PropertyTypeName = stream.Move<char>(trackData->PropertyTypeNameLength + 1);
const int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize * 3);
ASSERT(trackData->ValueSize > 0);
trackRuntime->ValueSize = trackData->ValueSize;
trackRuntime->KeyframesCount = trackData->KeyframesCount;
trackRuntime->Keyframes = stream.Move(keyframesDataSize);
@@ -375,6 +392,7 @@ Asset::LoadResult SceneAnimation::load()
trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1);
trackRuntime->PropertyTypeName = stream.Move<char>(trackData->PropertyTypeNameLength + 1);
trackRuntime->ValueSize = trackData->ValueSize;
ASSERT(trackData->ValueSize > 0);
trackRuntime->KeyframesCount = trackData->KeyframesCount;
const auto keyframesTimes = (float*)((byte*)trackRuntime + sizeof(StringPropertyTrack::Runtime));
const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackData->KeyframesCount);

View File

@@ -289,6 +289,7 @@ public:
/// The keyframes array (items count is KeyframesCount). Each keyframe is represented by pair of time (of type float) and the value data (of size ValueSize).
/// </summary>
void* Keyframes;
int32 KeyframesSize;
};
};

View File

@@ -7,6 +7,7 @@
#include "Engine/Level/SceneObjectsFactory.h"
#include "Engine/Level/Actors/Camera.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Audio/AudioClip.h"
#include "Engine/Audio/AudioSource.h"
#include "Engine/Graphics/RenderTask.h"
@@ -19,6 +20,7 @@
#include "Engine/Scripting/ManagedCLR/MField.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MMethod.h"
#include "Engine/Scripting/Internal/ManagedSerialization.h"
// This could be Update, LateUpdate or FixedUpdate
#define UPDATE_POINT Update
@@ -370,47 +372,96 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
case SceneAnimation::Track::Types::KeyframesProperty:
case SceneAnimation::Track::Types::ObjectReferenceProperty:
{
const auto trackDataKeyframes = track.GetRuntimeData<SceneAnimation::KeyframesPropertyTrack::Runtime>();
const int32 count = trackDataKeyframes->KeyframesCount;
const auto trackRuntime = track.GetRuntimeData<SceneAnimation::KeyframesPropertyTrack::Runtime>();
const int32 count = trackRuntime->KeyframesCount;
if (count == 0)
return false;
// Find the keyframe at time
int32 keyframeSize = sizeof(float) + trackDataKeyframes->ValueSize;
#define GET_KEY_TIME(idx) *(float*)((byte*)trackDataKeyframes->Keyframes + keyframeSize * (idx))
const float keyTime = Math::Clamp(time, 0.0f, GET_KEY_TIME(count - 1));
int32 start = 0;
int32 searchLength = count;
while (searchLength > 0)
// If size is 0 then track uses Json storage for keyframes data (variable memory length of keyframes), otherwise it's optimized simple data with O(1) access
if (trackRuntime->ValueSize != 0)
{
const int32 half = searchLength >> 1;
int32 mid = start + half;
if (keyTime < GET_KEY_TIME(mid))
// Find the keyframe at time (binary search)
int32 keyframeSize = sizeof(float) + trackRuntime->ValueSize;
#define GET_KEY_TIME(idx) *(float*)((byte*)trackRuntime->Keyframes + keyframeSize * (idx))
const float keyTime = Math::Clamp(time, 0.0f, GET_KEY_TIME(count - 1));
int32 start = 0;
int32 searchLength = count;
while (searchLength > 0)
{
searchLength = half;
const int32 half = searchLength >> 1;
int32 mid = start + half;
if (keyTime < GET_KEY_TIME(mid))
{
searchLength = half;
}
else
{
start = mid + 1;
searchLength -= half + 1;
}
}
int32 leftKey = Math::Max(0, start - 1);
#undef GET_KEY_TIME
// Return the value
void* value = (void*)((byte*)trackRuntime->Keyframes + keyframeSize * (leftKey) + sizeof(float));
if (track.Type == SceneAnimation::Track::Types::ObjectReferenceProperty)
{
// Object ref track uses Guid for object Id storage
Guid id = *(Guid*)value;
_objectsMapping.TryGet(id, id);
auto obj = Scripting::FindObject<ScriptingObject>(id);
value = obj ? obj->GetOrCreateManagedInstance() : nullptr;
*(void**)target = value;
}
else
{
start = mid + 1;
searchLength -= half + 1;
// POD memory
Platform::MemoryCopy(target, value, trackRuntime->ValueSize);
}
}
int32 leftKey = Math::Max(0, start - 1);
#undef GET_KEY_TIME
// Return the value
void* value = (void*)((byte*)trackDataKeyframes->Keyframes + keyframeSize * (leftKey) + sizeof(float));
if (track.Type == SceneAnimation::Track::Types::ObjectReferenceProperty)
{
Guid id = *(Guid*)value;
_objectsMapping.TryGet(id, id);
auto obj = Scripting::FindObject<ScriptingObject>(id);
value = obj ? obj->GetOrCreateManagedInstance() : nullptr;
*(void**)target = value;
}
else
{
Platform::MemoryCopy(target, value, trackDataKeyframes->ValueSize);
// Clear pointer
*(void**)target = nullptr;
// Find the keyframe at time (linear search)
MemoryReadStream stream((byte*)trackRuntime->Keyframes, trackRuntime->KeyframesSize);
int32 prevKeyPos = sizeof(float);
int32 jsonLen;
for (int32 key = 0; key < count; key++)
{
float keyTime;
stream.ReadFloat(&keyTime);
if (keyTime > time)
break;
prevKeyPos = stream.GetPosition();
stream.ReadInt32(&jsonLen);
stream.Move(jsonLen);
}
// Read json text
stream.SetPosition(prevKeyPos);
stream.ReadInt32(&jsonLen);
const StringAnsiView json((const char*)stream.GetPositionHandle(), jsonLen);
// Create empty value of the keyframe type
const auto trackData = track.GetData<SceneAnimation::KeyframesPropertyTrack::Data>();
const StringAnsiView propertyTypeName(trackRuntime->PropertyTypeName, trackData->PropertyTypeNameLength);
MClass* klass = Scripting::FindClass(propertyTypeName);
if (!klass)
return false;
MObject* obj = MCore::Object::New(klass);
if (!obj)
return false;
if (!klass->IsValueType())
MCore::Object::Init(obj);
// Deserialize value from json
ManagedSerialization::Deserialize(json, obj);
// Set value
*(void**)target = obj;
}
break;
}
@@ -479,13 +530,13 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
}
case SceneAnimation::Track::Types::StringProperty:
{
const auto trackDataKeyframes = track.GetRuntimeData<SceneAnimation::StringPropertyTrack::Runtime>();
const int32 count = trackDataKeyframes->KeyframesCount;
const auto trackRuntime = track.GetRuntimeData<SceneAnimation::StringPropertyTrack::Runtime>();
const int32 count = trackRuntime->KeyframesCount;
if (count == 0)
return false;
const auto keyframesTimes = (float*)((byte*)trackDataKeyframes + sizeof(SceneAnimation::StringPropertyTrack::Runtime));
const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackDataKeyframes->KeyframesCount);
const auto keyframesValues = (Char**)((byte*)keyframesLengths + sizeof(int32) * trackDataKeyframes->KeyframesCount);
const auto keyframesTimes = (float*)((byte*)trackRuntime + sizeof(SceneAnimation::StringPropertyTrack::Runtime));
const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackRuntime->KeyframesCount);
const auto keyframesValues = (Char**)((byte*)keyframesLengths + sizeof(int32) * trackRuntime->KeyframesCount);
// Find the keyframe at time
#define GET_KEY_TIME(idx) keyframesTimes[idx]
@@ -522,7 +573,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
auto& childTrack = anim->Tracks[childTrackIndex];
if (childTrack.Disabled || childTrack.ParentIndex != trackIndex)
continue;
const auto childTrackRuntimeData = childTrack.GetRuntimeData<SceneAnimation::PropertyTrack::Runtime>();
const auto childTrackRuntime = childTrack.GetRuntimeData<SceneAnimation::PropertyTrack::Runtime>();
auto& childTrackState = _tracks[stateIndexOffset + childTrack.TrackStateIndex];
// Cache field
@@ -532,7 +583,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
if (!type)
continue;
MClass* mclass = MCore::Type::GetClass(type);
childTrackState.Field = mclass->GetField(childTrackRuntimeData->PropertyName);
childTrackState.Field = mclass->GetField(childTrackRuntime->PropertyName);
if (!childTrackState.Field)
continue;
}
@@ -956,7 +1007,8 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
if (TickPropertyTrack(j, stateIndexOffset, anim, time, track, state, value))
{
// Set the value
if (MCore::Type::IsPointer(valueType))
auto valueTypes = MCore::Type::GetType(valueType);
if (valueTypes == MTypes::Object || MCore::Type::IsPointer(valueType))
value = (void*)*(intptr*)value;
if (state.Property)
{

View File

@@ -69,7 +69,7 @@ uint64 RawDataAsset::GetMemoryUsage() const
Locker.Lock();
uint64 result = BinaryAsset::GetMemoryUsage();
result += sizeof(RawDataAsset) - sizeof(BinaryAsset);
result += Data.Count();
result += Data.Capacity();
Locker.Unlock();
return result;
}

View File

@@ -198,6 +198,12 @@ void ContentService::Dispose()
Graphics::DisposeDevice();
}
IAssetFactory::Collection& IAssetFactory::Get()
{
static Collection Factories(1024);
return Factories;
}
AssetsCache* Content::GetRegistry()
{
return &Cache;

View File

@@ -20,11 +20,7 @@ public:
/// <summary>
/// Gets the all registered assets factories. Key is asset typename, value is the factory object.
/// </summary>
static Collection& Get()
{
static Collection Factories(1024);
return Factories;
}
static Collection& Get();
public:
/// <summary>

View File

@@ -943,6 +943,11 @@ void DebugDraw::DrawDirection(const Vector3& origin, const Vector3& direction, c
DrawLine(origin, origin + direction, color, duration, depthTest);
}
void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float duration, bool depthTest)
{
DrawLine(origin, origin + direction, color, duration, depthTest);
}
void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float length, float duration, bool depthTest)
{
if (isnan(length) || isinf(length))

View File

@@ -87,6 +87,17 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
API_FUNCTION() static void DrawDirection(const Vector3& origin, const Vector3& direction, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true);
/// <summary>
/// Draws the line in a direction.
/// [Deprecated in v1.8]
/// </summary>
/// <param name="origin">The origin of the line.</param>
/// <param name="direction">The direction of the line.</param>
/// <param name="color">The color.</param>
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
API_FUNCTION() DEPRECATED static void DrawRay(const Vector3& origin, const Vector3& direction, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true);
/// <summary>
/// Draws the line in a direction.
/// </summary>

View File

@@ -7,7 +7,7 @@ namespace FlaxEngine
/// <summary>
/// Virtual input axis binding. Helps with listening for a selected axis input.
/// </summary>
public class InputAxis
public class InputAxis : IComparable, IComparable<InputAxis>
{
/// <summary>
/// The name of the axis to use. See <see cref="Input.AxisMappings"/>.
@@ -47,13 +47,13 @@ namespace FlaxEngine
Input.AxisValueChanged += Handler;
Name = name;
}
private void Handler(string name)
{
if (string.Equals(Name, name, StringComparison.OrdinalIgnoreCase))
ValueChanged?.Invoke();
}
/// <summary>
/// Finalizes an instance of the <see cref="InputAxis"/> class.
/// </summary>
@@ -61,7 +61,7 @@ namespace FlaxEngine
{
Input.AxisValueChanged -= Handler;
}
/// <summary>
/// Releases this object.
/// </summary>
@@ -70,5 +70,35 @@ namespace FlaxEngine
Input.AxisValueChanged -= Handler;
GC.SuppressFinalize(this);
}
/// <inheritdoc />
public int CompareTo(InputAxis other)
{
return string.Compare(Name, other.Name, StringComparison.Ordinal);
}
/// <inheritdoc />
public int CompareTo(object obj)
{
return obj is InputAxis other ? CompareTo(other) : -1;
}
/// <inheritdoc />
public override int GetHashCode()
{
return Name?.GetHashCode() ?? 0;
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is InputAxis other && string.Equals(Name, other.Name, StringComparison.Ordinal);
}
/// <inheritdoc />
public override string ToString()
{
return Name;
}
}
}

View File

@@ -7,7 +7,7 @@ namespace FlaxEngine
/// <summary>
/// Virtual input action binding. Helps with listening for a selected input event.
/// </summary>
public class InputEvent
public class InputEvent : IComparable, IComparable<InputEvent>
{
/// <summary>
/// The name of the action to use. See <see cref="Input.ActionMappings"/>.
@@ -21,7 +21,7 @@ namespace FlaxEngine
public bool Active => Input.GetAction(Name);
/// <summary>
/// Returns the event state. Use Use <see cref="Pressed"/>, <see cref="Pressing"/>, <see cref="Released"/> to catch events without active waiting.
/// Returns the event state. Use <see cref="Pressed"/>, <see cref="Pressing"/>, <see cref="Released"/> to catch events without active waiting.
/// </summary>
public InputActionState State => Input.GetActionState(Name);
@@ -35,12 +35,12 @@ namespace FlaxEngine
/// Occurs when event is pressed (e.g. user pressed a key). Called before scripts update.
/// </summary>
public event Action Pressed;
/// <summary>
/// Occurs when event is being pressing (e.g. user pressing a key). Called before scripts update.
/// </summary>
public event Action Pressing;
/// <summary>
/// Occurs when event is released (e.g. user releases a key). Called before scripts update.
/// </summary>
@@ -102,5 +102,35 @@ namespace FlaxEngine
Input.ActionTriggered -= Handler;
GC.SuppressFinalize(this);
}
/// <inheritdoc />
public int CompareTo(InputEvent other)
{
return string.Compare(Name, other.Name, StringComparison.Ordinal);
}
/// <inheritdoc />
public int CompareTo(object obj)
{
return obj is InputEvent other ? CompareTo(other) : -1;
}
/// <inheritdoc />
public override int GetHashCode()
{
return Name?.GetHashCode() ?? 0;
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is InputEvent other && string.Equals(Name, other.Name, StringComparison.Ordinal);
}
/// <inheritdoc />
public override string ToString()
{
return Name;
}
}
}

View File

@@ -48,7 +48,7 @@ namespace FlaxEngine.Interop
#endif
private static Dictionary<object, ManagedHandle> classAttributesCacheCollectible = new();
private static Dictionary<Assembly, ManagedHandle> assemblyHandles = new();
private static Dictionary<Type, int> _typeSizeCache = new();
private static ConcurrentDictionary<Type, int> _typeSizeCache = new();
private static Dictionary<string, IntPtr> loadedNativeLibraries = new();
internal static Dictionary<string, string> libraryPaths = new();
@@ -1594,7 +1594,7 @@ namespace FlaxEngine.Interop
private static IntPtr PinValue<T>(T value) where T : struct
{
// Store the converted value in unmanaged memory so it will not be relocated by the garbage collector.
int size = GetTypeSize(typeof(T));
int size = TypeHelpers<T>.MarshalSize;
uint index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length;
ref (IntPtr ptr, int size) alloc = ref pinnedAllocations[index];
if (alloc.size < size)
@@ -1727,25 +1727,36 @@ namespace FlaxEngine.Interop
return tuple;
}
internal static int GetTypeSize(Type type)
internal static class TypeHelpers<T>
{
if (!_typeSizeCache.TryGetValue(type, out var size))
public static readonly int MarshalSize;
static TypeHelpers()
{
Type type = typeof(T);
try
{
var marshalType = type;
if (type.IsEnum)
marshalType = type.GetEnumUnderlyingType();
size = Marshal.SizeOf(marshalType);
MarshalSize = Marshal.SizeOf(marshalType);
}
catch
{
// Workaround the issue where structure defined within generic type instance (eg. MyType<int>.MyStruct) fails to get size
// https://github.com/dotnet/runtime/issues/46426
var obj = Activator.CreateInstance(type);
size = Marshal.SizeOf(obj);
var obj = RuntimeHelpers.GetUninitializedObject(type);
MarshalSize = Marshal.SizeOf(obj);
}
_typeSizeCache.Add(type, size);
}
}
internal static int GetTypeSize(Type type)
{
if (!_typeSizeCache.TryGetValue(type, out int size))
{
var marshalSizeField = typeof(TypeHelpers<>).MakeGenericType(type).GetField(nameof(TypeHelpers<int>.MarshalSize), BindingFlags.Static | BindingFlags.Public);
size = (int)marshalSizeField.GetValue(null);
_typeSizeCache.AddOrUpdate(type, size, (t, v) => size);
}
return size;
}

View File

@@ -1764,14 +1764,6 @@ bool Actor::FromBytes(const Span<byte>& data, Array<Actor*>& output, ISerializeM
}
Scripting::ObjectsLookupIdMapping.Set(nullptr);
// Link objects
//for (int32 i = 0; i < objectsCount; i++)
{
//SceneObject* obj = sceneObjects->At(i);
// TODO: post load or post spawn?
//obj->PostLoad();
}
// Update objects order
//for (int32 i = 0; i < objectsCount; i++)
{

View File

@@ -390,7 +390,7 @@ namespace FlaxEngine
}
#if FLAX_EDITOR
internal bool ShowTransform => !(this is UIControl);
private bool ShowTransform => !(this is UIControl);
#endif
}
}

View File

@@ -61,7 +61,7 @@ void SceneObject::BreakPrefabLink()
String SceneObject::GetNamePath(Char separatorChar) const
{
Array<String> names;
Array<StringView> names;
const Actor* a = dynamic_cast<const Actor*>(this);
if (!a)
a = GetParent();
@@ -75,6 +75,8 @@ String SceneObject::GetNamePath(Char separatorChar) const
int32 length = names.Count() - 1;
for (int32 i = 0; i < names.Count(); i++)
length += names[i].Length();
if (length == 0)
return String::Empty;
String result;
result.ReserveSpace(length);
Char* ptr = result.Get();

View File

@@ -325,7 +325,7 @@ void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::
dummyScript->Data = MoveTemp(bufferStr);
}
#endif
LOG(Warning, "Parent actor of the missing object: {0}", parent->GetName());
LOG(Warning, "Parent actor of the missing object: '{0}' ({1})", parent->GetNamePath(), String(parent->GetType().Fullname));
}
}
#endif

View File

@@ -3,6 +3,7 @@
#include "SceneQuery.h"
#include "Engine/Scripting/Script.h"
#include "Engine/Profiler/Profiler.h"
#include "Scripts/MissingScript.h"
Actor* SceneQuery::RaycastScene(const Ray& ray)
{
@@ -51,6 +52,15 @@ bool GetAllSerializableSceneObjectsQuery(Actor* actor, Array<SceneObject*>& obje
return false;
objects.Add(actor);
objects.Add(reinterpret_cast<SceneObject* const*>(actor->Scripts.Get()), actor->Scripts.Count());
#if USE_EDITOR
// Skip saving Missing Script instances
for (int32 i = 0; i < actor->Scripts.Count(); i++)
{
const int32 idx = objects.Count() - i - 1;
if (objects.Get()[idx]->GetTypeHandle() == MissingScript::TypeInitializer)
objects.RemoveAtKeepOrder(idx);
}
#endif
return true;
}

View File

@@ -11,7 +11,9 @@ int main(int argc, char* argv[])
StringBuilder args;
for (int i = 1; i < argc; i++)
{
args.Append(argv[i]);
String arg;
arg.SetUTF8(argv[i], StringUtils::Length(argv[i]));
args.Append(arg);
if (i + 1 != argc)
args.Append(TEXT(' '));

View File

@@ -46,6 +46,7 @@ bool NavCrowd::Init(float maxAgentRadius, int32 maxAgents, NavMeshRuntime* navMe
{
if (!_crowd || !navMesh)
return true;
PROFILE_CPU();
// This can happen on game start when no navmesh is loaded yet (eg. navmesh tiles data is during streaming) so wait for navmesh
if (navMesh->GetNavMesh() == nullptr)
@@ -145,6 +146,7 @@ void NavCrowd::ResetAgentMove(int32 id)
void NavCrowd::RemoveAgent(int32 id)
{
CHECK(id >= 0 && id < _crowd->getAgentCount());
_crowd->removeAgent(id);
}

View File

@@ -10,7 +10,7 @@
/// Actor script component that synchronizes the Transform over the network.
/// </summary>
/// <remarks>Interpolation and prediction logic based on https://www.gabrielgambetta.com/client-server-game-architecture.html.</remarks>
API_CLASS(Namespace="FlaxEngine.Networking") class FLAXENGINE_API NetworkTransform : public Script, public INetworkSerializable
API_CLASS(Namespace="FlaxEngine.Networking", Attributes="Category(\"Flax Engine\")") class FLAXENGINE_API NetworkTransform : public Script, public INetworkSerializable
{
API_AUTO_SERIALIZATION();
DECLARE_SCRIPTING_TYPE(NetworkTransform);

View File

@@ -3469,7 +3469,8 @@ void* PhysicsBackend::CreateVehicle(WheeledVehicle* actor)
Array<PxShape*, InlinedAllocation<8>> shapes;
shapes.Resize(actorPhysX->getNbShapes());
actorPhysX->getShapes(shapes.Get(), shapes.Count(), 0);
const PxTransform centerOfMassOffset = actorPhysX->getCMassLocalPose();
PxTransform centerOfMassOffset = actorPhysX->getCMassLocalPose();
centerOfMassOffset.q = PxQuat(PxIdentity);
// Initialize wheels simulation data
PxVec3 offsets[PX_MAX_NB_WHEELS];

View File

@@ -366,7 +366,13 @@ void PlatformBase::Error(const Char* msg)
#if PLATFORM_HAS_HEADLESS_MODE
if (CommandLine::Options.Headless)
{
#if PLATFORM_TEXT_IS_CHAR16
StringAnsi ansi(msg);
ansi += PLATFORM_LINE_TERMINATOR;
printf("Error: %s\n", ansi.Get());
#else
std::cout << "Error: " << msg << std::endl;
#endif
}
else
#endif

View File

@@ -28,8 +28,8 @@ const DateTime UnixEpoch(1970, 1, 1);
bool LinuxFileSystem::ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array<String, HeapAllocation>& filenames)
{
const StringAsANSI<> initialDirectoryAnsi(*initialDirectory, initialDirectory.Length());
const StringAsANSI<> titleAnsi(*title, title.Length());
const StringAsUTF8<> initialDirectoryAnsi(*initialDirectory, initialDirectory.Length());
const StringAsUTF8<> titleAnsi(*title, title.Length());
const char* initDir = initialDirectory.HasChars() ? initialDirectoryAnsi.Get() : ".";
String xdgCurrentDesktop;
StringBuilder fileFilter;
@@ -113,7 +113,7 @@ bool LinuxFileSystem::ShowOpenFileDialog(Window* parentWindow, const StringView&
bool LinuxFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& title, String& path)
{
const StringAsANSI<> titleAnsi(*title, title.Length());
const StringAsUTF8<> titleAnsi(*title, title.Length());
String xdgCurrentDesktop;
Platform::GetEnvironmentVariable(TEXT("XDG_CURRENT_DESKTOP"), xdgCurrentDesktop);
@@ -158,7 +158,7 @@ bool LinuxFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const StringV
bool LinuxFileSystem::ShowFileExplorer(const StringView& path)
{
const StringAsANSI<> pathAnsi(*path, path.Length());
const StringAsUTF8<> pathAnsi(*path, path.Length());
char cmd[2048];
sprintf(cmd, "xdg-open %s &", pathAnsi.Get());
system(cmd);
@@ -167,7 +167,7 @@ bool LinuxFileSystem::ShowFileExplorer(const StringView& path)
bool LinuxFileSystem::CreateDirectory(const StringView& path)
{
const StringAsANSI<> pathAnsi(*path, path.Length());
const StringAsUTF8<> pathAnsi(*path, path.Length());
// Skip if already exists
struct stat fileInfo;
@@ -258,7 +258,7 @@ bool DeleteUnixPathTree(const char* path)
bool LinuxFileSystem::DeleteDirectory(const String& path, bool deleteContents)
{
const StringAsANSI<> pathANSI(*path, path.Length());
const StringAsUTF8<> pathANSI(*path, path.Length());
if (deleteContents)
{
return DeleteUnixPathTree(pathANSI.Get());
@@ -272,7 +272,7 @@ bool LinuxFileSystem::DeleteDirectory(const String& path, bool deleteContents)
bool LinuxFileSystem::DirectoryExists(const StringView& path)
{
struct stat fileInfo;
const StringAsANSI<> pathANSI(*path, path.Length());
const StringAsUTF8<> pathANSI(*path, path.Length());
if (stat(pathANSI.Get(), &fileInfo) != -1)
{
return S_ISDIR(fileInfo.st_mode);
@@ -282,8 +282,8 @@ bool LinuxFileSystem::DirectoryExists(const StringView& path)
bool LinuxFileSystem::DirectoryGetFiles(Array<String>& results, const String& path, const Char* searchPattern, DirectorySearchOption option)
{
const StringAsANSI<> pathANSI(*path, path.Length());
const StringAsANSI<> searchPatternANSI(searchPattern);
const StringAsUTF8<> pathANSI(*path, path.Length());
const StringAsUTF8<> searchPatternANSI(searchPattern);
// Check if use only top directory
if (option == DirectorySearchOption::TopDirectoryOnly)
@@ -297,7 +297,7 @@ bool LinuxFileSystem::GetChildDirectories(Array<String>& results, const String&
DIR* dir;
struct stat statPath, statEntry;
struct dirent* entry;
const StringAsANSI<> pathANSI(*directory, directory.Length());
const StringAsUTF8<> pathANSI(*directory, directory.Length());
const char* path = pathANSI.Get();
// Stat for the path
@@ -353,7 +353,7 @@ bool LinuxFileSystem::GetChildDirectories(Array<String>& results, const String&
bool LinuxFileSystem::FileExists(const StringView& path)
{
struct stat fileInfo;
const StringAsANSI<> pathANSI(*path, path.Length());
const StringAsUTF8<> pathANSI(*path, path.Length());
if (stat(pathANSI.Get(), &fileInfo) != -1)
{
return S_ISREG(fileInfo.st_mode);
@@ -363,7 +363,7 @@ bool LinuxFileSystem::FileExists(const StringView& path)
bool LinuxFileSystem::DeleteFile(const StringView& path)
{
const StringAsANSI<> pathANSI(*path, path.Length());
const StringAsUTF8<> pathANSI(*path, path.Length());
return unlink(pathANSI.Get()) != 0;
}
@@ -371,7 +371,7 @@ uint64 LinuxFileSystem::GetFileSize(const StringView& path)
{
struct stat fileInfo;
fileInfo.st_size = 0;
const StringAsANSI<> pathANSI(*path, path.Length());
const StringAsUTF8<> pathANSI(*path, path.Length());
if (stat(pathANSI.Get(), &fileInfo) != -1)
{
// Check for directories
@@ -385,7 +385,7 @@ uint64 LinuxFileSystem::GetFileSize(const StringView& path)
bool LinuxFileSystem::IsReadOnly(const StringView& path)
{
const StringAsANSI<> pathANSI(*path, path.Length());
const StringAsUTF8<> pathANSI(*path, path.Length());
if (access(pathANSI.Get(), W_OK) == -1)
{
return errno == EACCES;
@@ -395,7 +395,7 @@ bool LinuxFileSystem::IsReadOnly(const StringView& path)
bool LinuxFileSystem::SetReadOnly(const StringView& path, bool isReadOnly)
{
const StringAsANSI<> pathANSI(*path, path.Length());
const StringAsUTF8<> pathANSI(*path, path.Length());
struct stat fileInfo;
if (stat(pathANSI.Get(), &fileInfo) != -1)
{
@@ -422,15 +422,15 @@ bool LinuxFileSystem::MoveFile(const StringView& dst, const StringView& src, boo
if (overwrite)
{
unlink(StringAsANSI<>(*dst, dst.Length()).Get());
unlink(StringAsUTF8<>(*dst, dst.Length()).Get());
}
if (rename(StringAsANSI<>(*src, src.Length()).Get(), StringAsANSI<>(*dst, dst.Length()).Get()) != 0)
if (rename(StringAsUTF8<>(*src, src.Length()).Get(), StringAsUTF8<>(*dst, dst.Length()).Get()) != 0)
{
if (errno == EXDEV)
{
if (!CopyFile(dst, src))
{
unlink(StringAsANSI<>(*src, src.Length()).Get());
unlink(StringAsUTF8<>(*src, src.Length()).Get());
return false;
}
}
@@ -441,8 +441,8 @@ bool LinuxFileSystem::MoveFile(const StringView& dst, const StringView& src, boo
bool LinuxFileSystem::CopyFile(const StringView& dst, const StringView& src)
{
const StringAsANSI<> srcANSI(*src, src.Length());
const StringAsANSI<> dstANSI(*dst, dst.Length());
const StringAsUTF8<> srcANSI(*src, src.Length());
const StringAsUTF8<> dstANSI(*dst, dst.Length());
int srcFile, dstFile;
char buffer[4096];
@@ -752,7 +752,7 @@ bool LinuxFileSystem::getFilesFromDirectoryAll(Array<String>& results, const cha
DateTime LinuxFileSystem::GetFileLastEditTime(const StringView& path)
{
struct stat fileInfo;
const StringAsANSI<> pathANSI(*path, path.Length());
const StringAsUTF8<> pathANSI(*path, path.Length());
if (stat(pathANSI.Get(), &fileInfo) == -1)
{
return DateTime::MinValue();

Some files were not shown because too many files have changed in this diff Show More